当前位置: 首页 > news >正文

java 局域网 rtsp 取流 WebSocket 推送到前端显示 低延迟

众所周知 摄像头取流推流显示前端延迟大

传统方法是服务器取摄像头的rtsp流 然后客户端连服务器

中转多了,延迟一定不小。

假设相机没有专网

公网 1相机自带推流 直接推送到云服务器  然后客户端拉去  

         2相机只有rtsp ,边缘服务器拉流推送到云服务器

私网 情况类似

但是我想能不能直接点对点

于是(我这边按照这个参数可以到和大华相机,海康相机web预览的画面实时的延迟速度


import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.HashMap;/***  获取rtsp流,抓取每帧,通过websocket传递给前台显示*/
@Slf4j
@Component
@EnableAsync
public class RTSPToImage {public static String urls="";public static String pds="0";/*** 异步开启获取rtsp流,通过websocket传输数据*/@Asyncpublic void live(String rtspUrl) {rtspUrl="rtsp://admin:admin123@192.168.1.108:554/cam/realmonitor?channel=1&subtype=0";//大华rtsp取流地址if(urls.equals(rtspUrl)) {return;}else {pds="1";}FFmpegFrameGrabber grabber = null;try {grabber = new FFmpegFrameGrabber(rtspUrl);grabber.setVideoCodecName("H.264");grabber.setFrameRate(grabber.getFrameRate());grabber.setImageWidth(960);//宽高设置小一点,否则会有延迟grabber.setImageHeight(540);// rtsp格式一般添加TCP配置,否则丢帧会比较严重grabber.setOption("rtsp_transport", "tcp"); // 使用TCP传输方式,避免丢包//grabber.setOption("buffer_size", "1024"); // 设置缓冲区大小为1MB,提高流畅度grabber.setOption("rtsp_flags", "prefer_tcp"); // 设置优先使用TCP方式传输//设置帧率grabber.setFrameRate(25);//设置视频bit率grabber.setVideoBitrate(3000000);//设置日志等级avutil.av_log_set_level(avutil.AV_LOG_ERROR);grabber.start();log.info("创建并启动grabber成功");}catch (Exception e){e.printStackTrace();}pds="0";//推送图片Java2DFrameConverter java2DFrameConverter = new Java2DFrameConverter();while (true) {try {if(pds.equals("1")){try {grabber.stop();} catch (Exception e1) {e1.printStackTrace();} finally {grabber = null;}return;}if (grabber != null) {Frame frame = grabber.grabImage();if (null == frame) {continue;}BufferedImage bufferedImage = java2DFrameConverter.getBufferedImage(frame);ByteArrayOutputStream out = new ByteArrayOutputStream();ImageIO.write(bufferedImage, "jpg", out);byte[] imageData = out.toByteArray();//通过WebSocket推送到前端 WebSocket具体代码网上有WebSocketServer.sendMessageByObject(out.toByteArray());// 4. 控制帧率 (30fps)//Thread.sleep(33);}} catch (Exception e) {e.printStackTrace();if (grabber != null) {try {grabber.stop();} catch (Exception e1) {e1.printStackTrace();} finally {grabber = null;}}}}}}

前端

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>WebSocket实时图像传输</title><style>body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);color: white;margin: 0;padding: 20px;min-height: 100vh;}.container {max-width: 1200px;margin: 0 auto;padding: 20px;}header {text-align: center;margin-bottom: 30px;padding: 20px;background: rgba(0, 0, 0, 0.3);border-radius: 15px;box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);}h1 {margin: 0;font-size: 2.5rem;text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);}.subtitle {font-size: 1.2rem;opacity: 0.9;}.content {display: flex;flex-wrap: wrap;gap: 30px;}.video-container {flex: 1;min-width: 300px;background: rgba(0, 0, 0, 0.3);border-radius: 15px;padding: 20px;box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);}.video-title {margin-top: 0;display: flex;align-items: center;gap: 10px;}.video-title i {font-size: 1.5rem;color: #4CAF50;}#videoStream {width: 100%;background: #000;border-radius: 8px;display: block;aspect-ratio: 4/3;}.stats-container {flex: 1;min-width: 300px;background: rgba(0, 0, 0, 0.3);border-radius: 15px;padding: 20px;box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);}.stat-cards {display: grid;grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));gap: 20px;margin-top: 20px;}.stat-card {background: rgba(255, 255, 255, 0.1);border-radius: 10px;padding: 15px;text-align: center;}.stat-value {font-size: 2rem;font-weight: bold;margin: 10px 0;color: #4CAF50;}.stat-label {font-size: 0.9rem;opacity: 0.8;}.controls {display: flex;gap: 15px;margin-top: 20px;flex-wrap: wrap;}button {flex: 1;min-width: 120px;padding: 12px 20px;background: #4CAF50;color: white;border: none;border-radius: 8px;cursor: pointer;font-size: 1rem;font-weight: bold;transition: all 0.3s;box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);}button:hover {background: #45a049;transform: translateY(-2px);box-shadow: 0 6px 14px rgba(0, 0, 0, 0.4);}button:active {transform: translateY(1px);}button.stop {background: #f44336;}button.stop:hover {background: #d32f2f;}.info {margin-top: 20px;padding: 15px;background: rgba(0, 0, 0, 0.3);border-radius: 10px;font-size: 0.9rem;}.latency-graph {height: 100px;background: rgba(0, 0, 0, 0.2);border-radius: 8px;margin-top: 20px;position: relative;overflow: hidden;}.graph-bar {position: absolute;bottom: 0;width: 4px;background: #4CAF50;transition: left 0.1s linear;}footer {text-align: center;margin-top: 40px;padding: 20px;font-size: 0.9rem;opacity: 0.7;}@media (max-width: 768px) {.content {flex-direction: column;}}</style>
</head>
<body>
<div class="container"><header><h1>WebSocket实时图像传输</h1><p class="subtitle">使用二进制数据传输实现高性能视频流</p></header><div class="content"><div class="video-container"><h2 class="video-title"><i>▶️</i> 实时视频流</h2><img id="videoStream" src="" alt="视频流"><div class="controls"><button id="startBtn">开始传输</button><button id="stopBtn" class="stop">停止传输</button></div><div class="info"><p><strong>技术说明:</strong> 图像数据通过WebSocket以二进制格式传输,前端使用Blob和ObjectURL高效渲染,避免了Base64编码开销。</p></div></div><div class="stats-container"><h2>性能指标</h2><div class="stat-cards"><div class="stat-card"><div class="stat-label">帧率 (FPS)</div><div id="fps" class="stat-value">0</div></div><div class="stat-card"><div class="stat-label">延迟 (ms)</div><div id="latency" class="stat-value">0</div></div><div class="stat-card"><div class="stat-label">数据大小</div><div id="dataSize" class="stat-value">0 KB</div></div><div class="stat-card"><div class="stat-label">连接状态</div><div id="status" class="stat-value">断开</div></div></div><div class="latency-container"><div class="stat-label">延迟变化趋势</div><div id="latencyGraph" class="latency-graph"></div></div></div></div><footer><p>WebSocket实时图像传输演示 | 使用Java WebSocket服务端</p></footer>
</div><script>// 全局变量const videoElement = document.getElementById('videoStream');const startBtn = document.getElementById('startBtn');const stopBtn = document.getElementById('stopBtn');const fpsElement = document.getElementById('fps');const latencyElement = document.getElementById('latency');const dataSizeElement = document.getElementById('dataSize');const statusElement = document.getElementById('status');const latencyGraph = document.getElementById('latencyGraph');let ws = null;let frameCount = 0;let lastFrameTime = 0;let fps = 0;let latencyValues = [];let animationFrameId = null;// 初始化function init() {startBtn.addEventListener('click', startStream);stopBtn.addEventListener('click', stopStream);updateStats();}// 启动视频流function startStream() {if (ws && ws.readyState === WebSocket.OPEN) return;stopStream(); // 确保先关闭现有连接// 创建WebSocket连接ws = new WebSocket('ws://192.168.1.103/ws/10002');// 设置二进制类型为arraybufferws.binaryType = 'arraybuffer';// 连接打开ws.onopen = () => {statusElement.textContent = '已连接';statusElement.style.color = '#4CAF50';lastFrameTime = performance.now();frameCount = 0;fps = 0;latencyValues = [];clearGraph();};// 接收消息ws.onmessage = (event) => {const receiveTime = performance.now();// 处理二进制图像数据const arrayBuffer = event.data;const blob = new Blob([arrayBuffer], { type: 'image/jpeg' });const url = URL.createObjectURL(blob);// 释放前一个URL的内存if (videoElement.previousUrl) {URL.revokeObjectURL(videoElement.previousUrl);}videoElement.previousUrl = url;videoElement.src = url;// 计算帧率和延迟frameCount++;const now = performance.now();const elapsed = now - lastFrameTime;if (elapsed >= 1000) {fps = Math.round((frameCount * 1000) / elapsed);frameCount = 0;lastFrameTime = now;}// 计算数据大小const kb = (arrayBuffer.byteLength / 1024).toFixed(1);// 更新性能指标fpsElement.textContent = fps;dataSizeElement.textContent = `${kb} KB`;// 添加到延迟图表addLatencyPoint(kb);};// 错误处理ws.onerror = (error) => {console.error('WebSocket Error:', error);statusElement.textContent = '错误';statusElement.style.color = '#f44336';};// 连接关闭ws.onclose = () => {statusElement.textContent = '已断开';statusElement.style.color = '#ff9800';};}// 停止视频流function stopStream() {if (ws) {if (ws.readyState === WebSocket.OPEN) {ws.close();}ws = null;}if (videoElement.previousUrl) {URL.revokeObjectURL(videoElement.previousUrl);videoElement.previousUrl = null;}videoElement.src = '';statusElement.textContent = '已断开';statusElement.style.color = '#ff9800';}// 更新统计信息function updateStats() {// 模拟延迟值变化if (ws && ws.readyState === WebSocket.OPEN && latencyValues.length > 0) {const avgLatency = latencyValues.reduce((a, b) => a + b, 0) / latencyValues.length;latencyElement.textContent = avgLatency.toFixed(1);}requestAnimationFrame(updateStats);}// 添加延迟点function addLatencyPoint(value) {latencyValues.push(parseFloat(value));if (latencyValues.length > 100) {latencyValues.shift();}// 更新图表updateGraph();}// 清除图表function clearGraph() {latencyGraph.innerHTML = '';}// 更新图表function updateGraph() {if (latencyValues.length === 0) return;const maxValue = Math.max(...latencyValues) * 1.2 || 10;const graphHeight = latencyGraph.clientHeight;const barWidth = Math.max(2, latencyGraph.clientWidth / 50);// 清空现有图表latencyGraph.innerHTML = '';// 添加新数据点latencyValues.forEach((value, index) => {const barHeight = (value / maxValue) * graphHeight;const bar = document.createElement('div');bar.className = 'graph-bar';bar.style.height = `${barHeight}px`;bar.style.left = `${index * (barWidth + 1)}px`;bar.style.width = `${barWidth}px`;bar.style.backgroundColor = value > maxValue * 0.8 ? '#f44336' : '#4CAF50';latencyGraph.appendChild(bar);});}// 初始化应用init();
</script>
</body>
</html>

相关文章:

  • 11.RV1126-ROCKX项目
  • MySQL 索引优化
  • Linux驱动:再看静态映射和动态映射
  • 数学:数的概念是如何发展的?
  • Python 训练营打卡 Day 45
  • 高等数学》(同济大学·第7版)第二章第一节“导数的概念“
  • C文件操作2
  • error: subprocess-exited-with-error【已解决】
  • SCAU数值计算OJ
  • 2.1 Windows编译环境介绍
  • 《UE5_C++多人TPS完整教程》学习笔记37 ——《P38 变量复制(Variable Replication)》
  • WinCC学习系列-变量模拟器(WinCC TAG Simulator )
  • MajicTryOn(基于wanvideo的虚拟试穿项目)
  • @Minikube 部署与配置
  • 使用 Python 构建并调用 ComfyUI 图像生成 API:完整实战指南
  • 【大厂机试题解法笔记】观看文艺演出问题
  • 使用扩散模型解决Talking Head生成中的头像抖动问题
  • 毫米波雷达基础理论(3D+4D)
  • 20250606-C#知识:匿名函数、Lambda表达式与闭包
  • C#中datagridview单元格value为{}大括号
  • 中国电子商务网站建设情况/怎么建个人网站
  • 建设银行网站怎么下载/做网站的公司有哪些
  • angularjs 网站开发/百度推广关键词怎么设置好
  • 凯里建设局网站/长沙百家号seo
  • 刷单做任务的网站/新出的app推广在哪找
  • 上海集团平台app/seo服务是什么