初探 Android MediaCodec:用硬编码压缩视频
前言
最近对音视频开发产生了兴趣,想了解下 Android 上是怎么处理视频编解码的。花了几天时间研究了 MediaCodec API,写了个简单的视频压缩 Demo,记录下学习过程。
为什么要学 MediaCodec
之前做 Launcher 定制时,遇到过一个需求:用户录制的视频太大,需要压缩后再上传。当时用的第三方库 FFmpeg,但打包后 APK 体积增加了 20MB+。
后来发现 Android 原生就有 MediaCodec API,可以直接调用硬件编解码器,性能好、体积小。所以决定研究下。
MediaCodec 是什么
MediaCodec 是 Android 提供的底层编解码 API(API 16+),可以访问设备的硬件编解码器(如高通的 Adreno、联发科的 MDP)。
优势:
- 硬件加速,速度快
- 功耗低
- 无需引入第三方库
劣势:
- API 比较底层,使用复杂
- 不同设备兼容性问题
视频编解码基础
在开始前,先理解几个概念:
1. 编码格式
- H.264 (AVC):最常用,兼容性好
- H.265 (HEVC):压缩率更高,但部分设备不支持
- VP8/VP9:Google 推的开源格式
2. 关键帧和 P 帧
- I 帧(关键帧):完整的图像,可以独立解码
- P 帧:只存储与前一帧的差异,体积小
视频压缩的本质就是减少 I 帧数量,增加 P 帧。
3. 码率(Bitrate)
码率决定视频质量和文件大小。常见设置:
- 1080p:8-10 Mbps
- 720p:4-6 Mbps
- 480p:1-2 Mbps
MediaCodec 工作流程
1. 创建编码器:MediaCodec.createEncoderByType("video/avc")
2. 配置参数:configure(format, null, null, CONFIGURE_FLAG_ENCODE)
3. 启动编码器:start()
4. 循环处理:
- 获取输入缓冲区:dequeueInputBuffer()
- 填充原始数据:getInputBuffer()
- 提交数据:queueInputBuffer()
- 获取输出缓冲区:dequeueOutputBuffer()
- 读取编码数据:getOutputBuffer()
- 释放缓冲区:releaseOutputBuffer()
5. 停止编码器:stop() / release()
实战:压缩视频
1. 创建和配置编码器
MediaCodec encoder = MediaCodec.createEncoderByType("video/avc");
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, 2000000); // 2Mbps
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // 1秒一个关键帧
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();
2. 解码原视频并重新编码
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(inputPath);
// 找到视频轨道
int videoTrack = -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/")) {
videoTrack = i;
break;
}
}
extractor.selectTrack(videoTrack);
// 创建解码器
MediaCodec decoder = MediaCodec.createDecoderByType(mime);
decoder.configure(format, null, null, 0);
decoder.start();
3. 处理循环(简化版)
boolean inputDone = false;
boolean outputDone = false;
while (!outputDone) {
// 喂数据给解码器
if (!inputDone) {
int inputIndex = decoder.dequeueInputBuffer(10000);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = decoder.getInputBuffer(inputIndex);
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) {
decoder.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
inputDone = true;
} else {
decoder.queueInputBuffer(inputIndex, 0, sampleSize, extractor.getSampleTime(), 0);
extractor.advance();
}
}
}
// 获取解码后的数据,喂给编码器
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int outputIndex = decoder.dequeueOutputBuffer(info, 10000);
if (outputIndex >= 0) {
// 这里需要将解码后的 YUV 数据传给编码器
// 实际项目中会用 Surface 来传递,避免数据拷贝
decoder.releaseOutputBuffer(outputIndex, true);
}
}
踩过的坑
坑1:颜色格式不匹配
一开始解码出来的是 YUV420 格式,但编码器期望的是 Surface 输入。导致画面颜色错乱。
解决: 使用 Surface 模式,让解码器直接输出到 Surface,编码器从 Surface 读取。
Surface surface = MediaCodec.createPersistentInputSurface();
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.setInputSurface(surface);
decoder.configure(format, surface, null, 0);
坑2:时间戳不连续
编码后的视频播放卡顿,检查发现是时间戳(PTS)没处理好。
解决: 必须保证时间戳单调递增,且间隔均匀。
long presentationTimeUs = extractor.getSampleTime();
decoder.queueInputBuffer(inputIndex, 0, sampleSize, presentationTimeUs, 0);
坑3:部分设备不支持硬编码
在某些低端设备上,createEncoderByType() 直接崩溃。
解决: 先检查设备是否支持硬编码。
MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
for (MediaCodecInfo info : codecInfos) {
if (info.isEncoder() && info.getName().contains("avc")) {
// 支持 H.264 硬编码
}
}
性能测试
测试环境:小米 10(骁龙 865),压缩 1080p 30fps 视频
| 方案 | 耗时 | CPU 占用 | 文件大小 |
|---|---|---|---|
| FFmpeg 软编码 | 45秒 | 80% | 15MB |
| MediaCodec 硬编码 | 8秒 | 20% | 12MB |
硬编码速度快 5 倍,CPU 占用低很多。
学习总结
通过这次实践,对音视频编解码有了初步认识:
- MediaCodec 是底层 API,使用复杂但性能好
- 时间戳很重要,直接影响播放流畅度
- 硬件兼容性是个大问题,需要做好降级方案
后续计划继续学习:
- MediaMuxer 封装音视频
- OpenGL 处理视频滤镜
- 实时音视频通信(WebRTC)
参考资料
- Android MediaCodec 官方文档
- 《Android 音视频开发》
- Grafika 开源项目
音视频是个大坑,慢慢填。有问题欢迎交流!