Android双Bitmap列表转视频:FFmpeg与MediaCodec问题求助
如何高效将两个Bitmap列表合成为带叠加层的视频?
我需要将两个长度一致的Bitmap列表转换为视频——第一个列表是视频的主帧,第二个列表是逐帧叠加在主帧上的图层。
现有FFmpeg方案的问题
我用FFmpeg实现了需求,但存在两个明显问题:
- APK体积增大:引入FFmpeg库会显著增加安装包大小
- 转换耗时过长:当前代码需要先把所有Bitmap保存为缓存目录下的PNG文件,再调用FFmpeg处理,IO操作拖慢了速度
FFmpeg实现代码:
fun convertToVideo(context: Context, bitmapList: List<Bitmap?>, effectedBitmaps: List<Bitmap?>, executed: (Boolean) -> Unit) { val outputDirCap = File(context.cacheDir, "temp_cap") val outputDirEff = File(context.cacheDir, "temp_effect") outputDirCap.mkdirs() outputDirEff.mkdirs() val outputFilePath = File( context.cacheDir, "output.mp4" ).absolutePath for (i in bitmapList.indices) { val bitmap = bitmapList[i] val outputFile = File(outputDirCap, "frame$i.png") try { val fileOutputStream = FileOutputStream(outputFile) bitmap?.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream) fileOutputStream.flush() fileOutputStream.close() } catch (e: IOException) { e.printStackTrace() } } for (i in effectedBitmaps.indices) { val bitmap = effectedBitmaps[i] val outputFile = File(outputDirEff, "frame$i.png") try { val fileOutputStream = FileOutputStream(outputFile) bitmap?.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream) fileOutputStream.flush() fileOutputStream.close() } catch (e: IOException) { e.printStackTrace() } } val command = arrayOf( "-i", "${outputDirCap.absolutePath}/frame%d.png", "-i", "${outputDirEff.absolutePath}/frame%d.png", "-filter_complex", "[0:v][1:v]overlay", outputFilePath ) try { FFmpeg.executeAsync(command ) { executionId, returnCode -> if (returnCode == RETURN_CODE_SUCCESS) { Log.d(TAG, "Async command execution completed successfully."); outputDirCap.deleteRecursively() outputDirEff.deleteRecursively() executed(true) } else if (returnCode == RETURN_CODE_CANCEL) { Log.d(TAG, "Async command execution cancelled by user."); outputDirCap.deleteRecursively() outputDirEff.deleteRecursively() executed(false) } else { outputDirCap.deleteRecursively() outputDirEff.deleteRecursively() executed(false) Log.d( TAG, String.format( "Async command execution failed with returnCode=%d.", returnCode ) ); } } } catch (e: Exception) { e.printStackTrace() } }
MediaCodec方案的问题
我尝试用MediaCodec实现,但代码卡在var inputBufferIndex = encoder.dequeueInputBuffer(-1)这一行,无法继续执行。
MediaCodec实现代码:
private fun convertBitmapsToVideo(list1: List<Bitmap?>, list2: List<Bitmap?>) { val OUTPUT_VIDEO_PATH = requireActivity().cacheDir.absolutePath + File.separator + "output.mp4" try { val encoder = MediaCodec.createEncoderByType(OUTPUT_VIDEO_MIME_TYPE) val format = MediaFormat.createVideoFormat( OUTPUT_VIDEO_MIME_TYPE, OUTPUT_VIDEO_WIDTH, OUTPUT_VIDEO_HEIGHT ) format.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE) format.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE) format.setInteger( MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface ) format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1) encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) val muxer = MediaMuxer(OUTPUT_VIDEO_PATH, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4) encoder.start() val inputBuffers = encoder.inputBuffers val outputBuffers = encoder.outputBuffers val bufferInfo = BufferInfo() var trackIndex = -1 var isMuxerStarted = false var presentationTimeUs: Long = 0 val maxFrames = Math.max(list1.size, list2.size) Log.d(TAG, "convertBitmapsToVideo: Max Frames $maxFrames") for (i in 0 until maxFrames) { val bitmap1 = if (i < list1.size) list1[i] else null val bitmap2 = if (i < list2.size) list2[i] else null var inputBufferIndex = encoder.dequeueInputBuffer(-1) if (inputBufferIndex >= 0) { var inputBuffer = inputBuffers[inputBufferIndex] inputBuffer.clear() // Convert bitmaps to YUV420 format val frameData1: ByteArray? = bitmap1?.let { convertBitmapToYUV(it) } val frameData2: ByteArray? = bitmap2?.let { convertBitmapToYUV(it) } if (frameData1 != null) { inputBuffer.put(frameData1) encoder.queueInputBuffer( inputBufferIndex, 0, frameData1.size, presentationTimeUs, 0 ) } if (frameData2 != null) { inputBufferIndex = encoder.dequeueInputBuffer(-1) if (inputBufferIndex >= 0) { inputBuffer = inputBuffers[inputBufferIndex] inputBuffer.clear() inputBuffer.put(frameData2) encoder.queueInputBuffer( inputBufferIndex, 0, frameData2.size, presentationTimeUs, 0 ) } } presentationTimeUs += 1000000 / OUTPUT_VIDEO_FRAME_RATE var outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0) while (outputBufferIndex >= 0) { Log.d(TAG, "convertBitmapsToVideo: while") val outputBuffer = outputBuffers[outputBufferIndex] if (!isMuxerStarted) { trackIndex = muxer.addTrack(encoder.outputFormat) muxer.start() isMuxerStarted = true } outputBuffer.position(bufferInfo.offset) outputBuffer.limit(bufferInfo.offset + bufferInfo.size) muxer.writeSampleData(trackIndex, outputBuffer, bufferInfo) encoder.releaseOutputBuffer(outputBufferIndex, false) outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0) } } } encoder.signalEndOfInputStream() var outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0) while (outputBufferIndex >= 0) { Log.d(TAG, "convertBitmapsToVideo: ") val outputBuffer = outputBuffers[outputBufferIndex] if (!isMuxerStarted) { trackIndex = muxer.addTrack(encoder.outputFormat) muxer.start() isMuxerStarted = true } outputBuffer.position(bufferInfo.offset) outputBuffer.limit(bufferInfo.offset + bufferInfo.size) muxer.writeSampleData(trackIndex, outputBuffer, bufferInfo) encoder.releaseOutputBuffer(outputBufferIndex, false) outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 0) } encoder.stop() encoder.release() muxer.stop() muxer.release() Log.d(TAG, "Video conversion completed. Output file path: $OUTPUT_VIDEO_PATH") } catch (e: IOException) { Log.d(TAG, "Error converting bitmaps to video: " + e.message) } } private fun convertBitmapToYUV(bitmap: Bitmap): ByteArray? { val width = bitmap.width val height = bitmap.height val argb = IntArray(width * height) val yuv = ByteArray(width * height * 3 / 2) // YUV420 format bitmap.getPixels(argb, 0, width, 0, 0, width, height) var inputOffset = 0 var outputOffset = 0 // Convert ARGB to YUV420 format for (i in 0 until height) { for (j in 0 until width) { val rgb = argb[inputOffset++] val r = rgb shr 16 and 0xFF val g = rgb shr 8 and 0xFF val b = rgb and 0xFF var y = (0.299 * r + 0.587 * g + 0.114 * b).toInt() var u = (-0.14713 * r - 0.28886 * g + 0.436 * b).toInt() var v = (0.615 * r - 0.51498 * g - 0.10001 * b).toInt() // Clamp values y = Math.max(0, Math.min(y, 255)) u = Math.max(0, Math.min(u, 255)) v = Math.max(0, Math.min(v, 255)) yuv[outputOffset++] = y.toByte() if (i % 2 == 0 && j % 2 == 0) { yuv[outputOffset++] = u.toByte() yuv[outputOffset++] = v.toByte() } } inputOffset += width - width } return yuv }
请求帮助
希望得到以下任一解决方案:
- 修复上述MediaCodec代码中的阻塞问题,实现双Bitmap列表的视频合成
- 优化FFmpeg方案,减少APK体积或降低转换耗时
- 提供更高效的替代实现方案
内容的提问来源于stack exchange,提问作者Adnan Aslam
相关产品推荐
相关产品推荐

