4. 内部状态机转换:uninitialized → executing
做多媒体开发的人都知道,MediaCodec 的 start() 调用不是瞬间完事的。它背后是一套严谨的状态机跳转,从 Uninitialized 一路走到 Executing。我把这条路径上的每一步都掰开揉碎,看看时间到底花在哪。
4.1 状态机跳转全景
先搞清楚整个生命周期:
- Uninitialized — 刚 new 出来,啥都没干
- Initialized — 内部分配了基础对象,但还没配
- Configured — 配置完 format,等待启动
- Executing — 跑起来了,能处理数据
从 Uninitialized → Executing,中间会经历两次关键的系统调用:configure() 和 start()。别小看这两个调用,里面藏着的耗时操作,才是性能瓶颈的真身。
核心结论:80% 的 start 耗时发生在 Configured → Executing 这个阶段,尤其是底层组件初始化。
4.2 每一步的耗时拆解
我把每一步都用手上的 trace 数据整理了一下,你可以对照自己项目里的 log 看看。
| 状态转换 | 触发方法 | 典型耗时 (ms) | 主要耗时点 |
|---|---|---|---|
| Uninitialized → Initialized | new MediaCodec() | 0.5~2 | 对象构造、注册进程端 |
| Initialized → Configured | configure() | 3~15 | MediaFormat 校验、Codec 选择、资源预申请 |
| Configured → Executing | start() | 10~80 | 组件启动、buffer 池分配、硬件/软件引擎创建 |
有意思的是,第二行和第三行加起来几乎占了整个启动链路的 95% 以上。尤其是第三段,我测过一些低端手机,能跑到 80ms,用户直接感觉卡了一下。
4.3 深入 Configured → Executing 的“暗箱”
为什么这段最慢?我开了 trace 才看清内部干了啥:
- 底层组件创建 — 如果是硬件解码器,要去跟驱动握手;软编解码则要加载动态库,做指令集检测。
- Buffer 池初始化 — 申请输入/输出 buffer,有些设备还会提前映射 GPU 内存。
- 线程启动 — Codec 内部会创建几个工作线程,等待 buffer 流转。
- 参数协商 — 把你在
configure()里传的 format 往下传,驱动或库要确认是否支持。
这里我贴一段从系统 log 里抓到的关键时序(简化版):
// 伪代码示意 start() 内部流程
status_t MediaCodec::start() {
// [1] 状态检查 + 锁
sp<AMessage> msg = new AMessage(kWhatStart, mCodecLooper);
// [2] 发送到 looper,等待执行
status_t err = postAndWait(msg);
// [3] 内部实际处理
// - 设置状态为 kStateExecuting
// - 调用 mCodec->start()
// - 启动 input/output 轮询线程
return err;
}
你可能会觉得奇怪:不就发个消息等结果吗?实际上 postAndWait 里面的同步开销不大,真正吃时间的是 mCodec->start() 那个方法。在底层,那是另一个世界。
4.4 我踩过的坑
我曾经在项目中遇到过 某款芯片解码 H.265 时 start 耗时高达 120ms。查了三天,发现是 configure() 里传的 KEY_FRAME_RATE 和实际流不匹配,驱动反复做分辨率重协商。去掉那个参数后降到 25ms。
所以你的配置参数尽量精准,别让底层去做 guess work。
避坑指南:别在 configure() 里传过多冗余参数,尤其是帧率、比特率这类辅助提示。某些底层驱动会据此做大量判断,反而拖慢 start。
4.5 状态机转换流程图
下面这张图把整个跳转路径和各自的主要耗时因素画清楚了,建议你保存下来做复盘对比。
4.6 实战建议
- 预热: 如果你的 App 要频繁开关 Codec(比如视频通话),可以在后台提前
configure()一次,但不要start(),保留Configured状态,需要时立刻start()。这样跳过前两步的耗时。 - 对症下药: 如果 start 时间集中在 10ms 以下,可以不必费神;如果超过 30ms,先去查硬件初始化过程,看驱动是否在协商环节有问题。
- 离线预分配: 我尝试过在 Application 初始化时主动调用
MediaCodecList获取支持的信息,让系统把一些缓存资源提前 warm up,不过效果因品牌而异。
技巧: 用 adb shell dumpsys media.codec 可以 dump 出当前 Codec 实例的状态和耗时统计,比直接看 log 更直观。配合 systrace 定位具体哪个 binder 调用拖了后腿。
说到这你应该明白了:状态机不是白纸黑字的抽象概念,而是实实在在的时间开销分布。下回别人问起 start 为什么慢,你就把这张图扔给他——问题的根,往往在 Configured → Executing 那一步的四个子步骤里。