第二章 实例化流程全景:从 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();

很多同学以为就这么三行…… 但底层跑了一串复杂的流程。

我整理了一张流程图,你边看边对照:

MediaCodec 实例化流程全景 createEncoder/Decoder openComponent(native) setCodecSpecificData configure start 关键耗时节点(我在项目中常碰到的) • openComponent: 加载 OMX 插件 / Stagefright 组件,可能涉及权限检查与进程通信 • setCodecSpecificData: 设置 CSD(如 SPS/PPS),部分编码器需要解析后再创建内部资源 • configure: 分配 buffer 池、设置参数、协商底层能力 • 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 或 YUV420Surface 模式快 30% 以上
比特率模式CQ / VBR / CBRCBR 在低端芯片上配置较慢

2.6 第五步:start —— 真正“拉闸”

start 是最后一步。

它做的事:

  • 启动工作线程:编解码线程开始跑起来
  • 发送 start 命令给组件:可能触发硬件初始化、时钟开始
  • 初次 buffer 分配:如果有 buffer 还没配好,这里会补全

嗯,这里要注意:如果你的编解码器在 start 之前已经通过 setInputBufferInfo 塞了 CSD,那么 start 会先处理这些 CSD,然后才进入运行状态。

我曾经在调试一个编码器时,发现 start 耗时 200ms——后来发现是因为配置了 KEY_BITRATE_MODE 为 CBR,而底层驱动在 start 时要做一次初始码率校准。换成 BITRATE_MODE_CQ 后直接降到 30ms。

实例化整体耗时分布(基于某骁龙 8 平台实测):

createEncoder15ms
configure45ms
start60ms
总和120ms

注意:这里还没算首次 buffer 交换的等待时间。如果你做性能优化,要把 configure 和 start 之间的“死区”也考虑进去。

2.7 我从这个流程学到的东西

  • 绝不要在 UI 线程直接调 create 和 start,我见过 ANR 就是因为 configure 跨进程卡住了。
  • 尽量预创建 MediaCodec 实例并缓存,避免频繁实例化。
  • 如果一定要多次创建,考虑复用同一个组件(但不是所有芯片都支持 reset)。
  • 不同芯片的流程耗时差异很大:高通、MTK、三星各有一套底层实现,start 耗时可能差 5 倍。

好,这一章就把实例化的来龙去脉讲清楚了。