4. MediaCodec 生命周期:停止、配置、启动、输入、输出、刷新、停止、释放
MediaCodec 的生命周期是一组严格的状态转换,每次状态变化都对应明确的 API 调用。理解状态机是正确使用硬编解码的前提,错误的状态转移会导致崩溃、数据丢失或编解码失败。
本章核心目标:掌握 MediaCodec 从停止到释放的完整状态机,能够在实际开发中准确调用
stop()、configure()、start()、flush()、release() 等关键方法,并处理输入输出缓冲区的生命周期。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 典型实现流程
从停止到释放的标准调用顺序如下(以解码为例):
- 创建:
MediaCodec.createDecoderByType(mimeType)进入 Uninitialized。 - 配置:
codec.configure(format, surface, crypto, 0)进入 Configured。 - 启动:
codec.start()进入 Executing(含 I/O 子状态)。 - 循环 I/O:调用
dequeueInputBuffer(10000)获取输入索引,填充数据后queueInputBuffer(...);再用dequeueOutputBuffer(...)获取输出索引,处理完后releaseOutputBuffer(...)。 - 刷新(可选):
codec.flush()清除所有未处理的缓冲区,用于 seek 或格式变化。 - 停止:
codec.stop()回到 Stopped。 - 释放:
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 |
完整掌握生命周期,才能正确管理硬件编解码资源,避免应用崩溃或耗电异常。下一阶段将进入实际的数据流处理细节。