广大网友们,很久没上CSDN了,暨上次RTSP转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
由于之前写过一篇文章: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++都能做!具体的实现流程如下所示
按照以上思路,我实现的核心步骤
(1)设备的动态添加删除
(2)视频的接入
(3)websocket视频点播
我们通过设备id、通道、码流类型(主码流和子码流)
rtsp转websocket播放
这里采用的是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();
}
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)最后献上视频播放效果