## 第1章 MediaCodec基础概念:编解码器类型、同步与异步模式、输入输出缓冲区模型 ### 1.1 编解码器类型 MediaCodec 是 Android 平台提供的底层多媒体编解码 API,支持软件与硬件加速编解码。根据实现方式与用途,编解码器可分为以下三类:
类型 实现方式 典型场景 性能特点
软件编解码器 纯 CPU 运行,依赖 OMX 组件 测试、兼容性验证、低分辨率 稳定性高、功耗较高、帧率受限
硬件编解码器 DSP/GPU 专用单元 实时视频通话、高帧率录制 低功耗、高吞吐、延迟敏感
MediaCodec 安全编解码器 TrustZone / 安全通路 DRM 内容播放、付费视频 加密传输、缓冲区不可直接读取
关键结论:Android 优先返回硬件编解码器实例。通过 MediaCodecList 查询 FEATURE_HardwareAccelerated 标志可确认当前编码器是否为硬件加速。
在编解码器类型选择上,遵循“应用场景优先”原则: - 实时通信(WebRTC / 直播推流) → 强制硬件编码,降低延迟 - 本地视频编辑 / 转码 → 按分辨率与码率自动选择 - DRM 保护播放 → 必须使用安全编解码器 ### 1.2 同步与异步模式 MediaCodec 提供两种操作模式,决定应用如何与编解码器交互输入输出缓冲区。
模式 API 形态 缓冲区管理 推荐场景
同步模式 dequeueInputBuffer() / dequeueOutputBuffer() 手动轮询,阻塞或超时 简单播放器、批量转码
异步模式 设置 MediaCodec.Callback 回调驱动,自动排队 实时流、低延迟应用
常见错误:在异步模式下混合使用同步 API(直接调用 dequeueOutputBuffer)会触发 IllegalStateException。必须先通过 setCallback 注册回调,再调用 start()。
**同步模式典型流程:**
  1. 调用 dequeueInputBuffer(-1) 获取空闲输入缓冲区索引(阻塞等待)
  2. 填充数据到 ByteBuffer,调用 queueInputBuffer(index, ...)
  3. 调用 dequeueOutputBuffer(info, TIMEOUT_US) 获取输出
  4. 处理输出 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 个)。应用和编解码器之间通过**索引**交换缓冲区所有权。 MediaCodec 输入输出缓冲区模型 输入缓冲区池 缓冲 0 缓冲 1 缓冲 2 缓冲 3 输出缓冲区池 缓冲 0 缓冲 1 缓冲 2 缓冲 3 dequeueInputBuffer dequeueOutputBuffer MediaCodec 编解码引擎 (同步/异步) 应用进程 填充/消费缓冲区 queueInputBuffer releaseOutputBuffer ● 同步模式:应用主动 dequeue / queue,阻塞或超时 ● 异步模式:Callback 回调 onInputAvailable / onOutputAvailable **缓冲区所有权流转规则:** | 阶段 | 所有者 | 描述 | |------|--------|------| | 空闲 | 应用 | 编解码器返回缓冲区索引,应用可填充/读取 | | 提交 | 编解码器 | 调用 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 线程 |
调试技巧:启用编解码器日志 adb shell setprop debug.stagefright.ccodec 1 可输出详细的缓冲区流转信息,用于定位死锁或缓冲区饥饿。
### 1.6 总结 - 硬件编解码器是首选,安全编解码器仅用于 DRM 场景 - 同步模式适用于简单转码,异步模式适合实时流处理 - 缓冲区模型基于索引所有权转移,注意同步 API 不可在异步模式下混用 - 输入缓冲区的填充速率需与输出消费速率匹配,否则引发丢帧或内存压力