ffmpeg实现视频流抽帧

ffmpeg 实现视频流抽帧

抽取实时视频帧

如果你的实时视频是通过 RTSP、UDP 或其他协议获取的,可以直接调用 FFmpeg 命令来抽取帧。

ffmpeg 命令

示例 1

ffmpeg -i rtsp://your_rtsp_stream_url -vf fps=1 -update 1 output.jpg

说明:

  • -i rtsp://your_rtsp_stream_url:指定输入的实时视频流 URL。
  • -vf fps=1:使用视频滤镜,每秒抽取 1 帧(可根据需要调整帧率,例如 fps=1/5 表示每 5 秒抽取一帧)。
  • -update 1 output.jpg:参数 -update 1 表示不断用最新的帧更新同一个输出文件(output.jpg),适合用于监控场景;如果需要保存多张图片,则可以使用类似 output_%04d.jpg 的命名格式保存为连续文件。

示例 2

ffmpeg -i "rtmp://ns8.indexforce.com/home/mystream" -vf fps=1 "frame_%03d.jpg"

说明:

  • -i "rtsp://your_rtsp_stream_url":指定 RTSP 视频流的 URL。
  • -vf fps=1:设置帧率,每秒抽取一帧。您可以根据需要调整此值,例如 fps=1/5 表示每 5 秒抽取一帧。
  • "frame_%03d.jpg":指定输出的图像文件名,%03d 表示编号,生成的文件名将依次为 frame_001.jpgframe_002.jpg 等。

示例 3

ffmpeg -i "rtmp://ns8.indexforce.com/home/mystream" -ss 1 -frames:v 1 "C:\Users\26913\Videos\ffmpeg-img\frame.jpg"

说明:

  • 从指定的 RTSP 流中读取数据,并只输出一帧图像,保存到 C:\Users\26913\Videos\ffmpeg-img 目录下,文件名为 frame.jpg
  • -ss 1:表示设置时间偏移量为 1 秒。也就是说,从视频的第 1 秒处开始处理。对于抽帧来说,FFmpeg 会在视频的 1 秒处截取当前帧。
    • 不加这个参数的话,会获取视频开头的第一帧。
    • 若视频流开头是黑屏或加载帧,可能会影响抓取效果。
    • 效率较低,因为 FFmpeg 需要解析部分流的关键帧来决定输出。

代码示例

引入依赖

        <!-- 集成javacv -->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.11</version>
        </dependency>
        <!-- 集成ffmpeg:https://mvnrepository.com/artifact/org.bytedeco/ffmpeg -->
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>7.1-1.5.11</version>
        </dependency>

1、javacv 写法

public class LiveStreamFrameExtractor {

    private static final Logger log = LoggerFactory.getLogger(LiveStreamFrameExtractor.class);

    /**
     * javacv实时抓取视频帧
     */
    public static void main(String[] args) {
        String streamUrl = VideoConstant.RTSP_URL_2;
        String outputDirPath = VideoConstant.FRAME_FILE_PREFIX;
        File outputDir = new File(outputDirPath);
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }

        // 使用 try-with-resources 自动管理资源
        try (
                FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(streamUrl);
                Java2DFrameConverter converter = new Java2DFrameConverter()
        ) {
            // 启动抓取器,开始实时视频读取
            grabber.start();
            int frameCount = 0;
            Frame frame;
            // 无限循环抓取实时视频帧
            while ((frame = grabber.grabImage()) != null) {
                // 可根据需要添加实时显示、处理等操作
                BufferedImage image = converter.convert(frame);
                if (image != null) {
                    // 构造图片输出路径,例如 live_frame_0001.jpg
                    String outputFileName = outputDirPath + File.separator + String.format("live_frame_%04d.jpg", frameCount);
                    File outputFile = new File(outputFileName);

                    ImageIO.write(image, "jpg", outputFile);
                    System.out.println("保存帧:" + frameCount + " 到文件:" + outputFileName);
                }
                frameCount++;
                // 根据抓取帧率或其他需求设置适当延时,避免过快抽帧
                TimeUnit.MILLISECONDS.sleep(50);

                if (frameCount > 5) {
                    break;
                }
            }
            System.out.println("抓取结束,共抓取 " + frameCount + " 帧");
            // 停止抓取器
            grabber.stop();
        } catch (Exception e) {
            log.error("抓取视频帧出错:", e);
        }
    }
}

2、ffmpeg 命令写法

@Slf4j
public class FfmpegProcess {

    static String rtspUrl = VideoConstant.RTSP_URL_1;
    static String streamOutputFile = VideoConstant.FRAME_FILE_PREFIX + File.separator + DateUtil.format(DateUtil.date(), "yyyyMMddHHmmss") + ".jpg";

    public static void main(String[] args) throws Exception {
        
        String command = getStreamFrameExtractionCommand(rtspUrl, streamOutputFile);

        System.out.println("执行命令: " + command);
        // 创建操作系统进程
        ProcessBuilder builder = new ProcessBuilder();

        // 执行命令
        executeCommand(builder, command, "windows");

        // 合并标准输出和标准错误输出流
        builder.redirectErrorStream(true);
        Process process = builder.start();

        // 异步读取输出流,避免阻塞
        CompletableFuture<Void> future = readOutputAsync(process.getInputStream());

        int exitCode = process.waitFor();
        if (exitCode == 0) {
            System.out.println("执行成功");
        } else {
            System.err.println("执行过程中出现错误,退出代码:" + exitCode);
        }
    }

    /**
     * 获取实时视频流抽帧的命令(只抓取一张图片)
     *
     * @param url        视频流 url,例如 rtsp 流地址
     * @param outputFile 输出文件路径
     * @return 实时视频流抽帧的命令
     */
    private static String getStreamFrameExtractionCommand(String url, String outputFile) {
        return String.format("%s -y -i %s -ss 1 -frames:v 1 %s", VideoConstant.FFMPEG_PATH, url, outputFile);
    }

    /**
     * 异步读取 ffmpeg 输出流
     */
    private static CompletableFuture<Void> readOutputAsync(InputStream inputStream) {
        return CompletableFuture.runAsync(() -> {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                log.error("读取 ffmpeg 输出流异常", e);
            }
        });
    }

    /**
     * 执行命令
     *
     * @param builder 进程构建器
     * @param command 命令
     * @param osType  操作系统类型
     */
    private static void executeCommand(ProcessBuilder builder, String command, String osType) {
        switch (osType) {
            case "windows":
                builder.command("cmd", "/c", command);
                break;
            case "Linux":
            case "macOS":
                builder.command("bash", "-c", command);
                break;
            default:
                throw new RuntimeException("不支持的操作系统类型");
        }
    }
}

学习参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值