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>
相关文章:

java 局域网 rtsp 取流 WebSocket 推送到前端显示 低延迟
众所周知 摄像头取流推流显示前端延迟大 传统方法是服务器取摄像头的rtsp流 然后客户端连服务器 中转多了,延迟一定不小。 假设相机没有专网 公网 1相机自带推流 直接推送到云服务器 然后客户端拉去 2相机只有rtsp ,边缘服务器拉流推送到云服务器 …...

免费批量Markdown转Word工具
免费批量Markdown转Word工具 一款简单易用的批量Markdown文档转换工具,支持将多个Markdown文件一键转换为Word文档。完全免费,无需安装,解压即用! 官方网站 访问官方展示页面了解更多信息:http://mutou888.com/pro…...
【Redis】Redis从入门到实战:全面指南
Redis从入门到实战:全面指南 一、Redis简介 Redis(Remote Dictionary Server)是一个开源的、基于内存的键值存储系统,它可以用作数据库、缓存和消息代理。由Salvatore Sanfilippo于2009年开发,因其高性能、丰富的数据结构和广泛的语言支持而广受欢迎。 Redis核心特点:…...
LeetCode 0386.字典序排数:细心总结条件
【LetMeFly】386.字典序排数:细心总结条件 力扣题目链接:https://leetcode.cn/problems/lexicographical-numbers/ 给你一个整数 n ,按字典序返回范围 [1, n] 内所有整数。 你必须设计一个时间复杂度为 O(n) 且使用 O(1) 额外空间的算法。…...
智能体革命:企业如何构建自主决策的AI代理?
OpenAI智能代理构建实用指南详解 随着大型语言模型(LLM)在推理、多模态理解和工具调用能力上的进步,智能代理(Agents)成为自动化领域的新突破。与传统软件仅帮助用户自动化流程不同,智能代理能够自主执行工…...

以太网PHY布局布线指南
1. 简介 对于以太网布局布线遵循以下准则很重要,因为这将有助于减少信号发射,最大程度地减少噪声,确保器件作用,最大程度地减少泄漏并提高信号质量。 2. PHY设计准则 2.1 DRC错误检查 首先检查DRC规则是否设置正确,然…...
linux设备重启后时间与网络时间不同步怎么解决?
linux设备重启后时间与网络时间不同步怎么解决? 设备只要一重启,时间又错了/偏了,明明刚刚对时还是对的! 这在物联网、嵌入式开发环境特别常见,尤其是开发板、树莓派、rk3588 这类设备。 解决方法: 加硬件…...

若依项目部署--传统架构--未完待续
若依项目介绍 项目源码获取 #Git工具下载 dnf -y install git #若依项目获取 git clone https://gitee.com/y_project/RuoYi-Vue.git项目背景 随着企业信息化需求的增加,传统开发模式存在效率低,重复劳动多等问题。若依项目通过整合主流技术框架&…...
零基础在实践中学习网络安全-皮卡丘靶场(第十一期-目录遍历模块)
经过前面几期的内容我们学习了很多网络安全的知识,而这期内容就涉及到了前面的第六期-RCE模块,第七期-File inclusion模块,第八期-Unsafe Filedownload模块。 什么是"遍历"呢:对学过一些开发语言的朋友来说应该知道&…...
mcts蒙特卡洛模拟树思想
您这个观察非常敏锐,而且在很大程度上是正确的!您已经洞察到了MCTS算法在不同阶段的两种不同行为模式。我们来把这个关系理得更清楚一些,您的理解其实离真相只有一步之遥。 您说的“select是在二次选择的时候起作用”,这个观察非…...

华为云Flexus+DeepSeek征文 | 基于Dify构建具备联网搜索能力的知识库问答助手
华为云FlexusDeepSeek征文 | 基于Dify构建具备联网搜索能力的知识库问答助手 一、构建知识库问答助手引言二、构建知识库问答助手环境2.1 基于FlexusX实例的Dify平台2.2 基于MaaS的模型API商用服务 三、构建知识库问答助手实战3.1 配置Dify环境3.2 创建知识库问答助手3.3 使用知…...
Qt学习及使用_第1部分_认识Qt---Qt开发基本流程
前言 学以致用,通过QT框架的学习,一边实践,一边探索编程的方方面面. 参考书:<Qt 6 C开发指南>(以下称"本书") 标识说明:概念用粗体倾斜.重点内容用(加粗黑体)---重点内容(红字)---重点内容(加粗红字), 本书原话内容用深蓝色标识,比较重要的内容用加粗倾…...
[特殊字符] Spring Boot底层原理深度解析与高级面试题精析
一、Spring Boot底层原理详解 Spring Boot的核心设计哲学是约定优于配置和自动装配,通过简化传统Spring应用的初始化和配置流程,显著提升开发效率。其底层原理可拆解为以下核心机制: 自动装配(Auto-Configuration) 核…...
MeanFlow:何凯明新作,单步去噪图像生成新SOTA
1.简介 这篇文章介绍了一种名为MeanFlow的新型生成模型框架,旨在通过单步生成过程高效地将先验分布转换为数据分布。文章的核心创新在于引入了平均速度的概念,这一概念的引入使得模型能够通过单次函数评估完成从先验分布到数据分布的转换,显…...
【2D与3D SLAM中的扫描匹配算法全面解析】
引言 扫描匹配(Scan Matching)是同步定位与地图构建(SLAM)系统中的核心组件,它通过对齐连续的传感器观测数据来估计机器人的运动。本文将深入探讨2D和3D SLAM中的各种扫描匹配算法,包括数学原理、实现细节以及实际应用中的性能对比,特别关注…...

