Pu Zhibing
2025-04-11 2b4f8907a65c347400432c8db1bccb97c6a26d30
ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/util/JavaCVStreamUtil.java
@@ -2,19 +2,25 @@
import cn.hutool.core.io.FileUtil;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.system.service.ICarService;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 音视频推流拉流工具类
 * 参考资料:https://juejin.cn/post/7311230172237561866
 *
 * @author zhibing.pu
 * @Date 2025/4/8 16:03
 */
@Component
public class JavaCVStreamUtil {
   
   private static Map<Integer, FFmpegFrameGrabber> grabberMap = new ConcurrentHashMap<>();
@@ -25,27 +31,33 @@
   
   private static Map<Integer, Boolean> statusMap = new ConcurrentHashMap<>();
   
   @Resource
   private RedisTemplate redisTemplate;
   @Resource
   private ICarService carService;
   
   
   /**
    * 视频拉流和推流
    */
   public static void push_flv(String inputUrl, String outputUrl, Integer deviceNumber){
      OpenCVFrameGrabber grabber = null;
//      FFmpegFrameGrabber grabber = null;
   public static void push_flv(String inputUrl, String outputUrl, Integer deviceNumber) {
//      OpenCVFrameGrabber grabber = null;
      FFmpegFrameGrabber grabber = null;
      FFmpegFrameRecorder recorder = null;
      try {
         //关闭上一个没有正确关闭的流
         FFmpegFrameGrabber fFmpegFrameGrabber = grabberMap.get(deviceNumber);
         if(null != fFmpegFrameGrabber){
         if (null != fFmpegFrameGrabber) {
            fFmpegFrameGrabber.close();
         }
         OpenCVFrameGrabber openCVFrameGrabber = grabberMap1.get(deviceNumber);
         if(null != openCVFrameGrabber){
            openCVFrameGrabber.close();;
         if (null != openCVFrameGrabber) {
            openCVFrameGrabber.close();
            ;
         }
         FFmpegFrameRecorder fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
         if(null != fFmpegFrameRecorder){
         if (null != fFmpegFrameRecorder) {
            fFmpegFrameRecorder.close();
         }
         
@@ -54,26 +66,26 @@
         FFmpegLogCallback.set();
         
         //视频抓帧
//         grabber = new FFmpegFrameGrabber(inputUrl);
//         grabber.setOption("rtsp_transport", "tcp");
//         // 正确设置超时时间
//         grabber.setOption("timeout", "120000");
//         grabber.start();
//         grabberMap.put(deviceNumber, grabber);
         grabber = new OpenCVFrameGrabber(0);
         grabber = new FFmpegFrameGrabber(inputUrl);
         grabber.setOption("rtsp_transport", "tcp");
         // 正确设置超时时间
         grabber.setOption("timeout", "120000");
         grabber.start();
         grabberMap1.put(deviceNumber, grabber);
         grabberMap.put(deviceNumber, grabber);
//         grabber = new OpenCVFrameGrabber(0);
//         grabber.start();
//         grabberMap1.put(deviceNumber, grabber);
         
         //录制视频,推送到流媒体服务器(nginx)
         recorder = new FFmpegFrameRecorder(outputUrl, grabber.getImageWidth(), grabber.getImageHeight());
         recorder.setFormat("flv");
         // 设置视频比特率
         recorder.setVideoBitrate(grabber.getVideoBitrate());
//         // 设置帧率
//         recorder.setFrameRate(grabber.getVideoFrameRate());
//         // 设置关键帧间隔
//         recorder.setGopSize((int) grabber.getVideoFrameRate());
         // 设置帧率
         recorder.setFrameRate(grabber.getVideoFrameRate());
         // 设置关键帧间隔
         recorder.setGopSize((int) grabber.getVideoFrameRate());
         // CRF 是一种用于控制视频/音频质量的参数,它允许在保持目标质量的同时动态地调整比特率。较低的CRF值表示更高的质量,但也可能导致较大的文件大小
         recorder.setAudioOption("crf", "23");
         
@@ -95,13 +107,16 @@
         while ((frame = grabber.grab()) != null) {
            recorder.record(frame);
            //判断状态为停止,则结束此线程任务
            if(!statusMap.get(deviceNumber)){
            if (!statusMap.get(deviceNumber)) {
               break;
            }
            Thread.sleep(500);
         }
      } catch (FrameGrabber.Exception | FrameRecorder.Exception e) {
         e.printStackTrace();
      }finally {
      } catch (InterruptedException e) {
         throw new RuntimeException(e);
      } finally {
         close(deviceNumber, null);
      }
   }
@@ -110,7 +125,7 @@
   /**
    * 视频拉流和推流
    */
   public static void push_hls(String inputUrl, String outputUrl, Integer deviceNumber, String folderPath){
   public static void push_hls(String inputUrl, String outputUrl, Integer deviceNumber, String folderPath) {
      OpenCVFrameGrabber grabber = null;
//      FFmpegFrameGrabber grabber = null;
      FFmpegFrameRecorder recorder = null;
@@ -118,15 +133,16 @@
      try {
         //关闭上一个没有正确关闭的流
         FFmpegFrameGrabber fFmpegFrameGrabber = grabberMap.get(deviceNumber);
         if(null != fFmpegFrameGrabber){
         if (null != fFmpegFrameGrabber) {
            fFmpegFrameGrabber.close();
         }
         OpenCVFrameGrabber openCVFrameGrabber = grabberMap1.get(deviceNumber);
         if(null != openCVFrameGrabber){
            openCVFrameGrabber.close();;
         if (null != openCVFrameGrabber) {
            openCVFrameGrabber.close();
            ;
         }
         FFmpegFrameRecorder fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
         if(null != fFmpegFrameRecorder){
         if (null != fFmpegFrameRecorder) {
            fFmpegFrameRecorder.close();
         }
         
@@ -194,13 +210,13 @@
         while ((frame = grabber.grab()) != null) {
            recorder.record(frame);
            //判断状态为停止,则结束此线程任务
            if(!statusMap.get(deviceNumber)){
            if (!statusMap.get(deviceNumber)) {
               break;
            }
         }
      } catch (FrameGrabber.Exception | FrameRecorder.Exception e) {
         e.printStackTrace();
      }finally {
      } finally {
         close(deviceNumber, folderPath);
      }
   }
@@ -208,12 +224,13 @@
   
   /**
    * 关闭推流和拉流进程
    *
    * @param deviceNumber
    */
   public static void close(Integer deviceNumber, String folderPath){
   public static void close(Integer deviceNumber, String folderPath) {
      //设置状态为停止
      Boolean status = statusMap.get(deviceNumber);
      if(null == status || !status){
      if (null == status || !status) {
         return;
      }
      statusMap.put(deviceNumber, false);
@@ -222,26 +239,28 @@
      FFmpegFrameRecorder fFmpegFrameRecorder = null;
      try {
         fFmpegFrameGrabber = grabberMap.get(deviceNumber);
         if(null != fFmpegFrameGrabber){
         if (null != fFmpegFrameGrabber) {
            fFmpegFrameGrabber.flush();
            fFmpegFrameGrabber.close();
         }
         openCVFrameGrabber = grabberMap1.get(deviceNumber);
         if(null != openCVFrameGrabber){
         if (null != openCVFrameGrabber) {
            openCVFrameGrabber.flush();
            openCVFrameGrabber.close();
         }
         fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
         if(null != fFmpegFrameRecorder){
         if (null != fFmpegFrameRecorder) {
            fFmpegFrameRecorder.flush();
            fFmpegFrameRecorder.close();
         }
      }catch (Exception e){
      } catch (Exception e) {
         e.printStackTrace();
      }
      //开始清除视频文件
      if(StringUtils.isNotEmpty(folderPath)){
      if (StringUtils.isNotEmpty(folderPath)) {
         FileUtil.del(folderPath);
      }
   }
}