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

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>

http://www.xdnf.cn/news/12563.html

相关文章:

  • vsCode使用本地低版本node启动配置文件
  • sklearn 和 pytorch tensorflow什么关系
  • k8s部署dify
  • Python打卡第46天
  • 埃文科技智能数据引擎产品入选《中国网络安全细分领域产品名录》
  • for AC500 PLCs 3ADR025003M9903的安全说明
  • Linux配置yum 时间同步服务 关闭防火墙 关闭ESlinux
  • DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
  • 12.5Swing控件3Jpanel JOptionPane
  • 03 mysql 的环境搭建
  • 计算机视觉与深度学习 | 基于MATLAB的相机标定
  • 【Go语言基础【7】】条件语句
  • PhpStorm代码编辑器内置数据库配置与使用
  • 学习设计模式《十二》——命令模式
  • VR视频制作有哪些流程?
  • Day46 Python打卡训练营
  • spark 执行 hive sql数据丢失
  • 89.实现添加收藏的功能的后端实现
  • 04 Deep learning神经网络编程基础 梯度下降 --吴恩达
  • ONLYOFFICE协作空间3.1.1 企业版 介绍及部署说明:家庭云计算专家
  • Git分布式版本控制工具
  • Grid 布局学习一
  • 矩阵QR分解
  • 有声书画本
  • 刷题记录(7)二叉树
  • WebRTC源码线程-1
  • 【Mini-F5265-OB开发板试用测评】显示RTC日历时钟
  • 在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
  • 视频的分片上传,断点上传
  • Java-IO流之压缩与解压缩流详解