3. MediaCodec入门
MediaCodec 是 Android 多媒体框架中负责“编解码”的核心组件。它并不直接负责采集摄像头、读取文件或渲染画面,而是把一段符合特定格式的压缩码流解码成原始帧,或把原始帧编码成压缩码流。理解 MediaCodec 的关键,不是记住某个方法名,而是建立“输入缓冲区—编解码器—输出缓冲区”的数据流模型,并掌握它严格的状态机约束。
一、MediaCodec 的作用与定位
在 Android 的多媒体栈中,MediaExtractor 负责从容器中分离出音视频轨道,MediaMuxer 负责把编码后的码流封装成 MP4 等容器,而 MediaCodec 位于两者之间,承担真正的压缩与解压缩工作。它既可以对接硬件编解码器,也可以回退到软件实现,因此同一套 API 在不同设备上可能表现出不同的性能、延迟和格式支持能力。
从数据角度看,MediaCodec 处理的是“缓冲区”而不是“文件”。输入侧送入的是未压缩数据(编码场景)或压缩数据(解码场景),输出侧取出的则相反。每个缓冲区都带有时间戳、标志位等元信息,这些信息决定了后续音视频同步与渲染时机。
二、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 问题时,第一步永远是确认当前处于哪个状态。
五、典型场景与常见误区
以“摄像头预览编码”为例:Camera2 把预览数据送到 Surface,编码器通过 createInputSurface 获得输入 Surface,应用只需在输出回调中取出编码后的码流并写入 MediaMuxer。这个场景中,输入侧不再手动 queueInputBuffer,而是由 Surface 自动填充,这是 MediaCodec 与图形管线结合的重要用法。
常见误区包括:第一,在回调中执行耗时操作,导致缓冲区堆积和延迟升高;第二,忘记在输出缓冲区处理后调用 releaseOutputBuffer,使可用缓冲区耗尽;第三,在 Error 状态下继续调用 API,而不是先 reset 或 release;第四,混淆同步与异步的缓冲区索引管理,造成重复提交或越界访问。
掌握 MediaCodec 的作用、API、同步异步差异以及状态机约束,是后续理解 Codec2 适配与硬件编解码差异的基础。只有把缓冲区流转和状态迁移看清楚,才能在真实设备上写出稳定、低延迟的编解码代码。