- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
本文是《国标GB28181协议设备端开发》系列的第四篇,介绍了实时视频数据传输的过程。通过解读INVITE报文中的SDP信息,读取和解析视频文件或图片文件,进行数据编码,以及h264封装为PS格式,最终通过RTP数据发送,实现了GB28181协议设备端的视频传输功能。本文将逐步详细介绍每个模块的实现步骤和相关技术要点,帮助读者理解和应用GB28181协议进行实时视频传输.
在GB28181协议中,在实时音视频传输过程中,使用INVITE报文携带SDP(Session Description Protocol)信息。SDP信息描述了会话的属性和参数,包括媒体类型、传输协议、编解码器、网络地址等。下面是一个示例INVITE报文的SDP内容,并对其中的每一项进行详细解释:
v=0
o=34020000002000000001 0 0 IN IP4 192.168.1.10
s=Play
c=IN IP4 192.168.1.10
t=0 0
m=video 40052 RTP/AVP 96
a=recvonly
a=rtpmap:96 PS/90000
y=0358902090
f=
v=0 。
表示SDP协议版本号,此处为0.
o=34020000002000000001 0 0 IN 192.168.1.10 。
o字段标识了会话的发起者和会话的唯一标识。 "34020000002000000001" 表示该会话会话发起者的SIP ID。 0 0 表示会话的起始和结束时间戳。 IN IP4 192.168.1.10 表示会话的网络地址,这里为IPv4地址.
s字段为会话的名称或描述,此处为"Play"表面是实时音视频 。
c=IN IP4 192.168.1.10 。
c字段指定了会话的连接信息。 IN 表示网络类型为Internet。 IP4 192.168.1.10 表示会话的IPv4地址.
t=0 0 。
t字段指定了会话的时间信息。 0 0 表示会话的起始和结束时间都为0,即持续时间未定义.
m=video 40052 RTP/AVP 96 。
m字段定义了会话中的媒体类型和相关参数。 video 表示媒体类型为视频。 40052 表示媒体流的传输端口号。 RTP/AVP 表示传输协议为RTP,使用AVP(Audio-Visual Profile)配置。 96 表示媒体流使用编号96表示.
a=rtpmap:96 PS/90000 。
a字段包含了媒体流的属性。 rtpmap:96 表示将编号为96的负载类型。 PS 表示使用MPEG-PS格式进行数据封装。 90000 表示时钟速率,即每秒的时钟滴答数.
y=0358902090 。
y字段为十进制整数字符串,表示SSRC值 。
f= 。
f字段:f= v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率 这里并没有设置f字段,由数据发送端来填充 。
为了进行视频数据传输,我们首先需要读取和解析视频文件或图片文件。我们需要使用相应的库或工具,从文件中读取视频或图片数据,并进行解析,以获取关键的视频帧或图像数据,为后续的编码和封装做准备.
在GB28181协议中,视频数据通常以MPEG-PS(MPEG Program Stream)格式进行封装。需要将经过编码的视频数据进行PS格式的封装,包括添加包装头和起始码,然后再进一步封装RTP.
以下是使用C++将H.264的NALU封装为MPEG-PS格式的主要过程(仅展示部分代码):
// 将H.264的NALU列表封装为MPEG-PS格式
void MakeMPEGPS(unsigned char* h264Data, int h264Length,
unsigned char* psData)
{
int totalPES = (h264Length + MAX_PES_LENGTH - 1) / MAX_PES_LENGTH; // 计算总的PES包数
int remainingBytes = h264Length; // 剩余待处理的字节数
// MPEG-PS包头
unsigned char mpegPSHeader[] = {0x00, 0x00, 0x01, 0xBA};
// 分割并封装H.264数据
for (int i = 0; i < totalPES; i++)
{
unsigned char* pbuf = psData;
int pesLength = (remainingBytes > MAX_PES_LENGTH) ? MAX_PES_LENGTH : remainingBytes; // 当前PES包的长度
remainingBytes -= pesLength; // 更新剩余待处理的字节数
// PES包头
unsigned char pesHeader[] = {0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x80, 0x00};
// 设置PES包长度
pesHeader[4] = (pesLength + 8) >> 8; // 高8位
pesHeader[5] = (pesLength + 8) & 0xFF; // 低8位
// 输出MPEG-PS包头和当前PES包头
memcpy(pbuf, mpegPSHeader, sizeof(mpegPSHeader));
pbuf += sizeof(mpegPSHeader);
memcpy(pbuf, pesHeader, sizeof(pesHeader));
pbuf += sizeof(pesHeader);
// 输出当前PES包的H.264数据
memcpy(pbuf, h264Data + (i * MAX_PES_LENGTH), pesLength);
pbuf += pesLength;
int payload_len = (pbuf - psData);
// 封装RTP包并发送
MakeAndSendRTP(psData, payload_len);
}
}
需要注意到,当h264帧比较大的时候,会超出PES可表述的长度大小,这个时候必须对h264帧进行切分,封装成多个PES,再合成到PS包中.
RTP数据发送的逻辑比较简单,以下为程序中的代码示意图 。
以下为RTP封装的演示代码(仅展示部分代码):
struct RTPHeader
{
uint8_t version; // RTP协议版本号,固定为2
uint8_t padding: 1; // 填充位
uint8_t extension: 1; // 扩展位
uint8_t csrcCount: 4; // CSRC计数器,指示CSRC标识符的个数
uint8_t marker: 1; // 标记位
uint8_t payloadType: 7; // 负载类型
uint16_t sequenceNumber; // 序列号
uint32_t timestamp; // 时间戳
uint32_t ssrc; // 同步信源标识符
};
void MakeRTPHeader(struct RTPHeader* header, uint16_t sequenceNumber, uint32_t timestamp, uint32_t ssrc, bool isMark)
{
// 设置RTP协议版本号为2
header->version = 2;
// 填充位、扩展位、CSRC计数器等字段根据具体需求进行设置
header->padding = 0;
header->extension = 0;
header->csrcCount = 0;
// 设置标记位为0(如果需要设置为1,则在需要设置的地方进行修改)
header->marker = isMark ? 1 : 0;
// 设置负载类型(payload type),根据具体需求进行设置
header->payloadType = 96;
// 设置序列号和时间戳
header->sequenceNumber = htons(sequenceNumber); // 需要进行字节序转换(网络字节序)
header->timestamp = htonl(timestamp); // 需要进行字节序转换(网络字节序)
// 设置同步信源标识符
header->ssrc = htonl(ssrc); // 需要进行字节序转换(网络字节序)
}
void sendRTPPacket(const uint8_t* mpegPSData, int mpegPSLength, uint16_t sequenceNumber, uint32_t timestamp, uint32_t ssrc)
{
int offset = 0; // 偏移量,用于遍历MPEG-PS包数据
int remainingLength = mpegPSLength; // 剩余长度,用于判断是否需要分割RTP报文
uint8_t rtpbuf[RTP_PAYLOAD_MAX_SIZE]; // RTP负载数据缓冲区
struct RTPHeader rtpHeader; // RTP报文头部
while (remainingLength > 0)
{
// 计算当前RTP负载数据长度(不超过RTP负载最大大小)
bool is_mark = false;
int data_len = RTP_PAYLOAD_MAX_SIZE;
if (remainingLength <= RTP_PAYLOAD_MAX_SIZE)
{
data_len = remainingLength;
is_mark = true;
}
// 填写RTP报文头部
MakeRTPHeader(&rtpHeader, sequenceNumber, timestamp, ssrc);
// 复制RTP头部到RTP负载缓冲区
memcpy(rtpbuf, &rtpHeader, sizeof(RTPHeader));
// 复制MPEG-PS数据到RTP负载缓冲区
memcpy(rtpbuf + RTP_HEADER_LEN, mpegPSData + offset, data_len);
// 将完整RTP包发送出去
if (udp_channel_)
{
udp_channel_->PostSendBuf(rtpbuf, RTP_HEADER_LEN + data_len);
}
// 更新偏移量、剩余长度、序列号等信息
offset += data_len;
remainingLength -= data_len;
sequenceNumber++;
}
}
合作请加WX:hbstream 合作请加作者hbstream( http://haibindev.cnblogs.com ),转载请注明作者和出处 。
最后此篇关于国标GB28181协议客户端开发(四)实时视频数据传输的文章就讲到这里了,如果你想了解更多关于国标GB28181协议客户端开发(四)实时视频数据传输的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
internal protocol Reducer { associatedtype S : BaseState associatedtype A : BaseActi
我在考虑我的应用程序中的验证检查,我认为在任何模型上调用 ValidatorFactory,实现 Validee,这意味着说哪个类负责 ValidatorCreation 听起来不错。但是下面的代码不
我已经定义了 2 个协议(protocol)。我需要第一个 (NameProtocol) 来执行 Equatable 协议(protocol)。而另一个类 (BuilderProtocol) 有一个返
在上传方面,WebDAV 协议(protocol)在哪些方面优于 HTTP 协议(protocol)。 Socket Upload 协议(protocol)和 WebDav Upload 协议(pro
是否可以在任何版本的 Swift 中扩展具有混合类/协议(protocol)类型约束的协议(protocol)?例如,仅当 Self 是 UIViewController 的子类并且符合 Protoc
我有一个协议(protocol) (ProtocolA),其中包含符合第二个协议(protocol) (ProtocolB) 的单个属性。 public protocol ProtocolA {
NSObject 协议(protocol)带有常用的协议(protocol)模板,但它似乎并不是协议(protocol)实际实现所必需的。将其排除在外似乎完全没有任何改变。那么,协议(protocol
我想根据这两种协议(protocol)的一般特征(例如开销(数据包)、安全性、信息建模和可靠性)来比较 OPC UA 和 MQTT。我在哪里可以找到每个协议(protocol)的开销和其他特性的一些示
使用 Swift 4,我正在尝试编写一个自定义协议(protocol),它提供对 @objc 协议(protocol)的一致性。 一些代码 更具体地说,我有一个自定义协议(protocol) Sear
我想定义一个在 Viper 架构中使用的协议(protocol),以使用具有弱属性的协议(protocol)在 Viper 组件之间建立连接,但我收到以下错误消息: 'weak' may only b
我在同一个网络中有 3 个 docker 容器: 存储 (golang) - 它提供了用于上传视频文件的 API。 主播 (nginx) - 它流式传输上传的文件 反向代理 (姑且称之为代理) 我有
我打算在我的项目中使用 php socket。它需要用户登录才能根据 session 填充内容。所以我的问题是,TCP/IP 协议(protocol)也像 HTTP 协议(protocol)一样为每个
目前,我的网站有两个版本。一种带有 https://-证书,一种没有。我想将我网站的 http 版本上的所有用户 301 重定向到我网站的 https://版本。 这似乎不可能,因为创建重定向将导致重
目前,我的网站有两个版本。一种带有 https://-证书,一种没有。我想将我网站的 http 版本上的所有用户 301 重定向到我网站的 https://版本。 这似乎不可能,因为创建重定向将导致重
我有一个 Swift View Controller ,它定义了一个在 Objective-C View Controller 中应该遵循的协议(protocol): ChildViewControl
我在客户那里有数百个硬件设备,需要通过telnet接口(interface)发送HTTP数据。 目标是等待数据的 Apache 2 Web 服务器和 PHP 脚本。 这已经可以正常工作了,但是我们发现
我发现如果我创建一个这样的协议(protocol): protocol MyProtocol { } 我不能这样做: weak var myVar: MyProtocol? 我找到了解决这个问题的方法
Xcode 基于模板生成了这个头文件: // this file is XYZAppDelegate.h #import @interface XYZAppDelegate : UIRespond
我在 github 中有一个公开的存储库,我正在开发一个开源应用程序,用于制作产品目录和小型 cms 内容。 我还有一个私有(private)仓库(不托管在github),它是在托管在github的开
您好,我想让别人看到私有(private) repo 代码,但不想公开我的 repo ,也不希望他们有能力更改内容。这可能吗?我查看了网站的“管理”部分,但没有找到合适的内容。谢谢大家。 最佳答案 据
我是一名优秀的程序员,十分优秀!