如何在Android Studio中提取视频的全部帧?
Android 提取视频全部帧的可行实现方案
一、优化 MediaMetadataRetriever 实现
如果你之前用这个类没成功,大概率是时间戳处理或参数设置的问题,以下是修正后的完整实现:
- 核心逻辑:按视频帧率计算帧间隔,遍历所有时间点提取帧,优先同步关键帧保证有效性
- 代码示例:
public void extractFramesWithMMR(String videoPath, String outputDir) { MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { retriever.setDataSource(videoPath); // 获取视频总时长(毫秒) String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); long totalDuration = Long.parseLong(durationStr); // 获取原视频帧率,兼容无法获取的情况 String fpsStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE); float fps = fpsStr == null || Float.parseFloat(fpsStr) <= 0 ? 30 : Float.parseFloat(fpsStr); long frameIntervalMs = (long) (1000 / fps); File outputFolder = new File(outputDir); if (!outputFolder.exists()) outputFolder.mkdirs(); for (long currentTime = 0; currentTime <= totalDuration; currentTime += frameIntervalMs) { // 用OPTION_CLOSEST_SYNC获取关键帧附近的有效帧,避免空帧 Bitmap frame = retriever.getFrameAtTime(currentTime, MediaMetadataRetriever.OPTION_CLOSEST_SYNC); if (frame != null) { File frameFile = new File(outputFolder, "frame_" + currentTime + ".png"); try (FileOutputStream fos = new FileOutputStream(frameFile)) { frame.compress(Bitmap.CompressFormat.PNG, 100, fos); } catch (IOException e) { e.printStackTrace(); } frame.recycle(); // 及时回收Bitmap,防止内存溢出 } } } catch (Exception e) { e.printStackTrace(); } finally { retriever.release(); } }
二、集成 FFmpeg 实现高效帧提取
FFmpeg对复杂视频格式兼容性更强,提取速度也更快,适合处理各类视频:
- 实现步骤:
- 在项目
build.gradle中引入预编译的FFmpeg依赖(如com.writingminds:FFmpegAndroid:0.3.2) - 执行命令提取全部帧:
- 在项目
public void extractFramesWithFFmpeg(String videoPath, String outputDir) { // -r 指定输出帧率,可替换为原视频帧率;%d会自动生成递增的帧序号 String cmd = String.format("-i %s -r %d %s/frame_%%04d.png", videoPath, 30, outputDir); FFmpeg ffmpeg = FFmpeg.getInstance(context); try { ffmpeg.execute(cmd, new ExecuteBinaryResponseHandler() { @Override public void onSuccess(String message) { // 帧提取完成后的逻辑 } @Override public void onFailure(String message) { // 处理提取失败情况 } }); } catch (FFmpegCommandAlreadyRunningException e) { e.printStackTrace(); } }
- 注意事项:需申请文件读写权限,大视频建议在后台线程执行,避免阻塞UI
三、使用 ExoPlayer 逐帧渲染提取
适合需要对帧做实时处理的场景,通过监听帧渲染事件获取每一帧:
- 核心逻辑:用ExoPlayer加载视频,监听帧渲染回调,从Surface中获取帧数据
- 代码示例:
private SimpleExoPlayer player; private String saveDir; public void initExoPlayerFrameExtract(String videoPath, String outputDir) { saveDir = outputDir; File folder = new File(saveDir); if (!folder.exists()) folder.mkdirs(); player = new SimpleExoPlayer.Builder(context).build(); // 监听播放结束,释放资源 player.addListener(new Player.Listener() { @Override public void onPlaybackStateChanged(int playbackState) { if (playbackState == Player.STATE_ENDED) { player.release(); } } }); // 监听帧渲染事件 player.addVideoRendererEventListener(new VideoRendererEventListener() { @Override public void onVideoFrameRendered(Surface surface, long presentationTimeUs, long releaseTimeNs, float pixelWidthHeightRatio) { Bitmap frame = Bitmap.createBitmap(surface.getWidth(), surface.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = surface.lockCanvas(null); canvas.drawBitmap(frame, 0, 0, null); surface.unlockCanvasAndPost(canvas); File frameFile = new File(saveDir, "frame_" + presentationTimeUs + ".png"); try (FileOutputStream fos = new FileOutputStream(frameFile)) { frame.compress(Bitmap.CompressFormat.PNG, 100, fos); } catch (IOException e) { e.printStackTrace(); } frame.recycle(); } }); // 加载并播放视频 MediaItem mediaItem = MediaItem.fromUri(videoPath); player.setMediaItem(mediaItem); player.prepare(); player.play(); }
内容的提问来源于stack exchange,提问作者Ashutosh
相关产品推荐
相关产品推荐

