📋 目录
- [一文搞懂 Android MediaCodec 是什么](#一文搞懂 Android MediaCodec 是什么)
- 四层架构设计深度剖析
- 状态机:编解码器的生命周期
- 数据类型:三种数据格式全解析
- 创建与配置:正确的初始化方式
- [同步模式 vs 异步模式](#同步模式 vs 异步模式)
- Surface模式:为什么效率更高
- 实战一:H.264视频解码到Surface
- [实战二:Camera2 + MediaCodec + MediaMuxer 录像](#实战二:Camera2 + MediaCodec + MediaMuxer 录像)
- MIME类型与编解码器支持一览
- Codec特定数据(CSD)处理
- 错误处理与异常恢复
- 最佳实践与性能优化
- 常见问题汇总(FAQ)
- 总结
一、MediaCodec 是什么
1.1 基本概念
MediaCodec 是 Android 系统提供的底层媒体编解码接口 ,用于访问设备上的硬件或软件编解码器。它是 Android 多媒体支持基础设施的核心组件之一,自 API Level 16 (Android 4.1) 开始引入。
简单来说,MediaCodec 就是 Android 系统中负责压缩数据 ↔ 原始数据转换的"翻译官":
📦 压缩数据(H.264/AAC/MP3等) ←------→ 📱 原始数据(YUV帧/PCM音频)
↑
MediaCodec
1.2 核心特点
| 特性 | 说明 |
|---|---|
| 底层API | 比 MediaPlayer、VideoView 更底层,提供了更精细的控制 |
| 软硬通吃 | 可使用软件编解码器,也可使用硬件加速编解码器 |
| 零拷贝 | 支持 Surface 模式,避免数据在内存中拷贝 |
| 异步处理 | 基于生产者-消费者模型的缓冲区队列 |
| 音视频通吃 | 同时支持视频(H.264/H.265/VP8/VP9)和音频(AAC/MP3/Ogg)编解码 |
| DRM支持 | 支持加密内容的解密播放(Android 4.3+) |
1.3 组件协同关系
MediaCodec 通常与其他 Android 多媒体组件配合使用:
┌──────────────────────────────────────────────────────────────────┐
│ Android 多媒体架构 │
├──────────────────────────────────────────────────────────────────┤
│ │
│ MediaExtractor ──────► MediaCodec ──────► MediaMuxer │
│ │ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 音视频分离 编解码处理 音视频封装 │
│ │ │ │ │
│ │ Surface │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ MediaRetriever SurfaceFlinger MP4/WebM │
│ │
│ MediaCrypto ◄─────► MediaDrm (DRM加密内容) │
│ │
└──────────────────────────────────────────────────────────────────┘
| 组件 | 功能 |
|---|---|
| MediaExtractor | 解析媒体文件,分离音视频轨道 |
| MediaCodec | 编解码核心,负责压缩数据↔原始数据转换 |
| MediaMuxer | 将编码后的音视频轨混合封装成 MP4/WebM 等容器 |
| MediaSync | 音视频同步控制 |
| MediaCrypto | 处理加密/DRM保护的内容 |
| Surface | 图形缓冲区,用于高效的视频数据传递 |
二、四层架构设计
MediaCodec 采用四层架构设计,从 Java API 到硬件抽象,层层分离:
┌────────────────────────────────────────────────────────────────────────┐
│ 四层架构总览 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 第一层:Java API Layer │ │
│ │ media/java/android/media/ │ │
│ │ │ │
│ │ MediaCodec.java MediaCodecList.java MediaFormat.java │ │
│ │ MediaCodecInfo.java MediaCodecInfo.CodecCapabilities │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ ↓ JNI │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 第二层:JNI Layer │ │
│ │ media/jni/android_media_MediaCodec.cpp │ │
│ │ │ │
│ │ • Java ↔ C++ 类型转换 (ByteBuffer ↔ ABuffer) │ │
│ │ • Surface ↔ ANativeWindow 映射 │ │
│ │ • MediaFormat ↔ AMessage 转换 │ │
│ │ • 错误异常转换 (status_t → Java Exception) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 第三层:C++ Native Layer │ │
│ │ frameworks/av/media/ │ │
│ │ │ │
│ │ MediaCodec.cpp ◄──► JMediaCodec.cpp (JNI适配器) │ │
│ │ │ │ │
│ │ ├──► CodecBase (编解码器基类) │ │
│ │ │ │ │
│ │ ├──► ACodec (OMX IL 实现,遗留路径) │ │
│ │ │ │ │
│ │ └──► C2Codec (Codec 2.0 实现,现代路径) │ │
│ │ │ │
│ │ ALooper / AMessage / AHandler (异步消息循环) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 第四层:HAL / Hardware Layer │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ OMX IL HAL │ │ Codec 2.0 HAL │ │ │
│ │ │ (传统接口) │ │ (现代接口) │ │ │
│ │ └────────┬────────┘ └────────┬────────┘ │ │
│ │ │ │ │ │
│ │ └──────────┬─────────────┘ │ │
│ │ ↓ │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ Vendor HAL (厂商) │ │ │
│ │ │ 华为/高通/联发科... │ │ │
│ │ └───────────┬───────────┘ │ │
│ │ ↓ │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ 硬件: VPU/DSP/ │ │ │
│ │ │ GPU (硬件编解码) │ │ │
│ │ └───────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ 软件编解码库 │ │ libavc/libhevc│ │ │
│ │ │ (软解路径) │ │ libvpx/libaac │ │ │
│ │ └─────────────────┘ └─────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘
2.1 代码位置速查表
| 层级 | 文件路径 | 行数 | 职责 |
|---|---|---|---|
| Java API | media/java/android/media/MediaCodec.java |
~6300行 | 应用层主接口 |
| Java API | media/java/android/media/MediaCodecList.java |
~500行 | 编解码器列表 |
| Java API | media/java/android/media/MediaCodecInfo.java |
~300行 | 编解码器元数据 |
| JNI | media/jni/android_media_MediaCodec.cpp |
~4400行 | JNI桥接实现 |
| JNI | media/jni/android_media_MediaCodecList.cpp |
~800行 | 编解码器列表JNI |
| C++ | frameworks/av/media/libstagefright/MediaCodec.cpp |
~1500行 | Native主逻辑 |
| C++ | frameworks/av/media/libstagefright/ACodec.cpp |
~3000行 | OMX IL实现 |
| C++ | frameworks/av/media/codec2/ |
多个文件 | Codec 2.0实现 |
2.2 软解 vs 硬解路径对比
┌────────────────────────────────────────────────────────────────────────┐
│ 软解路径 (Software) │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Java: MediaCodec │
│ ↓ │
│ JNI: android_media_MediaCodec.cpp │
│ ↓ │
│ C++: MediaCodec → C2Component / OMX Component │
│ ↓ │
│ Library: libavc.so (H.264) / libhevc.so (H.265) / libvpx.so (VP9) │
│ ↓ │
│ Execution: CPU 计算 (ARM NEON 优化) │
│ │
└────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────┐
│ 硬解路径 (Hardware) │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ Java: MediaCodec │
│ ↓ │
│ JNI: android_media_MediaCodec.cpp │
│ ↓ │
│ C++: MediaCodec → ACodec / C2Codec │
│ ↓ │
│ HAL: OMX IL HAL / Codec 2.0 HAL │
│ ↓ │
│ IPC: Binder / HIDL (跨进程通信) │
│ ↓ │
│ Vendor HAL: 厂商实现 (高通/华为/联发科/三星...) │
│ ↓ │
│ Driver: Linux 内核驱动 │
│ ↓ │
│ Hardware: VPU / DSP / 专用编解码电路 │
│ │
└────────────────────────────────────────────────────────────────────────┘
| 对比项 | 软件编解码 | 硬件编解码 |
|---|---|---|
| CPU占用 | 高 | 低 |
| 功耗 | 高 | 低 |
| 兼容性 | 100% | 依赖厂商实现 |
| 性能 | 取决于CPU | 通常更优 |
| 灵活性 | 高,可修改 | 低,固件固定 |
| 内存占用 | Java堆内存 | DMA共享内存 |
三、状态机:编解码器的生命周期
3.1 完整状态转换图
┌─────────────────────────────────────────────────────────────────────────┐
│ MediaCodec 状态机 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ │
│ │ │ │
│ │ Uninitialized │ │
│ │ (未初始化) │ │
│ │ │ │
│ └────────┬─────────┘ │
│ │ │
│ │ createByCodecName() │
│ │ createDecoderByType() │
│ │ createEncoderByType() │
│ ↓ │
│ ┌──────────────────┐ │
│ │ │ │
│ │ Configured │ │
│ │ (已配置) │ │
│ └────────┬─────────┘ │
│ │ │
│ │ start() │
│ ↓ │
│ ┌──────────────────┐ │
│ │ │ │
│ │ Executing │ │
│ │ (执行中) │ │
│ │ │ │
│ │ ┌────────────┐ │ │
│ │ │ Flushed │ │ start() 后 / flush() 后 │
│ │ └─────┬──────┘ │ │
│ │ │ │ │
│ │ │ dequeueInputBuffer() │
│ │ ↓ │ │
│ │ ┌────────────┐ │ │
│ │ │ Running │ │ 处理数据中 │
│ │ └─────┬──────┘ │ │
│ │ │ │ │
│ │ │ BUFFER_FLAG_ │ │
│ │ │ END_OF_STREAM │
│ │ ↓ │ │
│ │ ┌────────────┐ │ │
│ │ │End of Stream│ │ │
│ │ └────────────┘ │ │
│ └──────────────────┘ │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ↓ ↓ ↓ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ stop() │ │ stop() │ │ release()│ │
│ ↓ │ ↓ │ ↓ │ │
│ ┌──────────┐ │ │ ┌──────────┐ │ │
│ │Uninitialized│ │ │ │Uninitialized│ │ │
│ └──────────┘ │ │ └──────────┘ │ │
│ │ └─────────────────────────┘ │
│ │ │
│ │ reset() ──► Uninitialized │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.2 状态详细说明
| 状态 | 说明 | 可执行操作 |
|---|---|---|
| Uninitialized | 初始状态,未分配资源 | createByCodecName() / createDecoderByType() / createEncoderByType() / reset() / release() |
| Configured | 已配置,格式和Surface已设置 | start() / setOutputSurface() / setCallback() / stop() / release() |
| Executing | 执行中,正在处理数据 | stop() / flush() / queueInputBuffer() / dequeueOutputBuffer() 等 |
3.3 Executing 状态的三种子状态
| 子状态 | 进入条件 | 缓冲区状态 | 说明 |
|---|---|---|---|
| Flushed | start() 后 / flush() 后 |
持有所有缓冲区 | 初始状态,需要先消费完输入缓冲区才能进入Running |
| Running | 第一个输入缓冲区出队后 | 正常流转中 | 正常处理状态 |
| End of Stream | 收到 BUFFER_FLAG_END_OF_STREAM 标志 |
不再接收新输入 | 输出剩余数据后结束 |
3.4 状态转换方法详解
| 方法 | 转换方向 | 说明 |
|---|---|---|
createByCodecName(name) |
→ Configured | 按名称创建解码器 |
createDecoderByType(type) |
→ Configured | 按MIME创建解码器 |
createEncoderByType(type) |
→ Configured | 按MIME创建编码器 |
configure(format, surface, crypto, flags) |
Uninitialized → Configured | 配置编解码器 |
start() |
Configured → Executing(Flushed) | 启动编解码器 |
flush() |
Executing → Executing(Flushed) | 刷新,清空缓冲区 |
stop() |
Executing → Uninitialized | 停止,保留编解码器实例 |
reset() |
任意 → Uninitialized | 重置到初始状态 |
release() |
任意 → Released | 释放所有资源 |
四、数据类型:三种数据格式
MediaCodec 处理三种类型的数据,每种都有其特定的处理方式:
┌─────────────────────────────────────────────────────────────────────────┐
│ MediaCodec 数据类型 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 压缩数据 │ │ 原始音频数据 │ │ 原始视频数据 │ │
│ │ (Compressed) │ │ (Audio) │ │ (Video) │ │
│ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │
│ │ • 解码器输入 │ │ │ │ │ │
│ │ (H.264/AAC) │ │ PCM 音频帧 │ │ YUV/RGB 视频帧 │ │
│ │ │ │ │ │ │ │
│ │ • 编码器输出 │ │ │ │ │ │
│ │ (H.264/AAC) │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 处理方式对比 │ │
│ ├────────────────────────────────────────────────────────────────┤ │
│ │ 方式 │ ByteBuffer模式 │ Surface模式 │ │
│ │ ─────────────────────────────────────────────────────────── │ │
│ │ 压缩数据 │ ✅ 必须使用 │ ❌ 不适用 │ │
│ │ 原始音频 │ ✅ 必须使用 │ ❌ 不适用 │ │
│ │ 原始视频 │ ✅ 可用 │ ✅ 推荐使用 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
4.1 视频颜色格式详解
视频缓冲区支持三种颜色格式类别:
| 格式类型 | 说明 | Surface支持 | 示例常量 |
|---|---|---|---|
| Native Raw Video | 原生原始视频格式 | ✅ | COLOR_FormatSurface |
| Flexible YUV | 灵活的YUV缓冲区 | ✅ | COLOR_FormatYUV420Flexible |
| Specific YUV | 特定YUV格式 | ❌ 仅ByteBuffer | COLOR_FormatYUV420Planar |
4.2 常用颜色格式对照表
| 常量名 | 值 | 说明 | 采样格式 |
|---|---|---|---|
COLOR_FormatSurface |
0x7F000789 | 与Surface配合使用 | - |
COLOR_FormatYUV420Flexible |
0x7F000788 | 灵活的YUV420 | Y/U/V 分离 |
COLOR_FormatYUV420Planar |
0x13 | YUV420P (I420) | Y plane, U plane, V plane |
COLOR_FormatYUV420PackedPlanar |
0x14 | YUV420P (紧凑) | YUV连续存储 |
COLOR_FormatYUV420SemiPlanar |
0x15 | NV12 | Y plane, UV交错 |
COLOR_FormatYUV420PackedSemiPlanar |
0x16 | NV21 | Y plane, VU交错 |
COLOR_FormatRGB |
0x07 | RGB格式 | - |
COLOR_FormatRGBA |
0x1F | RGBA格式 | - |
五、创建与配置
5.1 创建编解码器的三种方式
java
// 方式1:按MIME类型创建解码器(最常用)
MediaCodec decoder = MediaCodec.createDecoderByType("video/avc");
// 方式2:按MIME类型创建编码器
MediaCodec encoder = MediaCodec.createEncoderByType("video/avc");
// 方式3:按编解码器名称创建(精确指定)
MediaCodec codec = MediaCodec.createByCodecName("c2.android.avc.decoder");
5.2 获取可用编解码器信息
java
// 获取所有编解码器列表
MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
for (MediaCodecInfo info : codecList.getCodecInfos()) {
Log.d("Codec", "Name: " + info.getName());
Log.d("Codec", "Is Encoder: " + info.isEncoder());
Log.d("Codec", "Is Hardware: " + info.isHardwareAccelerated());
Log.d("Codec", "Is Software: " + info.isSoftwareOnly());
// 获取支持的类型
for (String type : info.getSupportedTypes()) {
Log.d("Codec", " Supported Type: " + type);
}
}
5.3 配置 MediaCodec
java
/**
* 配置编解码器
*
* @param format 输入数据格式(解码器)或期望的输出格式(编码器)
* @param surface 用于解码输出的Surface,或编码输入的Surface
* @param crypto DRM加密对象,用于安全解密
* @param flags 配置标志,CONFIGURE_FLAG_ENCODE表示编码器
*/
public void configure(MediaFormat format, Surface surface,
MediaCrypto crypto, int flags)
5.3.1 常用 MediaFormat 配置项
视频格式配置:
| Key | 类型 | 说明 | 典型值 |
|---|---|---|---|
KEY_MIME |
String | MIME类型 | "video/avc" |
KEY_WIDTH |
Integer | 视频宽度 | 1920 |
KEY_HEIGHT |
Integer | 视频高度 | 1080 |
KEY_BIT_RATE |
Integer | 码率(bps) | 4_000_000 |
KEY_FRAME_RATE |
Integer | 帧率 | 30 |
KEY_I_FRAME_INTERVAL |
Integer | I帧间隔(秒) | 1 |
KEY_COLOR_FORMAT |
Integer | 颜色格式 | COLOR_FormatSurface |
KEY_MAX_INPUT_SIZE |
Integer | 最大输入缓冲区大小 | 0(自动) |
KEY_PROFILE |
Integer | 编码profile | CodecProfileLevel.AVCProfileHigh |
KEY_LEVEL |
Integer | 编码level | CodecProfileLevel.AVCLevel4 |
音频格式配置:
| Key | 类型 | 说明 | 典型值 |
|---|---|---|---|
KEY_MIME |
String | MIME类型 | "audio/mp4a-latm" |
KEY_SAMPLE_RATE |
Integer | 采样率 | 44100 |
KEY_CHANNEL_COUNT |
Integer | 声道数 | 2 |
KEY_BIT_RATE |
Integer | 码率(bps) | 128_000 |
KEY_AAC_PROFILE |
Integer | AAC配置文件 | 2 (LC) |
KEY_MAX_INPUT_SIZE |
Integer | 最大输入缓冲区大小 | 0(自动) |
5.4 配置示例
java
// ==================== 解码器配置示例 ====================
private MediaCodec createVideoDecoder(String mimeType, Surface outputSurface) {
// 1. 创建格式
MediaFormat format = MediaFormat.createVideoFormat(mimeType, 1920, 1080);
// 2. 创建解码器
MediaCodec decoder = MediaCodec.createDecoderByType(mimeType);
// 3. 配置:与Surface关联用于输出
decoder.configure(format, outputSurface, null, 0);
// 4. 启动
decoder.start();
return decoder;
}
// ==================== 编码器配置示例 ====================
private MediaCodec createVideoEncoder(int width, int height, int bitRate) {
// 1. 创建格式
MediaFormat format = MediaFormat.createVideoFormat(
MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); // 码率
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30); // 帧率
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // I帧间隔1秒
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // 输入Surface
// 2. 创建编码器
MediaCodec encoder = MediaCodec.createEncoderByType(
MediaFormat.MIMETYPE_VIDEO_AVC);
// 3. 配置为编码模式
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
// 4. 获取输入Surface并启动
encoder.start();
return encoder;
}
六、同步模式 vs 异步模式
6.1 同步模式(Synchronous)
同步模式需要开发者手动管理缓冲区的获取和释放,通过轮询 dequeueInputBuffer() 和 dequeueOutputBuffer() 来驱动编解码过程。
java
/**
* 同步模式解码器
*/
public class SyncDecoder {
private MediaCodec decoder;
private MediaExtractor extractor;
private MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
public void decode(String filePath, Surface surface) {
// 1. 创建并配置
extractor = new MediaExtractor();
extractor.setDataSource(filePath);
// 选择视频轨道
for (int i = 0; i < extractor.getTrackCount(); i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) {
extractor.selectTrack(i);
decoder = MediaCodec.createDecoderByType(mime);
decoder.configure(format, surface, null, 0);
decoder.start();
break;
}
}
// 2. 编解码循环
boolean inputDone = false;
boolean outputDone = false;
while (!outputDone) {
// ---- 输入端:填充数据 ----
if (!inputDone) {
int inputBufferId = decoder.dequeueInputBuffer(10000); // 10ms超时
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = decoder.getInputBuffer(inputBufferId);
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) {
// 发送结束标志
decoder.queueInputBuffer(inputBufferId, 0, 0, 0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
inputDone = true;
} else {
// 发送正常数据
long presentationTimeUs = extractor.getSampleTime();
decoder.queueInputBuffer(inputBufferId, 0,
sampleSize, presentationTimeUs, 0);
extractor.advance();
}
}
}
// ---- 输出端:获取结果 ----
int outputBufferId = decoder.dequeueOutputBuffer(bufferInfo, 10000);
if (outputBufferId >= 0) {
// 检查结束标志
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
outputDone = true;
}
// 渲染到Surface
if (bufferInfo.size > 0) {
// 计算正确的显示时间戳
long renderTimeNs = bufferInfo.presentationTimeUs * 1000;
decoder.releaseOutputBuffer(outputBufferId, renderTimeNs);
} else {
decoder.releaseOutputBuffer(outputBufferId, false);
}
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// 格式变化处理
MediaFormat format = decoder.getOutputFormat();
Log.d("Decoder", "Output format changed: " + format);
}
}
// 3. 释放资源
decoder.stop();
decoder.release();
extractor.release();
}
}
6.2 异步模式(Asynchronous)
异步模式通过设置回调接口,让系统自动通知缓冲区可用性,代码更简洁,响应更及时。
java
/**
* 异步模式解码器
*/
public class AsyncDecoder {
private MediaCodec decoder;
private MediaExtractor extractor;
private AtomicBoolean isEndOfStream = new AtomicBoolean(false);
public void decode(String filePath, Surface surface) {
// 1. 创建并配置
extractor = new MediaExtractor();
extractor.setDataSource(filePath);
// 选择视频轨道
for (int i = 0; i < extractor.getTrackCount(); i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) {
extractor.selectTrack(i);
decoder = MediaCodec.createDecoderByType(mime);
// 设置异步回调
decoder.setCallback(new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
// 获取可用输入缓冲区
if (isEndOfStream.get()) return;
ByteBuffer inputBuffer = codec.getInputBuffer(index);
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) {
codec.queueInputBuffer(index, 0, 0, 0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
isEndOfStream.set(true);
} else {
long presentationTimeUs = extractor.getSampleTime();
codec.queueInputBuffer(index, 0, sampleSize,
presentationTimeUs, 0);
extractor.advance();
}
}
@Override
public void onOutputBufferAvailable(MediaCodec codec,
int index, MediaCodec.BufferInfo info) {
// 获取解码后的输出
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
return;
}
if (info.size > 0) {
long renderTimeNs = info.presentationTimeUs * 1000;
codec.releaseOutputBuffer(index, renderTimeNs);
} else {
codec.releaseOutputBuffer(index, false);
}
}
@Override
public void onOutputFormatChanged(MediaCodec codec,
MediaFormat format) {
// 格式变化时调用
Log.d("Decoder", "Output format: " + format);
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
Log.e("Decoder", "Codec error: " + e.getMessage());
}
}, new Handler(Looper.getMainLooper())); // 可指定Handler线程
decoder.configure(format, surface, null, 0);
decoder.start();
break;
}
}
}
}
6.3 两种模式对比
| 特性 | 同步模式 | 异步模式 |
|---|---|---|
| 线程控制 | 主线程/工作线程手动轮询 | 回调自动触发 |
| 代码复杂度 | 较高(需要管理循环) | 较低(事件驱动) |
| 响应速度 | 依赖轮询间隔 | 更快(立即回调) |
| 错误处理 | 集中处理 | 分散在回调中 |
| 适用场景 | 简单场景、需要精确控制 | 复杂视频处理、实时应用 |
| API版本 | API 16+ | API 21+,回调API 23+ |
七、Surface模式
7.1 为什么使用Surface
使用Surface进行编解码有以下几个关键优势:
┌─────────────────────────────────────────────────────────────────────────┐
│ Surface 模式 vs ByteBuffer 模式 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ByteBuffer 模式: │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 输入数据 │───►│ 解码器 │───►│ 输出Buffer│───►│ 应用处理 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │
│ 内存拷贝 (Copy) │
│ │
│ Surface 模式: │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 输入数据 │───►│ 解码器 │───►│ Surface │───────► 显示 │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │
│ 零拷贝 (Zero-Copy) │
│ │
├─────────────────────────────────────────────────────────────────────────┤
│ 性能对比 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┬───────────────┬───────────────┐ │
│ │ 指标 │ ByteBuffer │ Surface │ │
│ │ ───────────────────────────────────────────── │ │
│ │ 内存拷贝次数 │ 1-2次 │ 0次 │ │
│ │ CPU占用 │ 较高 │ 低 │ │
│ │ 延迟 │ 较高 │ 低 │ │
│ │ 实现复杂度 │ 复杂 │ 简单 │ │
│ │ 数据可访问性 │ 可直接访问 │ 需用ImageReader│ │
│ └─────────────────┴───────────────┴───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7.2 Surface 工作原理
Surface 内部使用 BufferQueue 实现生产者-消费者模式:
┌─────────────────────────────────────────────────────────────────────────┐
│ Surface + BufferQueue │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ Producer │ ←── MediaCodec (解码器) │
│ │ (生产者) │ Camera2 / Canvas │
│ └──────┬──────┘ │
│ │ │
│ │ dequeueBuffer() / queueBuffer() │
│ ↓ │
│ ┌─────────────┐ │
│ │ BufferQueue │ ← 共享内存区域 (Ashmem/GPU) │
│ │ (缓冲区队列) │ │
│ └──────┬──────┘ │
│ │ │
│ │ acquireBuffer() / releaseBuffer() │
│ ↓ │
│ ┌─────────────┐ │
│ │ Consumer │ ← SurfaceFlinger (合成显示) │
│ │ (消费者) │ ImageReader (获取原始帧) │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
7.3 Surface 模式下的数据获取
虽然Surface模式下数据不经过ByteBuffer,但我们仍然可以通过 ImageReader 获取原始视频帧:
java
/**
* 使用 ImageReader 获取 Surface 模式的解码输出
*/
public class SurfaceDecoderWithImageReader {
private ImageReader imageReader;
private Surface decoderSurface;
public void setup(int width, int height) {
// 1. 创建 ImageReader 用于获取原始帧
imageReader = ImageReader.newInstance(width, height,
ImageFormat.YV12, 2);
// 2. 创建解码器
MediaCodec decoder = MediaCodec.createDecoderByType("video/avc");
// 3. 获取解码器输入Surface
decoderSurface = decoder.createInputSurface();
// 4. 配置解码器输出到ImageReader
decoder.configure(videoFormat, null, null, 0);
decoder.setOutputSurface(imageReader.getSurface());
decoder.start();
// 5. 设置ImageReader回调获取帧
imageReader.setOnImageAvailableListener(reader -> {
Image image = reader.acquireLatestImage();
if (image != null) {
// 处理原始图像数据
processImage(image);
image.close();
}
}, new Handler(Looper.getMainLooper()));
}
private void processImage(Image image) {
// YV12格式: Y平面 + V平面 + U平面
Image.Plane[] planes = image.getPlanes();
// Y平面数据
ByteBuffer yBuffer = planes[0].getBuffer();
int yStride = planes[0].getRowStride();
// U平面数据
ByteBuffer uBuffer = planes[1].getBuffer();
int uStride = planes[1].getRowStride();
// V平面数据
ByteBuffer vBuffer = planes[2].getBuffer();
int vStride = planes[2].getRowStride();
// TODO: 处理YUV数据
}
}
八、实战一:H.264视频解码到Surface
8.1 项目结构
app/
├── java/com/example/videodecoder/
│ ├── MainActivity.kt
│ ├── VideoDecoder.kt
│ └── player/
│ ├── SurfaceHolderCallback.kt
│ └── VideoPlayerView.kt
├── res/layout/
│ └── activity_main.xml
└── AndroidManifest.xml
8.2 完整代码实现
java
// VideoDecoder.kt
package com.example.videodecoder
import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaExtractor
import android.media.MediaFormat
import android.media.MediaCodecList
import android.util.Log
import android.view.Surface
import java.util.concurrent.atomic.AtomicBoolean
/**
* H.264 视频解码器
*
* 功能:从文件读取H.264编码的视频流,解码后渲染到Surface
*/
class VideoDecoder {
companion object {
private const val TAG = "VideoDecoder"
private const val TIMEOUT_US = 10000L // 10ms超时
}
// 组件
private var mediaExtractor: MediaExtractor? = null
private var decoder: MediaCodec? = null
private var surface: Surface? = null
// 状态标志
private val isDecoding = AtomicBoolean(false)
private val isEof = AtomicBoolean(false)
// 时间相关
private var presentationStartUs = 0L
private var firstFrameTimeUs = -1L
/**
* 初始化解码器
*
* @param filePath 视频文件路径
* @param surface 用于渲染的Surface
* @return 是否初始化成功
*/
fun init(filePath: String, surface: Surface): Boolean {
this.surface = surface
// 1. 创建MediaExtractor并设置数据源
mediaExtractor = MediaExtractor()
try {
mediaExtractor!!.setDataSource(filePath)
} catch (e: Exception) {
Log.e(TAG, "Failed to set data source", e)
return false
}
// 2. 查找视频轨道
var videoTrackIndex = -1
var videoFormat: MediaFormat? = null
for (i in 0 until mediaExtractor!!.trackCount) {
val format = mediaExtractor!!.getTrackFormat(i)
val mime = format.getString(MediaFormat.KEY_MIME)
if (mime?.startsWith("video/") == true) {
videoTrackIndex = i
videoFormat = format
break
}
}
if (videoTrackIndex == -1 || videoFormat == null) {
Log.e(TAG, "No video track found")
return false
}
// 3. 选择视频轨道
mediaExtractor!!.selectTrack(videoTrackIndex)
// 4. 创建并配置解码器
try {
val mime = videoFormat.getString(MediaFormat.KEY_MIME)!!
decoder = createDecoder(mime, videoFormat, surface)
} catch (e: Exception) {
Log.e(TAG, "Failed to create decoder", e)
return false
}
return true
}
/**
* 根据MIME类型创建合适的解码器
*/
private fun createDecoder(mime: String, format: MediaFormat, surface: Surface): MediaCodec? {
// 方式1:使用MediaCodecList查找支持该格式的解码器
val codecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
for (codecInfo in codecList.codecInfos) {
// 跳过编码器
if (codecInfo.isEncoder) continue
// 检查是否支持该MIME类型
for (supportedMime in codecInfo.supportedTypes) {
if (supportedMime.equals(mime, ignoreCase = true)) {
Log.d(TAG, "Found decoder: ${codecInfo.name}")
val decoder = MediaCodec.createByCodecName(codecInfo.name)
decoder.configure(format, surface, null, 0)
return decoder
}
}
}
// 方式2:直接按类型创建(使用系统首选解码器)
Log.d(TAG, "Using default decoder for $mime")
val decoder = MediaCodec.createDecoderByType(mime)
decoder.configure(format, surface, null, 0)
return decoder
}
/**
* 启动解码
*/
fun start() {
if (decoder == null) {
Log.e(TAG, "Decoder not initialized")
return
}
decoder!!.start()
isDecoding.set(true)
// 启动解码线程
Thread(decodeLoop).start()
}
/**
* 解码循环(同步模式)
*/
private val decodeLoop = Runnable {
val extractor = mediaExtractor!!
val decoder = decoder!!
val bufferInfo = MediaCodec.BufferInfo()
Log.d(TAG, "Start decoding loop")
while (isDecoding.get() && !isEof.get()) {
// === 输入端:喂入压缩数据 ===
val inputBufferId = decoder.dequeueInputBuffer(TIMEOUT_US)
if (inputBufferId >= 0) {
val inputBuffer = decoder.getInputBuffer(inputBufferId)
if (inputBuffer != null) {
val sampleSize = extractor.readSampleData(inputBuffer, 0)
if (sampleSize < 0) {
// 输入结束,发送EOS标志
decoder.queueInputBuffer(
inputBufferId, 0, 0, 0,
MediaCodec.BUFFER_FLAG_END_OF_STREAM
)
isEof.set(true)
Log.d(TAG, "Sent EOS to decoder")
} else {
val presentationTimeUs = extractor.sampleTime
decoder.queueInputBuffer(
inputBufferId, 0, sampleSize, presentationTimeUs, 0
)
extractor.advance()
}
}
}
// === 输出端:获取解码数据 ===
val outputBufferId = decoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_US)
when {
outputBufferId >= 0 -> {
// 有效输出
val render = bufferInfo.size > 0
if (render) {
// 记录首帧时间用于同步
if (firstFrameTimeUs < 0) {
firstFrameTimeUs = bufferInfo.presentationTimeUs
presentationStartUs = System.nanoTime() / 1000
}
// 计算渲染时间戳
val renderTimeNs =
(bufferInfo.presentationTimeUs - firstFrameTimeUs) * 1000 +
presentationStartUs * 1000
// 释放输出缓冲区并渲染到Surface
decoder.releaseOutputBuffer(outputBufferId, renderTimeNs)
} else {
decoder.releaseOutputBuffer(outputBufferId, false)
}
// 检查EOS标志
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d(TAG, "Received EOS from decoder")
break
}
}
outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
// 输出格式变化
val newFormat = decoder.outputFormat
Log.d(TAG, "Output format changed: $newFormat")
}
outputBufferId == MediaCodec.INFO_TRY_AGAIN_LATER -> {
// 暂时没有输出,继续等待
}
}
}
Log.d(TAG, "Decoding loop finished")
}
/**
* 停止解码
*/
fun stop() {
isDecoding.set(false)
try {
decoder?.stop()
decoder?.release()
extractor?.release()
} catch (e: Exception) {
Log.e(TAG, "Error stopping decoder", e)
}
decoder = null
extractor = null
}
}
kotlin
// MainActivity.kt
package com.example.videodecoder
import android.os.Bundle
import android.view.SurfaceHolder
import android.view.SurfaceView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var surfaceView: SurfaceView
private var videoDecoder: VideoDecoder? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
surfaceView = findViewById(R.id.surfaceView)
setupSurface()
}
private fun setupSurface() {
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
// Surface创建后初始化解码器并开始播放
val videoPath = "/sdcard/test.mp4"
startPlayback(videoPath, holder.surface)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int,
width: Int, height: Int) {
// Surface尺寸变化处理
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
// 停止解码
videoDecoder?.stop()
}
})
}
private fun startPlayback(filePath: String, surface: Surface) {
videoDecoder = VideoDecoder()
if (videoDecoder!!.init(filePath, surface)) {
videoDecoder!!.start()
}
}
override fun onDestroy() {
super.onDestroy()
videoDecoder?.stop()
}
}
xml
<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
九、实战二:Camera2 + MediaCodec + MediaMuxer 录像
9.1 整体架构
┌─────────────────────────────────────────────────────────────────────────┐
│ Camera2 + MediaCodec + MediaMuxer 架构 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Camera2 API │ │
│ │ │ │
│ │ CameraDevice ──► CaptureSession ──► CaptureRequest │ │
│ │ │ │ │ │
│ │ │ ┌───────────────────┘ │ │
│ │ │ │ │ │
│ │ ↓ ↓ │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
│ │ │ Preview │ │ Encoder │ │ │
│ │ │ Surface │ │ InputSurface │ │ │
│ │ └────┬─────┘ └──────┬───────┘ │ │
│ └────────┼────────────────┼────────────────────────────────────┘ │
│ │ │ │
│ ↓ ↓ │
│ ┌─────────────────────────────────────┐ │
│ │ MediaCodec (编码器) │ │
│ │ │ │
│ │ Input: Surface (YUV帧) │ │
│ │ ↓ │ │
│ │ Process: H.264 编码 │ │
│ │ ↓ │ │
│ │ Output: 编码后的ByteBuffer │ │
│ │ │ │
│ │ 回调: onOutputBufferAvailable() │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────┐ │
│ │ MediaMuxer (封装器) │ │
│ │ │ │
│ │ addTrack(videoTrack) │ │
│ │ addTrack(audioTrack) ← AudioRecord │ │
│ │ ↓ │ │
│ │ writeSampleData() │ │
│ │ ↓ │ │
│ │ Output: .mp4 文件 │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
9.2 完整代码实现
java
// CameraRecorder.java
package com.example.recorder;
import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.camera2.*;
import android.media.*;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
/**
* 相机录像器
* 使用 Camera2 + MediaCodec + MediaMuxer 实现视频录制
*/
public class CameraRecorder {
private static final String TAG = "CameraRecorder";
// ==================== 常量定义 ====================
private static final String VIDEO_MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
private static final int VIDEO_WIDTH = 1920;
private static final int VIDEO_HEIGHT = 1080;
private static final int VIDEO_BITRATE = 8_000_000; // 8 Mbps
private static final int VIDEO_FRAME_RATE = 30;
private static final int I_FRAME_INTERVAL = 1; // 1秒I帧间隔
// ==================== 组件定义 ====================
private CameraManager cameraManager;
private CameraDevice cameraDevice;
private CameraCaptureSession captureSession;
private CaptureRequest.Builder previewRequestBuilder;
private MediaCodec videoEncoder;
private MediaMuxer mediaMuxer;
private Surface encoderInputSurface;
private int videoTrackIndex = -1;
private boolean isMuxerStarted = false;
// ==================== 线程定义 ====================
private HandlerThread backgroundThread;
private Handler backgroundHandler;
// ==================== 状态标志 ====================
private boolean isRecording = false;
private long recordingStartTime = 0;
// 回调
private RecorderCallback recorderCallback;
// ==================== 回调接口 ====================
public interface RecorderCallback {
void onRecordingStarted();
void onRecordingStopped(String filePath);
void onError(String message);
}
// ==================== 初始化 ====================
/**
* 初始化录像器
*
* @param context Context
* @param outputPath 输出文件路径
* @param callback 状态回调
*/
public void init(Context context, String outputPath, RecorderCallback callback) {
this.recorderCallback = callback;
// 启动后台线程
startBackgroundThread();
// 初始化CameraManager
cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
// 初始化MediaCodec编码器
initVideoEncoder();
// 初始化MediaMuxer
initMediaMuxer(outputPath);
}
/**
* 初始化视频编码器
*/
private void initVideoEncoder() {
try {
// 1. 创建编码器
videoEncoder = MediaCodec.createEncoderByType(VIDEO_MIME);
// 2. 配置编码参数
MediaFormat format = MediaFormat.createVideoFormat(VIDEO_MIME,
VIDEO_WIDTH, VIDEO_HEIGHT);
format.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_BITRATE); // 码率
format.setInteger(MediaFormat.KEY_FRAME_RATE, VIDEO_FRAME_RATE); // 帧率
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL); // I帧间隔
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, // 输入格式
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_PROFILE, // H.264 Profile
MediaCodecInfo.CodecProfileLevel.AVCProfileHigh);
format.setInteger(MediaFormat.KEY_LEVEL, // H.264 Level
MediaCodecInfo.CodecProfileLevel.AVCLevel4);
// 3. 配置为编码模式
videoEncoder.configure(format, null, null,
MediaCodec.CONFIGURE_FLAG_ENCODE);
// 4. 创建输入Surface(用于Camera直接写入)
encoderInputSurface = videoEncoder.createInputSurface();
// 5. 设置输出回调
videoEncoder.setCallback(videoEncoderCallback, backgroundHandler);
// 6. 启动编码器
videoEncoder.start();
Log.d(TAG, "Video encoder initialized successfully");
} catch (IOException | MediaCodec.CodecException e) {
Log.e(TAG, "Failed to initialize video encoder", e);
if (recorderCallback != null) {
recorderCallback.onError("编码器初始化失败: " + e.getMessage());
}
}
}
/**
* 初始化媒体混合器
*/
private void initMediaMuxer(String outputPath) {
try {
mediaMuxer = new MediaMuxer(outputPath,
MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
Log.d(TAG, "MediaMuxer initialized: " + outputPath);
} catch (IOException e) {
Log.e(TAG, "Failed to initialize MediaMuxer", e);
if (recorderCallback != null) {
recorderCallback.onError("混合器初始化失败: " + e.getMessage());
}
}
}
// ==================== 编码器回调 ====================
private MediaCodec.Callback videoEncoderCallback = new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
// Surface模式下不需要手动填充输入缓冲区
// Camera会直接将帧写入encoderInputSurface
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index,
MediaCodec.BufferInfo info) {
if (!isRecording) {
codec.releaseOutputBuffer(index, false);
return;
}
// 忽略编解码器配置数据
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
codec.releaseOutputBuffer(index, false);
return;
}
// 检查是否有有效数据
if (info.size > 0 && isMuxerStarted) {
ByteBuffer outputBuffer = codec.getOutputBuffer(index);
if (outputBuffer != null) {
// 写入混合器
mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, info);
}
}
// 释放输出缓冲区
codec.releaseOutputBuffer(index, false);
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
Log.d(TAG, "Encoder output format changed: " + format);
// 格式变化时,添加视频轨道并启动混合器
if (!isMuxerStarted) {
videoTrackIndex = mediaMuxer.addTrack(format);
mediaMuxer.start();
isMuxerStarted = true;
recordingStartTime = System.nanoTime();
Log.d(TAG, "MediaMuxer started, video track index: " + videoTrackIndex);
}
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
Log.e(TAG, "Encoder error: " + e.getMessage(), e);
if (recorderCallback != null) {
recorderCallback.onError("编码错误: " + e.getMessage());
}
}
};
// ==================== 相机操作 ====================
/**
* 启动录像
*/
@SuppressLint("MissingPermission")
public void startRecording() {
try {
// 打开相机
cameraManager.openCamera(getBackCameraId(), cameraStateCallback,
backgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "Failed to open camera", e);
if (recorderCallback != null) {
recorderCallback.onError("无法打开相机: " + e.getMessage());
}
}
}
private String getBackCameraId() throws CameraAccessException {
for (String cameraId : cameraManager.getCameraIdList()) {
CameraCharacteristics characteristics =
cameraManager.getCameraCharacteristics(cameraId);
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {
return cameraId;
}
}
return cameraManager.getCameraIdList()[0];
}
private final CameraDevice.StateCallback cameraStateCallback =
new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
cameraDevice = camera;
createCaptureSession();
}
@Override
public void onDisconnected(CameraDevice camera) {
camera.close();
cameraDevice = null;
}
@Override
public void onError(CameraDevice camera, int error) {
camera.close();
cameraDevice = null;
if (recorderCallback != null) {
recorderCallback.onError("相机错误: " + error);
}
}
};
/**
* 创建相机捕获会话
* 将预览Surface和编码器输入Surface绑定到同一个会话
*/
private void createCaptureSession() {
try {
// 创建预览Surface(用于显示)
Surface previewSurface = getPreviewSurface(); // 需要自己实现获取SurfaceView的Surface
// 配置Surface列表
List<Surface> surfaces = Arrays.asList(previewSurface, encoderInputSurface);
// 创建捕获请求
previewRequestBuilder = cameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
previewRequestBuilder.addTarget(previewSurface);
previewRequestBuilder.addTarget(encoderInputSurface);
// 配置为连续自动对焦
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO);
// 创建会话
cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
captureSession = session;
try {
// 启动预览和编码
session.setRepeatingRequest(previewRequestBuilder.build(),
null, backgroundHandler);
isRecording = true;
if (recorderCallback != null) {
recorderCallback.onRecordingStarted();
}
Log.d(TAG, "Recording started");
} catch (CameraAccessException e) {
Log.e(TAG, "Failed to start capture session", e);
}
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
if (recorderCallback != null) {
recorderCallback.onError("相机配置失败");
}
}
}, backgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "Failed to create capture session", e);
}
}
/**
* 停止录像
*/
public void stopRecording() {
isRecording = false;
try {
// 1. 停止相机捕获
if (captureSession != null) {
captureSession.stopRepeating();
captureSession.close();
captureSession = null;
}
// 2. 关闭相机
if (cameraDevice != null) {
cameraDevice.close();
cameraDevice = null;
}
// 3. 停止编码器(发送EOS)
if (videoEncoder != null) {
videoEncoder.signalEndOfInputStream();
}
Log.d(TAG, "Recording stopped");
} catch (CameraAccessException e) {
Log.e(TAG, "Error stopping recording", e);
}
}
// ==================== 资源释放 ====================
/**
* 释放所有资源
*/
public void release() {
// 停止录像
if (isRecording) {
stopRecording();
}
// 停止编码器
if (videoEncoder != null) {
videoEncoder.stop();
videoEncoder.release();
videoEncoder = null;
}
// 停止混合器
if (mediaMuxer != null) {
if (isMuxerStarted) {
mediaMuxer.stop();
}
mediaMuxer.release();
mediaMuxer = null;
}
// 停止后台线程
stopBackgroundThread();
Log.d(TAG, "Resources released");
}
private void startBackgroundThread() {
backgroundThread = new HandlerThread("CameraRecorder");
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}
private void stopBackgroundThread() {
if (backgroundThread != null) {
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
} catch (InterruptedException e) {
Log.e(TAG, "Error stopping background thread", e);
}
}
}
/**
* 获取预览Surface(示例,需要配合SurfaceView使用)
*/
private Surface getPreviewSurface() {
// TODO: 实现获取SurfaceView的Surface
return null;
}
}
十、MIME类型与编解码器支持
10.1 支持的视频格式
| MIME类型 | 格式名称 | 容器支持 | 说明 |
|---|---|---|---|
video/avc |
H.264/AVC | MP4/MKV/WebM | 最广泛支持 |
video/hevc |
H.265/HEVC | MP4/MKV | Android 5.0+ |
video/x-vnd.on2.vp8 |
VP8 | WebM | Android 4.3+ |
video/x-vnd.on2.vp9 |
VP9 | WebM | Android 4.4+ |
video/mp4v-es |
MPEG-4 SP | MP4 | 基本支持 |
video/3gpp |
H.263 | 3GP | 老设备 |
video/av01 |
AV1 | MP4/MKV | Android 10+ |
10.2 支持的音频格式
| MIME类型 | 格式名称 | 说明 |
|---|---|---|
audio/mp4a-latm |
AAC | 最广泛支持 |
audio/mpeg |
MP3 | 广泛支持 |
audio/vorbis |
Vorbis | WebM |
audio/opus |
Opus | Android 10+ |
audio/g711-alaw |
G.711 A-law | 语音 |
audio/g711-mlaw |
G.711 μ-law | 语音 |
audio/3gpp |
AMR-NB | 语音 |
audio/amr-wb |
AMR-WB | 宽带语音 |
audio/flac |
FLAC | 无损 |
10.3 查询设备支持的编解码器
java
/**
* 列出设备支持的所有编解码器
*/
public class CodecUtils {
public static void listSupportedCodecs() {
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
Log.d("Codecs", "=== 设备支持的编解码器 ===");
for (MediaCodecInfo info : codecList.getCodecInfos()) {
Log.d("Codecs", "");
Log.d("Codecs", "┌─────────────────────────────────────────");
Log.d("Codecs", "│ 名称: " + info.getName());
Log.d("Codecs", "│ 类型: " + (info.isEncoder() ? "编码器" : "解码器"));
Log.d("Codecs", "│ 硬件加速: " + info.isHardwareAccelerated());
Log.d("Codecs", "│ 软件实现: " + info.isSoftwareOnly());
Log.d("Codecs", "│ 厂商内建: " + info.isVendor());
// 支持的类型
String[] types = info.getSupportedTypes();
Log.d("Codecs", "│ 支持类型:");
for (String type : types) {
// 获取该类型的编码能力
MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(type);
Log.d("Codecs", "│ - " + type);
Log.d("Codecs", "│ 颜色格式: " + Arrays.toString(caps.colorFormats));
}
Log.d("Codecs", "└─────────────────────────────────────────");
}
}
/**
* 检查是否支持特定编解码
*/
public static boolean isCodecSupported(String mimeType, boolean isEncoder) {
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo info : codecList.getCodecInfos()) {
if (info.isEncoder() != isEncoder) continue;
for (String type : info.getSupportedTypes()) {
if (type.equalsIgnoreCase(mimeType)) {
return true;
}
}
}
return false;
}
/**
* 获取特定格式的编解码能力
*/
public static MediaCodecInfo.CodecCapabilities getCodecCapabilities(
String codecName, String mimeType) {
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo info : codecList.getCodecInfos()) {
if (info.getName().equals(codecName)) {
return info.getCapabilitiesForType(mimeType);
}
}
return null;
}
}
十一、Codec特定数据(CSD)处理
某些编码格式需要编解码器特定的初始化数据,这些数据必须在正常帧数据之前发送。
11.1 CSD数据对照表
| 格式 | CSD Buffer #0 | CSD Buffer #1 | CSD Buffer #2 | 备注 |
|---|---|---|---|---|
| H.264 AVC | SPS (序列参数集) | PPS (图像参数集) | - | 必须以起始码 \x00\x00\x00\x01 开头 |
| H.265 HEVC | VPS + SPS + PPS | - | - | 组合在一个buffer中 |
| VP8 | CodecPrivate (可选) | - | - | 通常不需要 |
| VP9 | CodecPrivate (可选) | - | - | 通常不需要 |
| AAC | Decoder-specific info (ESDS) | - | - | 包含AudioSpecificConfig |
| Vorbis | Identification header | Setup header | - | |
| OPUS | Identification header | Pre-skip (纳秒) | Seek pre-roll (纳秒) |
11.2 H.264 CSD处理示例
java
/**
* 从MediaFormat中提取H.264 SPS/PPS
*/
public class H264CSDParser {
/**
* 提取SPS和PPS
*
* @param format MediaFormat from MediaExtractor
* @return 包含sps和pps的数组
*/
public static byte[][] extractH264SPSPPS(MediaFormat format) {
byte[] csd0 = format.getByteBuffer("csd-0").array();
byte[] csd1 = format.getByteBuffer("csd-1").array();
// 确保SPS以起始码开头
if (!hasStartCode(csd0)) {
csd0 = addStartCode(csd0);
}
// 确保PPS以起始码开头
if (!hasStartCode(csd1)) {
csd1 = addStartCode(csd1);
}
return new byte[][] { csd0, csd1 };
}
private static boolean hasStartCode(byte[] data) {
return data.length >= 4 &&
data[0] == 0x00 && data[1] == 0x00 &&
data[2] == 0x00 && data[3] == 0x01;
}
private static byte[] addStartCode(byte[] data) {
byte[] withStartCode = new byte[data.length + 4];
withStartCode[0] = 0x00;
withStartCode[1] = 0x00;
withStartCode[2] = 0x00;
withStartCode[3] = 0x01;
System.arraycopy(data, 0, withStartCode, 4, data.length);
return withStartCode;
}
}
/**
* 手动发送H.264 CSD数据
*/
public class H264DecoderWithManualCSD {
private MediaCodec decoder;
private byte[][] csdData; // 存储SPS/PPS
public void init(MediaFormat format, Surface surface) {
// 1. 提取CSD数据
ByteBuffer csd0 = format.getByteBuffer("csd-0");
ByteBuffer csd1 = format.getByteBuffer("csd-1");
// 转换为byte数组(去除映射缓冲区的position/limit)
csdData = new byte[][] {
getBytesFromBuffer(csd0),
getBytesFromBuffer(csd1)
};
// 2. 创建解码器
String mime = format.getString(MediaFormat.KEY_MIME);
decoder = MediaCodec.createDecoderByType(mime);
decoder.configure(format, surface, null, 0);
decoder.start();
// 3. 手动发送CSD数据
sendCSDData();
}
private byte[] getBytesFromBuffer(ByteBuffer buffer) {
buffer.rewind();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
return data;
}
/**
* 发送CSD数据到解码器
*/
private void sendCSDData() {
// H.264: CSD Buffer 0 = SPS, CSD Buffer 1 = PPS
// 使用BUFFER_FLAG_CODEC_CONFIG标志
if (csdData.length > 0 && csdData[0] != null) {
int inputIndex = decoder.dequeueInputBuffer(10000);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = decoder.getInputBuffer(inputIndex);
inputBuffer.clear();
inputBuffer.put(csdData[0]);
decoder.queueInputBuffer(inputIndex, 0, csdData[0].length, 0,
MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
}
}
if (csdData.length > 1 && csdData[1] != null) {
int inputIndex = decoder.dequeueInputBuffer(10000);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = decoder.getInputBuffer(inputIndex);
inputBuffer.clear();
inputBuffer.put(csdData[1]);
decoder.queueInputBuffer(inputIndex, 0, csdData[1].length, 0,
MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
}
}
}
}
十二、错误处理与异常恢复
12.1 异常类型
java
// MediaCodec 异常继承关系
Exception
├── RuntimeException
│ └── MediaCodec.CodecException // 编解码器错误
│ ├── isRecoverable() = true // 可恢复错误
│ ├── isTransient() = true // 临时错误
│ └── isRecoverable() = false && isTransient() = false // 致命错误
│
└── MediaCodec.CryptoException // DRM/加密错误
├── errorCode // 错误码
└── getErrorCode() // 获取具体错误
12.2 错误处理策略
| 错误类型 | isRecoverable | isTransient | 处理策略 |
|---|---|---|---|
| 可恢复错误 | true | false | stop() → configure() → start() |
| 临时错误 | false | true | 等待后重试操作 |
| 致命错误 | false | false | reset() 或 release() |
12.3 完整的错误处理示例
java
/**
* 带错误处理的健壮解码器
*/
public class RobustDecoder {
private MediaCodec decoder;
private boolean isRunning = false;
public void decode(String filePath, Surface surface) {
isRunning = true;
int retryCount = 0;
final int MAX_RETRIES = 3;
while (isRunning && retryCount < MAX_RETRIES) {
try {
// 正常解码流程
doDecode(filePath, surface);
break; // 成功则退出循环
} catch (MediaCodec.CodecException e) {
Log.e("Decoder", "Codec error: " + e.getMessage());
Log.e("Decoder", " isRecoverable: " + e.isRecoverable());
Log.e("Decoder", " isTransient: " + e.isTransient());
if (e.isRecoverable()) {
// 可恢复错误:重新配置
Log.d("Decoder", "Attempting to recover...");
recoverDecoder();
retryCount++;
} else if (e.isTransient()) {
// 临时错误:等待后重试
Log.d("Decoder", "Transient error, waiting...");
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
break;
}
retryCount++;
} else {
// 致命错误:放弃
Log.e("Decoder", "Unrecoverable error, giving up");
break;
}
} catch (IllegalStateException e) {
// 状态错误,通常是调用顺序问题
Log.e("Decoder", "Illegal state: " + e.getMessage());
break;
} finally {
cleanup();
}
}
}
/**
* 恢复解码器
*/
private void recoverDecoder() {
cleanup(); // 先清理
// 重新创建和配置
// ... 初始化代码 ...
}
/**
* 清理资源
*/
private void cleanup() {
try {
if (decoder != null) {
decoder.stop();
decoder.release();
decoder = null;
}
} catch (Exception e) {
Log.e("Decoder", "Error during cleanup", e);
}
}
}
/**
* 异步模式下的错误处理
*/
public class AsyncDecoderWithErrorHandling {
private MediaCodec decoder;
private MediaCodec.Callback callback = new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
// 填充输入数据
}
@Override
public void onOutputBufferAvailable(MediaCodec codec, int index,
MediaCodec.BufferInfo info) {
// 处理输出
}
@Override
public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
// 格式变化处理
}
@Override
public void onError(MediaCodec codec, MediaCodec.CodecException e) {
Log.e("AsyncDecoder", "Codec error: " + e.getMessage());
// 统一的错误处理逻辑
handleCodecError(e);
}
};
private void handleCodecError(MediaCodec.CodecException e) {
if (e.isRecoverable()) {
// 触发恢复流程
postRecoverTask();
} else if (e.isTransient()) {
// 触发重试
postRetryTask();
} else {
// 触发完全重启
postRestartTask();
}
}
}
十三、最佳实践与性能优化
13.1 性能优化清单
| 优化项 | 具体做法 | 预期收益 |
|---|---|---|
| 使用Surface | 解码输出到Surface而非ByteBuffer | 零拷贝,显著降低CPU和延迟 |
| 选择合适分辨率 | 按目标设备选择720p/1080p | 降低解码负载 |
| 控制码率 | 编码时设置合理码率(2-8Mbps) | 平衡质量与性能 |
| I帧间隔 | 设置I帧间隔(1-5秒) | 减少关键帧数量,降低带宽 |
| 重用缓冲区 | 避免频繁创建/销毁ByteBuffer | 减少GC压力 |
| 异步模式 | 复杂场景使用异步回调 | 更好的响应性 |
| 预分配内存 | 预先分配输入缓冲区 | 减少运行时分配 |
| 硬件加速 | 优先使用硬件编解码器 | 降低CPU占用 |
13.2 编解码器选择策略
java
/**
* 智能编解码器选择器
*/
public class SmartCodecSelector {
/**
* 选择最佳解码器
*
* 优先级:硬件加速 > 软件实现 > 任意
*/
public static MediaCodec selectDecoder(Context context, String mimeType) {
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
MediaCodecInfo preferredCodec = null;
MediaCodecInfo fallbackCodec = null;
for (MediaCodecInfo info : codecList.getCodecInfos()) {
if (info.isEncoder()) continue; // 只看解码器
// 检查是否支持该MIME
boolean supportsType = false;
for (String type : info.getSupportedTypes()) {
if (type.equalsIgnoreCase(mimeType)) {
supportsType = true;
break;
}
}
if (!supportsType) continue;
// 优先选择硬件加速的
if (info.isHardwareAccelerated()) {
if (preferredCodec == null) {
preferredCodec = info;
}
} else if (info.isSoftwareOnly() && fallbackCodec == null) {
fallbackCodec = info;
}
}
// 返回最佳选择
MediaCodecInfo selected = preferredCodec != null ?
preferredCodec : fallbackCodec;
if (selected != null) {
try {
return MediaCodec.createByCodecName(selected.getName());
} catch (IOException e) {
Log.e("CodecSelector", "Failed to create codec", e);
}
}
// 兜底:使用系统默认
return MediaCodec.createDecoderByType(mimeType);
}
/**
* 检查设备能力
*/
public static boolean isResolutionSupported(String mimeType,
int width, int height) {
MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
for (MediaCodecInfo info : codecList.getCodecInfos()) {
if (info.isEncoder()) continue;
for (String type : info.getSupportedTypes()) {
if (type.equalsIgnoreCase(mimeType)) {
MediaCodecInfo.VideoCapabilities videoCaps =
info.getCapabilitiesForType(type).getVideoCapabilities();
if (videoCaps != null) {
return videoCaps.isSizeSupported(width, height);
}
}
}
}
return false;
}
}
13.3 内存管理最佳实践
java
/**
* 内存优化的缓冲区管理
*/
public class OptimizedDecoder {
// 预分配的缓冲区,避免运行时分配
private ByteBuffer[] inputBuffers;
private ByteBuffer[] outputBuffers;
private MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
public void init(String mimeType, Surface surface) {
MediaCodec decoder = MediaCodec.createDecoderByType(mimeType);
// 配置...
decoder.configure(format, surface, null, 0);
decoder.start();
// 获取并缓存缓冲区引用(虽然已弃用getInputBuffers(),
// 但对于需要频繁访问的场景,缓存是合理的)
// 注意:在API 21+应使用getInputBuffer(index)单个获取
}
/**
* 高效的数据处理
*/
private void processFrame(MediaCodec decoder) {
// 1. 获取输入缓冲区
int inputIndex = decoder.dequeueInputBuffer(TIMEOUT_US);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = decoder.getInputBuffer(inputIndex);
// 使用Buffer而非创建新的
}
// 2. 获取输出缓冲区
int outputIndex = decoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_US);
if (outputIndex >= 0) {
// 处理输出...
// 3. 及时释放,避免缓冲区耗尽
decoder.releaseOutputBuffer(outputIndex, render);
}
}
}
十四、常见问题汇总(FAQ)
Q1: MediaCodec 创建失败怎么办?
A: 常见原因及解决方案:
| 原因 | 解决方案 |
|---|---|
| MIME类型不支持 | 使用 MediaCodecList 检查设备支持 |
| 设备资源不足 | 关闭其他编解码会话后重试 |
| 编码器名称无效 | 使用 createDecoderByType() 代替 |
| 权限问题 | 检查 Camera/Microphone 权限 |
java
try {
decoder = MediaCodec.createDecoderByType("video/avc");
} catch (IOException e) {
// 检查设备是否支持
if (!isCodecSupported("video/avc", false)) {
Log.e("Decoder", "AVC not supported on this device");
}
}
Q2: 解码延迟很高怎么优化?
A: 从以下几个方面入手:
- 使用Surface模式 - 零拷贝渲染
- 增大输入缓冲区 -
KEY_MAX_INPUT_SIZE - 设置正确的帧率 -
KEY_FRAME_RATE - 检查编码器设置 - 确保编码时没有过大的IDR间隔
java
// 优化配置
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1920 * 1080);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
Q3: 如何处理自适应播放?
A: Android 5.0+ 支持自适应播放,在配置时启用:
java
// 检查是否支持自适应播放
MediaCodecInfo.CodecCapabilities caps =
codecInfo.getCapabilitiesForType("video/avc");
boolean supportsAdaptivePlayback =
caps.isFeatureSupported(MediaCodecInfo.CodecFeature.AdaptivePlayback);
// 在配置时,Surface会自动支持自适应播放
decoder.configure(format, surface, null, 0);
Q4: 解码器输出格式变化怎么处理?
A: 在 dequeueOutputBuffer 返回 INFO_OUTPUT_FORMAT_CHANGED 时获取新格式:
java
int outputIndex = decoder.dequeueOutputBuffer(bufferInfo, timeoutUs);
if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = decoder.getOutputFormat();
Log.d("Decoder", "Format changed: " + newFormat);
// 更新相关参数,如宽高、码率等
int newWidth = newFormat.getInteger(MediaFormat.KEY_WIDTH);
int newHeight = newFormat.getInteger(MediaFormat.KEY_HEIGHT);
// 通知UI更新
notifyResolutionChanged(newWidth, newHeight);
}
Q5: 如何实现视频seek到指定位置?
A: 使用 MediaExtractor.seekTo() 配合解码器刷新:
java
public void seekTo(long timeUs) {
// 1. 解码器刷新
decoder.flush();
// 2. 调整Extractor位置
extractor.seekTo(timeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
// 3. 丢弃当前缓冲区
// ... 继续正常解码
}
Q6: 编码出来的视频颜色不对?
A: 检查颜色格式配置:
| 问题 | 原因 | 解决 |
|---|---|---|
| 颜色偏蓝 | NV12/NV21混淆 | 检查 COLOR_FormatYUV420SemiPlanar |
| 颜色失真 | 位深不匹配 | 使用 COLOR_FormatSurface |
| 黑屏 | YUV范围错误 | 检查编码器配置 |
Q7: 如何实现音视频同步?
A: 使用 MediaSync 或手动同步:
java
// 使用 MediaSync(推荐)
MediaSync sync = new MediaSync();
sync.setSurface(surface);
sync.setCallback(new MediaSync.Callback() {
@Override
public void onBufferAvailable(MediaSync.BufferItem item) {
// 处理音频/视频同步
}
}, handler);
Q8: 如何处理DRM加密内容?
A: 使用 MediaCrypto:
java
// 创建MediaCrypto
MediaDrm mediaDrm = new MediaDrm(UUID.fromString("..."));
MediaCrypto crypto = new MediaCrypto(uuid, sessionId);
// 配置到解码器
decoder.configure(format, surface, crypto, 0);
十五、总结
15.1 核心要点回顾
┌─────────────────────────────────────────────────────────────────────────┐
│ MediaCodec 知识全景图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 四大核心概念 │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ 1️⃣ 生产者-消费者模型 │ │
│ │ 输入缓冲区 ──► 编解码器 ──► 输出缓冲区 │ │
│ │ │ │
│ │ 2️⃣ 状态机管理 │ │
│ │ Uninitialized → Configured → Executing │ │
│ │ │ │
│ │ 3️⃣ 三种数据处理方式 │ │
│ │ 压缩数据 / 原始音频 / 原始视频 (Surface) │ │
│ │ │ │
│ │ 4️⃣ 同步 vs 异步模式 │ │
│ │ 轮询 dequeue*Buffer / Callback 回调 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 使用场景速查表 │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ 🎬 视频播放 │ MediaExtractor + MediaCodec + Surface│ │
│ │ 📹 视频录制 │ Camera2 + MediaCodec + MediaMuxer │ │
│ │ 🖥️ 屏幕录制 │ MediaProjection + MediaCodec │ │
│ │ 🔄 格式转换 │ MediaExtractor + MediaCodec + Muxer │ │
│ │ 📡 实时流处理 │ MediaCodec + 自定义传输协议 │ │
│ │ 🛡️ DRM播放 │ MediaCrypto + MediaCodec │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 性能优化要点 │ │
│ ├─────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ✅ 优先使用 Surface 模式 (零拷贝) │ │
│ │ ✅ 选择合适的分辨率和码率 │ │
│ │ ✅ 使用异步模式处理复杂场景 │ │
│ │ ✅ 及时释放缓冲区 │ │
│ │ ✅ 做好错误处理和恢复 │ │
│ │ ✅ 正确处理 CSD 数据 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
15.2 学习路线建议
第一阶段:基础入门
├── 理解 MediaCodec 在 Android 多媒体架构中的位置
├── 掌握编解码器创建和配置
├── 实现简单的视频解码到 Surface
└── 实现简单的视频编码从 Surface
第二阶段:进阶提升
├── 深入理解状态机和缓冲区管理
├── 掌握同步和异步两种模式
├── 实现 Camera2 + MediaCodec 录像
├── 处理 CSD 特定数据
└── 实现音视频同步
第三阶段:高级应用
├── 性能调优和问题排查
├── DRM 内容处理
├── 自适应播放支持
├── 实时流处理
└── 自定义编解码器集成
15.3 参考资源
| 资源 | 链接 |
|---|---|
| 官方文档 | https://developer.android.com/reference/android/media/MediaCodec |
| Google Grafika | https://github.com/google/grafika |
| 学习项目 | https://github.com/jiemojiemo/LearnMediaCodec |
| CTS 测试 | cts/tests/media/src/android/media/cts/ |
📝 写在最后
MediaCodec 是 Android 音视频开发的核心API,掌握它就等于打开了多媒体开发的大门。希望本文能帮助你从入门到精通,在音视频开发的道路上越走越远!
如有问题,欢迎在评论区交流讨论!
关于作者:码流怪侠,CSDN 博主,研究方向包括视频编解码、视频质量评估、超分辨率、AI等。
本文首发于:CSDN