第二章 创建与配置 Codec:MediaFormat 构建、MIME 类型选择、编码器/解码器实例化

MediaCodec 管线的基础操作是创建和配置编解码器实例。本章将详细讲解如何通过 MediaFormat 描述媒体格式、选择合适的 MIME 类型,以及通过 createEncoderByType / createDecoderByType 实例化 Codec 并完成配置。这些步骤是后续数据入队、出队和渲染的前提。

核心结论:一个有效的 Codec 实例必须经过三步:① 填充 MediaFormat 关键参数;② 调用 createEncoderByType/createDecoderByType 获取 Codec 对象;③ 调用 configure() + start() 进入运行态。任何一步缺失或参数错误都会导致管线失效。
Codec 创建与配置流程图 MediaFormat 构建 key: value 参数填充 MIME 类型选择 video/avc, audio/mp4a-latm 实例化 Codec createEncoderByType configure() MediaFormat + Surface + flags start() 进入运行态,可收发数据 错误处理 & 参数校验 检查返回值 / 日志 / 异常 ⚠️ 关键里程碑点 configure() 之前必须设置正确的 MIME 和所有必要键值;start() 后即开始入队/出队循环。 错误多发生在 MIME 不匹配、缺少 csd-0 或分辨率不兼容。

2.1 MediaFormat 构建

MediaFormat 是描述媒体格式的容器,使用键值对存储参数。构建方式有两种:

  • 使用 MediaFormat.createVideoFormat() / createAudioFormat()(推荐,自动预填 MIME)。
  • new MediaFormat() + setString() / setInteger() 手动填充。

常用键值对照表:

键(key)类型说明典型值
MediaFormat.KEY_MIMEStringMIME 类型"video/avc"
MediaFormat.KEY_WIDTH / KEY_HEIGHTInteger视频分辨率1920, 1080
MediaFormat.KEY_BIT_RATEInteger编码目标码率 (bps)8000000
MediaFormat.KEY_FRAME_RATEInteger / Float帧率30
MediaFormat.KEY_I_FRAME_INTERVALInteger关键帧间隔 (秒),-1 表示无1
MediaFormat.KEY_COLOR_FORMATInteger输入缓冲区颜色格式MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar
MediaFormat.KEY_IS_ADTSIntegerAAC 音频是否带 ADTS 头1
MediaFormat.KEY_CHANNEL_COUNTInteger音频声道数2
MediaFormat.KEY_SAMPLE_RATEInteger音频采样率 (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 时使用)

实例化完成后需要依次调用:

  1. configure(MediaFormat format, Surface surface, int flags, MediaDescrambler descrambler)
    • format:前面构建的 MediaFormat
    • surface:输出 Surface(仅当输出为 Surface 时有效,解码器常见)
    • flags:MediaCodec.CONFIGURE_FLAG_ENCODE (0x01) 表示编码器,其它为 0
    • descrambler:DRM 相关,通常传 null
  2. 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 返回 nullMIME 类型不匹配或设备无对应 Codec通过 MediaCodecList.getCodecInfos() 打印所有组件
configure() 抛出 MediaCodec.CodecExceptionMediaFormat 缺少必要键值或值不合理检查 KEY_WIDTH/KEY_HEIGHT、KEY_COLOR_FORMAT 等是否设置正确
start() 后状态仍为 Configuredconfigure 传入了不兼容的 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(); } 防止内存泄漏。