如何实现H.264的RTP封装及传输?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何实现H.264的RTP封装及传输?相关的知识,希望对你有一定的参考价值。

参考技术A

RTP(Real-time Transport Protocol)实时传输协议,由IETF的多媒体传输工作小组发布的网络传输协议,标准为RFC3550/3551。RTP协议支持TCP和UDP两种传输方式,RTP协议负责对流媒体数据进行封包并实现媒体流的实时传输,但并不能为按顺序传送的数据包提供可靠的传送机制,也不提供流量和拥塞控制,这些是依靠RTCP协议来完成的,两者配合使用。本文主要从数据处理的角度实现对H.264的RTP封装进行详细介绍。

RTP协议是由RTP Header和RTP Payload两部分组成的,具体如下图所示:

1、RTP Header

RTP头部前12个字节的含义是固定的,具体的含义如下所示:
V:RTP协议的版本号,占2位,当前协议版本号为2。
P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
X:扩展标志,占1位,如果X=1,则在RTP报头后有一个扩展头。
CC:CSRC计数器,占4位,指示CSRC 标志符的个数。
M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
PT: 有效荷载类型,占7位,用于说明RTP报文中有效载荷的类型。
sequence number:序列号,占16位,用于表示发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。这个字段当下层的承载协议用UDP的时候,网络状况不好的时候可以用来检查丢包。同时出现网络抖动的情况可以用来对数据进行重新排序,序列号的初始值是随机的,同时音频包和视频包的sequence是分别计数的。
timestamp:相对时间戳,占32位,反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。
SSRC:同步信源标识符,占32位,用于标志同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。
CSRC:特约信源标志符,每个CSRC标识符占32位,可以有0~15个。每个CSRC表示了包含在该RTP报文有效载荷中的所有特约信源。

2、RTP Payload

RTP Payload负载有很多形式,可以传输编码的数据如视频H264、H265、音频G711A(U)、AAC等数据,也可以传输封装好的数据,如GB28181中常用的PS流等,本文接下来介绍一下RTP Payload Format for H.264 Video(ES流)的具体实现形式。H264的NALU作为RTP Payload负载,结构类型如下图所示:

rtp payload数据包的形式主要包含以下三种:

(1) 单个NALU包模式

rtp payload 仅由一个完整NALU单元组成,这种情况一般用于H264 NALU单元小于MTU(Maximum Transmission Unit,最大传输单元)时,而且RTP Payload Header 类型字段和原始的 H.264的NALU Header类型字段是一样的,此时仅需要把NALU单元的开始头去掉,加上RTP Header即可。

示例:
一个H264的原始NALU单元如下所示:
【00 00 00 01 67 4d 00 2a 96 35 40 f0 04 4f ......】
这是一个SPS类型的H264 NAL单元,封装为RTP包如下所示:
【RTP Header】【67 4d 00 2a 96 35 40 f0 04 4f ......】

(2)组合包模式

开头1个字节表示NAL单元类型,接着1个或多个聚合单元,后面是可选的RTP填充,组合包模式有以下四种类型:

组合包模式是当H264 NALU单元的长度较小时,将几个NALU单元封装在一个RTP数据包中,以STAP-A为例进行详细介绍。

单时刻聚合包(STAP-A)应该用于当聚合在一起的NAL单元共享相同的NALU时间。STAP-A荷载不包括DON(Decoding Order Number),并且至少包含一个单时刻聚合单元,如下图所示:

single-time aggregation unit的格式如下图所示:

NAL unit size:表示NAL unit的长度,不包含其本身。
NAL unit:H264 NALU单元
一个RTP包含一个STAP-A组合包,一个STAP-A包含两个单时间聚合单元的示例如下图所示:

具体数据示例:
【00 00 00 01 67 42 C0 1F 8C 8D 40 48 14 B2 F0 0F 08 84 6A】
【00 00 00 01 68 CE 3C 80】
将以上SPS、PPS两个H264 NAL单元封装成STAP-A形式如下所示:
【RTP Header】【78 00 0F 67 42 C0 1F 8C 8D 40 48 14 B2 F0 0F 08 84 6A 00 04 68 CE 3C 80 】
78:第一位表示F为,值为0;第二三位表示NRI重要程度;后五位Type,值为24,表示STAP-A类型。
00 0F:NALU 1 size
67:NALU 1 HDR
42 C0 1F 8C 8D 40 48 14 B2 F0 0F 08 84 6A:NALU 1 data
00 04:NALU 2 size
68:NALU 2 HDR
CE 3C 80:NALU 2 data

