在Android音视频开发中,解码是绕不开的核心环节,很多人一开始会用FFmpeg等库做软解码,后来接触到MediaCodec硬解码时,往往会疑惑它到底是怎么运作的。本文就从软解码和硬解码的差异入手,带大家彻底搞懂MediaCodec的工作原理。

软解码和硬解码的差异
软解码完全依赖CPU进行运算,比如用FFmpeg解码H.264视频,所有的解码逻辑都在CPU上执行,优点是兼容性好,几乎所有格式都支持,缺点是CPU占用高,高分辨率视频解码时容易出现卡顿、发热问题。
硬解码则是调用设备硬件(通常是GPU或者专用的解码芯片)来完成解码,MediaCodec就是Android系统提供的硬解码接口,它的优点是解码速度快、CPU占用低,适合处理高码率、高分辨率的音视频流,缺点是兼容性受设备硬件限制,部分老旧设备可能不支持某些编码格式。
MediaCodec的核心架构
MediaCodec的工作基于生产者-消费者模型,内部维护了两套缓冲区:输入缓冲区和输出缓冲区。
- 输入缓冲区:用来存放需要解码的原始音视频数据,比如H.264的NALU单元、AAC的音频帧
- 输出缓冲区:用来存放解码完成后的原始数据,比如YUV视频帧、PCM音频帧
整个工作流程就是不断往输入缓冲区塞数据,然后从输出缓冲区取解码后的数据,中间的解码过程由硬件完成,应用层不需要关心具体的解码实现。
MediaCodec的状态切换
MediaCodec的生命周期会经历几个关键状态,理解状态切换是正确使用它的前提:
| 状态 | 说明 |
|---|---|
| Uninitialized | 初始状态,调用MediaCodec.createDecoderByType或者createEncoderByType后进入 |
| Configured | 调用configure方法配置解码参数后进入,此时已经设置好 MIME类型、分辨率、采样率等参数 |
| Executing | 调用start方法后进入,分为Flushed、Running、End of Stream三个子状态,是实际处理数据的状态 |
| Released | 调用release方法后进入,释放相关资源,之后不能再使用这个MediaCodec实例 |
MediaCodec解码视频的完整流程
下面以解码H.264视频流为例,展示MediaCodec的实际使用步骤:
1. 创建并配置MediaCodec
首先根据视频的MIME类型创建解码器,然后配置对应的参数,比如视频宽高、CSD参数(编码特定的数据,比如SPS、PPS):
// 创建H.264解码器
String mimeType = "video/avc";
MediaCodec mediaCodec = MediaCodec.createDecoderByType(mimeType);
// 配置解码参数,设置视频宽高、CSD信息
MediaFormat mediaFormat = MediaFormat.createVideoFormat(mimeType, 1920, 1080);
// 假设已经拿到SPS和PPS的字节数组 sps, pps
mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(sps));
mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(pps));
// 配置解码器,surface可以为null,后续从输出缓冲区拿YUV数据
mediaCodec.configure(mediaFormat, null, null, 0);
// 启动解码器
mediaCodec.start();2. 输入编码数据到缓冲区
循环获取可用的输入缓冲区,把需要解码的H.264 NALU数据塞进去,然后提交缓冲区给MediaCodec处理:
// 获取可用的输入缓冲区索引,超时时间10ms
int inputBufferIndex = mediaCodec.dequeueInputBuffer(10000);
if (inputBufferIndex >= 0) {
// 拿到输入缓冲区
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
inputBuffer.clear();
// 把H.264数据写入缓冲区,假设h264Data是待解码的NALU字节数组
inputBuffer.put(h264Data);
// 提交输入缓冲区,pts是时间戳,单位是微秒
mediaCodec.queueInputBuffer(inputBufferIndex, 0, h264Data.length, pts, 0);
}3. 获取解码后的输出数据
循环获取已经解码完成的输出缓冲区,拿到里面的YUV数据做后续处理,处理完之后释放缓冲区还给MediaCodec:
// 创建输出缓冲区信息对象
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
// 获取可用的输出缓冲区索引,超时时间10ms
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 10000);
if (outputBufferIndex >= 0) {
// 拿到输出缓冲区
ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex);
// 这里可以拿到解码后的YUV数据,做渲染或者后续处理
// 处理完成后释放输出缓冲区
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
// 输出缓冲区发生变化,重新获取即可
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// 输出格式发生变化,比如宽高变化,重新获取输出格式
MediaFormat newFormat = mediaCodec.getOutputFormat();
}4. 停止并释放资源
解码完成后,需要停止解码器并释放相关资源,避免内存泄漏:
// 停止解码器 mediaCodec.stop(); // 释放资源 mediaCodec.release();
常见问题说明
使用MediaCodec时经常会遇到解码失败的问题,大部分情况是CSD参数没有正确设置,或者输入的数据不是完整的NALU单元。另外不同设备的MediaCodec实现可能有差异,遇到兼容性问题时可以尝试切换不同的解码方式,或者做设备适配。
总的来说,MediaCodec的本质就是系统给应用层提供的硬件解码调用入口,通过缓冲区传递数据,让硬件完成解码工作,相比软解码能大幅降低CPU占用,提升解码效率,是Android音视频开发中必备的技能点。
MediaCodecAndroid_硬解码软解码音视频解码修改时间:2026-05-31 05:12:37