You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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对复杂视频格式兼容性更强,提取速度也更快,适合处理各类视频:

  • 实现步骤:
    1. 在项目build.gradle中引入预编译的FFmpeg依赖(如com.writingminds:FFmpegAndroid:0.3.2)
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 07:55:40