Android-Audio-编码和解码

好的,这是一个关于 Android 音频编解码的全面介绍,涵盖基本概念、原理和例子。

一、 基本概念

  1. 音频编解码 (Audio Codec):

    ◦ 编解码 是 编码 (Encode) 和 解码 (Decode) 的合称。

    ◦ 编码:将原始的、未压缩的音频数据(如 PCM)转换为压缩的、更小体积的数据格式(如 MP3, AAC)。目的是减小存储空间和传输带宽。

    ◦ 解码:将压缩的音频数据转换回(或近似)原始的、可以被扬声器播放的格式。目的是播放音频。

  2. 关键数据格式:

    ◦ PCM (Pulse Code Modulation, 脉冲编码调制):

    复制代码
    ▪   定义:是数字音频的"原始"或"无损"表示形式,直接从模拟信号通过采样、量化得到。它没有经过压缩,因此文件体积很大。
    
    ▪   特点:它是所有数字音频处理(如混音、特效)的基础格式。音频在设备内部处理和硬件驱动之间传输时,通常是 PCM 格式。

    ◦ 压缩音频格式:如 MP3, AAC, OGG, FLAC 等。它们是在 PCM 基础上,通过特定算法压缩后的格式,用于存储和网络传输。

  3. Android 中的核心类:

    ◦ MediaPlayer:高级 API,用于播放音频/视频。它内部处理了解码、同步等复杂任务,开发者只需提供文件路径或 URI。主要用于播放压缩格式的音频文件或网络流。

    ◦ MediaRecorder:高级 API,用于录制音频/视频。它内部处理了编码、文件封装等任务。

    ◦ AudioTrack:低级 API,用于播放 PCM 音频流。开发者需要提供 PCM 数据缓冲区,由 AudioTrack 写入音频硬件。它只处理播放,不负责解码。

    ◦ AudioRecord:低级 API,用于录制 PCM 音频流。它从音频硬件(麦克风)采集原始的 PCM 数据。它只负责采集,不负责编码。

    ◦ MediaCodec:最核心的低级编解码 API。用于对原始数据(如 PCM,YUV)进行编码或解码。它是连接压缩数据与原始数据的桥梁。

二、 原理与流程

在 Android 中,完整的音频处理流程通常涉及以上类的组合。

  1. 音频播放流程 (解码过程)

对于一个压缩音频文件(如 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 手动控制解码和播放过程,以实现音频特效、可视化或极低延迟播放。

  1. 音频录制流程 (编码过程)

录制音频并保存为压缩文件(如 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 = inputBuffersinputBufferIndex;

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 音频处理的关键。

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui