第二章 实例化流程全景:从 createEncoder/Decoder 到 configure 再到 start 的完整路径
说实话,很多做视频的同学对 MediaCodec 的印象就是“调几个方法就能用”。
但一旦碰到性能问题,尤其是实例化耗时超标,就抓瞎了。
我个人喜欢先把整条链路摸清,再逐段分析。这一章我就带你走一遍 MediaCodec 从创建到开始的完整路径,把每个环节的“坑”都指出来。
2.1 整条调用链到底有多长?
先看最核心的三个 API:
MediaCodec codec = MediaCodec.createEncoderByType(type);
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
codec.start();
很多同学以为就这么三行…… 但底层跑了一串复杂的流程。
我整理了一张流程图,你边看边对照:
2.2 第一步:createEncoder / createDecoder
普通接口确实就一行。
但底层做了什么?
- 创建 MediaCodec 对象:分配 Java 层对象 + Native 层的缓冲区管理器
- 查找合适的组件:Android 会根据 MIME 或名字去 MediaCodecList 里匹配
- 加载动态库 / 驱动:如果是硬件编解码,需要打开 HAL 或 OMX 插件
我记得之前在某款高通手机上,光这一步就花了 80ms。为啥?因为它要去检查多个 Codec 名,然后挨个 try open。
关键指标:
| API 调用 | createEncoderByType("video/avc") |
| 典型耗时 | 5ms - 100ms(取决于软/硬编解码、组件数量) |
| 主要瓶颈 | 组件名匹配、库加载、权限检查 |
2.3 第二步:openComponent(隐藏在 create 内部)
其实 create 完,Android 会立即调用 native_setup 去打开底层组件。
这个过程你可以理解为“向系统申请一个编解码实例”。
- 如果是 OMX 架构:调用
OMX_GetHandle,启动一个 IL 客户端 - 如果是 MediaCodec(ACodec):Stagefright 会实例化对应的 CodecBase
我遇到过几次这里抛 MediaCodec.CodecException,提示“服务端无响应”——说白了就是系统服务降级了,或者同时打开的编解码器太多。
⚠️ 注意:openComponent 可能会跨进程(mediaserver),如果 mediaserver 卡顿或死锁,create 就会超时。我曾经在一台低内存设备上试过,createEncoder 挂了 500ms 才返回,就是 mediaserver 在 GC。
2.4 第三步:setCodecSpecificData(可选但关键)
对于编码器,你通常不需要手动设置;但对于解码器,特别是 H.264/HEVC,你需要先设置 CSD(SPS/PPS)。
这个操作会在 configure 之后、start 之前,通过 setInputBufferInfo 传递,但底层在 start 时才会解析。
有个小技巧:提前准备好 CSD,避免 start 后临时传入造成 pipeline 阻塞。我个人习惯在 configure 之后立刻做一次 dequeueInputBuffer 然后塞 CSD,但要注意调用顺序。
💡 如果你使用的是 MediaCodec.createByCodecName 而不是 MIME 方式,可以跳过一部分组件匹配逻辑,减少 20-30ms 的 create 时长。
2.5 第四步:configure —— 真正干活的开始
configure(MediaFormat format, Surface surface, Crypto crypto, int flags)
这个方法会:
- 校验参数:分辨率、帧率、比特率、颜色格式是否被支持
- 配置底层:往组件写配置命令(比如 OMX_SetParameter)
- 分配 buffer 池:如果是 Surface 输入/输出,还会分配 GraphicBuffer;如果是 ByteBuffer 模式,则分配 HeapBuffer
这里有个坑:配置不合适的 profile/level 会失败。比如你把 AVC High 10 扔给一个只支持 Baseline 的编码器,configure 直接抛异常。
我曾经在改款平板项目上,configure 耗时从 10ms 变成 120ms,一查原因是它的 Surface 纹理格式要求在配置阶段重新分配 EGL 共享 buffer,导致掉进 drm 驱动里了。
| 配置项 | 推荐范围 | 实测耗时影响 |
|---|---|---|
| 分辨率 | 尽量匹配 16 对齐 | 对齐不当会增加 2-5ms |
| 颜色格式 | COLOR_FormatSurface 或 YUV420 | Surface 模式快 30% 以上 |
| 比特率模式 | CQ / VBR / CBR | CBR 在低端芯片上配置较慢 |
2.6 第五步:start —— 真正“拉闸”
start 是最后一步。
它做的事:
- 启动工作线程:编解码线程开始跑起来
- 发送 start 命令给组件:可能触发硬件初始化、时钟开始
- 初次 buffer 分配:如果有 buffer 还没配好,这里会补全
嗯,这里要注意:如果你的编解码器在 start 之前已经通过 setInputBufferInfo 塞了 CSD,那么 start 会先处理这些 CSD,然后才进入运行状态。
我曾经在调试一个编码器时,发现 start 耗时 200ms——后来发现是因为配置了 KEY_BITRATE_MODE 为 CBR,而底层驱动在 start 时要做一次初始码率校准。换成 BITRATE_MODE_CQ 后直接降到 30ms。
实例化整体耗时分布(基于某骁龙 8 平台实测):
| createEncoder | 15ms |
| configure | 45ms |
| start | 60ms |
| 总和 | 120ms |
注意:这里还没算首次 buffer 交换的等待时间。如果你做性能优化,要把 configure 和 start 之间的“死区”也考虑进去。
2.7 我从这个流程学到的东西
- 绝不要在 UI 线程直接调 create 和 start,我见过 ANR 就是因为 configure 跨进程卡住了。
- 尽量预创建 MediaCodec 实例并缓存,避免频繁实例化。
- 如果一定要多次创建,考虑复用同一个组件(但不是所有芯片都支持 reset)。
- 不同芯片的流程耗时差异很大:高通、MTK、三星各有一套底层实现,start 耗时可能差 5 倍。
好,这一章就把实例化的来龙去脉讲清楚了。