| 类型 | 实现方式 | 典型场景 | 性能特点 |
|---|---|---|---|
| 软件编解码器 | 纯 CPU 运行,依赖 OMX 组件 | 测试、兼容性验证、低分辨率 | 稳定性高、功耗较高、帧率受限 |
| 硬件编解码器 | DSP/GPU 专用单元 | 实时视频通话、高帧率录制 | 低功耗、高吞吐、延迟敏感 |
| MediaCodec 安全编解码器 | TrustZone / 安全通路 | DRM 内容播放、付费视频 | 加密传输、缓冲区不可直接读取 |
关键结论:Android 优先返回硬件编解码器实例。通过
在编解码器类型选择上,遵循“应用场景优先”原则:
- 实时通信(WebRTC / 直播推流) → 强制硬件编码,降低延迟
- 本地视频编辑 / 转码 → 按分辨率与码率自动选择
- DRM 保护播放 → 必须使用安全编解码器
### 1.2 同步与异步模式
MediaCodec 提供两种操作模式,决定应用如何与编解码器交互输入输出缓冲区。
MediaCodecList 查询 FEATURE_HardwareAccelerated 标志可确认当前编码器是否为硬件加速。
| 模式 | API 形态 | 缓冲区管理 | 推荐场景 |
|---|---|---|---|
| 同步模式 | dequeueInputBuffer() / dequeueOutputBuffer() |
手动轮询,阻塞或超时 | 简单播放器、批量转码 |
| 异步模式 | 设置 MediaCodec.Callback |
回调驱动,自动排队 | 实时流、低延迟应用 |
常见错误:在异步模式下混合使用同步 API(直接调用
**同步模式典型流程:**
dequeueOutputBuffer)会触发 IllegalStateException。必须先通过 setCallback 注册回调,再调用 start()。
- 调用
dequeueInputBuffer(-1)获取空闲输入缓冲区索引(阻塞等待) - 填充数据到
ByteBuffer,调用queueInputBuffer(index, ...) - 调用
dequeueOutputBuffer(info, TIMEOUT_US)获取输出 - 处理输出
ByteBuffer,调用releaseOutputBuffer(index, render)
codec.setCallback(object : MediaCodec.Callback() {
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
// 获取可用输入缓冲区
val inputBuffer = codec.getInputBuffer(index)
// 填充数据后调用 queueInputBuffer
}
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
// 获取可用输出缓冲区
val outputBuffer = codec.getOutputBuffer(index)
// 处理数据后调用 releaseOutputBuffer
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
// 编解码器异常处理
}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
// 输出格式变化(如动态分辨率变更)
}
})
### 1.3 输入输出缓冲区模型
MediaCodec 采用**环形生产者-消费者缓冲区模型**,编解码器维护一组固定数量的输入/输出缓冲区(通常 4~32 个)。应用和编解码器之间通过**索引**交换缓冲区所有权。
**缓冲区所有权流转规则:**
| 阶段 | 所有者 | 描述 |
|------|--------|------|
| 空闲 | 应用 | 编解码器返回缓冲区索引,应用可填充/读取 |
| 提交 | 编解码器 | 调用 queueInputBuffer 后,数据进入编解码器 |
| 处理中 | 编解码器 | 编解码器内部正在编码/解码(不可取消) |
| 输出就绪 | 应用 | dequeueOutputBuffer 返回后,应用可读取 |
| 释放 | 编解码器 | releaseOutputBuffer 归还缓冲区到池 |
性能要点:同步模式下 dequeueInputBuffer 若设为超时 0,必须准备好后备缓冲区预填充,否则会频繁返回 -1 导致丢帧。异步模式下 onInputBufferAvailable 回调不带超时,填充速度跟不上时需控制生产速率。
### 1.4 核心 API 速查
// 创建编解码器
val codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC) // 编码
val codec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC) // 解码
// 配置
val format = MediaFormat.createVideoFormat(MIMETYPE_VIDEO_AVC, 1920, 1080)
format.setInteger(MediaFormat.KEY_BIT_RATE, 4_000_000)
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
// 同步模式启动
codec.configure(format, surface, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
codec.start()
// 异步模式启动
codec.setCallback(callback, handler)
codec.configure(format, surface, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
codec.start()
// 结束与释放
codec.stop()
codec.release()
### 1.5 常见错误与调试
| 问题 | 原因 | 解决方案 |
|------|------|----------|
| MediaCodec.CodecException: 0x80000000 | 硬件资源耗尽 | 降级到软件编解码器,或增加超时重试 |
| ERROR_KEY_SERVICE: sized buffer offset 错误 | 输入缓冲区未正确填充 | 确认 `buffer.position()` 和 `buffer.limit()` |
| 输出帧顺序错乱 | B 帧未按显示顺序输出 | 使用 `BufferInfo.presentationTimeUs` 重排序 |
| 异步回调不触发 | 主线程 Looper 未准备 | 确保使用带 Handler 的 setCallback 或运行在 Looper 线程 |
调试技巧:启用编解码器日志
### 1.6 总结
- 硬件编解码器是首选,安全编解码器仅用于 DRM 场景
- 同步模式适用于简单转码,异步模式适合实时流处理
- 缓冲区模型基于索引所有权转移,注意同步 API 不可在异步模式下混用
- 输入缓冲区的填充速率需与输出消费速率匹配,否则引发丢帧或内存压力
adb shell setprop debug.stagefright.ccodec 1 可输出详细的缓冲区流转信息,用于定位死锁或缓冲区饥饿。