|
|
|
@ -2,9 +2,7 @@ package com.inspect.tcpserver.sip.media; |
|
|
|
|
|
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
|
import org.bytedeco.ffmpeg.global.avcodec; |
|
|
|
import org.bytedeco.javacv.FFmpegFrameGrabber; |
|
|
|
import org.bytedeco.javacv.FFmpegFrameRecorder; |
|
|
|
import org.bytedeco.javacv.Frame; |
|
|
|
import org.bytedeco.javacv.*; |
|
|
|
import org.springframework.stereotype.Service; |
|
|
|
|
|
|
|
@Slf4j |
|
|
|
@ -14,9 +12,79 @@ public class Gb28181StreamService { |
|
|
|
private Thread pushThread; |
|
|
|
private volatile boolean isPushing = false; |
|
|
|
|
|
|
|
private volatile boolean running = false; |
|
|
|
|
|
|
|
private final String rtspInputUrl = "rtsp://admin:wd19216811@192.168.1.244:554/h264/ch1/sub/av_stream"; |
|
|
|
private final String rtpOutputUrl= "rtp://192.168.1.116:30000"; |
|
|
|
|
|
|
|
public void startPush() { |
|
|
|
if (running) { |
|
|
|
log.warn("推流已在运行"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
running = true; |
|
|
|
pushThread = new Thread(() -> { |
|
|
|
FFmpegFrameGrabber grabber = null; |
|
|
|
FFmpegFrameRecorder recorder = null; |
|
|
|
|
|
|
|
try { |
|
|
|
// ================= 第一步:拉取 RTSP 源 ================= |
|
|
|
grabber = new FFmpegFrameGrabber("rtsp://admin:wd19216811@192.168.1.244:554/h264/ch1/sub/av_stream"); |
|
|
|
grabber.setOption("rtsp_transport", "tcp"); // 强制 TCP 拉流 |
|
|
|
grabber.setOption("fflags", "nobuffer"); // 降低延迟 |
|
|
|
grabber.setOption("flags", "low_delay"); |
|
|
|
grabber.setOption("analyzeduration", "2000000"); // 分析时长 |
|
|
|
grabber.setOption("probesize", "2000000"); |
|
|
|
grabber.start(); |
|
|
|
|
|
|
|
// ================= 第二步:配置 RTP_MPEGTS 输出 ================= |
|
|
|
String outputUrl = "rtp://192.168.1.116:30000?pkt_size=1400&localrtpport=50000"; |
|
|
|
|
|
|
|
recorder = new FFmpegFrameRecorder(outputUrl, grabber.getImageWidth(), grabber.getImageHeight(), 0); // 0 = 无音频通道 |
|
|
|
recorder.setFormat("rtp_mpegts"); // 核心:rtp_mpegts 封装 |
|
|
|
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // H264 |
|
|
|
recorder.setVideoCodecName("copy"); // -c:v copy(尽量不重新编码) |
|
|
|
recorder.setVideoBitrate(grabber.getVideoBitrate()); // 继承源码率 |
|
|
|
recorder.setFrameRate(grabber.getFrameRate()); // 继承帧率 |
|
|
|
|
|
|
|
// 模拟 -bsf:v h264_mp4toannexb(JavaCV 会自动处理 Annex-B 输出) |
|
|
|
recorder.setVideoOption("bsf:v", "h264_mp4toannexb"); |
|
|
|
|
|
|
|
// 模拟 -mpegts_flags resend_headers(重复发送 TS 头) |
|
|
|
//recorder.setFormatOption("mpegts_flags", "resend_headers"); |
|
|
|
recorder.setFormat("rtp_mpegts"); |
|
|
|
recorder.setVideoCodecName("copy"); // -c:v copy |
|
|
|
|
|
|
|
|
|
|
|
// pkt_size 和 localrtpport 通过输出 URL 参数已设置 |
|
|
|
|
|
|
|
recorder.start(); |
|
|
|
|
|
|
|
log.info("GB28181 PS over RTP 推流启动:{} -> {}", grabber.getFormat(), outputUrl); |
|
|
|
|
|
|
|
Frame frame; |
|
|
|
while (running && (frame = grabber.grabFrame()) != null) { |
|
|
|
recorder.record(frame); |
|
|
|
} |
|
|
|
|
|
|
|
} catch (FrameGrabber.Exception | FrameRecorder.Exception e) { |
|
|
|
log.error("推流异常", e); |
|
|
|
} finally { |
|
|
|
try { |
|
|
|
if (recorder != null) recorder.stop(); |
|
|
|
if (grabber != null) grabber.stop(); |
|
|
|
} catch (Exception e) { |
|
|
|
log.error("关闭资源异常", e); |
|
|
|
} |
|
|
|
running = false; |
|
|
|
log.info("推流线程结束"); |
|
|
|
} |
|
|
|
}, "rtp-push-thread"); |
|
|
|
|
|
|
|
pushThread.start(); |
|
|
|
} |
|
|
|
|
|
|
|
public void startPushStream() { |
|
|
|
if (isPushing) { |
|
|
|
log.warn("Push is already running."); |
|
|
|
|