3. MediaCodec 初始化流程与异常处理

MediaCodec 的初始化是编解码适配中最容易崩溃的环节。三个核心 API——createByCodecName、configure、start——对应状态机中的 Uninitialized → Configured → Executing 两次跃迁。每一步都可能因厂商实现差异、参数越界、资源竞争而抛出异常。本章梳理完整流程、异常捕获模式及常见错误的对策。

MediaCodec 初始化流程与异常分支 MediaCodec 初始化流程与异常分支 createByCodecName CodecNotFoundException → 回退 MIME 匹配 configure IllegalStateException / 参数越界 start CodecException / 资源竞争 异常处理 & 资源释放 建议:三步分别使用 try-catch,并依赖 MediaCodecList 选择备选 CodecName Android 10+ 推荐使用 MediaCodec.CodecException 的错误码分类处理

3.1 三步初始化原理

MediaCodec 生命周期由状态机驱动:

  1. Uninitialized → createByCodecName(String name) → Configured
  2. Configured → configure(MediaFormat format, Surface surface, CryptoInfo crypto, int flags) → Initialized
  3. Initialized → start() → Executing

每个过渡都可能抛出 MediaCodec.CodecException(推荐 API 21+)或 IllegalStateException(早期版本)。

兼容性提示: Android 5.0 (API 21) 起,createByCodecName 在找不到编解码器时抛出 IllegalArgumentException,而非 MediaCodecList 返回 null。务必先调用 MediaCodecList.findEncoderForFormat() 或 findDecoderForFormat() 预校验。

3.2 关键 API 与常见错误表

API常见异常根因对策
createByCodecName IllegalArgumentException / CodecNotFoundException 编解码器名称不存在;厂商未注册该组件 使用 MediaCodecList 遍历匹配 MIME 类型,避免硬编码名称
configure IllegalStateException / CodecException: ERROR_INSUFFICIENT_RESOURCE format 参数无效(如分辨率超限、profile/level 不支持);surface 已被释放 获取 CodecCapabilities 验证 range;使用 SurfaceHolder 或 TextureView 确保 surface 有效
start CodecException: ERROR_KEY_FRAME / 资源耗尽 并发配置多个编解码器超过硬件限制;时序问题导致内部状态错乱 串行初始化;捕获异常后调用 release() 再重试

3.3 防御性初始化代码模板

MediaCodec codec = null;
try {
    // 步骤1:通过 MIME 获取推荐名称(防硬编码)
    String mime = "video/avc"; // H.264
    String codecName = MediaCodecList.findDecoderForFormat(
        MediaFormat.createVideoFormat(mime, 1920, 1080));
    if (codecName == null) throw new MediaCodec.CodecException(
        "No decoder for " + mime, MediaCodec.CodecException.ERROR_INSUFFICIENT_RESOURCE);

    // 步骤2:createByCodecName
    codec = MediaCodec.createByCodecName(codecName);

    // 步骤3:configure
    MediaFormat format = MediaFormat.createVideoFormat(mime, 1920, 1080);
    format.setInteger(MediaFormat.KEY_BIT_RATE, 2000000);
    format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
    format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
    codec.configure(format, surface, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

    // 步骤4:start
    codec.start();

} catch (MediaCodec.CodecException e) {
    // 区分永久性错误与临时错误
    if (e.getErrorCode() == MediaCodec.CodecException.ERROR_INSUFFICIENT_RESOURCE) {
        try { Thread.sleep(200); } catch (InterruptedException ignored) {}
        // 重试或降级
    } else {
        Log.e("CodecInit", "Fatal codec error", e);
    }
} catch (IllegalStateException e) {
    Log.w("CodecInit", "State conflict, likely released elsewhere", e);
} finally {
    if (codec != null && exceptionOccurred) {
        codec.release();
    }
}
性能陷阱:不要在 configure 之前反复调用 createByCodecName 测试不同的名称。编解码器实例化会分配大量 native 资源,正确做法是一次性通过 MediaCodecList 获得合法名称。

3.4 调试与日志分析要点

  • 使用 adb shell dumpsys media.codec 查看当前编解码器会话数量和分配情况。
  • 在 createByCodecName 失败时抓取 CodecException.getDiagnosticInfo() 获取厂商定制错误码(如高通 VIDCODEC_ERR_RESOURCE_BUSY)。
  • 怀疑 surface 问题时,加 log 确认 surface.isValid() 为 true。

3.5 稳定性优化策略

  1. 预校验 + 降级链: 按优先级尝试 OMX.google.* 软解 → 厂商硬件编解码器 → 回退到 MediaExtractor + AudioTrack 方案。
  2. 限流重试: start 失败时,等待 100ms 后最多重试 3 次,每次增加间隔。
  3. 异步初始化: 将初始化放在 HandlerThread 中,避免阻塞 UI;但注意同步释放资源。
  4. 资源池化: 对全屏/小窗切换场景,复用 MediaCodec 对象(仅 stop → flush → start),避免重复 create/configure。
典型案例: 某厂商在 start() 后立即调用 dequeueOutputBuffer 返回 INFO_OUTPUT_FORMAT_CHANGED 之前,若抛出 CodecException 往往是 buffer 计数不足。解决方法是在 configure 中显式设置 KEY_MAX_INPUT_SIZE 或 KEY_COLOR_FORMAT 约束。

3.6 附录:异常码速查表

错误码含义推荐处理
ERROR_INSUFFICIENT_RESOURCE硬件资源(视频后处理、DSP 通道)耗尽延迟重试或降级到软件解码
ERROR_KEY_FRAME编码器要求刷新关键帧但未设置在 MediaFormat 中设置 KEY_I_FRAME_INTERVAL
ERROR_NATIVE底层崩溃(通常是驱动 bug)release 后重新创建,并上报厂商
ERROR_DRMDRM 解密权限不足检查 DRM 会话状态

掌握以上初始化的异常模式后,你可以在成千上百款 Android 设备上实现一致的编解码启动行为,为后续的缓冲区循环、格式协商和性能调优奠定可靠基础。