package com.ruoyi.system.util;
|
|
import org.bytedeco.ffmpeg.global.avcodec;
|
import org.bytedeco.ffmpeg.global.avutil;
|
import org.bytedeco.javacv.*;
|
|
import java.util.Map;
|
import java.util.concurrent.ConcurrentHashMap;
|
|
/**
|
* 音视频推流拉流工具类
|
* 参考资料:https://juejin.cn/post/7311230172237561866
|
* @author zhibing.pu
|
* @Date 2025/4/8 16:03
|
*/
|
public class JavaCVStreamUtil {
|
|
private static Map<Integer, FFmpegFrameGrabber> grabberMap = new ConcurrentHashMap<>();
|
|
private static Map<Integer, FFmpegFrameRecorder> frameRecorderMap = new ConcurrentHashMap<>();
|
|
private static Map<Integer, Boolean> statusMap = new ConcurrentHashMap<>();
|
|
|
|
/**
|
* 视频拉流和推流
|
*/
|
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){
|
fFmpegFrameGrabber.close();
|
}
|
FFmpegFrameRecorder fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
|
if(null != fFmpegFrameRecorder){
|
fFmpegFrameRecorder.close();
|
}
|
|
//设置FFmpeg日志级别
|
avutil.av_log_set_level(avutil.AV_LOG_INFO);
|
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.start();
|
|
//录制视频,推送到流媒体服务器(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());
|
// CRF 是一种用于控制视频/音频质量的参数,它允许在保持目标质量的同时动态地调整比特率。较低的CRF值表示更高的质量,但也可能导致较大的文件大小
|
recorder.setAudioOption("crf", "23");
|
|
//设置音频编码为AAC
|
// if (grabber.getAudioChannels() > 0) {
|
recorder.setAudioChannels(grabber.getAudioChannels());
|
recorder.setAudioBitrate(grabber.getAudioBitrate());
|
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
|
// }
|
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
|
//将解码后的帧记录到输出文件中
|
//recorder.start通常用于处理已经解码成图像的视频数据
|
recorder.start();
|
Frame frame;
|
while ((frame = grabber.grab()) != null) {
|
recorder.record(frame);
|
}
|
} catch (FrameGrabber.Exception | FrameRecorder.Exception e) {
|
e.printStackTrace();
|
}finally {
|
try {
|
statusMap.put(deviceNumber, false);
|
if(null != grabber){
|
grabber.stop();
|
}
|
if(null != recorder){
|
recorder.stop();
|
}
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
}
|
}
|
|
|
/**
|
* 视频拉流和推流
|
*/
|
public static void push_hls(String inputUrl, String outputUrl, Integer deviceNumber){
|
OpenCVFrameGrabber grabber = null;
|
// FFmpegFrameGrabber grabber = null;
|
FFmpegFrameRecorder recorder = null;
|
// outputUrl = "http://192.168.110.85:80/test.m3u8";
|
try {
|
//关闭上一个没有正确关闭的流
|
FFmpegFrameGrabber fFmpegFrameGrabber = grabberMap.get(deviceNumber);
|
if(null != fFmpegFrameGrabber){
|
fFmpegFrameGrabber.close();
|
}
|
FFmpegFrameRecorder fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
|
if(null != fFmpegFrameRecorder){
|
fFmpegFrameRecorder.close();
|
}
|
|
//设置FFmpeg日志级别
|
avutil.av_log_set_level(avutil.AV_LOG_INFO);
|
FFmpegLogCallback.set();
|
|
//视频抓帧
|
// grabber = new FFmpegFrameGrabber(inputUrl);
|
// grabber.setOption("rtsp_transport", "tcp");
|
// // 正确设置超时时间
|
// grabber.setOption("timeout", "30000");
|
// grabber.start();
|
// grabberMap.put(deviceNumber, grabber);
|
|
grabber = new OpenCVFrameGrabber(0);
|
grabber.start();
|
|
//录制视频,推送到流媒体服务器(nginx)
|
recorder = new FFmpegFrameRecorder(outputUrl, grabber.getImageWidth(), grabber.getImageHeight());
|
recorder.setFormat("hls");
|
// 设置视频比特率
|
recorder.setVideoBitrate(grabber.getVideoBitrate());
|
// 设置帧率
|
// recorder.setFrameRate(grabber.getVideoFrameRate());
|
// // 设置关键帧间隔
|
// recorder.setGopSize((int) grabber.getVideoFrameRate());
|
|
// 设置HLS切片参数
|
//将每个切片时长设置为10秒
|
recorder.setOption("hls_time", "15");
|
//设置切片数大小
|
recorder.setOption("hls_list_size", "20");
|
//设置切片循环次数为50
|
recorder.setOption("hls_wrap", "20");
|
//每次切片完成后,都会删除之前的切片文件。如果不设置或设置为其他值,则不会删除之前的切片文件。
|
recorder.setOption("hls_flags", "delete_segments");
|
//在使用 H.264 编码时,通常要求输入的像素格式为 YUV420P。如果输入的像素格式不匹配,就可能导致 avcodec_send_frame() 错误
|
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
|
//CRF 是一种用于控制视频/音频质量的参数,它允许在保持目标质量的同时动态地调整比特率。较低的CRF值表示更高的质量,但也可能导致较大的文件大小
|
recorder.setAudioOption("crf", "23");
|
|
//设置音频编码为AAC
|
if (grabber.getAudioChannels() > 0) {
|
recorder.setAudioChannels(grabber.getAudioChannels());
|
recorder.setAudioBitrate(grabber.getAudioBitrate());
|
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
|
}
|
|
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
|
//设置音频编码为AAC
|
if (grabber.getAudioChannels() > 0) {
|
recorder.setAudioChannels(grabber.getAudioChannels());
|
recorder.setAudioBitrate(grabber.getAudioBitrate());
|
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
|
}
|
//开始录制视频
|
recorder.start();
|
frameRecorderMap.put(deviceNumber, recorder);
|
//设置状态为开始
|
statusMap.put(deviceNumber, true);
|
|
Frame frame;
|
while ((frame = grabber.grab()) != null) {
|
recorder.record(frame);
|
//判断状态为停止,则结束此线程任务
|
if(!statusMap.get(deviceNumber)){
|
break;
|
}
|
}
|
} catch (FrameGrabber.Exception | FrameRecorder.Exception e) {
|
e.printStackTrace();
|
}finally {
|
try {
|
statusMap.put(deviceNumber, false);
|
if(null != grabber){
|
grabber.stop();
|
}
|
if(null != recorder){
|
recorder.stop();
|
}
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
}
|
}
|
|
|
/**
|
* 关闭推流和拉流进程
|
* @param deviceNumber
|
*/
|
public static void close(Integer deviceNumber){
|
//设置状态为停止
|
statusMap.put(deviceNumber, false);
|
FFmpegFrameGrabber fFmpegFrameGrabber = null;
|
FFmpegFrameRecorder fFmpegFrameRecorder = null;
|
try {
|
fFmpegFrameGrabber = grabberMap.get(deviceNumber);
|
if(null != fFmpegFrameGrabber){
|
fFmpegFrameGrabber.close();
|
}
|
fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
|
if(null != fFmpegFrameRecorder){
|
fFmpegFrameRecorder.close();
|
}
|
}catch (Exception e){
|
e.printStackTrace();
|
}finally {
|
try {
|
fFmpegFrameGrabber = grabberMap.get(deviceNumber);
|
if(null != fFmpegFrameGrabber){
|
fFmpegFrameGrabber.close();
|
}
|
fFmpegFrameRecorder = frameRecorderMap.get(deviceNumber);
|
if(null != fFmpegFrameRecorder){
|
fFmpegFrameRecorder.close();
|
}
|
}catch (Exception e){
|
e.printStackTrace();
|
}
|
}
|
}
|
}
|