Android MediaCodec 音视频编解码技术详解

发布于:2025-08-12 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、MediaCodec 简介

MediaCodec 是 Android 平台提供的底层音视频编解码 API,自 API 16 (Android 4.1) 引入,用于访问设备硬件加速的编解码器。作为 Android 多媒体框架的核心组件,它通常与 MediaExtractor(媒体提取)、MediaMuxer(媒体封装)、Surface(视频渲染)等组件配合使用,实现高效的音视频处理。其核心优势在于直接调用硬件编码器/解码器,相比纯软件实现(如 FFmpeg)具有更低功耗更低延迟的特点,适用于实时通信、直播推流、视频编辑等场景。

二、核心概念与架构

2.1 状态机模型

MediaCodec 生命周期分为三种状态,状态转换需严格遵循下图逻辑:

┌───────────── Stopped 状态 ───────────┐
│  ┌─────────┐  ┌─────────┐  ┌──────┐  │
│  │未初始化  │→│已配置   │→│错误  │  │
│  └─────────┘  └────┬────┘  └──────┘  │
└────────────────────┼──────────────────┘
                     ↓
┌───────────── Executing 状态 ───────────┐
│  ┌─────────┐  ┌─────────┐  ┌────────┐ │
│  │刷新中   │→│运行中   │→│流结束  │ │
│  └─────────┘  └─────────┘  └────────┘ │
└────────────────────┬──────────────────┘
                     ↓
               ┌───────────┐
               │ Released  │
               └───────────┘
  • Stopped:初始状态,包含 Uninitialized(创建后)、Configuredconfigure() 后)、Error(异常时)子状态。
  • Executing:调用 start() 后进入,包含 Flushed(初始)、Running(处理数据)、End-of-Stream(输入结束)子状态。
  • Released:调用 release() 后释放资源,不可再使用。

2.2 缓冲区队列模型

MediaCodec 采用 生产者-消费者模型,通过两组缓冲区队列实现数据流转:

  1. 输入缓冲区队列:接收原始数据(如 YUV 视频帧、PCM 音频),由开发者填充后提交给编解码器。
  2. 输出缓冲区队列:返回编解码后的数据(如 H.264 码流、AAC 音频),由开发者提取并处理。

核心流程

开发者 → 申请输入缓冲区 → 填充数据 → 提交输入缓冲区 → 编解码器处理
编解码器 → 输出缓冲区就绪 → 开发者提取数据 → 释放输出缓冲区 → 重复

2.3 数据类型

MediaCodec 支持三类数据处理:

  • 压缩数据:如 H.264 视频流(解码器输入/编码器输出)。
  • 原始视频数据:通常为 YUV 格式(如 Camera 采集的 NV21),需注意颜色格式兼容性。
  • 原始音频数据:通常为 PCM 格式(如 AudioRecord 采集的 16 位有符号整数)。

三、编解码工作流程

以视频编码为例,完整流程如下:

3.1 创建与配置

// 1. 创建 MediaFormat,指定编码格式、分辨率等参数
MediaFormat format = MediaFormat.createVideoFormat(
    MediaFormat.MIMETYPE_VIDEO_AVC,  // H.264 编码
    1280, 720                        // 分辨率
);
format.setInteger(MediaFormat.KEY_BIT_RATE, 4_000_000);  // 码率 4Mbps
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);       // 帧率 30fps
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5);  // I帧间隔 5秒
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 
    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);  // Surface输入

// 2. 创建编码器实例
MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);  // 编码模式

3.2 启动与缓冲区操作

// 3. 启动编码器
Surface inputSurface = encoder.createInputSurface();  // 获取输入Surface(摄像头数据直接接入)
encoder.start();

// 4. 循环处理缓冲区(同步模式)
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
boolean isEncoding = true;

while (isEncoding) {
    // 4.1 处理输入缓冲区
    int inputBufferIndex = encoder.dequeueInputBuffer(10000);  // 超时10ms
    if (inputBufferIndex >= 0) {
        ByteBuffer inputBuffer = encoder.getInputBuffer(inputBufferIndex);
        // 填充数据(如从Camera获取的YUV数据)
        inputBuffer.put(yuvData);
        encoder.queueInputBuffer(
            inputBufferIndex, 
            0, 
            yuvData.length, 
            System.nanoTime() / 1000,  // 时间戳(微秒)
            0  // 无标志
        );
    }

    // 4.2 处理输出缓冲区
    int outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 10000);
    if (outputBufferIndex >= 0) {
        ByteBuffer outputBuffer = encoder.getOutputBuffer(outputBufferIndex);
        // 提取编码后的数据(如H.264 NALU)
        byte[] encodedData = new byte[bufferInfo.size];
        outputBuffer.get(encodedData);
        // 写入文件或网络推流
        muxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo);
        // 释放缓冲区
        encoder.releaseOutputBuffer(outputBufferIndex, false);
    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
        // 输出格式变化,通常在第一帧数据前触发
        MediaFormat newFormat = encoder.getOutputFormat();
        videoTrackIndex = muxer.addTrack(newFormat);
        muxer.start();
    }
}

