Java中基于JavaCV封装的FFmpeg的视频流抽帧功能

831 阅读1分钟

一、依赖

导入JavaCV和绑定的FFmpeg依赖,不依赖于系统FFmpeg。可根据系统平台选择性导入,以减小打包体积:

        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>1.5.9</version>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>6.0-1.5.9</version>
            <classifier>windows-x86_64</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>6.0-1.5.9</version>
            <classifier>linux-x86_64</classifier>
        </dependency>

二、功能代码

  1. 实例化FFmpegFrameGrabber对象,传入视频流。
  2. 配置读取器参数。
  3. 启动读取器。
  4. 抓取一帧。
  5. 实例化Java2DFrameConverter对象。
  6. 将抓取到的帧通过Java2DFrameConverter转化为BufferedImage。
  7. 通过ImageIO.write()方法将BufferedImage保存到本地。
        String videoUrl = "videoUrl";
        String outputImagePath = "outputImagePath";
        // 实例化帧抓取器对象,将文件路径传入
        try(FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoUrl)) {
            // 配置rtsp流读取协议为tcp
            grabber.setOption("rtsp_transport", "tcp");
            // 启动读取器
//            grabber.start(false);
            grabber.startUnsafe(false);

            // 抓取一帧
            Frame frame = grabber.grabImage();
            if (frame != null) {
                // 转换为 BufferedImage
                try(Java2DFrameConverter converter = new Java2DFrameConverter()){
                    BufferedImage bufferedImage = converter.convert(frame);
                    // 保存图片
                    ImageIO.write(bufferedImage, "png", new File(outputImagePath));
                    System.out.println("图片保存成功:" + outputImagePath);
                }
            } else {
                System.out.println("未能抓取到视频帧!");
            }
        } catch (Exception e) {
//            e.printStackTrace();
            System.out.println("异常:" + e.getMessage());
        }

注:

  1. 启动读取器时使用startUnsafe()方法而不是start()方法,防止阻塞,特别是多线程条件下,一个阻塞会全部阻塞。
  2. 启动读取器时使用startUnsafe()/start()带false参数,实现不处理音频,加快启动速度。
  3. 此代码为简洁功能代码,可根据需求扩展读取器配置等其他内容,增强代码健壮性。如:可配合线程池ExecutorService和Future.get()方法实现代码超时重试功能。