好的,这是一个关于 Android 音频编解码的全面介绍,涵盖基本概念、原理和例子。
一、 基本概念
-
音频编解码 (Audio Codec):
◦ 编解码 是 编码 (Encode) 和 解码 (Decode) 的合称。
◦ 编码:将原始的、未压缩的音频数据(如 PCM)转换为压缩的、更小体积的数据格式(如 MP3, AAC)。目的是减小存储空间和传输带宽。
◦ 解码:将压缩的音频数据转换回(或近似)原始的、可以被扬声器播放的格式。目的是播放音频。
-
关键数据格式:
◦ PCM (Pulse Code Modulation, 脉冲编码调制):
▪ 定义:是数字音频的"原始"或"无损"表示形式,直接从模拟信号通过采样、量化得到。它没有经过压缩,因此文件体积很大。 ▪ 特点:它是所有数字音频处理(如混音、特效)的基础格式。音频在设备内部处理和硬件驱动之间传输时,通常是 PCM 格式。◦ 压缩音频格式:如 MP3, AAC, OGG, FLAC 等。它们是在 PCM 基础上,通过特定算法压缩后的格式,用于存储和网络传输。
-
Android 中的核心类:
◦ MediaPlayer:高级 API,用于播放音频/视频。它内部处理了解码、同步等复杂任务,开发者只需提供文件路径或 URI。主要用于播放压缩格式的音频文件或网络流。
◦ MediaRecorder:高级 API,用于录制音频/视频。它内部处理了编码、文件封装等任务。
◦ AudioTrack:低级 API,用于播放 PCM 音频流。开发者需要提供 PCM 数据缓冲区,由 AudioTrack 写入音频硬件。它只处理播放,不负责解码。
◦ AudioRecord:低级 API,用于录制 PCM 音频流。它从音频硬件(麦克风)采集原始的 PCM 数据。它只负责采集,不负责编码。
◦ MediaCodec:最核心的低级编解码 API。用于对原始数据(如 PCM,YUV)进行编码或解码。它是连接压缩数据与原始数据的桥梁。
二、 原理与流程
在 Android 中,完整的音频处理流程通常涉及以上类的组合。
- 音频播放流程 (解码过程)
对于一个压缩音频文件(如 MP3)的播放,原理如下:
压缩文件: e.g., MP3\] --(输入)--\> \[MediaCodec 解码器\] --(输出)--\> \[PCM 数据\] --(写入)--\> \[AudioTrack\] --(播放)--\> \[扬声器
• 步骤 1:解封装与解码:MediaPlayer 内部或开发者自己使用 MediaExtractor 和 MediaCodec 协作完成。
◦ MediaExtractor 从 MP4/MP3 等容器中分离出音频轨道。
◦ MediaCodec(配置为解码模式)接收压缩的音频帧(如 AAC 帧),将其解码为原始的 PCM 数据。
• 步骤 2:播放 PCM:解码得到的 PCM 数据被送入 AudioTrack,由 AudioTrack 管理音频缓冲区,并将数据推送至底层音频硬件(如 HAL)进行数模转换和放大,最终驱动扬声器发声。
简单使用:用 MediaPlayer 时,步骤 1 和 2 被自动完成。
高级/自定义使用:开发者可以使用 MediaCodec + AudioTrack 手动控制解码和播放过程,以实现音频特效、可视化或极低延迟播放。
- 音频录制流程 (编码过程)
录制音频并保存为压缩文件(如 AAC),原理如下:
麦克风\] --(采集)--\> \[AudioRecord\] --(输出)--\> \[PCM 数据\] --(输入)--\> \[MediaCodec 编码器\] --(输出)--\> \[压缩数据: e.g., AAC\] --(写入)--\> \[文件/网络
• 步骤 1:采集 PCM:AudioRecord 从麦克风硬件采集原始的 PCM 音频数据。
• 步骤 2:编码:采集到的 PCM 数据被送入 MediaCodec(配置为编码模式),编码器将其压缩为指定的格式(如 AAC)。
• 步骤 3:封装与写入:编码后的压缩数据(如 AAC 帧)被 MediaMuxer 封装到特定的容器格式(如 MP4)中,并写入文件。
简单使用:用 MediaRecorder 时,步骤 1、2、3 被自动完成。
高级/自定义使用:开发者可以使用 AudioRecord + MediaCodec + MediaMuxer 手动控制流程,以实现音频预处理、自定义编码参数或流式传输。
三、 例子
这里给出使用低级 API (AudioTrack, MediaCodec) 进行解码播放的简化版代码框架,以展示原理。
场景:解码一个 MP3 文件并用 AudioTrack 播放。
// 注意:此为示意代码,省略了异常处理、资源释放、同步等细节。
// 1. 创建并配置 MediaExtractor,定位音频轨道
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource("/sdcard/music.mp3");
int audioTrackIndex = selectAudioTrack(extractor); // 选择音频轨道的辅助函数
extractor.selectTrack(audioTrackIndex);
// 2. 从 Extractor 获取音频格式(包含 MIME 类型,如 "audio/mp4a-latm")
MediaFormat format = extractor.getTrackFormat(audioTrackIndex);
String mime = format.getString(MediaFormat.KEY_MIME);
// 3. 创建解码器 (MediaCodec)
MediaCodec decoder = MediaCodec.createDecoderByType(mime);
decoder.configure(format, null, null, 0); // 配置为解码模式
decoder.start();
// 4. 创建 AudioTrack 用于播放解码后的 PCM
// 从 format 中获取音频参数
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
int channelConfig = (channelCount == 1) ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 常见格式
int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);
AudioTrack audioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
sampleRate,
channelConfig,
audioFormat,
bufferSize,
AudioTrack.MODE_STREAM
);
audioTrack.play();
// 5. 解码循环
boolean sawInputEOS = false;
boolean sawOutputEOS = false;
ByteBuffer[] inputBuffers = decoder.getInputBuffers(); // API 21+ 后使用 getInput/OutputBuffer
ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
while (!sawOutputEOS) {
// 5.1 将压缩数据送入解码器输入缓冲区
if (!sawInputEOS) {
int inputBufferIndex = decoder.dequeueInputBuffer(TIMEOUT_US);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) { // 已读完
sawInputEOS = true;
decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
long presentationTimeUs = extractor.getSampleTime();
decoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0);
extractor.advance(); // 移动到下一帧
}
}
}
// 5.2 从解码器输出缓冲区获取解码后的 PCM 数据
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
if (outputBufferIndex >= 0) {
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
sawOutputEOS = true;
}
if (bufferInfo.size > 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
// 将 PCM 数据写入 AudioTrack
byte[] pcmData = new byte[bufferInfo.size];
outputBuffer.get(pcmData);
outputBuffer.clear(); // 必须清空或重置位置
audioTrack.write(pcmData, 0, pcmData.length);
}
decoder.releaseOutputBuffer(outputBufferIndex, false);
}
}
// 6. 清理资源
audioTrack.stop();
audioTrack.release();
decoder.stop();
decoder.release();
extractor.release();
总结:
• 高级API (MediaPlayer, MediaRecorder): 简单、快捷,适合大多数播放/录制场景。
• 低级API (AudioTrack, AudioRecord, MediaCodec): 灵活、强大,允许开发者深入控制音频流水线的每一个环节,适用于需要低延迟、实时处理、自定义编解码或特殊文件格式的场景。理解它们之间的数据流转(PCM <-> 压缩数据)是掌握 Android 音频处理的关键。