H5播放之Rtsp转Websocket点播录像抓拍

2023-11-17

H5播放之Rtsp转Websocket点播录像抓拍

广大网友们,很久没上CSDN了,暨上次RTSP转HLS文章发布以来,一直还有一个问题没有解决:如何避免HLS切片带来的不可避免的高延时

HLS的延时

广大网友们,很久没上CSDN了,暨上次RTSP转HLS文章发布以来,一直还有一个问题没有解决:如何避免HLS切片带来的不可避免的高延时

HLS因为协议本身问题,所以会带来较高的延时,延时引起原因可能包括
(1)HLS的TS切片必须要等到关键帧才能切下一个片,导致TS跨度大延时大。
(2)不同厂家的GOP图片分组序列不一样,如海康为50,导致切片较大,延时大。
(3)HLS协议实际是浏览器定期下载TS文件,定期间隔为1s左右,导致下载到播放产生延时
当然,以上可能是HLS的延时的主要部分,这里不一一列举,HLS的延时一般在10秒左右。

当我们意识到HLS延时无法避免时,就需要想办法去解决对应问题!

秋秋:961179337 wx:lixiang6153

websocket播放

由于之前写过一篇文章:web端视频点播之websocket播放。大家可以自行查看我的博客进行翻阅。不过我之前写的文章是通过Java端实现websocket,通过读取H264文件一帧一帧的将视频数据发送喂给wfs库,然后通过video标签显示出来。
这种实现方式或Demon的弊端是:
(1)Demon只能播放H264文件,无法做实时编解码或者实现难度很大
(2)Java端实现RTSP视频领取转码无法实现
(3)web端只能播放H264格式视频,无法播放H265,而目前大多数摄像机为H265模式

基于以上几点,我们可以看到我之前给的demon仅仅是示例性的了解web端是可以播放实时视频的,也就是存在技术实现可行性!那么如何将web端视频用到实际生产中呢?如果要用到生产环境中就需要解决一下几点问题
(1)支持H264和H265播放
(2)无插件播放,不需要flash
(3)低延时播放,特别是PTZ云台控制对低延时要求非常高
(4)方便录像录制、异常情况报警图片留图抓取
websocket就很好的解决了以上几个问题的(2)(3)(4)和(1)的H264播放问题,如果要播放H265在web端目前还没有开源的库区解码播放(可以通过ecms将c/c++代码转为js代码来实现–这个我之前的文章也有提及,不过这种方式难度太大),这里我用一种简单的方式实现:H265解压编码为H264即可。

实现思路

因为Java在音视频行业的弱势,所以还是需要c++来实现,它解决了rtsp视频拉去、H265解码、H264编码、websocket发送以及http协议提供等若干问题,Java能做的C++都能做!具体的实现流程如下所示

操作员 媒体服务 摄像机 web端 http添加设备 登录 rtsp协议 解码 编码 请求视频 H264帧 渲染 录像 抓拍 操作员 媒体服务 摄像机 web端

按照以上思路,我实现的核心步骤
(1)设备的动态添加删除
在这里插入图片描述
(2)视频的接入
在这里插入图片描述
(3)websocket视频点播
我们通过设备id、通道、码流类型(主码流和子码流)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="description" content="jMuxer - a simple javascript mp4 muxer for non-standard streaming communications protocol">
    <meta name="keywords" content="h264 player, mp4 player, mse, mp4 muxing, jmuxer, aac player">
    <title>rtsp转websocket播放</title>
	<style type="text/css">
		#time{
			position: relative;
			display: block;
			top: -100px;
			text-align: center;
			color: red;
			z-index: 1000;
			font-size: 40px;
		}
		button {
			border: 2px solid blue;
			width: 150px;
			height: 35px;
			font-weight: bold;
			font-size: 20px;
		}
	</style>
