安卓MediaCodec实现视频文件的转码压缩

以下为Android平台使用MediaCodec实现视频文件解码→转码→压缩的全流程技术原理与核心代码解析,结合硬件加速与参数优化策略,涵盖关键状态机管理与性能陷阱规避方案。


🔧 ​一、全流程架构与核心组件

  1. 组件分工

    • MediaExtractor:分离视频文件的音视频轨道,提取编码数据(如H.264)
    • MediaCodec:硬件加速编解码器,负责解码原始数据→处理→重新编码
    • MediaMuxer:封装编码后的数据为MP4等容器格式
  2. 流程概览

    graph LR A[输入视频] --> B(MediaExtractor提取轨道) B --> C{选择视频轨道} C --> D[MediaCodec解码] D --> E[帧处理/压缩] E --> F[MediaCodec编码] F --> G[MediaMuxer封装] G --> H[输出视频]

⚙️ ​二、解码原理与代码实现

1. ​初始化解码器

ini 复制代码
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(inputPath); // 输入文件路径
int videoTrackIndex = -1;
// 遍历轨道并选择视频轨道
for (int i = 0; i < extractor.getTrackCount(); i++) {
    MediaFormat format = extractor.getTrackFormat(i);
    String mime = format.getString(MediaFormat.KEY_MIME);
    if (mime.startsWith("video/")) {
        videoTrackIndex = i;
        extractor.selectTrack(i); // 选中轨道
        break;
    }
}
// 配置解码器(以H.264为例)
MediaCodec decoder = MediaCodec.createDecoderByType("video/avc");
decoder.configure(extractor.getTrackFormat(videoTrackIndex), null, null, 0); // Surface设为null
decoder.start();

2. ​解码循环(关键步骤)​

ini 复制代码
ByteBuffer[] inputBuffers = decoder.getInputBuffers();
ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
boolean eos = false;

while (!eos) {
    // 提交压缩数据到解码器
    int inputBufferIndex = decoder.dequeueInputBuffer(10000);
    if (inputBufferIndex >= 0) {
        ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
        int sampleSize = extractor.readSampleData(inputBuffer, 0);
        if (sampleSize < 0) {
            decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            eos = true;
        } else {
            decoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
            extractor.advance(); // 移动到下一帧
        }
    }
    
    // 获取解码后的原始帧(YUV格式)
    int outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 10000);
    if (outputBufferIndex >= 0) {
        ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
        // 此处可插入帧处理(如缩放、滤镜等)
        decoder.releaseOutputBuffer(outputBufferIndex, false); // 不渲染到Surface
    }
}

技术要点​:

  • 若需处理解码后的YUV数据,需通过outputBuffer获取原始帧
  • 结束标志BUFFER_FLAG_END_OF_STREAM必须发送,否则解码器会阻塞

🎚️ ​三、转码压缩原理与参数配置

1. ​编码器初始化(关键参数)​

ini 复制代码
MediaCodec encoder = MediaCodec.createEncoderByType("video/avc");
MediaFormat format = MediaFormat.createVideoFormat("video/avc", targetWidth, targetHeight);
// 压缩核心参数
format.setInteger(MediaFormat.KEY_BIT_RATE, 1_000_000); // 1Mbps码率
format.setInteger(MediaFormat.KEY_FRAME_RATE, 25);      // 帧率
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);  // 关键帧间隔(秒)
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();

2. ​压缩参数影响分析

参数 典型值范围 压缩效果
分辨率 1080p→720p 像素量减少50%,文件体积显著下降
码率 (KEY_BIT_RATE)​ 5Mbps→1Mbps 码率降低80%,可能引入画面模糊
帧率 30fps→24fps 动态细节减少,体积下降约20%
关键帧间隔 1秒→5秒 GOP增大,压缩率提升,但快进/seek可能卡顿

注意 ​:颜色格式需通过MediaCodecInfo查询设备支持,优先选COLOR_FormatYUV420Flexible


🔄 ​四、转码全流程桥接(解码→编码→封装)​

1. ​MediaMuxer初始化

ini 复制代码
MediaMuxer muxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
int videoTrack = -1; // 等待编码器输出格式确定后再添加轨道

2. ​数据流转核心循环

