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字节。
- V:版本号,2 位。根据RFC3984,目前使用的RTP 版本号应设为0x10。
- P:填充位,1 位。当前不使用特殊的加密算法,因此该位设为 0。
- X:扩展位,1 位。当前固定头后面不跟随头扩展,因此该位也为 0。
- CC:CSRC 计数,4 位。表示跟在 RTP 固定包头后面CSRC 的数目,对于本文所要实现的基本的流媒体服务器来说,没有用到混合器,该位也设为 0x0。
- M:标示位,1 位。如果当前 NALU为一个接入单元最后的那个NALU,那么将M位置 1;或者当前RTP 数据包为一个NALU 的最后的那个分片时(NALU 的分片在后面讲述),M位置 1。其余情况下M 位保持为 0。
- PT:载荷类型,7 位。对于H.264 视频格式,当前并没有规定一个默认的PT 值。因此选用大于 95 的值可以。此处设为0x60(十进制96)。
RFC2250 建议96 表示PS 封装,建议97 为MPEG-4,建议98 为H264
即我们接收到的RTP 包首先需要判断负载类型,若负载类型为96,则采用PS 解复用,将音视频分开解码。若负载类型为98,直接按照H264 的解码类型解码。
各种说法不一,具体就看解包的时候,是自己写解包代码,还是用别人的代码了,然后具体分析格式和type. - SQ:序号,16 位。序号的起始值为随机值,此处设为 0,每发送一个RTP 数据包,序号值加 1。
- TS:时间戳,32 位。同序号一样,时间戳的起始值也为随机值,此处设为0。根据RFC3984, 与时间戳相应的时钟频率必须为90000HZ。
- 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 = 8个字节(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