Android平台MP4视频全帧提取慢且丢帧问题求助
解决Android中MP4视频逐帧提取慢、丢帧的问题
原代码的核心问题
- 用固定1秒间隔取帧,完全忽略视频实际帧率,必然丢帧(比如25fps的视频,1秒有25帧,你只取1帧,直接丢24帧)
MediaMetadataRetriever.getFrameAtTime依赖视频关键帧,非关键帧位置大概率取不到准确帧,只会返回最近的关键帧,导致重复或丢失- 原代码里每次取到帧都切换到主线程更新ImageView,现在你不需要显示,这部分完全是额外开销
最优解决方案:用MediaCodec硬件加速逐帧解码
MediaCodec是Android官方推荐的硬解码API,能直接读取视频的所有原始帧,速度快且无丢帧,适合批量处理帧的场景。
以下是适配raw资源的完整实现:
import android.media.MediaCodec import android.media.MediaExtractor import android.media.MediaFormat import android.os.Environment import android.util.Log import java.io.File import java.io.FileOutputStream import java.nio.ByteBuffer private fun extractAllFramesFromRawVideo(rawResId: Int) { val extractor = MediaExtractor() var codec: MediaCodec? = null val bufferInfo = MediaCodec.BufferInfo() try { // 配置MediaExtractor读取raw资源 val fd = resources.openRawResourceFd(rawResId) extractor.setDataSource(fd.fileDescriptor, fd.startOffset, fd.length) fd.close() // 找到视频轨道 var videoTrackIndex = -1 for (i in 0 until extractor.trackCount) { val format = extractor.getTrackFormat(i) val mimeType = format.getString(MediaFormat.KEY_MIME) if (mimeType?.startsWith("video/") == true) { videoTrackIndex = i break } } if (videoTrackIndex == -1) { Log.e("FrameExtraction", "No video track found") return } extractor.selectTrack(videoTrackIndex) val videoFormat = extractor.getTrackFormat(videoTrackIndex) val mimeType = videoFormat.getString(MediaFormat.KEY_MIME) ?: "" val frameRate = videoFormat.getInteger(MediaFormat.KEY_FRAME_RATE) Log.d("FrameExtraction", "Video frame rate: $frameRate fps") // 初始化MediaCodec硬解码 codec = MediaCodec.createDecoderByType(mimeType) codec.configure(videoFormat, null, null, 0) codec.start() val inputBuffers = codec.inputBuffers var outputBuffers = codec.outputBuffers var isExtractorDone = false var frameCount = 0 while (true) { // 喂数据给解码器 if (!isExtractorDone) { val inputBufferIndex = codec.dequeueInputBuffer(10000) if (inputBufferIndex >= 0) { val inputBuffer = inputBuffers[inputBufferIndex] val sampleSize = extractor.readSampleData(inputBuffer, 0) if (sampleSize < 0) { // 数据喂完,发送结束信号 codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM) isExtractorDone = true } else { val presentationTimeUs = extractor.sampleTime codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0) extractor.advance() } } } // 从解码器获取解码后的帧 val outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 10000) when (outputBufferIndex) { MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED -> { // 输出缓冲区更新,重新获取 outputBuffers = codec.outputBuffers } MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> { // 输出格式变化,这里可以忽略,我们只需要帧数据 } MediaCodec.INFO_TRY_AGAIN_LATER -> { // 暂时没有输出,继续循环 } else -> { val outputBuffer = outputBuffers[outputBufferIndex] val buffer = ByteBuffer.allocate(bufferInfo.size) outputBuffer.position(bufferInfo.offset) outputBuffer.limit(bufferInfo.offset + bufferInfo.size) buffer.put(outputBuffer) buffer.flip() // 这里处理解码后的帧数据,比如转Bitmap或者直接保存为文件 // 示例:保存为PNG文件(你可以替换成自己的处理逻辑) val bitmap = android.graphics.BitmapFactory.decodeStream(buffer.inputStream()) bitmap?.let { val outputFile = File( getExternalFilesDir(Environment.DIRECTORY_PICTURES), "frame_${frameCount++}.png" ) FileOutputStream(outputFile).use { fos -> it.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, fos) } it.recycle() // 及时回收Bitmap避免内存泄漏 } // 释放输出缓冲区 codec.releaseOutputBuffer(outputBufferIndex, false) // 检查是否解码完成 if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) { break } } } } Log.d("FrameExtraction", "Extracted total $frameCount frames") } catch (e: Exception) { Log.e("FrameExtraction", "Error extracting frames: ${e.message}", e) } finally { codec?.stop() codec?.release() extractor.release() } } // 调用方式 extractAllFramesFromRawVideo(R.raw.video_h)
关键优化点
- 硬解码加速:MediaCodec利用硬件解码能力,比MediaMetadataRetriever的软提取快数倍
- 逐帧精准提取:完全按照视频的实际帧率和帧顺序提取,不会丢帧或重复
- 去掉冗余操作:移除了原代码中主线程更新ImageView的逻辑,减少线程切换开销
- 资源及时释放:在finally块中确保所有媒体资源被正确释放,避免内存泄漏
额外提示
- 如果不需要转Bitmap,直接处理ByteBuffer中的原始YUV数据,速度会更快(适合图像处理算法直接输入)
- 可以根据需要调整帧处理的逻辑,比如批量保存、异步处理等,进一步提升效率
内容的提问来源于stack exchange,提问作者Arnold Brown
相关产品推荐
相关产品推荐