ini 复制代码
while (true) {
    // 从解码器获取原始帧(YUV)
    int decoderOutputIndex = decoder.dequeueOutputBuffer(bufferInfo, TIMEOUT);
    if (decoderOutputIndex >= 0) {
        ByteBuffer rawFrame = decoder.getOutputBuffer(decoderOutputIndex);
        
        // 提交原始帧到编码器
        int encoderInputIndex = encoder.dequeueInputBuffer(TIMEOUT);
        if (encoderInputIndex >= 0) {
            ByteBuffer encoderInputBuffer = encoder.getInputBuffer(encoderInputIndex);
            encoderInputBuffer.put(rawFrame);
            encoder.queueInputBuffer(encoderInputIndex, 0, bufferInfo.size, bufferInfo.presentationTimeUs, 0);
        }
        decoder.releaseOutputBuffer(decoderOutputIndex, false);
    }
    
    // 从编码器获取压缩数据
    int encoderOutputIndex = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT);
    if (encoderOutputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
        videoTrack = muxer.addTrack(encoder.getOutputFormat()); // 添加轨道
        muxer.start();
    } else if (encoderOutputIndex >= 0) {
        ByteBuffer encodedData = encoder.getOutputBuffer(encoderOutputIndex);
        muxer.writeSampleData(videoTrack, encodedData, bufferInfo); // 写入文件
        encoder.releaseOutputBuffer(encoderOutputIndex, false);
    }
}

关键逻辑​:

  • 编码器输出格式变化(INFO_OUTPUT_FORMAT_CHANGED)时必须通知MediaMuxer
  • 时间戳presentationTimeUs需保持连续性,否则播放异常

🚀 ​五、性能优化与进阶技巧

  1. 异步模式(API 21+)​

    避免轮询阻塞,使用Callback提升效率:

    less 复制代码
    encoder.setCallback(new MediaCodec.Callback() {
        @Override
        public void onInputBufferAvailable(MediaCodec mc, int index) {
            // 填充数据到编码器
        }
        @Override
        public void onOutputBufferAvailable(MediaCodec mc, int index, BufferInfo info) {
            // 处理压缩数据并提交给MediaMuxer
        }
    });
  2. Surface输入(零拷贝)​

    直接绑定Surface到编码器,跳过CPU处理:

    ini 复制代码
    Surface inputSurface = encoder.createInputSurface();
    // 将解码器输出渲染到inputSurface(需OpenGL ES上下文)
  3. 动态码率调整

    根据网络或存储需求实时调整码率:

    ini 复制代码
    Bundle params = new Bundle();
    params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate);
    encoder.setParameters(params);

⚠️ ​六、常见问题解决

  1. 绿屏/花屏

    • 原因:颜色格式不匹配或帧数据未对齐
    • 方案:确认设备支持的COLOR_FORMAT,YUV数据需满足分辨率对齐要求(通常是2的倍数)
  2. 帧率骤降

    • 原因:编码器处理超时
    • 优化:降低分辨率/码率,或使用Surface输入跳过YUV转换
  3. 封装失败

    • 原因:未添加轨道或时间戳错误
    • 检查:确保在INFO_OUTPUT_FORMAT_CHANGED后才启动MediaMuxer

💎 ​总结与最佳实践

  1. 核心流程 ​:Extractor→Decoder→Frame Processing→Encoder→Muxer

  2. 参数黄金法则​:分辨率 > 码率 > 帧率 > GOP,按需求牺牲画质换体积

  3. 必选优化​:

    • 异步模式处理I/O避免阻塞
    • Surface输入减少CPU-YUV拷贝
    • 动态码率适应场景需求

完整代码参考:Android MediaCodec官方示例

深入性能调优:分析dumpsys media.codec获取编解码器实时状态

相关推荐
教程分享大师1 小时前
中兴B860AV5.2-U_S905L3SB安卓9.0系统带root权限当贝纯净版线刷包
android
_小马快跑_1 小时前
Android | LiveData 与 Flow 的异同点对比
android
whysqwhw4 小时前
安卓MediaCodec录像功能
android
沅霖8 小时前
下载Android studio
android·ide·android studio
xzkyd outpaper8 小时前
Kotlin 协程线程切换机制详解
android·开发语言·kotlin
Near_Li9 小时前
uniapp-使用mumu模拟器调试安卓APP
android·uni-app
zhangphil10 小时前
Android MediaMetadataRetriever取视频封面,Kotlin(1)
android·kotlin
onthewaying13 小时前
详解 Android GLSurfaceView 与 Renderer:开启你的 OpenGL ES 之旅
android·opengl
aqi0013 小时前
FFmpeg开发笔记(八十)使用百变魔音AiSound实现变声特效
android·ffmpeg·音视频·直播·流媒体