(3)分包模式

当H264 NALU 的长度超过 MTU 时, 就必须对 NALU 单元进行分片封包. 也称为 Fragmentation Units ,主要包含FU-A和FU-B两种形式,FU-A分包格式如下图所示:

FU indicator的格式如下图所示:

F:禁止位,与NALU Header的F位一致
NRI:重要程度,与NALU Header的NRI一致
Type:分包类型,28表示FU-A类型,29表示FU-B类型
FU header的格式如下图所示:

S:占1bit,值为1,表示NAL分包的开始,其余情况值为0。
E:占1bit,值为1,表示NAL分包的结束,其余情况为0。
R:占1bit,值为0,表示保留位。
Type:占5bit,值为H264 NALU Header中的Type。
具体数据示例:
【00 00 00 01 65 b8 00 01 4d 00 00 ff bc 5d......】
将H264 I帧进行分包,具体的分包多少由I帧的数据长度和设置分包大小决定,示例如下所示:
【RTP Header】【7c 85】【H264 Payload Data】
【RTP Header】【7c 05】【H264 Payload Data】
【RTP Header】【7c 45】【H264 Payload Data】
7c 85:表示分包开始包。
7c 05:表示分包中间包。
7c 45:表示分包结束包。

读取一个H264文件,封装为RTP协议格式进行传输,具体实现如下所示:

1、SPS、PPS采用单包模式发送。
2、I帧、P帧采用FU-A分包模式发送。
3、注意RTP Header中Mark标记位的设置和相对时间戳的设置。

流媒体传输协议---RTP---荷载PS流

转自:https://blog.csdn.net/chen495810242/article/details/39207305

  针对H264 做如下PS 封装:每个IDR NALU 前一般都会包含SPS、PPS 等NALU,因此将SPS、PPS、IDR 的NALU 封装为一个PS 包,包括ps 头,然后加上PS system header,PS system map,PES header+h264 raw data。所以一个IDR NALU PS 包由外到内顺序是:PSheader| PS system header | PS system Map | PES header | h264 raw data。对于其它非关键帧的PS 包,就简单多了,直接加上PS头和PES 头就可以了。顺序为:PS header | PES header | h264raw data。以上是对只有视频video 的情况,如果要把音频Audio也打包进PS 封装,也可以。当有音频数据时,将数据加上PES header 放到视频PES 后就可以了。顺序如下:PS 包=PS头|PES(video)|PES(audio),再用RTP 封装发送就可以了。

        GB28181 对RTP 传输的数据负载类型有规定(参考GB28181 附录B),负载类型中96-127

        RFC2250 建议96 表示PS 封装,建议97 为MPEG-4,建议98 为H264

        即我们接收到的RTP 包首先需要判断负载类型,若负载类型为96,则采用PS 解复用,将音视频分开解码。若负载类型为98,直接按照H264 的解码类型解码。

        注:此方法不一定准确,取决于打包格式是否标准

PS 包中的流类型(stream type)的取值如下:

1)        MPEG-4 视频流: 0x10;

2)        H.264 视频流: 0x1B;

3)        SVAC 视频流: 0x80;

4)        G.711 音频流: 0x90;

5)        G.722.1 音频流: 0x92;

6)        G.723.1 音频流: 0x93;

7)        G.729 音频流: 0x99;

8)       SVAC音频流: 0x9B。
3.1、PS包头

技术分享图片

 


                                                 图7

1)        Pack start code:包起始码字段,值为0x000001BA的位串,用来标志一个包的开始

2)        System clock reference base,system clock reference extenstion:系统时钟参考字段

3)        Pack stuffing length :包填充长度字段,3 位整数,规定该字段后填充字节的个数

