RTP封装H264——学习笔记

H264结构详解

从这个链接拿过来的图。 

逻辑关系:

                                                  SODB  + RBSP trailing bits    =  RBSP

                                                       NAL header(1 byte)      +      RBSP   =  NALU

Start Code Prefix(3 bytes)  +   NALU  +  Start Code Prefix(3 bytes)  + NALU + ...+  = H.264BitsStream(非严格意义上的公式)

SODB:编码形成的真实码流,为了使一个RBSP为整字节数,需要加trailing bits。用1个1,若干0补齐。

RBSP:原始字节序列载荷,就是在SODB的后面添加了结尾比特

Start Code Prefix为3个字节. 但是,为了寻址方便,要求数据流在长度上对齐,因此H.264建议在Start Code Prefix前面加若干个0.    

EBSP相较于RBSP,多了防止竞争的一个字节:0x03。

我们知道,NALU的起始码为0x000001或0x00000001,同时H264规定,当检测到0x000000时,也可以表示当前NALU的结束。那这样就会产生一个问题,就是如果在NALU的内部,出现了0x000001或0x000000时该怎么办?

所以H264就提出了“防止竞争”这样一种机制,当编码器编码完一个NAL时,应该检测NALU内部,是否出现如下图左侧的四个序列。当检测到它们存在时,编码器就在最后一个字节前,插入一个新的字节:0x03。

所以,严格讲:NALU = NAL Header(1 byte)  + EBSP

H264句法元素解析流程:

H264结构

H264流中,其中一个 NAL 单元的结构

NAL HEADER 上面的1-8数字,表示占8位。后面RBSP没有写,是因为占位不定,类型也不确定。

F:       1个比特. forbidden_zero_bit. 在 H.264 规范中规定了这一位必须为 0.
NRI:   2个比特. nal_ref_idc. 取00~11, 似乎指示这个NALU的重要性,如00的NALU解码器可以丢弃它而不影响图像的回放.
Type: 5个比特. nal_unit_type. 这个NALU单元的类型.简述如下:

    

RTP协议头

 12字节的RTP头(不含CSRC的时候,是12个字节)后面的就是音视频数据。

标号表示位。8位 = 1个字节,一共96位,就是12字节。

  1. V:版本号,2 位。根据RFC3984,目前使用的RTP 版本号应设为0x10。 
  2. P:填充位,1 位。当前不使用特殊的加密算法,因此该位设为 0。 
  3. X:扩展位,1 位。当前固定头后面不跟随头扩展,因此该位也为 0。 
  4. CC:CSRC 计数,4 位。表示跟在 RTP 固定包头后面CSRC 的数目,对于本文所要实现的基本的流媒体服务器来说,没有用到混合器,该位也设为 0x0。 
  5. M:标示位,1 位。如果当前 NALU为一个接入单元最后的那个NALU,那么将M位置 1;或者当前RTP 数据包为一个NALU 的最后的那个分片时(NALU 的分片在后面讲述),M位置 1。其余情况下M 位保持为 0。 
  6. PT:载荷类型,7 位。对于H.264 视频格式,当前并没有规定一个默认的PT 值。因此选用大于 95 的值可以。此处设为0x60(十进制96)。

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

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

     各种说法不一,具体就看解包的时候,是自己写解包代码,还是用别人的代码了,然后具体分析格式和type.
  7. SQ:序号,16 位。序号的起始值为随机值,此处设为 0,每发送一个RTP 数据包,序号值加 1。 
  8. TS:时间戳,32 位。同序号一样,时间戳的起始值也为随机值,此处设为0。根据RFC3984, 与时间戳相应的时钟频率必须为90000HZ。 
  9. SSRC:同步源标示,32 位。SSRC应该被随机生成,以使在同一个RTP会话期中没有任何两个同步源具有相同的SSRC 识别符。此处仅有一个同步源,因此将其设为0x12345678。
取一段RTP传输的码流如下:

80 e0 00 1e 00 00 d2 f0 00 00 00 00 41 9b 6b 49 €?....??....A?kI
e1 0f 26 53 02 1a ff06 59 97 1d d2 2e 8c 50 01 ?.&S....Y?.?.?P.
cc 13 ec 52 77 4e e50e 7b fd 16 11 66 27 7c b4 ?.?RwN?.{?..f'|?
f6 e1 29 d5 d6 a4 ef3e 12 d8 fd 6c 97 51 e7 e9 ??)????>.??l?Q??
cfc7 5e c8 a9 51 f6 82 65 d6 48 5a 86 b0 e0 8c ??^??Q??e?HZ????

其中(80,是十六进制,代表1个字节,换成二进制,是8位。1111 1111 换成16进制,为 FF),
80               是V_P_X_CC
e0               是M_PT
00 1e          是SequenceNum
00 00 d2 f0 是Timestamp
00 00 00 00是SSRC

把前两字节换成二进制如下
1000 0000 1110 0000

