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

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)最后献上视频播放效果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/lixiang987654321/article/details/128501117
今日推荐