</head>
<body>
	<div id="container" style="margin: 0 auto;">
		<div>
			<label style="display: inline-block;text-align: left; width: 150px;">websocket视频地址: </label>
			<input type="input" style="width: 640px;height: 25px; color: red; font-size: 16px;" id="url" value="ws://localhost:8090/10/1/1"></input>
		</div>
		<div>
			<video style="border: 1px solid #333; width: 800px;height: 600px;" controls autoplay poster="images/loader-thumb.jpg" id="player">
			</video>
			<canvas style="display:none" id="canvas"></canvas>
			<label id="time"></label>
		</div>
		<div>
			<button onclick="record()">开始录制</button>
			<button onclick="record()">停止录制</button>
			<button onclick="capture()">视频抓拍</button>
		</div>
	</div>
	<script>
	var jmuxer;
	window.onload = function() {
		var socketURL = document.getElementById("url").value;
		if(socketURL === "") {
			alert("请输入websocket视频播放地址");
			return;
		}
		jmuxer = new JMuxer({
			node: 'player',
			mode: 'video',
			flushingTime: 1000,
			fps: 25,
			debug: true,
			onError: function(data) {
				if (/Safari/.test(navigator.userAgent) && /Apple Computer/.test(navigator.vendor)) {
					jmuxer.reset();
				}
			}
		 });

		 var ws = new WebSocket(socketURL);
		 ws.binaryType = 'arraybuffer';
		 ws.addEventListener('message',function(event) {
			  jmuxer.feed({
				video: new Uint8Array(event.data)
			  });
		 });

		 ws.addEventListener('error', function(e) {
			console.log('Socket Error');
		 });
		 
		 record = function(){
			window.screen_record.startRecord("canvas", "player");
			if(window.screen_record.isRecord()) {
				window.setInterval(()=>{
					document.getElementById("time").innerText = window.screen_record.getVideoTime();
				}, 1000);
			}
		 }
		 
		 capture = function(){
			window.screen_record.captureImage("player")
		 }
		 
		 window.screen_record = new ScreenRecord();
	 }
	</script>
	<script type="text/javascript" src="jmuxer.min.js"></script>
	<script type="text/javascript" src="screenRecord.js"></script>
</body>
</html>

这里采用的是JMuxer播放,没有使用wfs库播放,原因是经过测试JMuxer性能和播放效果要优于wfs库,而且JMuxer支持帧率控制,播放不会卡顿,而wfs则偶尔会卡顿!

(4)服务端线性平稳发送
服务端根据帧率编码并线性发送h264视频帧到web端

#include "stdafx.h"
#include "WsClient.h"
#include "TextUtil.h"

CWsClient::CWsClient(client_connc conc, conc_hdl client, std::string device, int channel, int stream)
	: m_server(conc)
	, m_client(client)
	, m_device(device)
	, m_channel(channel)
	, m_stream(stream)
	, m_type(CLIENT_TYPE_RTSP)
	, m_stop(false)
	, m_play(false)
{
	m_buffer.AllocateBuffer(50, 1024);
	m_manager = websocketpp::lib::make_shared<con_msg_man_type>();
}

CWsClient::~CWsClient()
{
	Stop();
}

conc_hdl CWsClient::GetId()
{
	return m_client;
}

std::string CWsClient::GetDeviceId()
{
	return m_device;
}

int CWsClient::GetChannel()
{
	return m_channel;
}

void CWsClient::SetChannel(int channel)
{
	m_channel = channel;
}

void CWsClient::SetPlay(bool value)
{
	m_play = value;
}

bool CWsClient::IsPlay()
{
	return m_play;
}

int CWsClient::GetStream()
{
	return m_stream;
}

int CWsClient::GetType()
{
	return m_type;
}

void CWsClient::Print()
{
	ATXTRACE0("设备:%s,码流:%d", m_device.c_str(), m_type);
}

bool CWsClient::Start()
{
	// 启动文件写线程
	m_stop = false;
	m_thread = boost::thread(&CWsClient::Write, this);
	return true;
}

void CWsClient::Stop()
{
	// 停止写线程
	m_stop = true;
	if (m_thread.joinable())
	{
		m_thread.join();
	}
}

void CWsClient::Notify(bool video, unsigned char* data, int length, bool keyFrame, unsigned __int64 time, unsigned __int64 duration)
{
	BufferPtr buffer = m_buffer.GetEmptyBuffer();
	if (buffer)
	{
		buffer->m_reserver.push_back(video);
		buffer->m_reserver.push_back(time);
		buffer->m_reserver.push_back(duration);
		buffer->FillData(data, length);
		m_buffer.AddFullBuffer(buffer);
	}
}

