3. MediaCodec 核心状态机:未初始化、配置、运行、停止、释放
3.1 状态机原理
MediaCodec 内部维护一个严格的状态机,应用程序的所有 API 调用必须在正确的状态下执行,否则抛出 IllegalStateException。正确理解状态转换是编写稳定编解码管道的前提。状态机包含 5 个核心状态:Uninitialized(未初始化)、Configured(已配置)、Running(运行中)、Stopped(已停止)、Released(已释放)。每个状态都对应一组允许的操作,状态切换由明确的 API 调用触发。
3.2 状态定义与转换条件
| 状态 | 含义 | 入口转换 | 出口转换 |
|---|---|---|---|
| Uninitialized | Codec 对象已创建,但未配置任何参数 | MediaCodec.createByCodecName() | configure() → Configured |
| Configured | 编解码参数(格式、flag)已设置,等待 start | configure() | start() → Runningrelease() → Released |
| Running | 正在处理输入/输出缓冲,可执行 dequeueInputBuffer、queueInputBuffer、dequeueOutputBuffer、releaseOutputBuffer | start() | stop() → Stoppedrelease() → Released |
| Stopped | 编解码停止,可重置或释放 | stop() | start() → Runningrelease() → Releasedreset() → Uninitialized |
| Released | 资源已释放,不能再进行任何操作 | release() | 无出口(终结态) |
提示: 从 Stopped 可以通过 reset() 回到 Uninitialized,从而实现复用同一个 Codec 实例而不必重新创建对象,这在短切片处理场景中可显著减少对象创建开销。
3.3 状态转换触发条件详细说明
3.3.1 Uninitialized → Configured
- 调用
configure(format, surface, crypto, flags)后,MediaCodec 解析 MediaFormat 并配置内部底层组件。若 surface 为 null,则为纯 Buffer 模式;否则为 Surface 输入/输出模式。 - 配置失败(格式不支持等)会抛出异常,状态仍保持 Uninitialized。
3.3.2 Configured → Running
- 调用
start()后,Codec 分配内部缓冲队列并启动编解码线程。此时输入输出端口就绪,可开始数据交互。 - 若
start()时底层资源不足,状态会回退到 Uninitialized(实际行为取决于厂商实现,建议捕获异常后手动 release 重建)。
3.3.3 Running ↔ Stopped
stop()强制停止编解码,清空所有排队的缓冲,输出端不再产生新数据。可再次start()恢复运行。- 注意:
stop()后 必须 重新start()才能继续生产数据,不能再次configure()(除非先reset())。
3.3.4 Stopped → Released / Uninitialized
release()彻底销毁 Codec,释放所有 native 资源。不可逆。reset()使 Codec 回到 Uninitialized 状态,之后可以重新configure()而无需新建对象。
3.4 标准 API 调用序列
一个典型生命周期代码片段:
MediaCodec codec = MediaCodec.createDecoderByType("video/avc");
// 状态: Uninitialized
MediaFormat format = MediaFormat.createVideoFormat("video/avc", 1920, 1080);
codec.configure(format, surface, null, 0);
// 状态: Configured
codec.start();
// 状态: Running
// 编解码循环...
while (!outputEndOfStream) {
int inputIndex = codec.dequeueInputBuffer(10000);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputIndex);
// 填充数据...
codec.queueInputBuffer(inputIndex, 0, dataSize, pts, 0);
}
BufferInfo info = new BufferInfo();
int outputIndex = codec.dequeueOutputBuffer(info, 10000);
if (outputIndex >= 0) {
// 渲染或处理输出
codec.releaseOutputBuffer(outputIndex, true);
}
}
codec.stop();
// 状态: Stopped
codec.reset();
// 状态: Uninitialized(可重用)
codec.configure(format2, null, null, 0);
// 或者直接释放:
codec.release();
// 状态: Released(终结)
3.5 调试方法:状态转换日志
启用 MediaCodec 内部日志(Android 10+):
adb shell setprop debug.media.codec.logging 1
adb logcat -s "ACodec" "MediaCodec"
日志输出示例:
ACodec : [OMX.qcom.video.decoder.avc] state UNINITIALIZED -> CONFIGURING
ACodec : [OMX.qcom.video.decoder.avc] state CONFIGURING -> CONFIGURED
ACodec : [OMX.qcom.video.decoder.avc] state CONFIGURED -> EXECUTING
ACodec : [OMX.qcom.video.decoder.avc] state EXECUTING -> FLUSHING
ACodec : [OMX.qcom.video.decoder.avc] state FLUSHING -> EXECUTING
ACodec : [OMX.qcom.video.decoder.avc] state EXECUTING -> FLUSHING
ACodec : [OMX.qcom.video.decoder.avc] state FLUSHING -> STOPPING
ACodec : [OMX.qcom.video.decoder.avc] state STOPPING -> STOPPED
ACodec : [OMX.qcom.video.decoder.avc] state STOPPED -> UNINITIALIZED (reset)
注意:实际日志中的状态名(如 CONFIGURING、EXECUTING)与 API 层略有不同,但转换关系一致。
3.6 常见错误与避坑
错误 1:在 Running 状态下调用 configure() → 抛出 IllegalStateException: configure() is not allowed now
错误 2:在使用 Surface 输入模式后,忘记调用 signalEndOfInputStream() → Codec 卡住无法输出 EOS 帧。正确做法:在输入完成后调用 codec.signalEndOfInputStream(),状态仍保持 Running,但输出队列最终会产生带 BUFFER_FLAG_END_OF_STREAM 的输出。
错误 3:在 stop() 后未 reset() 就重新 configure() → 抛出异常。必须通过 reset() 回到 Uninitialized 再重新配置。
错误 4:多次调用 release() → 第二次调用会直接返回(无异常),但建议通过标志位避免重复释放。
3.7 性能与稳定性优化
- 复用 Codec 实例: 若编码参数变化不大(如仅分辨率改变),先用
stop()+reset()回到 Uninitialized,再用新 MediaFormatconfigure(),避免频繁创建/销毁底层 OMX 组件。 - Surface 模式下的状态管理: 在 Surface 输入端,
stop()会触发 Surface 的 disconnect,需确保在 UI 线程重新连接 Surface(通常通过surface.release()+ 重建)。 - 使用异步模式减少状态转换阻塞: 回调方式(
setCallback())可以异步获取输入输出缓冲,避免主线程在dequeueInputBuffer上的长时间阻塞。 - 监控状态转换时间: 在
start()前后记录时间戳,若耗时超过 100ms 可能要检查 Codec 是否被其他进程占用,或者配置了过大的缓冲数。