Android MediaCodec实战:H264/H265解码性能优化全攻略(附完整代码)
如果你正在开发一个直播应用,或者在做视频编辑功能,大概率已经和 Android 的 MediaCodec 打过交道了。这个底层的编解码器接口功能强大,但初次接触时,那种“明明代码逻辑都对,为什么画面就是卡顿、延迟高”的挫败感,相信很多开发者都体会过。尤其是在处理高分辨率、高帧率的 H264 或 H265 视频流时,性能问题会像幽灵一样突然出现,让用户体验大打折扣。
这篇文章不会重复 MediaCodec 的基础用法,网上那样的教程已经很多了。我们直接切入核心:如何系统性地优化 MediaCodec 的解码性能,解决那些在真实项目中导致卡顿和延迟的深层次问题。我们将从缓冲区管理的微观调度,到线程模型的宏观设计,再到 MediaFormat 参数调优的细节,层层递进。目标很明确,就是让你在应对直播连麦、实时滤镜、高清视频播放这些对流畅度有严苛要求的场景时,手里有更趁手的工具和更清晰的思路。
1. 理解性能瓶颈:解码流程中的关键耗时点
在开始优化之前,我们得先弄清楚时间都花在哪了。一个典型的 MediaCodec 解码流程,可以粗略地分为几个阶段:输入缓冲区获取、数据填充与提交、解码运算、输出缓冲区渲染。每个阶段都可能成为瓶颈。
输入/输出缓冲区队列操作:dequeueInputBuffer 和 dequeueOutputBuffer 是同步调用,它们会阻塞调用线程直到有可用的缓冲区或超时。不合理的超时设置(比如示例中固定的 10000 毫秒)会导致线程无意义地长时间等待,影响整体吞吐量。更糟糕的是,如果生产者(填充数据)和消费者(取走解码后数据)速度不匹配,队列会很快被填满或掏空,造成卡顿。
解码器内部处理:这部分发生在 Native 层和硬件层面,我们无法直接控制,但可以通过传递正确的参数来影响其行为。例如,错误的 KEY_FRAME_RATE 设置可能导致解码器内部缓冲区分配不合理。
Surface 渲染:当使用 releaseOutputBuffer(bufferIndex, true) 将解码后的图像直接渲染到 Surface 时,渲染本身也可能成为瓶颈,特别是当 SurfaceView 的 UI 线程忙于其他工作,或者渲染的纹理尺寸过大时。
内存与数据拷贝:示例代码中将整个视频文件一次性读入内存(getBytes 方法),对于大视频文件这是不可取的。此外,在 Java 层和 Native 层之间来回拷贝 ByteBuffer 数据也会带来开销。
注意:性能优化永远是“测量-假设-验证”的循环。在动手修改代码前,务必使用
System.nanoTime()或 Profiler 工具对上述各个阶段进行耗时打点,找到当前场景下最突出的那个瓶颈。
2. 缓冲区管理:从粗放到精细的调度策略
原始示例中的缓冲区管理非常基础,它使用了已废弃的 getInputBuffers() 方法,并且采用轮询方式。在现代 Android 开发中,我们有更高效、更灵活的方式。
2.1 使用异步回调模式
从 Android 5.0 (API 21) 开始,MediaCodec 支持异步操作模式。这是提升吞吐量和降低延迟的首选方案。它通过回调通知你缓冲区事件,避免了轮询带来的 CPU 空转和线程调度开销。
// 创建解码器后,设置回调
mediaCodec.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
// 有可用的输入缓冲区,立即在此回调中填充数据
ByteBuffer inputBuffer = codec.getInputBuffer(index);
// ... 填充你的 H264/H265 数据
codec.queueInputBuffer(index, ...);
}
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
// 解码完成一帧,立即在此回调中处理或渲染
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// 处理编解码器特定数据,通常直接丢弃
codec.releaseOutputBuffer(index, false);
return;
}
// 渲染到 Surface
codec.releaseOutputBuffer(index, true);
// 或者,如果你需要获取 YUV 数据:
// ByteBuffer outputBuffer = codec.getOutputBuffer(index);
// ... 处理 outputBuffer 数据 ...
// codec.releaseOutputBuffer(index, false);
}

&spm=1001.2101.3001.5002&articleId=153558910&d=1&t=3&u=b35e74e181f943ba8faa987e66561aab)
1万+

被折叠的 条评论
为什么被折叠?