void CWsClient::Send(unsigned char* data, int length)
{
	// 创建client::message_ptr对象,容量初始大小为1024(可随便设置),用于设置数据帧的基本参数
	client::message_ptr msg = m_manager->get_message(websocketpp::frame::opcode::BINARY, length);
	// 是否最后一帧数据:true:最后一帧数据,false:还有下一帧数据
	msg->set_fin(true);
	// 数据格式是二进制,  CONTINUATION=0x0,TEXT=0x1,BINARY=0x2
	msg->set_opcode(websocketpp::frame::opcode::value::BINARY);
	// 发送数据的内容
	msg->set_payload(data, length);
	// 调用发送接口,发送数据帧
	m_server->send(msg);
}

void CWsClient::Write()
{
	int sleep_time = 31;
	__int64 previouse = GetTickCount64(), send_frame = 0;
	while (!m_stop)
	{
		// 平稳发送
		__int64 current = GetTickCount64();
		__int64 gap = current - previouse;

		// 当前应当发送数量与实际发送数量
		__int64 count = (gap / sleep_time);
		if (count <= send_frame)
		{
			boost::this_thread::sleep_for(boost::chrono::microseconds(1));
			continue;
		}

		// 获取一帧数据
		BufferPtr buffer = m_buffer.GetFullBuffer();
		if (!buffer)
		{
			boost::this_thread::sleep_for(boost::chrono::microseconds(1));
			continue;
		}

		// 上一次发送:平稳发送		
		send_frame += 1;

		// 平稳发送数据
		this->Send(buffer->m_pData, buffer->m_nDataSize);

		// 循环利用缓冲区
		m_buffer.AddEmptyBuffer(buffer);
	}
}

这里的核心代码保证了web端的平稳播放
(1)使用了环形双缓冲区
(2)使用了按时间增量线性发送视频帧

(5)最后献上视频播放效果
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

H5播放之Rtsp转Websocket点播录像抓拍 的相关文章

