Android MediaCodec 实战:高效解码 H264 到 Surface 的避坑指南
·
背景与痛点
在 Android 平台上处理视频解码时,MediaCodec 是开发者最常用的工具之一。但直接将 H264 视频流解码到 Surface 时,往往会遇到一些棘手的问题,比如帧丢失、延迟过高甚至内存泄漏。这些问题的背后,通常是由于对 MediaCodec 的工作机制理解不够深入,或者配置不当导致的。

常见痛点包括:
- 帧率不稳定:解码速度跟不上视频的帧率,导致卡顿或跳帧。
- 内存泄漏:未正确释放
MediaCodec或缓冲区资源,导致内存占用持续增加。 - 同步问题:解码线程与渲染线程未正确同步,导致画面撕裂或延迟。
技术选型对比
在 Android 中,渲染视频流的视图组件主要有 SurfaceView、TextureView 和 SurfaceTexture。它们各有优劣:
- SurfaceView:性能最佳,适合高帧率视频,但层级管理较复杂。
- TextureView:支持动画和变形,但性能略低于
SurfaceView。 - SurfaceTexture:更灵活,可直接与 OpenGL ES 结合使用,适合定制化需求。
对于大多数视频播放场景,SurfaceView 是首选,因为它直接由硬件合成器处理,性能最优。
核心实现
1. 配置 MediaCodec
MediaCodec 的配置是关键。以下是核心步骤:
- 创建
MediaCodec实例并配置解码器。 - 设置视频格式(如
MediaFormat.MIMETYPE_VIDEO_AVC)。 - 指定颜色格式(推荐
COLOR_FormatSurface以兼容大多数设备)。 - 绑定
Surface并启动解码器。
关键代码片段(Kotlin):
val mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
val format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height)
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
mediaCodec.configure(format, surface, null, 0)
mediaCodec.start()
2. 输入与输出缓冲区管理
MediaCodec 通过输入和输出缓冲区与开发者交互。以下是典型流程:
- 通过
dequeueInputBuffer获取输入缓冲区。 - 填充 H264 数据到缓冲区。
- 通过
queueInputBuffer提交数据。 - 通过
dequeueOutputBuffer获取解码后的帧。 - 渲染到
Surface后释放缓冲区。
3. Surface 绑定
绑定 Surface 是渲染的关键。通常通过 SurfaceView 或 TextureView 获取 Surface,并将其传递给 MediaCodec。
性能优化
1. 异步模式
MediaCodec 支持异步模式,通过回调机制减少主线程阻塞。配置方法:
mediaCodec.setCallback(object : MediaCodec.Callback() {
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
// 处理输入缓冲区
}
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, info: MediaCodec.BufferInfo) {
// 处理输出缓冲区
}
})
2. 硬件加速
确保设备支持硬件解码,可通过 MediaCodecList 查询:
val codecInfo = MediaCodecList(MediaCodecList.ALL_CODECS)
.codecInfos.find { it.isEncoder && it.supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_AVC) }
3. 缓冲区复用
避免频繁分配和释放缓冲区,可以在解码完成后复用缓冲区。
避坑指南
- 线程同步:确保解码和渲染在独立线程中运行,避免 ANR。
- 资源释放:在
onDestroy或onPause中正确释放MediaCodec和Surface。 - 帧率控制:根据设备性能动态调整解码帧率,避免过载。
完整代码示例
以下是一个完整的解码流水线示例(Kotlin):
// 创建 MediaExtractor 并设置数据源
val extractor = MediaExtractor()
extractor.setDataSource(context, uri, null)
// 选择视频轨道
val trackIndex = extractor.trackIndices.first {
extractor.getTrackFormat(it).getString(MediaFormat.KEY_MIME) == MediaFormat.MIMETYPE_VIDEO_AVC
}
extractor.selectTrack(trackIndex)
// 配置 MediaCodec
val format = extractor.getTrackFormat(trackIndex)
val mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
mediaCodec.configure(format, surfaceView.holder.surface, null, 0)
mediaCodec.start()
// 解码循环
while (true) {
val inputBufferId = mediaCodec.dequeueInputBuffer(TIMEOUT_US)
if (inputBufferId >= 0) {
val inputBuffer = mediaCodec.getInputBuffer(inputBufferId)
val sampleSize = extractor.readSampleData(inputBuffer!!, 0)
if (sampleSize > 0) {
mediaCodec.queueInputBuffer(inputBufferId, 0, sampleSize, extractor.sampleTime, 0)
extractor.advance()
}
}
val bufferInfo = MediaCodec.BufferInfo()
val outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US)
if (outputBufferId >= 0) {
mediaCodec.releaseOutputBuffer(outputBufferId, true)
}
}
结语
通过合理配置 MediaCodec 和优化解码流程,可以显著提升 H264 视频的解码性能。建议你在实际项目中尝试这些优化方法,并对比性能数据。如果有更好的实践或问题,欢迎分享交流!

更多推荐


所有评论(0)