第二章 创建与配置 Codec:MediaFormat 构建、MIME 类型选择、编码器/解码器实例化
MediaCodec 管线的基础操作是创建和配置编解码器实例。本章将详细讲解如何通过 MediaFormat 描述媒体格式、选择合适的 MIME 类型,以及通过 createEncoderByType / createDecoderByType 实例化 Codec 并完成配置。这些步骤是后续数据入队、出队和渲染的前提。
核心结论:一个有效的 Codec 实例必须经过三步:① 填充 MediaFormat 关键参数;② 调用 createEncoderByType/createDecoderByType 获取 Codec 对象;③ 调用 configure() + start() 进入运行态。任何一步缺失或参数错误都会导致管线失效。
2.1 MediaFormat 构建
MediaFormat 是描述媒体格式的容器,使用键值对存储参数。构建方式有两种:
- 使用 MediaFormat.createVideoFormat() / createAudioFormat()(推荐,自动预填 MIME)。
- new MediaFormat() + setString() / setInteger() 手动填充。
常用键值对照表:
| 键(key) | 类型 | 说明 | 典型值 |
|---|---|---|---|
| MediaFormat.KEY_MIME | String | MIME 类型 | "video/avc" |
| MediaFormat.KEY_WIDTH / KEY_HEIGHT | Integer | 视频分辨率 | 1920, 1080 |
| MediaFormat.KEY_BIT_RATE | Integer | 编码目标码率 (bps) | 8000000 |
| MediaFormat.KEY_FRAME_RATE | Integer / Float | 帧率 | 30 |
| MediaFormat.KEY_I_FRAME_INTERVAL | Integer | 关键帧间隔 (秒),-1 表示无 | 1 |
| MediaFormat.KEY_COLOR_FORMAT | Integer | 输入缓冲区颜色格式 | MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar |
| MediaFormat.KEY_IS_ADTS | Integer | AAC 音频是否带 ADTS 头 | 1 |
| MediaFormat.KEY_CHANNEL_COUNT | Integer | 音频声道数 | 2 |
| MediaFormat.KEY_SAMPLE_RATE | Integer | 音频采样率 (Hz) | 44100 |
提示:对于编码器,
KEY_COLOR_FORMAT 必须设置为 Codec 支持的输入格式。通常使用 MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible 以获得最大兼容性。
2.2 MIME 类型选择
MIME 类型决定了实例化哪个具体的编解码器组件。常见类型:
- 视频编码:
video/avc(H.264),video/hevc(H.265),video/mp4v-es(MPEG-4),video/x-vnd.on2.vp8,video/x-vnd.on2.vp9 - 视频解码:与编码对应,但使用
createDecoderByType() - 音频编码/解码:
audio/mp4a-latm(AAC),audio/opus,audio/amr-nb,audio/3gpp,audio/vorbis
选择点时注意硬件编解码器支持列表,通过 MediaCodecList 查询可用 Codec。
2.3 编码器/解码器实例化
实例化 API 有三组:
MediaCodec.createDecoderByType(String mimeType)— 创建解码器MediaCodec.createEncoderByType(String mimeType)— 创建编码器MediaCodec.createByCodecName(String name)— 通过组件名创建(当明确知道所需 Codec 时使用)
实例化完成后需要依次调用:
- configure(MediaFormat format, Surface surface, int flags, MediaDescrambler descrambler)
- format:前面构建的 MediaFormat
- surface:输出 Surface(仅当输出为 Surface 时有效,解码器常见)
- flags:
MediaCodec.CONFIGURE_FLAG_ENCODE(0x01) 表示编码器,其它为 0 - descrambler:DRM 相关,通常传 null
- start() — 将 Codec 切换到 Executing 状态,之后才能进行 buffer 操作。
2.4 完整实例化代码示例
// 示例:创建 H.264 编码器
MediaFormat format = MediaFormat.createVideoFormat(
"video/avc", // MIME
1280, // width
720 // height
);
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_FormatYUV420Flexible);
MediaCodec encoder = MediaCodec.createEncoderByType("video/avc");
encoder.configure(format, null, MediaCodec.CONFIGURE_FLAG_ENCODE, null);
encoder.start();
// 示例:创建 AAC 解码器
MediaFormat audioFormat = MediaFormat.createAudioFormat(
"audio/mp4a-latm", // MIME
44100, // sample rate
2 // channel count
);
audioFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
MediaCodec decoder = MediaCodec.createDecoderByType("audio/mp4a-latm");
decoder.configure(audioFormat, null, 0, null);
decoder.start();
2.5 常见错误与调试
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
| createEncoderByType 返回 null | MIME 类型不匹配或设备无对应 Codec | 通过 MediaCodecList.getCodecInfos() 打印所有组件 |
configure() 抛出 MediaCodec.CodecException | MediaFormat 缺少必要键值或值不合理 | 检查 KEY_WIDTH/KEY_HEIGHT、KEY_COLOR_FORMAT 等是否设置正确 |
start() 后状态仍为 Configured | configure 传入了不兼容的 Surface | 确认 Surface 未提前释放,或改用 null |
| logcat 输出 "failed to configure codec" | 底层驱动不支持当前参数组合 | 尝试降低分辨率、码率,或使用软件 Codec |
警告:
configure() 和 start() 必须在同一个线程中调用,且不能在 Executing 状态重复调用。一旦 start() 成功,必须通过 stop() + release() 释放资源,不可再次 configure。
2.6 性能与稳定性优化
- 避免频繁创建/释放:编解码器实例化开销较大(约 5~20ms),建议复用 Codec 实例,或使用
MediaCodecList.findDecoderForFormat()找到最合适的组件。 - 提前查询颜色格式:通过
codecInfo.getCapabilitiesForType(mime).colorFormats选择设备支持的格式,减少配置失败。 - 使用异步模式:Android API 21+ 推荐通过
setCallback()替代手动循环,降低 CPU 占用。 - 设置合理的码率与帧率:过高的码率可能导致底层编码器丢帧或拒绝配置;参考设备 Profile 级别限制。
- 释放前检查状态:
if (codec != null) { codec.stop(); codec.release(); }防止内存泄漏。