随机推荐

  • SQL操作

    一 查询语句 1 基本查询 SELECT FROM lt 表名 gt 查询表的所有行 SELECT FROM students 2 条件查询 SELECT FROM lt 表名 gt WHERE lt 条件表达式 gt 查询分数在80分以上
  • vscode+phpstudy连接使用mysql(解决phpstudy中mysql无法启动的问题)

    vscode phpstudy连接使用mysql 解决phpstudy中mysql无法启动的问题 使用vscode phpstudy配置php开发环境网上很文章都是挺好的 都成功解决了我的问题 但是对于使用mysql方面始终找不到很系统的文
  • 数据结构系列——先进先出队列queue

    本期主题 先进先出队列实现 目录 1 队列定义 2 实现一个简单的队列以及分析 1 代码实现分析 2 code 3 优缺点分析 3 循环队列实现 1 循环队列原理 2 循环队列实现分析 3 code 1 队列定义 队列是什么 定义 一个先进
  • unity,网格碰撞器(Mesh Collider)

    介绍 网格碰撞器 Mesh Collider 在实现物理碰撞检测时 可以自动检测凸面 但是它并不总是能够准确地生成凸多面体 这是因为在将一个网格模型转换为凸多面体的过程中 可能会出现模型内部空洞或者交叉的情况 这些情况会导致凸多面体的生成失
  • H5页面长按识别二维码

    vue 写的H5 内嵌在小程序上 img src 图片路径 style width 200px height 200px 直接在微信访问长按就可以实现 微信原生直接写长按识别二维码 aaa e let img e target datase
  • 八. springboot 的指标监控 (3、定制 Endpoint )

    3 定制 Endpoint 3 1 定制 Health 信息 import org springframework boot actuate health Health import org springframework boot act
  • http、https以及状态码超全详解

    文章目录 HTTP 概念 作用 http 请求 http 头部 http 连接 各版本的区别 http1 0 http1 1 http2 0 http3 0 HTTPS 概念 作用 工作原理 优点 缺点 http 和 https 区别 ht
  • [网络安全自学篇] 八.Web漏洞及端口扫描之Nmap、ThreatScan和DirBuster原理详解

    Web渗透技术的核心是发现Web漏洞 发现漏洞有手工和软件自动化扫描两种方式 对于用户验证漏洞 用户凭证管理问题 权限特权及访问控制漏洞 缓存漏洞 跨站脚本漏洞 加密漏洞 路径切换漏洞 代码注入漏洞 配置漏洞 数据和信息泄露 输入验证码漏洞
  • CA证书服务搭建

    准备一 域服务搭建 步骤一 搭建CA证书服务器 设置固定IP地址 192 168 0 与主机名 DC 打开服务器管理器 gt 点击添加角色和功能 步骤二 一路回车到达选择服务器角色位置 选中Active Directory域服务与Activ
  • 动态库装载及 dlsym的RTLD_NEXT参数详解

    在看公司spp框架代码的时候发现了如下一段宏定义 其中的dlsym函数及其RTLD NEXT参数的含义不是很明白 于是网上搜了下这里做个记录 define mt hook syscall name do if g mt syscall ta
  • 扔掉工具类!MyBatis 一个简单配置搞定加密、解密,好用!

    程序员的成长之路 互联网 程序员 技术 资料共享 关注 阅读本文大概需要 3 5 分钟 来自 https juejin cn post 6963811586184052767 目录 一 背景 二 解决方案 三 需求 四 实现思路 五 实现代
  • spring cloud alibaba 学习(二十五)nacos 服务地址动态更新

    目录 前言 服务地址动态更新 前言 通过配置endpoint来实现服务地址的动态更新 服务地址动态更新 1 在 NacosConfigBootstrapConfiguration 创建 NacosConfigManager 的过程中 会创建
  • Unity PlayerPrefs、JsonUtility

    Unity中有两个常用的数据存储方式 PlayerPrefs和JsonUtility PlayerPrefs PlayerPrefs是Unity内置的一种轻量级数据存储方式 可用于存储少量的游戏数据 如分数 解锁状态等 使用PlayerPr
  • LPDDR4协议规范之 (六)刷新

    LPDDR4协议规范之 六 刷新 刷新命令 刷新计数器 刷新时序 刷新前时序 刷新后时序 全存储体刷新时序 tRFCab tRFCpb 自刷新 自刷新期间进入掉电模式 自刷新中止 刷新命令 REFRESH命令在时钟的第一个上升沿以CS HI
  • GDI+ 中 Pen 使用总结

    背景 图形类 Graphics 是 GDI 的核心 它提供绘制图形 图像和文本的各种方法 Graphics 中使用 DrawString 方法在指定位置绘制文本或者在一个指定矩形内绘制文本 所有的 Graphics 类的绘制方法都得配合 P
  • MXNet简介

    MXNet是一个十分优秀的深度学习框架 目前包含了许多语言接口 如Python C Scala R等 目前 MXNet版本已经更新到1 3 0 本系列文章主要使用Python接口 在MXNet官网 1 上 官方建议新手使用Python接口
  • 网站代理是什么?有什么需要注意的?

    如今 网站代理已经成为一种不可或缺的经营方式 无论是企业还是个人 都需要通过代理来获得更多的流量和市场份额 一 网站代理的优势 网站代理的优势在于能够为您提供更加专业 周到的服务 这些优势包括 1 丰富的内容资源 能够满足客户对不同领域信息
  • Java-按照指定小时分割时间段

    按照指定小时分割时间段 param dateType 类型 M D H N gt 每月 每天 每小时 每分钟 param dBegin开始时间 param dEnd结束时间 param time 指定小时 如 1 2 3 4 return
  • 变分推断学习

    https zhuanlan zhihu com p 401456634 变分推断 1 变分推断的背景 在机器学习中 有很多求后验概率的问题 求后验概率的过程被称为推断 Inference 推断分为精确推断和近似推断 精确推断一般主要是根据
  • H5播放之Rtsp转Websocket点播录像抓拍

    H5播放之Rtsp转Websocket点播录像抓拍 HLS的延时 websocket播放 实现思路 广大网友们 很久没上CSDN了 暨上次RTSP转HLS文章发布以来 一直还有一个问题没有解决 如何避免HLS切片带来的不可避免的高延时 HL