80 60 53 1f 00 94 89 00 00 0000 00 00 00 01 ba €`S..??........?

7e ff 3e fb 44 01 00 5f 6b f8 00 00 01 e0 14 53 ~.>?D.._k?...?.S

80 80 05 2f bf cf bed1 1c 42 56 7b 13 58 0a 1e €€./????.BV{.X..

08 b1 4f 33 69 35 0453 6d 33 a8 04 15 58 d9 21 .?O3i5.Sm3?..X?!

9741 b9 f1 75 3d 94 2b 1f bc 0b b2 b4 97 bf 93 ?A??u=?+.?.?????
前12位是RTP Header,这里不再赘述;

000001ba是包头起始码;

接下来的9位包括了SCR,SCRE,MUXRate,具体看图7

最后一位是保留位(0xf8),定义了是否有扩展,二进制如下

1111 1000

前5位跳过,后3位指示了扩展长度,这里是0.

3.2、系统标题

技术分享图片

 

                                                           图8
Systemheader当且仅当pack是第一个数据包时才存在,即PS包头之后就是系统标题。取值0x000001BB的位串,指出系统标题的开始,暂时不需要处理,读取Header Length直接跳过即可
3.3、节目映射流
Systemheader当且仅当pack是第一个数据包时才存在,即系统标题之后就是节目流映射。取值0x000001BC的位串,指出节目流映射的开始,暂时不需要处理,读取Header Length直接跳过即可。前5字节的结构同系统标题,见图8。


取一段码流分析系统标题和节目映射流

00 00 01 ba 45 a9 d4 5c 34 0100 5f 6b f8 00 00  ...?E??4.._k?..

01 bb 00 0c 80 cc f5 04 e1 7f e0 e0 e8 c0 c0 20  .?..€??.?.?????

00 00 01 bc 00 1e e1 ff00 00 00 18 1b e0 00 0c ...?..?......?..

2a 0a 7f ff 00 00 0708 1f fe a0 5a 90 c0 00 00  *........??Z??..

00 00 00 00 00 00 01 e0 7f e0 80 80 0521 6a 75  .......?.?€€.!ju
前14个字节是PS包头(注意,没有扩展);

接下来的00 00 01 bb是系统标题起始码;

接下来的00 0c说明了系统标题的长度(不包括起始码和长度字节本身);

接下来的12个字节是系统标题的具体内容,这里不做解析;

继续看到00 00 01 bc,这是节目映射流起始码;

紧接着的00 1e同样代表长度;

跳过e1 ff,基本没用;

接下来是00 18,代表基本流长度,说明了后面还有24个字节;

接下来的1b,意思是H264编码格式;

下一个字节e0,意思是视频流;

接下里00 0c,同样代表接下的长度12个字节;

跳过这12个字节,看到90,这是G.711音频格式;

下一个字节是c0,代表音频流;

接下来的00 00同样代表长度,这里是0;

接下来4个字节是CRC,循环冗余校验。

到这里节目映射流解析完毕。(好累)。

 

 

好戏还在后头呢。

3.4、PES分组头部

技术分享图片

 


                                                         图9

别被这么长的图吓到,其实原理相同,但是,你必须处理其中的每一位。

1)        Packet start code prefix:值为0x000001的位串,它和后面的stream id 构成了标识分组开始的分组起始码,用来标志一个包的开始。

2)        Stream id:在节目流中,它规定了基本流的号码和类型。0x(C0~DF)指音频,0x(E0~EF)为视频

3)        PES packet length:16 位字段,指出了PES 分组中跟在该字段后的字节数目。值为0 表示PES 分组长度要么没有规定要么没有限制。这种情况只允许出现在有效负载包含来源于传输流分组中某个视频基本流的字节的PES 分组中。

4)        PTS_DTS:2 位字段。当值为‘10‘时,PTS 字段应出现在PES 分组标题中;当值为‘11‘时,PTS 字段和DTS 字段都应出现在PES 分组标题中;当值为‘00‘时,PTS 字段和DTS 字段都不出现在PES分组标题中。值‘01‘是不允许的。

5)        ESCR:1位。置‘1‘时表示ESCR 基础和扩展字段出现在PES 分组标题中;值为‘0‘表示没有ESCR 字段。

6)        ESrate:1 位。置‘1‘时表示ES rate 字段出现在PES 分组标题中;值为‘0‘表示没有ES rate 字段。

7)        DSMtrick mode:1 位。置‘1‘时表示有8 位特技方式字段;值为‘0‘表示没有该字段。

8)        Additionalinfo:1 位。附加版权信息标志字段。置‘1‘时表示有附加拷贝信息字段;值为‘0‘表示没有该字段。

9)        CRC:1 位。置‘1‘时表示CRC 字段出现在PES 分组标题中;值为‘0‘表示没有该字段。

10)    Extensionflag:1 位标志。置‘1‘时表示PES 分组标题中有扩展字段;值为‘0‘表示没有该字段。

PES header data length: 8 位。PES 标题数据长度字段。指出包含在PES 分组标题中的可选字段和任何填充字节所占用的总字节数。该字段之前的字节指出了有无可选字段。


老规矩,上码流:

00 00 01 e0 21 33 80 80 05 2b 5f df 5c 95 71 84 ...?!3€€.+_??q?

aa e4 e9 e9 ec 40 cc17 e0 68 7b 23 f6 89 df 90 [email protected]?.?h{#????

a9d4 be 74 b9 67 ad 34 6d f0 92 0d 5a 48 dd 13 ???t?g?4m??.ZH?.

00 00 01是起始码;
e0是视频流;

21 33 是帧长度;

接下来的两个80 80见下面的二进制解析;

下一个字节05指出了可选字段的长度,前一字节指出了有无可选字段;

接下来的5字节是PTS;

第7、8字节的二进制如下:

1000 0000 1000 0000

按顺序解析:

第7个字节:

10                         是标志位,必须是10;

00                         是加扰控制字段,‘00’表示没有加密,剩下的01,10,11由用户自定义;

0                           是优先级,1为高,0为低;

0                           是数据对齐指示字段;

0                           是版权字段;

0                           是原始或拷贝字段。置‘1‘时表示相关PES分组有效负载的内容是原始的;‘0‘表示内容是一份拷贝;

第8个字节:

10                         是PTS_DTS字段,这里是10,表示有PTS,没有DTS;

0                           是ESCR标志字段,这里为0,表示没有该段;

0                           是ES速率标志字段,,这里为0,表示没有该段;

0                           是DSM特技方式标志字段,,这里为0,表示没有该段;

0                           是附加版权信息标志字段,,这里为0,表示没有该段;

0                           是PESCRC标志字段,,这里为0,表示没有该段;

0                           是PES扩展标志字段,,这里为0,表示没有该段;

本段码流只有PTS,贴一下解析函数

unsigned long parse_time_stamp (const unsigned char *p)
{
unsigned long b;
//共33位,溢出后从0开始
unsigned long val;

//第1个字节的第5、6、7位
b = *p++;
val = (b & 0x0e) << 29;

//第2个字节的8位和第3个字节的前7位
b = (*(p++)) << 8;
b += *(p++);
val += ((b & 0xfffe) << 14);

//第4个字节的8位和第5个字节的前7位
b = (*(p++)) << 8;
b += *(p++);
val += ((b & 0xfffe) >> 1);

return val;
}

其他字段可参考协议解析

 

ps:

遇到00 00 01 bd的,这个是私有流的标识

ps:

另外,有的hk摄像头回调然后解读出来的原始h.264码流,有的一包里只有分界符数据(nal_unit_type=9)或补充增强信息单元(nal_unit_type=6),如果直接送入解码器,有可能会出现问题,这里的处理方式要么丢弃这两个部分,要么和之后的数据合起来,再送入解码器里,如有遇到的朋友可以交流一下:)



































以上是关于如何实现H.264的RTP封装及传输?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用FFMPEG+H264实现RTP传输数据

基于C/S架构的完整RTP/RTCP的H264/H265视频传输方案实现

流媒体专家(10)rtp传输H264

H264关于RTP协议的实现

实现RTP协议的H.264视频传输系统

使用 libavformat 通过 RTP 流式传输 H.264