Android MediaCodec 全面详解:从入门到精通

📋 目录

  1. [一文搞懂 Android MediaCodec 是什么](#一文搞懂 Android MediaCodec 是什么 "#%E4%B8%80mediaodec-%E6%98%AF%E4%BB%80%E4%B9%88")
  2. 四层架构设计深度剖析
  3. 状态机:编解码器的生命周期
  4. 数据类型:三种数据格式全解析
  5. 创建与配置:正确的初始化方式
  6. [同步模式 vs 异步模式](#同步模式 vs 异步模式 "#%E5%85%AD%E5%90%8C%E6%AD%A5%E6%A8%A1%E5%BC%8F-vs-%E5%BC%82%E6%AD%A5%E6%A8%A1%E5%BC%8F")
  7. Surface模式:为什么效率更高
  8. 实战一:H.264视频解码到Surface
  9. [实战二:Camera2 + MediaCodec + MediaMuxer 录像](#实战二:Camera2 + MediaCodec + MediaMuxer 录像 "#%E4%B9%9D%E5%AE%9E%E6%88%98%E4%BA%8Ccamera2-mediacodec-mediamuxer-%E5%BD%95%E5%83%8F")
  10. MIME类型与编解码器支持一览
  11. Codec特定数据(CSD)处理
  12. 错误处理与异常恢复
  13. 最佳实践与性能优化
  14. 常见问题汇总(FAQ)
  15. 总结

一、MediaCodec 是什么

1.1 基本概念

MediaCodec 是 Android 系统提供的底层媒体编解码接口 ,用于访问设备上的硬件或软件编解码器。它是 Android 多媒体支持基础设施的核心组件之一,自 API Level 16 (Android 4.1) 开始引入。

简单来说,MediaCodec 就是 Android 系统中负责压缩数据 ↔ 原始数据转换的"翻译官":

markdown 复制代码
📦 压缩数据(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 多媒体组件配合使用:

objectivec 复制代码
┌──────────────────────────────────────────────────────────────────┐
│                        Android 多媒体架构                          │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│   MediaExtractor ──────► MediaCodec ──────► MediaMuxer          │
│        │                     │                    │              │
│        │                     │                    │              │
│        ▼                     ▼                    ▼              │
│   音视频分离            编解码处理            音视频封装            │
│        │                     │                    │              │
│        │                 Surface                   │              │
│        │                     │                    │              │
│        ▼                     ▼                    ▼              │
│   MediaRetriever      SurfaceFlinger         MP4/WebM           │
│                                                                  │
│   MediaCrypto ◄─────► MediaDrm (DRM加密内容)                    │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘
组件 功能
MediaExtractor 解析媒体文件,分离音视频轨道
MediaCodec 编解码核心,负责压缩数据↔原始数据转换
MediaMuxer 将编码后的音视频轨混合封装成 MP4/WebM 等容器
MediaSync 音视频同步控制
MediaCrypto 处理加密/DRM保护的内容
Surface 图形缓冲区,用于高效的视频数据传递

二、四层架构设计

MediaCodec 采用四层架构设计,从 Java API 到硬件抽象,层层分离:

scss 复制代码
┌────────────────────────────────────────────────────────────────────────┐
│                         四层架构总览                                    │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│  ┌────────────────────────────────────────────────────────────────┐   │
│  │                    第一层: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 硬解路径对比

yaml 复制代码
┌────────────────────────────────────────────────────────────────────────┐
│                           软解路径 (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 完整状态转换图

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                           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 处理三种类型的数据,每种都有其特定的处理方式:

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                         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进行编解码有以下几个关键优势:

rust 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                        Surface 模式 vs ByteBuffer 模式                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ByteBuffer 模式:                                                       │
│   ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐       │
│   │ 输入数据  │───►│ 解码器   │───►│ 输出Buffer│───►│ 应用处理 │       │
│   └──────────┘    └──────────┘    └──────────┘    └──────────┘       │
│                         │                                                 │
│                    内存拷贝 (Copy)                                        │
│                                                                         │
│   Surface 模式:                                                         │
│   ┌──────────┐    ┌──────────┐    ┌──────────┐                        │
│   │ 输入数据  │───►│ 解码器   │───►│ Surface   │───────► 显示           │
│   └──────────┘    └──────────┘    └──────────┘                        │
│                         │                                                 │
│                    零拷贝 (Zero-Copy)                                    │
│                                                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                          性能对比                                        │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   ┌─────────────────┬───────────────┬───────────────┐                   │
│   │     指标        │  ByteBuffer   │    Surface    │                   │
│   │   ─────────────────────────────────────────────  │                   │
│   │   内存拷贝次数   │     1-2次      │      0次       │                   │
│   │   CPU占用       │     较高       │      低        │                   │
│   │   延迟          │     较高       │      低        │                   │
│   │   实现复杂度     │     复杂       │      简单       │                   │
│   │   数据可访问性   │   可直接访问    │  需用ImageReader│                  │
│   └─────────────────┴───────────────┴───────────────┘                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

7.2 Surface 工作原理

Surface 内部使用 BufferQueue 实现生产者-消费者模式:

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                          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 项目结构

bash 复制代码
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 整体架构

css 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                    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: 从以下几个方面入手:

  1. 使用Surface模式 - 零拷贝渲染
  2. 增大输入缓冲区 - KEY_MAX_INPUT_SIZE
  3. 设置正确的帧率 - KEY_FRAME_RATE
  4. 检查编码器设置 - 确保编码时没有过大的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 核心要点回顾

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────┐
│                         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 参考资源

资源 链接
官方文档 developer.android.com/reference/a...
Google Grafika github.com/google/graf...
学习项目 github.com/jiemojiemo/...
CTS 测试 cts/tests/media/src/android/media/cts/

📝 写在最后

MediaCodec 是 Android 音视频开发的核心API,掌握它就等于打开了多媒体开发的大门。希望本文能帮助你从入门到精通,在音视频开发的道路上越走越远!

如有问题,欢迎在评论区交流讨论!


关于作者:码流怪侠,CSDN 博主,研究方向包括视频编解码、视频质量评估、超分辨率、AI等。

本文首发于:CSDN

相关推荐
Zhu75820 小时前
在k8s集群部署MySQL单实例,支持多个主流发行版
android·mysql·kubernetes
AFinalStone1 天前
Android 7系统异常问题排查(四)Framework层(上)—System Server Watchdog
android·系统异常
codeGoogle1 天前
自研 IM 还是选择第三方 SDK?企业开发者应该如何权衡?
前端·后端·程序员
宝杰X71 天前
Android Room3 多平台数据库
android·数据库
huibin1478523691 天前
热缓解学习记录
android·学习
kango1 天前
iOS 项目工程化构成与 Xcode 架构详解
ios·程序员
前端开发张小七1 天前
Java 学习笔记 · 第三课:多线程与并发编程(线程、同步、死锁、Lock、乐观锁与悲观锁)
java·后端·程序员
小田的博客1 天前
SAP MM 供应商银行主数据更新报错!message R1228!
android·java·服务器
一笑的小酒馆1 天前
Android智能猫砂盆视频加载慢卡顿问题分析
android
只会cv的小前端1 天前
七巧低代码服务端脚本使用方法
android·低代码·rxjava