共计 2878 个字符,预计需要花费 8 分钟才能阅读完成。
背景痛点
在开发图片转视频功能时,很多开发者会第一时间想到 FFmpeg。虽然它功能强大,但在 Android 平台使用存在明显问题:

- 引入 FFmpeg 会导致 APK 体积显著增加(通常增加 5-10MB)
- 需要处理不同 CPU 架构的兼容性问题
- 命令行调用方式在 Android 上不够优雅,容易出现权限问题
- 性能优化空间有限,难以充分利用硬件加速
技术选型
对比主流方案后,我选择了 Android 原生多媒体 API:
- MediaCodec:硬件加速的编解码器
- MediaMuxer:音视频混合器
- MediaExtractor:媒体数据提取器
相比第三方库的优势:
- 无需额外依赖,APK 体积零增长
- 系统级硬件加速支持
- 更好的兼容性和稳定性
- 更精细的性能控制
核心实现
1. 初始化编码器
val format = MediaFormat.createVideoFormat(
MediaFormat.MIMETYPE_VIDEO_AVC,
width,
height
).apply {
setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)
setInteger(MediaFormat.KEY_BIT_RATE, bitRate)
setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval)
}
val encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
val inputSurface = encoder.createInputSurface()
encoder.start()
2. 图片解码与处理
使用 ImageDecoder 替代传统的 BitmapFactory:
val source = ImageDecoder.createSource(contentResolver, uri)
val bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
decoder.setTargetSize(targetWidth, targetHeight)
}
3. 音频轨道提取
val extractor = MediaExtractor().apply {setDataSource(audioPath)
}
val audioFormat = run {for (i in 0 until extractor.trackCount) {val format = extractor.getTrackFormat(i)
if (format.getString(MediaFormat.KEY_MIME)?.startsWith("audio/") == true) {extractor.selectTrack(i)
return@run format
}
}
null
}
4. 音视频同步逻辑
关键同步代码示例:
fun writeSampleData(
muxer: MediaMuxer,
trackIndex: Int,
buffer: ByteBuffer,
bufferInfo: MediaCodec.BufferInfo
) {if (bufferInfo.presentationTimeUs > lastPresentationTimeUs) {lastPresentationTimeUs = bufferInfo.presentationTimeUs}
muxer.writeSampleData(trackIndex, buffer, bufferInfo)
}
性能优化
内存管理
- Bitmap 对象池化:
object BitmapPool {private val pool = Stack<Bitmap>()
fun get(width: Int, height: Int): Bitmap {return if (pool.isNotEmpty() && pool.peek().width == width
&& pool.peek().height == height) {pool.pop()
} else {Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
}
}
fun recycle(bitmap: Bitmap) {if (pool.size < MAX_POOL_SIZE) {pool.push(bitmap)
} else {bitmap.recycle()
}
}
}
- 使用 Coroutine 避免主线程阻塞:
viewModelScope.launch(Dispatchers.Default) {val videoFile = createVideoFromImages(images, audioUri)
withContext(Dispatchers.Main) {// 更新 UI}
}
避坑指南
版本兼容性问题
- 针对 Android 10+ 需要添加存储权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
音视频参数匹配
当音频采样率与视频不匹配时,需要进行重采样:
fun resampleAudio(
input: ByteArray,
inputSampleRate: Int,
outputSampleRate: Int
): ByteArray {val ratio = inputSampleRate.toFloat() / outputSampleRate
val outputSize = (input.size / ratio).toInt()
val output = ByteArray(outputSize)
// 实现重采样算法...
return output
}
延伸思考
添加转场效果
可以在帧渲染时插入过渡帧:
- 计算两幅图片的差值
- 生成中间过渡帧
- 调整帧间隔实现平滑过渡
大批量图片处理
对于 100+ 图片的情况建议:
- 分批次处理(如每次处理 20 张)
- 使用临时文件存储中间结果
- 最后合并分段视频
总结
通过原生 API 实现视频合成虽然代码量较大,但可以获得更好的性能和可控性。关键点在于:
- 合理配置 MediaCodec 参数
- 妥善管理内存和资源回收
- 正确处理音视频同步
- 做好错误处理和兼容性适配
完整项目示例已上传 GitHub(伪代码),包含更多细节处理和性能监控代码,欢迎交流讨论。
正文完
