共计 2289 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点
在 Android 开发中,将图片序列合成为视频时,开发者常遇到以下典型问题:

- 帧率不稳定 :手动控制时间戳计算不准确导致视频播放卡顿
- 内存溢出 :未压缩的高分辨率图片直接加载引发 OOM
- 编码兼容性 :部分设备对 H.264 编码格式支持不一致
- 音视频不同步 :缺乏时间戳同步机制导致声画分离
技术选型对比
- MediaMuxer 方案
- 优点:
- 系统原生 API,无需引入第三方库
- 硬件加速支持良好
- 内存占用可控
-
缺点:
- 编码格式支持有限
- 需要手动处理时间戳
-
FFmpeg 方案
- 优点:
- 格式支持全面
- 成熟的滤镜系统
- 缺点:
- 增加 apk 体积
- NDK 开发门槛较高
核心实现
1. MediaMuxer 初始化
val muxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
// 建议使用 Surface 编码模式提升性能
val mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height)
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval)
val trackIndex = muxer.addTrack(mediaFormat)
2. 图片帧处理
关键步骤:
- 创建 MediaCodec 编码器
- 配置输入 Surface
- 使用 Canvas 将 Bitmap 绘制到 Surface
- 提交编码帧
3. 时间戳同步
// 计算每帧时间间隔(纳秒)val frameDuration = 1_000_000_000L / frameRate
// 设置 PresentationTimeUs
bufferInfo.presentationTimeUs = frameIndex * frameDuration / 1000
4. 内存优化
- 使用 BitmapFactory.Options.inSampleSize 进行图片采样
- 采用 Glide 等图片库管理内存
- 及时回收 Bitmap 对象
完整代码示例
fun createVideoFromImages(
context: Context,
imageUris: List<Uri>,
outputFile: File
) {val muxer = MediaMuxer(outputFile.path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
try {
// 初始化编码器配置
val mediaFormat = createVideoFormat()
val codec = MediaCodec.createEncoderByType("video/avc")
codec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
// 获取输入 Surface
val surface = codec.createInputSurface()
codec.start()
// 添加视频轨道
val trackIndex = muxer.addTrack(mediaFormat)
muxer.start()
// 帧编码处理
imageUris.forEachIndexed { index, uri ->
val bitmap = loadOptimizedBitmap(context, uri)
drawToSurface(surface, bitmap)
encodeFrame(codec, muxer, trackIndex, index)
bitmap.recycle()}
// 释放资源
codec.signalEndOfInputStream()
releaseResources(codec, muxer)
} catch (e: Exception) {// 错误处理}
}
性能优化
- 编码速度 :
- 使用硬件加速编码器
-
预加载下一帧图片
-
内存占用 :
- 限制同时加载的 Bitmap 数量
-
采用 RGB_565 格式替代 ARGB_8888
-
线程模型 :
- 分离 UI 线程与编码线程
- 使用 HandlerThread 处理编码任务
避坑指南
- 黑屏问题 :
- 确保所有帧都正确提交了时间戳
-
检查 ColorFormat 是否被设备支持
-
视频时长异常 :
- 验证 frameDuration 计算是否准确
-
检查最后一帧是否被正确标记为 END_OF_STREAM
-
编解码器不支持 :
- 添加 fallback 到其他编码格式的逻辑
- 运行时检查设备支持情况
安全考量
- 大图处理策略:
- 采用 RegionDecoder 分段加载
- 设置内存缓存上限
- 文件权限:
- 使用 FileProvider 处理外部存储访问
- 添加运行时权限检查
扩展思考
- 如何添加背景音乐?
- 使用 MediaExtractor 提取音频轨
-
通过 AudioTrack 进行混音
-
动态效果实现:
- 结合 OpenGL ES 添加转场动画
-
使用 MediaCodec 处理特效滤镜
-
云端合成方案:
- 分片上传图片
- 服务端使用 FFmpeg 处理
通过本文方案,开发者可以构建出帧率稳定、内存安全的图片转视频功能。建议在实际项目中根据设备性能动态调整编码参数,并做好异常情况的 fallback 处理。
正文完
发表至: 移动开发
近三天内
