共计 2548 个字符,预计需要花费 7 分钟才能阅读完成。
MediaCodec 工作流程全景图
MediaCodec 是 Android 多媒体框架中的核心编码组件,其工作流程可以概括为 ” 生产者 - 消费者 ” 模型。我们用一张流程图来理解这个抽象概念:

start
: 创建 MediaCodec 实例;
: 配置编码参数(分辨率 / 帧率等);
if (Surface 模式?) then (是)
: 创建 Surface 输入通道;
else (否)
: 准备 ByteBuffer 输入队列;
endif
: 启动编码器;
repeat
: 获取空闲输入缓冲区;
: 填充原始数据(YUV);
: 提交到编码器;
: 获取输出缓冲区;
: 处理编码后数据;
repeat while (还有数据?)
: 释放编码器资源;
stop
两种输入模式深度解析
1. Surface 模式
- 适用场景:Camera 预览或 OpenGL 渲染管线
- 优势:
- 自动处理帧率同步
- 零拷贝内存传输
- 支持硬件加速
- 典型代码:
val codec = MediaCodec.createEncoderByType("video/avc") val surface = codec.createInputSurface()
2. ByteBuffer 模式
- 适用场景:
- 已有 YUV 数据需要处理
- 需要精确控制每帧时间戳
- 关键步骤:
// 获取输入缓冲区索引 val inputBufferIdx = codec.dequeueInputBuffer(TIMEOUT_US) if (inputBufferIdx >= 0) {val inputBuffer = codec.getInputBuffer(inputBufferIdx) // 填充 YUV 数据 yuvConverter.fillYUVData(inputBuffer) // 提交编码器 codec.queueInputBuffer(inputBufferIdx, ...) }
编码参数配置黄金法则
- 比特率(Bitrate)
- 计算公式:
width * height * fps * motionFactor * 0.07 -
示例:1080p30 视频推荐
3500-5000kbps -
帧率(FPS)
- 必须与输入源帧率一致
-
错误设置会导致:
- 帧率过高:编码器丢帧
- 帧率过低:视频卡顿
-
关键帧间隔(I-frame Interval)
- 直播场景推荐 2 秒
- 点播视频可以 10-30 秒
- 设置方法:
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2)
YUV420P 格式转换实战
这是最常见的色彩空间转换场景:
fun NV21toYUV420Planar(nv21: ByteArray, width: Int, height: Int): ByteArray {val yuv420p = ByteArray(width * height * 3 / 2)
// Y 分量直接拷贝
System.arraycopy(nv21, 0, yuv420p, 0, width * height)
// UV 分量处理
val uvSize = width * height / 4
for (i in 0 until uvSize) {yuv420p[width * height + i] = nv21[width * height + i * 2 + 1] // U 分量
yuv420p[width * height + uvSize + i] = nv21[width * height + i * 2] // V 分量
}
return yuv420p
}
异步回调处理技巧
现代 Android 版本推荐使用异步模式:
codec.setCallback(object : MediaCodec.Callback() {override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {// 获取并填充输入缓冲区}
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo
) {val outputBuffer = codec.getOutputBuffer(index)
// 处理编码后的 H.264 数据
codec.releaseOutputBuffer(index, false)
}
// 错误处理不可少!override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {Log.e(TAG, "编码器错误: ${e.errorCode}")
}
})
高频踩坑点排查
1. 颜色空间不匹配
- 症状:视频发绿或颜色失真
- 解决方案:
- 确认 YUV 格式与
MediaFormat.KEY_COLOR_FORMAT一致 - 华为设备需要特殊处理
COLOR_FormatYUV420SemiPlanar
2. 编码器初始化失败
- 排查步骤:
- 检查 MIME 类型支持
MediaCodecList.findEncoderForFormat - 验证分辨率是否是 16 的倍数
- 某些设备要求设置
KEY_PROFILE和KEY_LEVEL
3. 内存泄漏
- 预防措施:
try {// 使用编码器} finally {codec.stop() codec.release() surface?.release()}
性能优化三板斧
- 线程模型优化
-
建议使用双线程架构:
- 生产者线程:处理输入数据
- 消费者线程:处理输出数据
-
内存复用
- 复用 YUV 转换缓冲区
-
输出 ByteBuffer 池化
-
动态参数调整
- 根据设备温度调整帧率
- 网络环境变化时动态修改比特率
版本兼容性处理
不同 Android 版本的特殊处理:
- Android 5.0+:必须处理
MediaCodec.CRYPTO_EXCEPTION - Android 7.0+:
createInputSurface()行为变化 - Android 10+:要求动态设置
KEY_MAX_B_FRAMES
实战建议
最后给新手开发者的三个建议:
- 始终添加
codec.release()的异常保护 - 关键节点添加
MediaCodec.BufferInfo.presentationTimeUs日志 - 使用
adb shell dumpsys media.codec调试编码状态
通过本指南的系统学习,相信你已经掌握了 Android 视频编码的核心要点。编码器的使用就像学习骑自行车 – 刚开始可能会摔几次,但一旦掌握平衡,就能自由驰骋。遇到问题时,多查阅官方文档和源码注释,它们往往藏着解决问题的钥匙。
正文完
发表至: 移动开发
近三天内