【Vue】scoped+组件通信+props校验
【scoped作用及原理】 【作用】 默认写在组件中style的样式会全局生效, 因此很容易造成多个组件之间的样式冲突问题 故而可以给组件加上scoped 属性, 令样式只作用于当前组件的标签 作用:防止不同vue组件样式污染 【原理】 给组件加上scoped 属性后…...
Docker环境下安装 Elasticsearch + IK 分词器 + Pinyin插件 + Kibana(适配7.10.1)
做RAG自己打算使用esmilvus自己开发一个,安装时好像网上没有比较新的安装方法,然后找了个旧的方法对应试试: 🚀 本文将手把手教你在 Docker 环境中部署 Elasticsearch 7.10.1 IK分词器 拼音插件 Kibana,适配中文搜索…...
第14节 Node.js 全局对象
JavaScript 中有一个特殊的对象,称为全局对象(Global Object),它及其所有属性都可以在程序的任何地方访问,即全局变量。 在浏览器 JavaScript 中,通常 window 是全局对象, 而 Node.js 中的全局…...

构建Docker镜像的Dockerfile文件详解
文章目录 前言Dockerfile 案例docker build1. 基本构建2. 指定 Dockerfile 路径3. 设置构建时变量4. 不使用缓存5. 删除中间容器6. 拉取最新基础镜像7. 静默输出完整示例 docker runDockerFile 入门syntax指定构造器FROM基础镜像RUN命令注释COPY复制ENV设置环境变量EXPOSE暴露端…...
Shell 解释器 bash 和 dash 区别
bash 和 dash 都是 Unix/Linux 系统中的 Shell 解释器,但它们在功能、语法和性能上有显著区别。以下是它们的详细对比: 1. 基本区别 特性bash (Bourne-Again SHell)dash (Debian Almquist SHell)来源G…...

从0开始学习R语言--Day17--Cox回归
Cox回归 在用医疗数据作分析时,最常见的是去预测某类病的患者的死亡率或预测他们的结局。但是我们得到的病人数据,往往会有很多的协变量,即使我们通过计算来减少指标对结果的影响,我们的数据中依然会有很多的协变量,且…...

ABAP设计模式之---“Tell, Don’t Ask原则”
“Tell, Don’t Ask”是一种重要的面向对象编程设计原则,它强调的是对象之间如何有效地交流和协作。 1. 什么是 Tell, Don’t Ask 原则? 这个原则的核心思想是: “告诉一个对象该做什么,而不是询问一个对象的状态再对它作出决策。…...
Oracle实用参考(13)——Oracle for Linux物理DG环境搭建(2)
13.2. Oracle for Linux物理DG环境搭建 Oracle 数据库的DataGuard技术方案,业界也称为DG,其在数据库高可用、容灾及负载分离等方面,都有着非常广泛的应用,对此,前面相关章节已做过较为详尽的讲解,此处不再赘述。 需要说明的是, DG方案又分为物理DG和逻辑DG,两者的搭建…...
CentOS 7.9安装Nginx1.24.0时报 checking for LuaJIT 2.x ... not found
Nginx1.24编译时,报LuaJIT2.x错误, configuring additional modules adding module in /www/server/nginx/src/ngx_devel_kit ngx_devel_kit was configured adding module in /www/server/nginx/src/lua_nginx_module checking for LuaJIT 2.x ... not…...
IP选择注意事项
IP选择注意事项 MTP、FTP、EFUSE、EMEMORY选择时,需要考虑以下参数,然后确定后选择IP。 容量工作电压范围温度范围擦除、烧写速度/耗时读取所有bit的时间待机功耗擦写、烧写功耗面积所需要的mask layer...

虚拟机网络不通的问题(这里以win10的问题为主,模式NAT)
当我们网关配置好了,DNS也配置好了,最后在虚拟机里还是无法访问百度的网址。 第一种情况: 我们先考虑一下,网关的IP是否和虚拟机编辑器里的IP一样不,如果不一样需要更改一下,因为我们访问百度需要从物理机…...

SOC-ESP32S3部分:30-I2S音频-麦克风扬声器驱动
飞书文档https://x509p6c8to.feishu.cn/wiki/SKZzwIRH3i7lsckUOlzcuJsdnVf I2S简介 I2S(Inter-Integrated Circuit Sound)是一种用于传输数字音频数据的通信协议,广泛应用于音频设备中。 ESP32-S3 包含 2 个 I2S 外设,通过配置…...
比较数据迁移后MySQL数据库和ClickHouse数据仓库中的表
设计一个MySQL数据库和Clickhouse数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

break 语句和 continue 语句
break语句和continue语句都具有跳转作用,可以让代码不按既有的顺序执行 break break语句用于跳出代码块或循环 1 2 3 4 5 6 for (var i 0; i < 5; i) { if (i 3){ break; } console.log(i); } continue continue语句用于立即终…...
使用 uv 工具快速部署并管理 vLLM 推理环境
uv:现代 Python 项目管理的高效助手 uv:Rust 驱动的 Python 包管理新时代 在部署大语言模型(LLM)推理服务时,vLLM 是一个备受关注的方案,具备高吞吐、低延迟和对 OpenAI API 的良好兼容性。为了提高部署效…...