4. MediaCodec 生命周期:停止、配置、启动、输入、输出、刷新、停止、释放

MediaCodec 的生命周期是一组严格的状态转换,每次状态变化都对应明确的 API 调用。理解状态机是正确使用硬编解码的前提,错误的状态转移会导致崩溃、数据丢失或编解码失败。

本章核心目标:掌握 MediaCodec 从停止到释放的完整状态机,能够在实际开发中准确调用 stop()、configure()、start()、flush()、release() 等关键方法,并处理输入输出缓冲区的生命周期。
MediaCodec 生命周期状态转换 Uninitialized Configured Executing Input/Output Flushed Stopped Released configure() start() start() flush() stop() stop() configure() release() 说明: 虚线箭头表示从 Stopped 状态可重新 configure() 回到 Configured;释放后不可再使用。

4.1 状态转换原理

MediaCodec 的内部状态机定义在 android.media.MediaCodec 官方文档中,主要状态包括:

状态含义可调用方法
Uninitialized刚创建或 release() 后的状态configure()
Configured已配置格式,但未开始编解码start()、release()、setParameters()
Executing编解码器正在运行,可进行 I/O 操作dequeueInputBuffer()、queueInputBuffer()、dequeueOutputBuffer()、releaseOutputBuffer()、flush()、stop()
Flushed所有待处理缓冲区已清除,但仍在 Running 子状态与 Executing 相同,但 dequeue 会返回空缓冲
Stopped编解码停止,须重新配置或释放configure()、release()
Released资源已释放,不再可用无

4.2 典型实现流程

从停止到释放的标准调用顺序如下(以解码为例):

  1. 创建:MediaCodec.createDecoderByType(mimeType) 进入 Uninitialized。
  2. 配置:codec.configure(format, surface, crypto, 0) 进入 Configured。
  3. 启动:codec.start() 进入 Executing(含 I/O 子状态)。
  4. 循环 I/O:调用 dequeueInputBuffer(10000) 获取输入索引,填充数据后 queueInputBuffer(...);再用 dequeueOutputBuffer(...) 获取输出索引,处理完后 releaseOutputBuffer(...)。
  5. 刷新(可选):codec.flush() 清除所有未处理的缓冲区,用于 seek 或格式变化。
  6. 停止:codec.stop() 回到 Stopped。
  7. 释放:codec.release() 回到 Released。

4.3 关键 API 示例

以下代码片段展示生命周期核心调用(省略异常处理):

// 创建
MediaCodec decoder = MediaCodec.createDecoderByType("video/avc");

// 配置
MediaFormat format = MediaFormat.createVideoFormat("video/avc", 1280, 720);
decoder.configure(format, surface, null, 0);

// 启动
decoder.start();

// 输入缓冲区循环
int inputIndex = decoder.dequeueInputBuffer(10000);
if (inputIndex >= 0) {
    ByteBuffer inputBuffer = decoder.getInputBuffer(inputIndex);
    // 填充 inputBuffer 并设置缓冲区信息
    int size = sampleData.remaining();
    inputBuffer.put(sampleData);
    decoder.queueInputBuffer(inputIndex, 0, size, presentationTimeUs, MediaCodec.BUFFER_FLAG_KEY_FRAME);
}

// 输出缓冲区循环
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int outputIndex = decoder.dequeueOutputBuffer(info, 10000);
if (outputIndex >= 0) {
    // 渲染或处理
    decoder.releaseOutputBuffer(outputIndex, true);
}

// 刷新
decoder.flush();

// 停止
decoder.stop();

// 释放
decoder.release();

4.4 调试与日志分析

生命周期错误常表现为 IllegalStateException。通过开启 MediaCodec 日志可定位问题:

// 在应用启动时设置日志级别
adb shell setprop media.codec.debug 1
// 或直接在代码中捕获异常
try {
    decoder.stop();
} catch (IllegalStateException e) {
    Log.e("MediaCodecDemo", "stop() 失败,可能未处于 Executing 状态", e);
}

常见错误日志:

E/MediaCodec( xx ): stop() called in state 1 (Configured) -- 必须 start() 后才能 stop
E/MediaCodec( xx ): configure() called in state 4 (Executing) -- 必须先 stop()
注意:在 Executing 状态多次调用 flush() 是安全的,但在 Stopped 状态调用 flush() 会抛出异常。任何 API 调用前应检查当前状态(可通过 android.media.MediaCodecStateException 辅助)。

4.5 性能与稳定性优化

  • 避免频繁 stop/start:每次 stop 会释放硬件资源,重新 start 需要重新分配,推荐使用 flush() 来重置缓冲区。
  • 及时释放输出缓冲区:延迟 releaseOutputBuffer() 会导致内部缓冲区池耗尽,造成 dequeueOutputBuffer 返回 -1 甚至死锁。
  • 异步模式生命周期:使用 setCallback() 时,状态转换由回调驱动,但仍需遵守同一状态机,切勿在回调外随意调用 stop()。
  • 合理处理 EOS:输入队列标记 BUFFER_FLAG_END_OF_STREAM 后,输出端会收到相同标记,此时应调用 stop() 而非继续 enqueue。

4.6 常见错误与排查

错误现象根本原因解决方案
启动后 dequeueInputBuffer 一直返回 -1输入缓冲区未及时填充,内部超时检查循环超时时间,确保填充数据块大小合理
stop() 时抛出 IllegalStateException在 Stopped 或 Released 状态调用 stop调用前检查状态,或包裹 try-catch
configure() 失败返回 -1004格式参数不支持(如分辨率、HDR)查询 MediaCodecList 确认是否支持
release() 后未解除引用导致内存泄漏对象仍被其他组件持有在 onRelease 后将 codec 置为 null

完整掌握生命周期,才能正确管理硬件编解码资源,避免应用崩溃或耗电异常。下一阶段将进入实际的数据流处理细节。