JavaCV神技:0.1% CPU占用,高效处理实时RTSP视频流

当处理实时视频流时,我们常常希望能够针对关键帧(I帧)进行抓取,以便在视频显示和处理过程中减少计算资源的开销。关键帧是视频编码序列中的重要帧,其他帧(P帧和B帧)则是通过引用关键帧进行编码。在本文中,我们将深入介绍如何使用 JavaCV 的 FFmpegFrameGrabber 来实现只抓取关键帧的功能,以及为什么这对于实时视频处理是有益的。

背景

实时视频流的处理在许多应用中都非常重要,如监控系统、视频会议、流媒体等。然而,随着视频分辨率和帧率的增加,解码和显示视频帧所需的计算资源也会增加。在某些场景下,我们并不需要每一帧都进行解码和显示,而只关心关键帧即可满足需求,从而减少了计算的开销。

JavaCV 和 FFmpeg

JavaCV 是一个在 Java 中使用计算机视觉库的强大工具,其中包括了对 FFmpeg 的封装。FFmpeg 是一个流行的多媒体处理库,可以用于解码、编码、转码、处理音视频数据等。通过结合 JavaCV 和 FFmpeg,我们可以在 Java 中轻松处理视频流。

实现关键帧抓取

以下是一个详细的示例代码,演示了如何使用 JavaCV 的 FFmpegFrameGrabber 来只抓取关键帧并显示:

import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.ffmpeg.global.avutil;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class RTSPCaptureKeyFrames {
    public static void main(String[] args) {
        String rtspUrl = "rtsp://10.10.13.13/test26";

        try {
            FFmpegFrameGrabber.tryLoad();
        } catch (Exception e) {
            throw new RuntimeException("Failed to load FFmpeg", e);
        }

        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(rtspUrl);
        grabber.setVideoOption("skip_frame", "nokey");  // 仅抓取关键帧
        grabber.setVideoOption("fflags", "nobuffer");    // 禁用缓冲
        grabber.setVideoOption("rtsp_transport", "tcp"); // 使用TCP传输
        avutil.av_log_set_level(avutil.AV_LOG_ERROR); // 设置日志级别

        CanvasFrame canvasFrame = new CanvasFrame("Key Frame Capture", CanvasFrame.getDefaultGamma() / grabber.getGamma());

        try {
            grabber.start();

            while (true) {
                Frame frame = grabber.grabImage();
                if (frame == null) {
                    //TODO:连续n次为null,进行重连
                    System.out.println("frame is null");
                }
                else{
                    canvasFrame.showImage(frame);
                }
                LocalDateTime now = LocalDateTime.now();
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
                String formattedDateTime = now.format(formatter);

                System.out.println("当前时间: " + formattedDateTime);

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                grabber.stop();
                grabber.close();
                canvasFrame.dispose();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

多路拉流


import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.ffmpeg.global.avutil;

public class MultiRTSPCaptureKeyFrames {
    public static void main(String[] args) {
        String[] rtspUrls = {
                "rtsp://admin:[email protected]:554/Streaming/Channels/101?transportmode=multicast",
                "rtsp://admin:[email protected]:554/Streaming/Channels/101?transportmode=multicast",
                // Add more RTSP URLs here
        };

        try {
            FFmpegFrameGrabber.tryLoad();
        } catch (Exception e) {
            throw new RuntimeException("Failed to load FFmpeg", e);
        }

        CanvasFrame[] canvasFrames = new CanvasFrame[rtspUrls.length];

        for (int i = 0; i < rtspUrls.length; i++) {
            FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(rtspUrls[i]);
            // 设置读取的最大数据,单位字节
            grabber.setOption("probesize", "10000");
            // 设置分析的最长时间,单位微秒
            grabber.setOption("analyzeduration", "10000");
            grabber.setOption("stimoout", "5*1000*1000");
            grabber.setVideoOption("skip_frame", "nokey");
            grabber.setVideoOption("fflags", "nobuffer");
            grabber.setVideoOption("rtsp_transport", "tcp");
            avutil.av_log_set_level(avutil.AV_LOG_ERROR);

            canvasFrames[i] = new CanvasFrame("Key Frame Capture " + i, CanvasFrame.getDefaultGamma() / grabber.getGamma());

            final int index = i;
            Thread thread = new Thread(() -> {
                try {
                    grabber.start();

                    while (true) {
                        Frame frame = grabber.grabImage();
                        if (frame == null || frame.image == null) {
                            System.out.println("Frame is null or image is null for stream " + index);
                        } else {
                            canvasFrames[index].showImage(frame);
                        }
//                        grabber.flush();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });

            thread.start();
        }

        // Keep the program running
        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // Close the canvas frames
        for (CanvasFrame canvasFrame : canvasFrames) {
            canvasFrame.dispose();
        }
    }
}

实际测试结果

在进行实际测试时,我们使用 JavaCV 的 FFmpegFrameGrabber 来实现只抓取关键帧的功能。通过设置相关的参数,我们成功地实现了只抓取关键帧,并在测试过程中观察到了显著的 CPU 占用率下降。在一路 RTSP 流的抓取过程中,我们仅使用不到 0.1% 的 CPU 资源。这一结果极大地突显了只抓取关键帧对于优化性能的重要性。

为何只抓关键帧有益?

  1. 减少计算开销: 关键帧通常包含完整的图像信息,无需引用其他帧。通过只抓取关键帧,可以避免解码和处理大量的 P 帧和 B 帧,从而减少了计算开销。

  2. 提高性能: 在某些情况下,实时视频处理可能需要在有限的计算资源下运行。只抓取关键帧可以让系统更轻松地处理视频流,从而提高整体性能和流畅度。

  3. 降低延迟: 关键帧独立于其他帧,因此在只抓取关键帧时可以更快地开始显示和处理视频,从而降低了系统的延迟。

总结

通过使用 JavaCV 的 FFmpegFrameGrabber,我们可以在实时视频处理中轻松实现只抓取关键帧的功能。这对于降低计算开销、提高性能和降低延迟非常有益。根据实际需求,您可以根据示例代码进行调整和优化,以获得最佳的效果。无论是监控系统还是流媒体应用,只抓取关键帧都是一个强大的工具,可以让您更有效地处理视频流。

你可能感兴趣的:(javacv,rtsp,cpu,关键帧,拉流)