GB28181系列文章:
总述:https://blog.csdn.net/www_dong/article/details/132515446
注册与注销:https://blog.csdn.net/www_dong/article/details/132654525
心跳保活:https://blog.csdn.net/www_dong/article/details/132796612
网络设备信息查询:https://blog.csdn.net/www_dong/article/details/132912085
视音频点播(信令传输部分):https://blog.csdn.net/www_dong/article/details/132950064
媒体服务器
提供媒体流的转发、媒体存储、历史媒体信息的检索和点播服务的服务器。
设计方案
- jrtplib+jthread:监听、视频数据接收与转发;
- libmpeg:对ps流解复用;
- ffmpeg:数据解码;
- Qt(QOpenGLWidget):视频播放;
数据接收
jrtplib
jrtplib是一个面向对象的RTP封装库。
特点:
- 该库使用户能够发送和接收数据使用RTP,无需担心SSRC冲突、调度和传输RTCP数据等。用户只需提供库通过发送有效负载数据,库为用户提供访问权限输入RTP和RTCP数据;
- 该库提供了几个类,这些类有助于创建RTP应用程序。大多数用户可能只需要RTPSession类来构建应用程序,或者从RTPSecureSession派生一个类来支持SRTP。这些类提供了发送RTP数据的必要功能,并在内部处理RTCP部分;
jthread
jrtplib的使用依赖于jthread,使用方式用两种:
- 用 jthread 库提供的线程自动在后台执行对数据的接收;
- 用户自己调用 RTPSession 中的 Poll 方法;
下载
下载地址:https://research.edm.uhasselt.be/jori/page/Cs/JrtplibOld.html 。
该项目目前使用的是jrtplib-3.11.2.zip+jthread-1.3.3.zip。下载完成后使用cmake生成windows下.sln工程编译生成静态库使用。
流程
- 数据接收流程,该流程目前在线程中处理。
uint8_t payload;
while (m_running)
{
Poll();
BeginDataAccess();
if (GotoFirstSourceWithData())
{
do
{
RTPPacket* packet = nullptr;
while (nullptr != (packet = GetNextPacket()))
{
payload = packet->GetPayloadType();
if (0 == payload)
{
DeletePacket(packet);
continue;
}
// ...
// rtp载荷数据处理流程
DeletePacket(packet);
}
} while (GotoNextSourceWithData());
}
EndDataAccess();
Sleep(30);
}
Destroy();
数据解复用
该流程使用的是libmpeg库,下载地址:https://github.com/ireader/media-server.git
由于国标流是ps封装,故需要将ps流解复用获取原始数据。
解复用代码流程:
static void* Alloc(void* /*param*/, size_t bytes)
{
return malloc(bytes);
}
static void Free(void* /*param*/, void* packet)
{
free(packet);
}
static int Write(void* param, int avtype, void* pes, size_t bytes)
{
assert(param);
CPSParse* parse = (CPSParse*)param;
return parse->Package(avtype, pes, bytes);
}
CPSParse::CPSParse()
{
struct ps_muxer_func_t func;
func.alloc = Alloc;
func.free = Free;
func.write = Write;
m_ps = ps_muxer_create(&func, this);
m_ps_stream = ps_muxer_add_stream(m_ps, STREAM_VIDEO_H264, nullptr, 0);
}
CPSParse::~CPSParse()
{
if (m_ps)
ps_muxer_destroy(m_ps);
}
int CPSParse::InputData(void* data, int len)
{
if (nullptr == m_ps || nullptr == data || len <= 0)
return -1;
uint64_t clock = time64_now();
if (0 == m_ps_clock)
m_ps_clock = clock;
ps_muxer_input(m_ps, m_ps_stream, 0, (clock - m_ps_clock) * 90, (clock - m_ps_clock) * 90, data, len);
return 0;
}
int CPSParse::Package(int avtype, void* payload, size_t bytes)
{
// 数据处理
return 0;
}
数据解码
通过对ps数据流解复用获取原始数据,本项目通过ffmpeg对原始数据进行解码获取yuv数据。
关于ffmpeg的使用,可以查看:
音视频播放器设计(一)——环境配置:https://blog.csdn.net/www_dong/article/details/124459467
音视频播放器设计(二)——ffmpeg视频处理流程:https://blog.csdn.net/www_dong/article/details/124561444
音视频播放器设计(三)——OpenGL绘制视频:https://blog.csdn.net/www_dong/article/details/124638515
音视频播放器设计(四)——ffmpeg音频处理流程:https://blog.csdn.net/www_dong/article/details/125323635
视频播放
通过ffmpeg解码获取yuv数据,本项目通过qt的QOpenGLWidget对yuv数据进行渲染达到播放的目的。
- 使用方法:
- 新建一个PlayWidget类继承于QOpenGLWidget;
- 将ffmpeg解码后的yuv数据传入PlayWidget类,保存并调用update函数;
- update()被调用后,QOpenGLWidget::paintGL()会自动被调用,在里面进行显示;
- 使用QOpenGLFunctions::glTexImage2D传入一帧数据进行显示;
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
class PlayWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
public:
PlayWidget(QWidget *parent = nullptr);
virtual ~PlayWidget();
// ...
};