MediaCodec 简介与 start 阶段概述
我最早接触 Android 多媒体开发的时候,最头疼的就是 MediaCodec 的 start 方法。明明代码逻辑都写对了,可每次调用 start(),总感觉卡了那么几百毫秒甚至一两秒。尤其在做视频录制或者实时直播的时候,这种延迟简直就是灾难。后来我花了大量时间把整个启动流程拆开来看,才搞清楚里面的门道。
MediaCodec 是 Android 提供的底层音视频编解码 API,说白了就是系统给应用层的一个“编解码开关”。你不需要直接操作硬件驱动,也不用纠结底层是 OMX 还是 Codec2 框架,只要调几个方法,就能让芯片去干编解码的脏活累活。但问题就在于 —— 这个“开关”从拧下去到真正通电干活,中间有一大堆步骤。
MediaCodec 到底是个啥?
可以先把它理解成一个“黑盒子”。你喂给它原始视频数据,它吐出来压缩后的码流;或者反过来,喂码流,吐原始画面。Android 里几乎所有音视频相关的 App(相机、播放器、直播推流)背后都在用这个 API。
它的生命周期很清晰:
- createByCodecName / createDecoderByType —— 创建编解码器实例
- configure —— 设置参数,比如分辨率、帧率、码率、色彩格式
- start —— 启动编解码器,准备接收数据
- dequeueInputBuffer / queueInputBuffer —— 往里送数据
- dequeueOutputBuffer —— 取出结果
- stop / release —— 停止并释放资源
其中 start 阶段 是整个生命周期里最不可控、最容易出耗时的环节。我自己的项目里,有一次在低端机上测到 start 耗时超过 800ms,直接导致首帧渲染延迟。嗯,后面我们详细拆。
start 阶段到底干了什么?
你以为 start() 只是一个状态切换?其实它背后是一整套“硬件就绪”流程。我按自己的理解把它分成几个核心步骤:
| 步骤 | 描述 | 典型耗时 |
|---|---|---|
| 1. 组件实例化 | 在底层创建真正的编解码器组件,分配内部对象 | 5-50ms |
| 2. 底层驱动加载 | 如果是硬件编解码,需要加载对应厂商的驱动模块(OMX 或 Codec2 插件) | 20-200ms |
| 3. 缓冲区分配 | 分配输入输出 Buffer,涉及到物理内存或 Ion 内存 | 10-80ms |
| 4. 参数内部协商 | 编解码器根据你 configure 时传入的参数,做最终确认和修正 | 2-20ms |
| 5. 硬件初始化 | 真正的硬件寄存器配置、编解码核心准备就绪 | 50-300ms |
| 6. 异步回调就绪 | 等待底层通知“我已经准备好了”,这个回调会触发 onOutputFormatChanged | 0-50ms |
你想想看,这些步骤很多都是在底层完成的,Android 应用层根本控制不了。所以为什么不同手机上 MediaCodec start 耗时差别巨大?就是因为硬件驱动实现差异。
我踩过的坑
我曾经在做一个直播推流 App 时,遇到一个奇怪现象:每过几分钟重新启动一次编码器,UI 就会卡一下。后来一测,发现是每次 start 都去重新加载 OMX 驱动模块,耗了 150ms 左右。我当时的解决方法是:保持一个 idle 状态的编码器实例,下次 start 时复用,而不是每次都 create + start。
还有一个问题——不要在主线程里调用 start()。我记得有个同事直接把 MediaCodec 的所有操作都写在 UI 线程里,结果 start 耗时太长导致 ANR。这种低级错误我后来每次培训都会提:所有编解码操作必须放到独立线程,最好用 HandlerThread 或者 ExecutorService。
onOutputFormatChanged 回调才能真正开始送数据。
start 阶段的核心流程
为了更直观地看清这些步骤之间的关系,我画了一张流程图(下面这个 SVG 就是)。它把 start 内部的主要环节按“用户态”、“底层框架”、“硬件驱动”三个层面做了分组,箭头表示调用顺序。
从图上能看得很清楚:start() 调用后不是直接返回,而是先经过底层框架的组件实例化和驱动加载,然后下到硬件驱动层做缓冲区分配、硬件初始化,最后再通过回调告诉上层“我准备好了”。每个环节都可能成为瓶颈。
个人经验总结
我建议你在做性能优化的时候,先拿 Trace 或者 systrace 抓一下 start 阶段各个线程的耗时分布。用 adb 命令就行:
adb shell trace-start MediaCodecStart
# 在你代码里执行 create -> configure -> start
adb shell trace-stop
抓出来的 trace 里,重点关注 MediaCodec.start 以及下面的 OMXNodeInstance::allocateBuffer、Codec2Client::start 这些 tag。如果硬件初始化时间特别长,那基本就是芯片驱动的问题,只能通过提前预热或者复用实例来绕过去。
当然,start 阶段的耗时也不是一成不变的。不同 Android 版本、不同芯片、甚至同一台机器不同负载下,测量结果都可能相差很大。这也是我为什么一直强调:一定要在目标设备上实测,别光看文档。
嗯,这一章就先把整体流程和耗时来源理清楚。后面我们会深入每一个环节,看看代码层面到底发生了什么。