3. MediaCodec入门

MediaCodec 是 Android 多媒体框架中负责“编解码”的核心组件。它并不直接负责采集摄像头、读取文件或渲染画面,而是把一段符合特定格式的压缩码流解码成原始帧,或把原始帧编码成压缩码流。理解 MediaCodec 的关键,不是记住某个方法名,而是建立“输入缓冲区—编解码器—输出缓冲区”的数据流模型,并掌握它严格的状态机约束。

一、MediaCodec 的作用与定位

在 Android 的多媒体栈中,MediaExtractor 负责从容器中分离出音视频轨道,MediaMuxer 负责把编码后的码流封装成 MP4 等容器,而 MediaCodec 位于两者之间,承担真正的压缩与解压缩工作。它既可以对接硬件编解码器,也可以回退到软件实现,因此同一套 API 在不同设备上可能表现出不同的性能、延迟和格式支持能力。

从数据角度看,MediaCodec 处理的是“缓冲区”而不是“文件”。输入侧送入的是未压缩数据(编码场景)或压缩数据(解码场景),输出侧取出的则相反。每个缓冲区都带有时间戳、标志位等元信息,这些信息决定了后续音视频同步与渲染时机。

核心理解:MediaCodec 是一个带状态约束的异步缓冲区处理器。你给它输入缓冲区,它按配置的 MIME 类型和格式进行编解码,再通过输出缓冲区把结果交还给你。

二、API 概览:从创建到释放

MediaCodec 的常用 API 可以按生命周期分为四组:创建与配置、启动与运行、数据交互、停止与释放。下表列出典型方法及其作用。

阶段 典型 API 作用说明
创建 createDecoderByType / createEncoderByType 按 MIME 类型创建解码器或编码器实例
配置 configure(MediaFormat, Surface, MediaCrypto, flags) 指定格式、输出 Surface、加密与标志位
启动 start() 进入 Running 状态,开始接受输入缓冲区
输入 dequeueInputBuffer / queueInputBuffer 同步模式下获取并提交输入缓冲区
输出 dequeueOutputBuffer / releaseOutputBuffer 同步模式下获取并释放输出缓冲区
异步回调 setCallback / onInputBufferAvailable / onOutputBufferAvailable 异步模式下由回调驱动缓冲区流转
结束 stop() / release() 停止运行并释放底层资源

配置阶段最关键的是 MediaFormat。解码器通常需要设置 KEY_MIME、KEY_WIDTH、KEY_HEIGHT,编码器还需要设置码率、帧率、关键帧间隔和颜色格式。若输出到 Surface,则 configure 时必须传入有效的 Surface,否则后续无法正确渲染。

MediaFormat format = MediaFormat.createVideoFormat("video/avc", 1280, 720);
format.setInteger(MediaFormat.KEY_BIT_RATE, 4_000_000);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
        MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

MediaCodec codec = MediaCodec.createEncoderByType("video/avc");
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
codec.start();

三、同步模式与异步模式

同步模式下,调用方主动轮询输入和输出缓冲区。典型流程是:先 dequeueInputBuffer 拿到一个空闲输入索引,填入数据后 queueInputBuffer 提交;再 dequeueOutputBuffer 等待输出,处理完调用 releaseOutputBuffer 归还。同步模式逻辑直观,但需要自己控制轮询节奏,容易因超时设置不当造成卡顿或空转。

异步模式下,通过 setCallback 注册回调,MediaCodec 在输入缓冲区可用和输出缓冲区可用时主动通知。调用方不再轮询,而是在 onInputBufferAvailable 中填充数据,在 onOutputBufferAvailable 中消费数据。异步模式更适合与 Surface、AudioTrack 等异步管线配合,也能减少主线程阻塞,但回调线程模型和缓冲区索引管理需要格外小心。

选择建议:如果编解码流程简单、需要精确控制每一步时序,可先用同步模式验证;如果面向实时预览、录制或低延迟播放,优先考虑异步模式,并确保回调中不做耗时操作。

四、生命周期与状态机

MediaCodec 的生命周期由内部状态机严格管理。创建后处于 Uninitialized 状态;调用 configure 后进入 Configured;调用 start 后进入 Running,此时才允许输入输出缓冲区流转。运行过程中可能因错误进入 Error 状态,也可能因 stop 回到 Uninitialized。最终必须调用 release 释放资源。

状态机的意义在于:很多调用顺序错误并不是“逻辑 bug”,而是直接抛出 IllegalStateException。例如未 configure 就 start、未 start 就 queueInputBuffer、在 Error 状态下继续操作,都会失败。因此排查 MediaCodec 问题时,第一步永远是确认当前处于哪个状态。

MediaCodec 生命周期状态机 Uninitialized 创建后 / stop 后 Configured configure() 之后 Running start() 之后 Error 异常 / 不可恢复 configure() start() 异常 错误传播 reset() release() Released 资源已释放 stop() Stopped 可重新 configure configure()

五、典型场景与常见误区

以“摄像头预览编码”为例:Camera2 把预览数据送到 Surface,编码器通过 createInputSurface 获得输入 Surface,应用只需在输出回调中取出编码后的码流并写入 MediaMuxer。这个场景中,输入侧不再手动 queueInputBuffer,而是由 Surface 自动填充,这是 MediaCodec 与图形管线结合的重要用法。

常见误区包括:第一,在回调中执行耗时操作,导致缓冲区堆积和延迟升高;第二,忘记在输出缓冲区处理后调用 releaseOutputBuffer,使可用缓冲区耗尽;第三,在 Error 状态下继续调用 API,而不是先 reset 或 release;第四,混淆同步与异步的缓冲区索引管理,造成重复提交或越界访问。

排障顺序建议:先确认状态机位置,再检查 MediaFormat 是否与设备能力匹配,然后核对输入输出缓冲区是否成对释放,最后观察时间戳与标志位是否正确。多数“花屏、卡顿、无输出”问题都能在这四步中定位。

掌握 MediaCodec 的作用、API、同步异步差异以及状态机约束,是后续理解 Codec2 适配与硬件编解码差异的基础。只有把缓冲区流转和状态迁移看清楚,才能在真实设备上写出稳定、低延迟的编解码代码。