H264码流的RTP封装
一、RTP协议头:
1.RTP头定义:
RTP协议头一般固定为12个字段,在每一个RTP数据包中都存在。各字段的含义如下:
version(V):2bits:标识RTP的版本,当前协议版本固定为2.
padding(P):1bits:填充位。默认0,如果为1,则在该报文的末尾填充一个或多个额外的八位组,它们不是有效载荷的一部分。某些具有固定块大小的加密算法可能会使用到填充
extension(X):1bits:扩展位。默认0,如果为1,设置了扩展位,固定头部必须紧跟一个头部扩展。
CSRC count(CC):4bits:CSRC计数器。默认为0,指示CSRC标识符的个数。
marker(M):1bits:不同的载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
payload type(PT):7bits:载荷类型。常见的H264视频为96,AAC音频为97
sequence number(SEQ):16bits:序列号。用于标识发送者发送RTP报文的序列号,每次加1,初始值应该是随机的。接收方可以用于检测丢包,或者重排。音频包和视频包是分别计数的。
timestamp:32bits:时间戳。反映RTP数据包中第一个八位字节的采样瞬间,接收者可以用来计算延迟和延迟抖动,结合RTCP包的NTP时间基等概念,接受端可以进行音视频同步控制(以后再深入学习)
ssrc:32bits:同步信源标识。该标识符应当是随机选择的,且同一RTP会话中不同同步源需要有不同的ssrc标识符。
csrc list:0~15个32bits:特约信源。标识符的个数由CC字段给出,一般在混音器插入时才出现,默认不需要。
2.ZLM中关于H264流RTP的构造:
二、RTP封包类型:
H264可以由三种RTP打包方式:1)单NALU打包,一个RTP包包含一个完整的NALU;2)聚合打包,对于较小的NALU,一个RTP包可以包含多个完整的NALU;3)分片打包:对于较大的NALU,一个NALU可以分为多个RTP包发送。比较常见的是单NALU打包和分片打包。
单NALU打包简单,将整个NALU的数据放入到RTP包的载荷中即可。
分片打包。在RTP载荷开始之前有两个字节的信息,然后是NALU的内容。
第一个字节是FU Indicator。标识封包类型。格式如下:F禁止位,NRI两个bite,Type为封包类型。解析方法:futype = fu_indicator & 0x1f;
第二个字节是FU header。用于判断包的位置,和包的类型。FU-A打包中的第一个包时,S设为1;FU-A打包的最后一个包时,E设为1;R为保留位。Type为原来NALU的Type的低位,0x01,0x05,0x07,0x08这些
H264帧的NALU TYPE类型:nal_fu = (fu_indicator & 0xe0) | (fu_header & 0x1f)
在ZLM中使用到的RTP分别类型有3种:Single包、STAP-A包、FU-A包。
三。STAP-A包
1.STAP-A包的完整格式:
2.SPS帧抓包:
其中:80 60 31 4a 00 57 40 e0 00 00 00 02为RTP HEAD,对照RTP HEAD定义可以进行解析。可以得出PT为96,SEQ为12618,Timestamp为5718240,SSRC为000002
剩余部分:78 00 18 67 64 ......
78 -->0111 1000。0x78 & 0x1f = 0x18 。后5位type为24。表示为STAP-A类型包
00 18 载荷长度:24
67 -->NALU TYPE表示为SPS包
之后的内容是SPS包的具体内容,不再分析。
3.PPS帧抓包:
其中:80 60 31 4b 00 57 40 e0 00 00 00 02为RTPHEAD, 可以得出PT为96,SEQ为12619,Timestamp为5718240,SSRC为000002。对比SPS包可以看出,SEQ加1,同一时刻构造的包,SSRC一致。
剩余部分:78 00 05 68 ee 31 b2 1b
78 -->0111 1000。0x78 & 0x1f = 0x18 。后5位type为24。表示为STAP-A类型包
00 15 载荷长度:5
68 -->NALU TYPE表示为PPS包。
之后的内容是PPS包的具体内容,不再分析
四、FU-A包
1、FU-A包完整格式:
2、I帧(起始帧)抓包
其中:80 60 31 4c 00 57 40 e0 00 00 00 02为RTPHEAD, 可以得出PT为96,SEQ为12620,Timestamp为5718240,SSRC为000002。对比SPS包可以看出,SEQ加1,同一时刻构造的包,SSRC一致。
剩余部分:7c 85 b8 ...
7c --> 0111 1100。0x7c & 0x1f = 0x1c。后5位type为28表示为FU-A类型包
85 -->1000 0101。对比FU Head解析,S为1,说明是起始包。后5位为5,说明是I帧。更加完整的NAL TYPE为:(0x7c & 0xe0) | (0x85 & 0x1f) = 0x65
之后的内容是NALU负载,不再分析
3、I帧(中间帧)抓包
其中:80 60 31 4d 00 57 40 e0 00 00 00 02为RTPHEAD, 可以得出PT为96,SEQ为12621,Timestamp为5718240,SSRC为000002。对比SPS包可以看出,SEQ加1,同一时刻构造的包,SSRC一致
剩余部分:7c 05 ad ...
7c --> 0111 1100。0x7c & 0x1f = 0x1c。后5位type为28表示为FU-A类型包
05 -->0000 0101。对比FU Head解析,S为0,E为0,说明是中间包。后5位为5,说明是I帧。更加完整的NAL TYPE为:(0x7c & 0xe0) | (0x05 & 0x1f) = 0x65
之后的内容是NALU负载,不再分析
4、I帧(结尾帧)抓包:
其中:80 e0 31 db 00 57 40 e0 00 00 00 02为RTPHEAD, 可以得出PT为96,SEQ为12763,Timestamp为5718240,SSRC为000002。对比SPS包可以看出,SEQ加了很多,同一时刻构造的包,SSRC一致
剩余部分:7c 05 ad ...
7c --> 0111 1100。0x7c & 0x1f = 0x1c。后5位type为28表示为FU-A类型包
45 -->0100 0101。对比FU Head解析,S为0,E为1,说明是结尾包。后5位为5,说明是I帧。更加完整的NAL TYPE为:(0x7c & 0xe0) | (0x45 & 0x1f) = 0x65
之后的内容是NALU负载,不再分析
至此一个完整的I帧结束
5、P帧(分包)抓包
其中:80 60 31 f2 00 58 83 9c 00 00 00 02为RTPHEAD, 可以得出PT为96,SEQ为12786,Timestamp为5800860,SSRC为000002。对比SPS包可以看出,SEQ加了很多, 时间戳增加,SSRC一致
剩余部分:7c 81 e2 ...
7c --> 0111 1100。0x7c & 0x1f = 0x1c。后5位type为28表示为FU-A类型包
81 -->1000 0001。对比FU Head解析,S为1,E为0,说明是起始包。后5位为1,说明是P帧。更加完整的NAL TYPE为:(0x7c & 0xe0) | (0x81 & 0x1f) = 0x61
五、ZLM对RTP的封装实现:
1、根据H264帧的长度,决定单包还是分包:
2、对于单包,zlm根据配置分别采用单nalu和stap-a两种方式:
3、singlenalu打包:直接将NALU内容放在RTP头后面
4、stap-a打包:作者注释是为了兼容性webrtc。第一个字节为0x24,第二和第三字节为负载长度,后面为NALU内容
5、fu-a打包:设置第一个字节为28,第二个字节有NALU的TYPE和起始结束标识进行构造。在while循环中进行分包处理。
六、ZLM对RTP的解封装实现
1、根据RTP头部之后的第一个字节判读包类型:nal = frame[0] & 0x1f。其中小于24为单包,24为stap-a,28为su-a,其它不支持
2、解封装单包:在负载内容frame之前添加帧分隔符00 00 00 01,组装成H264裸帧
3、解封装stap-a包:stap-a包和单包类似,只不过多了两个字节表示载荷长度,可用于包长度校验。取出nalu载荷之后通上述单包逻辑,组成成H264裸帧。
4、解封装fu-a包:根据RTP头后面的第二个字段,可以判断出是起始/中间/结尾包。根据第一个和第二个字段可以计算出H264帧的类型。如果是起始包,添加00 00 00 01帧头,添加帧类型,缓存后续包;如果是中间包,缓存第二个字节之后的后续包;如果是结尾包,缓存第二个字节之后的后续包之后,至此组装出一个完整的H264裸帧。在分包缓存的过程中根据SEQ序号还就行了帧连续性判断