按顺序解释如下:
10               是V;
0                 是P;
0                 是X;
0000           是CC;
1                 是M;
110 0000    是PT;

参考链接 

对于每一个NALU,根据其包含的数据量的不同,其大小也有差异。在IP网络中,当要传输的IP 报文大小超过最大传输单元MTU(Maximum Transmission Unit )时就会产生IP分片情况。在以太网环境中可传输的最大 IP 报文(MTU)的大小为 1500 字节。如果发送的IP数据包大于MTU,数据包就会被拆开来传送,这样就会产生很多数据包碎片,增加丢包率,降低网络速度。对于视频传输而言,若RTP 包大于MTU 而由底层协议任意拆包,可能会导致接收端播放器的延时播放甚至无法正常播放。因此对于大于MTU 的NALU 单元,必须进行拆包处理。

RFC3984 给出了3 中不同的RTP 打包方案:

(1)Single NALU Packet:在一个RTP 包中只封装一个NALU,在本文中对于小于 1400字节的NALU 便采用这种打包方案。 
(2)Aggregation Packet:在一个RTP 包中封装多个NALU,对于较小的NALU 可以采用这种打包方案,从而提高传输效率。 
(3)Fragmentation Unit:一个NALU 封装在多个RTP包中,在本文中,对于大于1400字节的NALU 便采用这种方案进行拆包处理。

注意:前12个字节出现在每个RTP包中,仅仅在被混合器插入时,才出现CSRC识别符列表.

单一包传输模式

单一模式,是[RTP header] + { [nal header] + [rbsp] }   。其中花括号内,就是{ rtp payload}

NAL单元的第一字节。根据NAL Header 的后5位 TYPE,就知道载荷的类型。下面的图仅是nalu的部分。

例:
  如有一个 H.264 的 NALU 是这样的:

  [00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]

  这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容.

  封装成 RTP 包将如下:

  [ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ...]

  即只要去掉 4 个字节的开始码就可以了.

nal->len 就是指去掉 RTP Header剩下部分的长度字节数。如上例子中,去掉 ... ,则nal->len = 7个字节(67 42 A0 1E 23 56 0E 2F)

组合传输模式

如有一个 H.264 的 NALU 是这样的:

  [00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]

  [00 00 00 01 68 42 B0 12 58 6A D4 FF ... ]

  封装成 RTP 包将如下:

  [ RTP Header ] [STAP-A头 (占用1个字节,用78表示)] [第一个NALU长度 (占用两个字节)] [ 67 42 A0 1E 23 56 0E 2F ...] [第二个NALU长度 (占用两个字节)] [68 42 B0 12 58 6A D4 FF ... ]

 

分片传输模式 (FU-A)

  

RTP HEADER + FU indicator + FU header + FU payload. 就是分片传输。 其中 FU payload 就是H264中一个nal unit去掉头的 EBSP部分。
FU indicator 的 TYPE 就是 FU-A

FU header 的 TYPE 就是 nal header 的 type。取1-23的那个值

取一段码流分析如下:

80 60 01 0f 00 0e 10 00 00 00 00 00 7c 85 88 82 €`..........|???
00 0a 7f ca 94 05 3b7f 3e 7f fe 14 2b 27 26 f8 ...??.;.>.?.+'&?
89 88 dd 85 62 e1 6dfc 33 01 38 1a 10 35 f2 14 ????b?m?3.8..5?.
84 6e 21 24 8f 72 62f0 51 7e 10 5f 0d 42 71 12 ?n!$?rb?Q~._.Bq.
17 65 62 a1 f1 44 dc df 4b 4a 38 aa 96 b7 dd 24 .eb??D??KJ8????$
 
前12字节(80 60 01 0f 00 0e 10 00 00 00 00 00)是RTP Header
7c是FU indicator
85是FU Header
FU indicator(0x7C)和FU Header(0x85)换成二进制如下:
0111 1100 1000 0101

按顺序解析如下:
0                        是F
11                       是NRI
11100                    是FU Type,这里是28,即FU-A
1                        是S,Start,说明是分片的第一包
0                        是E,End,如果是分片的最后一包,设置为1,这里不是
0                        是R,Remain,保留位,总是0
00101                    是NAl Type,这里是5,说明是关键帧(不知道为什么是关键帧请自行谷歌)
 
打包时:
FUindicator的F、NRI是NAL Header中的F、NRI,Type是28;
FU Header的S、E、R分别按照分片起始位置设置,Type是 NAL Header中的Type。

解包时:
取FU indicator的前三位和FU Header的后五位,即0110 0101(0x65)为NAL类型。

自己整理的学习笔记,可能有点混乱。如果读者想深一步学习,可以参考下面的链接。


参考链接:

https://www.jianshu.com/p/a19f3e63b433

https://www.cnblogs.com/lidabo/p/4482480.html

https://www.jianshu.com/p/5f89ea2c3a28

发布了417 篇原创文章 · 获赞 156 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qq_34732729/article/details/104969107