3. MediaCodec 初始化流程与异常处理
MediaCodec 的初始化是编解码适配中最容易崩溃的环节。三个核心 API——createByCodecName、configure、start——对应状态机中的 Uninitialized → Configured → Executing 两次跃迁。每一步都可能因厂商实现差异、参数越界、资源竞争而抛出异常。本章梳理完整流程、异常捕获模式及常见错误的对策。
3.1 三步初始化原理
MediaCodec 生命周期由状态机驱动:
- Uninitialized →
createByCodecName(String name)→ Configured - Configured →
configure(MediaFormat format, Surface surface, CryptoInfo crypto, int flags)→ Initialized - 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 稳定性优化策略
- 预校验 + 降级链: 按优先级尝试
OMX.google.*软解 → 厂商硬件编解码器 → 回退到 MediaExtractor + AudioTrack 方案。 - 限流重试: start 失败时,等待 100ms 后最多重试 3 次,每次增加间隔。
- 异步初始化: 将初始化放在
HandlerThread中,避免阻塞 UI;但注意同步释放资源。 - 资源池化: 对全屏/小窗切换场景,复用 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_DRM | DRM 解密权限不足 | 检查 DRM 会话状态 |
掌握以上初始化的异常模式后,你可以在成千上百款 Android 设备上实现一致的编解码启动行为,为后续的缓冲区循环、格式协商和性能调优奠定可靠基础。