共计 1725 个字符,预计需要花费 5 分钟才能阅读完成。
在移动开发中,经常遇到需要将图片序列合成视频的场景,比如监控录像回放、短视频制作等。前段时间我正好接手了一个类似需求,过程中踩了不少坑,也积累了一些经验,今天就来分享一下如何使用 MediaMuxer 高效实现这一功能。

需求背景与痛点
先说说为什么会有这个需求。我们项目需要将用户拍摄的多张照片自动合成为一段视频,类似手机相册的 ” 回忆 ” 功能。最开始尝试了几种方案:
- 服务器端合成:图片上传到服务器处理,但延迟高、流量消耗大
- FFmpeg 命令行:需要集成 so 库,增加包体积
- MediaCodec+MediaMuxer:原生 API,但实现复杂
经过对比,最终选择了第三种方案,因为:
- 无需额外依赖
- 性能更好
- 可以精细控制每一帧
但在实际开发中遇到了几个典型问题:
- 帧率不稳定,视频播放卡顿
- 大分辨率图片导致内存溢出
- 音视频轨道同步困难
- 部分机型兼容性问题
技术实现详解
1. 整体流程设计
核心流程分为四个步骤:
- 初始化 MediaMuxer 和 MediaCodec
- 配置视频格式和编码参数
- 逐帧输入图片数据
- 释放资源完成合成
2. 关键代码实现
// 1. 初始化 MediaMuxer
val muxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
// 2. 配置 MediaCodec
val format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height)
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval)
val codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
val surface = codec.createInputSurface()
codec.start()
3. 时间戳控制
精确控制帧率是关键,这里采用固定时间间隔:
val frameInterval = 1000000L / frameRate // 微秒单位
fun encodeFrame(bitmap: Bitmap, frameIndex: Int) {
val presentationTime = frameIndex * frameInterval
// ... 将 bitmap 绘制到 surface...
}
4. 内存优化技巧
处理大图时容易 OOM,解决方案:
-
采样压缩:
val options = BitmapFactory.Options() options.inSampleSize = 2 // 降低采样率 -
使用对象池复用 ByteBuffer
- 异步处理避免阻塞 UI 线程
常见问题解决
1. addTrack failed 错误
确保在收到 MediaCodec 的 INFO_OUTPUT_FORMAT_CHANGED 回调后再添加轨道
2. 视频不同步
- 确保每帧时间戳计算正确
- 使用固定帧率模式
- 检查编码器是否丢帧
3. 资源释放
必须按顺序释放:
- 先停止 MediaCodec
- 再停止 MediaMuxer
- 最后释放 Surface
扩展思考
基础功能完成后,还可以考虑:
- 添加背景音乐
- 动态调整输出分辨率
- 支持滤镜效果
- 添加转场动画
完整实现代码已放在 GitHub(伪链接),欢迎参考。通过这次实践,我深刻体会到 ” 魔鬼在细节 ” 这句话的含义。特别是时间戳处理和资源释放顺序,稍有差错就会导致各种奇怪问题。希望这篇分享能帮你少走弯路。
如果有更好的实现方案,欢迎在评论区交流讨论。
正文完
发表至: 移动开发
近三天内
