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

相关推荐
程序员陆业聪2 小时前
Android插件化江湖:从DroidPlugin到Shadow的技术演进
android
敲代码的瓦龙2 小时前
Android?广播!!!
android·java·开发语言·android-studio
橙某人2 小时前
体感在前端,难题不分端:小编眼中的 AI Coding 能力边界
程序员
黄林晴2 小时前
Android Studio Quail 1 Canary 5 发布,Compose 截图测试 + R8 混淆 mapping 问题解决
android·android studio
山上春2 小时前
MT-Workflow2:面向 Odoo 的可视化审批工作流引擎
android·workflow·odoo·bpmn
恋猫de小郭2 小时前
Flutter GenUI 0.9 和 A2UI 0.9 发布,全动动态 UI 支持,AI 在 App 里直出界面
android·flutter·ios
Carson带你学Android3 小时前
Flutter 官方 Skills:一条命令,让 AI 写出「专家级别」的代码
android
三少爷的鞋3 小时前
Android 架构系列之MVVM 和 MVI 算架构吗?
android·kotlin
火车叼位12 小时前
Windows 双网关自动切换:Node.js + 计划任务实现旁路由优先
网络协议·程序员