3.3 停止与资源释放

// 5. 停止编码
encoder.stop();
encoder.release();
muxer.stop();
muxer.release();

四、关键技术与最佳实践

4.1 颜色格式兼容性

问题:Camera 采集的 YUV 格式(如 NV21)与 MediaCodec 支持的颜色格式可能不匹配,导致花屏或变色。
解决方案

  1. 查询设备支持的颜色格式:
    MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
    for (int format : capabilities.colorFormats) {
        Log.d("SupportedFormat", format + "");  // 如 COLOR_FormatYUV420Flexible
    }
    
  2. 使用 COLOR_FormatYUV420Flexible(Android 6.0+),兼容大多数设备。
  3. 必要时通过 libyuv 库转换格式(如 NV21→I420)。

4.2 低延迟优化

针对实时场景(如视频通话),可通过以下方式将延迟降至 20ms 以内

  1. 启用低延迟模式(Android 11+):
    format.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);  // 低延迟标记
    
  2. 减少缓冲区大小:避免使用默认缓冲区,通过 KEY_MAX_INPUT_SIZE 限制输入大小。
  3. 禁用 B 帧:B 帧虽提升压缩率,但增加延迟,实时场景建议关闭。
  4. 异步回调模式:API 21+ 支持 setCallback(),避免同步等待阻塞线程:
    encoder.setCallback(new MediaCodec.Callback() {
        @Override
        public void onInputBufferAvailable(MediaCodec codec, int index) { ... }
        @Override
        public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { ... }
    });
    

4.3 错误处理与兼容性

常见问题 解决方案
配置崩溃(configure()失败) 检查分辨率是否超过设备能力(如低端机不支持4K),或同时解码多路流导致资源耗尽。
解码花屏 确保从关键帧开始解码,避免丢帧导致参考帧缺失。
音频不同步 精确设置时间戳(presentationTimeUs),避免累积误差。
设备兼容性差异 使用 MediaCodecList 查询设备支持的编解码器, fallback 至软编解码。

五、MediaCodec 与 MediaCodec2 对比

Android Q (API 29) 引入 MediaCodec2,作为 MediaCodec 的替代方案,解决碎片化问题并提升性能:

特性 MediaCodec MediaCodec2 (Android Q+)
架构 ACodec + OpenMAX IL 基于 C2 组件,支持零拷贝和组件链
更新方式 厂商定制,碎片化严重 Mainline 模块,Google 统一维护
性能 依赖厂商实现,效率参差不齐 优化缓冲区管理,支持硬件加速渲染
兼容性 需适配不同厂商实现 标准化接口,减少设备差异

迁移建议:新应用优先使用 MediaCodec2,通过 MediaCodecList 动态选择编解码器:

MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
String codecName = list.findEncoderForFormat(format);
MediaCodec codec = MediaCodec.createByCodecName(codecName);

六、支持格式与设备查询

6.1 主流编解码格式

类型 格式 MIME 类型 支持版本
视频 H.264 (AVC) video/avc Android 4.1+
视频 H.265 (HEVC) video/hevc Android 5.0+
视频 VP9 video/x-vnd.on2.vp9 Android 4.4+
视频 AV1 video/av01 Android 14+
音频 AAC audio/mp4a-latm Android 4.1+
音频 Opus audio/opus Android 5.0+

6.2 查询设备编解码能力

通过 MediaCodecInfo 获取设备支持的详细能力:

for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
    MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
    if (!info.isEncoder()) continue;  // 筛选编码器
    for (String type : info.getSupportedTypes()) {
        if (type.startsWith("video/")) {
            Log.d("Codec", info.getName() + " supports " + type);
        }
    }
}

七、应用场景与案例

  1. 实时视频通话:结合 Camera2 采集和 MediaCodec 硬编,实现低延迟推流。
  2. 短视频编辑:使用 MediaCodec 解码原始视频,叠加滤镜后重新编码。
  3. 直播推流:将编码后的 H.264/AAC 数据通过 RTMP 协议推送到服务器。
  4. 离线转码:利用 MediaMuxer 将多轨道音视频封装为 MP4 文件。

八、总结

MediaCodec 作为 Android 音视频开发的核心 API,掌握其状态管理、缓冲区操作和兼容性处理是实现高效编解码的关键。随着 MediaCodec2 的普及,未来 Android 音视频生态将更加标准化,开发者可专注于业务逻辑而非设备适配。建议结合官方示例(如 Grafika)深入实践,同时关注 Android 新版本对 AV1、HDR 等格式的支持,打造更优质的多媒体体验。

参考资料


网站公告

今日签到

点亮在社区的每一天
去签到