
Android MediaCodec 是 Android 系统提供的底层 API,用于访问设备的硬件(或软件)编解码器,实现高效、低功耗的音视频编码和解码。它是构建高性能多媒体应用(如视频播放器、视频录制、直播推流、视频编辑等)的核心组件。
MediaCodec 采用异步的生产者 - 消费者模型,通过输入和输出缓冲区队列处理数据。
生产者:应用(提供原始数据,如 YUV 格式的视频帧或 PCM 格式的音频采样)。 消费者:MediaCodec 编码器(接收原始数据,输出压缩后的数据,如 H.264 视频流或 AAC 音频流)。
生产者:应用(提供压缩数据)。 消费者:MediaCodec 解码器(接收压缩数据,输出原始数据)。
MediaCodec 采用双缓冲区队列(输入/输出)实现异步数据处理,其架构可分为以下三层:
客户端(Client)
编解码器(Codec)
缓冲区队列(Buffer Queue)

生命周期执行顺序和各各声明周期详解如下:

flush() → 回到 Flushed 子状态(清空所有缓冲区,用于 seek 或重新开始) stop() → 跳转到 Uninitialized release() → 跳转到 Released 4. Released(已释放) 进入方式:在任何状态下调用 release()。 特点: 所有资源被释放,包括底层硬件编解码器实例。 对象不可再使用,任何方法调用都会抛出 IllegalStateException。 GC 会回收 Java 对象,但 native 资源必须手动 release() 才能释放。 实践:在 Activity/Fragment 销毁、Surface 被销毁、或编码完成时,必须调用 release()
import android.media.MediaCodec;
import android.media.MediaFormat;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class H264Encoder {
private static final String MIME_TYPE = "video/avc"; // H.264 MIME 类型
private MediaCodec mEncoder;
private FileOutputStream mOutputStream; // 用于写入 H.264 文件
public void configure(int width, int height, int bitrate, int frameRate, int iframeInterval) {
try {
// 1. 创建编码器
mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
// 2. 创建 MediaFormat 并设置参数
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
// 关键参数设置
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); // 推荐使用 Flexible
format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); // 码率,单位 bps
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); // 帧率
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iframeInterval); // I帧间隔,单位秒
format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR); // 码率模式,可选 CBR/VBR/CQ
// 3. 配置编码器(指定为编码器,传入 null 表示不关联 Surface)
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
// 4. 启动编码器
mEncoder.start();
// 5. 打开输出文件
mOutputStream = new FileOutputStream("output.h264");
} catch (IOException e) {
e.printStackTrace();
}
}
}public void encodeFrame(byte[] yuvData, long presentationTimeUs) {
try {
// --- 处理输入 ---
// 1. 获取输入缓冲区的索引
int inputBufferIndex = mEncoder.dequeueInputBuffer(10000); // 超时 10ms
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = mEncoder.getInputBuffer(inputBufferIndex);
inputBuffer.clear();
// 2. 将 YUV 数据复制到输入缓冲区
// 注意:这里假设 yuvData 的格式与 MediaFormat 中设置的 COLOR_FORMAT 一致!
// 如果格式不匹配,需要先进行转换(例如 NV21 -> I420 或 NV12)
inputBuffer.put(yuvData);
// 3. 提交输入缓冲区给编码器
// presentationTimeUs 是此帧的时间戳,单位微秒 (us)
// 如果是最后一帧,需要加上 BUFFER_FLAG_END_OF_STREAM 标志
mEncoder.queueInputBuffer(inputBufferIndex, 0, yuvData.length, presentationTimeUs, 0);
}
// --- 处理输出 ---
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex;
while ((outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 10000)) >= 0) {
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// 这是编解码器配置信息 (SPS/PPS),通常需要保存并在文件开头或每个 I 帧前写入
// 对于 H.264 文件,通常将 SPS/PPS 写在文件最开头
ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);
byte[] spsPps = new byte[bufferInfo.size];
outputBuffer.get(spsPps);
// 将 spsPps 写入文件
mOutputStream.write(spsPps);
// 释放输出缓冲区
mEncoder.releaseOutputBuffer(outputBufferIndex, false);
continue;
}
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
// 编码结束
break;
}
// 获取包含编码后 H.264 数据的输出缓冲区
ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);
byte[] encodedData = new byte[bufferInfo.size];
outputBuffer.get(encodedData);
// 将编码后的 H.264 NAL 单元写入文件
// 通常需要在每个 NAL 单元前添加起始码 0x00000001
mOutputStream.write(new byte[]{0, 0, 0, 1});
mOutputStream.write(encodedData);
// 释放输出缓冲区
mEncoder.releaseOutputBuffer(outputBufferIndex, false);
}
} catch (IOException e) {
e.printStackTrace();
}
}public void stopAndRelease() {
try {
// 1. 发送结束信号
int inputBufferIndex = mEncoder.dequeueInputBuffer(-1); // 阻塞等待
if (inputBufferIndex >= 0) {
mEncoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
// 2. 处理所有剩余的输出数据
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex;
do {
outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 10000);
if (outputBufferIndex >= 0) {
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break; // 确认结束
}
ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);
byte[] encodedData = new byte[bufferInfo.size];
outputBuffer.get(encodedData);
mOutputStream.write(new byte[]{0, 0, 0, 1});
mOutputStream.write(encodedData);
mEncoder.releaseOutputBuffer(outputBufferIndex, false);
}
} while (outputBufferIndex >= 0 || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER);
// 3. 关闭文件流
if (mOutputStream != null) {
mOutputStream.close();
}
// 4. 停止并释放编码器资源
mEncoder.stop();
mEncoder.release();
mEncoder = null;
} catch (IOException e) {
e.printStackTrace();
}
}视频编码关键参数 (Encoder) 这些参数在创建视频编码器(如 H.264, H.265)时配置,决定了输出视频流的特性和质量。
参数键 (Key) | 类型 | 含义说明 | 常用值 / 示例 | 重要性 | 备注 |
|---|---|---|---|---|---|
KEY_MIME | String | 编码格式 MIME 类型 | “video/avc” (H.264), “video/hevc” (H.265), “video/x-vnd.on2.vp8” (VP8) | 必须 | 与 createEncoderByType() 一致 |
KEY_WIDTH | int | 视频帧宽度(像素) | 1920, 1280, 720 | 必须 | |
KEY_HEIGHT | int | 视频帧高度(像素) | 1080, 720, 480 | 必须 | |
KEY_COLOR_FORMAT | int | 输入原始数据的颜色格式 | COLOR_FormatYUV420Flexible(推荐), COLOR_FormatYUV420SemiPlanar (NV12) | 必须 | 格式不匹配会导致花屏!需转换相机 NV21 → NV12/I420 |
KEY_BIT_RATE | int | 目标码率(单位:bps) | 2 * 1024 * 1024(2Mbps) | 核心 | 影响画质与体积 |
KEY_FRAME_RATE | int | 目标帧率(FPS) | 30, 25, 60 | 核心 | 应与输入帧率匹配 |
KEY_I_FRAME_INTERVAL | int | I 帧(关键帧)间隔(单位:秒) | 1, 2, 3 | 重要 | 直播推荐 1~3 秒;影响拖动和容错 |
KEY_BITRATE_MODE | int | 码率控制模式 | BITRATE_MODE_CBR, BITRATE_MODE_VBR, BITRATE_MODE_CQ | 重要 | CBR 适合直播,VBR 节省空间 |
KEY_PROFILE | int | 编码 Profile(档次) | AVCProfileBaseline, AVCProfileMain, AVCProfileHigh | 可选 | 不设置则用默认,影响兼容性 |
KEY_LEVEL | int | 编码 Level(级别) | AVCLevel3, AVCLevel4, AVCLevel5 | 可选 | 限制分辨率/码率上限 |
视频解码关键参数 (Decoder)
参数键 (Key) | 类型 | 含义说明 | 常用值 / 示例 | 重要性 | 备注 |
|---|---|---|---|---|---|
KEY_MIME | String | 解码格式 MIME 类型 | “video/avc”, “video/hevc” | 必须 | 需与码流一致 |
KEY_WIDTH | int | 视频帧宽度(从码流中解析) | 1920, 1280 | 自动获取 | 通常从容器读取 |
KEY_HEIGHT | int | 视频帧高度(从码流中解析) | 1080, 720 | 自动获取 | |
KEY_COLOR_FORMAT | int | 通常被忽略,输出格式由 Surface 决定 | - | 无关 | 解码到 ByteBuffer 时使用默认格式 |
KEY_MAX_INPUT_SIZE | int | 建议输入缓冲区最大大小(字节) | 根据分辨率估算,如 width * height * 3 / 2 + 1024 | 推荐设置 | 避免 queueInputBuffer 失败 |
音频关键参数
参数键 (Key) | 类型 | 含义说明 | 常用值 / 示例 | 重要性 | 备注 |
|---|---|---|---|---|---|
KEY_MIME | String | 音频编码格式 | “audio/mp4a-latm” (AAC), “audio/opus”, “audio/vorbis” | 必须 | |
KEY_SAMPLE_RATE | int | 采样率(Hz) | 44100, 48000, 22050, 16000 | 必须 | |
KEY_CHANNEL_COUNT | int | 声道数 | 1 (单声道), 2 (立体声) | 必须 | |
KEY_BIT_RATE | int | 音频码率(bps) | 128000, 64000, 96000 | 核心 | VBR 模式下为平均/最大码率 |
KEY_AAC_PROFILE | int | AAC 编码 Profile | AACObjectLC(最常用), AACObjectHE, AACObjectHE_PS | 推荐设置 | LC 兼容性好,HE 低码率高效 |