参考
https://blog.csdn.net/u010178611/article/details/82592393
https://blog.csdn.net/davebobo/article/details/52994596
1、H264基本概念
SODB:数据比特串:最原始的编码数据
RBSP:原始字节序列载荷:在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)若干比特“0”,以便字节对齐。
EBSP:扩展字节序列载荷:在RBSP基础上填加了仿校验字节(0X03)它的原因是: 在NALU加到Annexb上时,需要填加每组NALU之前的开始码 StartCodePrefix,如果该NALU对应的slice为一帧的开始则用4位字节表示,ox00000001,否则用3位字节表示 ox000001.
为了使NALU主体中不包括与开始码相冲突的,在编码时,每遇到两个字节连续为0,就插入一个字节的0x03。解码时将0x03去掉。 也称为脱壳操作。
H.264的功能分为两层,视频编码层(VCL)和网络提取层(NAL)
VCL数据即被压缩编码后的视频数据序列。把VCL数据要封装到NAL单元中之后,才可以用来传输或存储。NAL单元格式如下图:
H.264 的编码视频序列包括一系列的NAL 单元,每个NAL 单元包含一个RBSP,见表1。编码片(包括数据分割片IDR 片)和序列RBSP 结束符被定义为VCL NAL 单元,其余为NAL 单元。典型的RBSP 单元序列如图2 所示。每个单元都按独立的NAL 单元传送。单元的信息头(一个字节)定义了RBSP 单元的类型,NAL 单元的其余部分为RBSP 数据。
2、H264帧结构
H264帧由多个NALU组成和起始码(start code),NALU之间有一个称为“起始码”字段分开,就是上图所示的黑色部分,起始码有两种格式:0x000001和0x00000001;有了起始码,我们就可以将H264帧里面的每个NALU取出来进行处理了。细心的读者们可能会有一个疑惑,如果NALU这个数据里面也包含有0x000001或0x00000001那就有问题了,是的!你能想到的问题,H264的专家也会想到的,H264规范里面对这个有特殊处理的,会进行相应的转码,确保NALU里面内容是不会出现0x000001和0x00000001的。
一个NALU由Header和RBSP两部分构成。
3、NALU打包到RTP
上图展示了如何将一个NALU打包到RTP的payload;上图的左边的打包流程对应的场景是“NALU的长度 <= MTU”,直接将NALU的header拷贝到H264 RTP Payload Header上,将NALU的RBSP拷贝到H264 RTP Payload Content上。
上图右边的打包流程对应的场景是“NALU的长度 > MTU”,要将NALU的RBSP进行分片,以保证打包后的RTP报文长度不大于MTU,H264 RTP Payload Header由FU-identity + FU Header组成;FU-identity字段和NALU header字段的格式一样(如果不一样的话,接收端就搞不清这是一个NALU分片还是一整个NALU了),其最低的5bits表示payload的类型(不同的取值表示RTP的类型不同,见下表);FU payload就是NALU的RBSP(一部分);另外,属于同一H264帧的所有RTP头的时间戳都要打成相同的,接收端根据时间戳来判断哪些包是属于同一个H264帧的。
4、打包流程
1)从H264编码器读出一帧数据(一个完整的H264帧)
2)然后遍历H264的所有NALU; H264帧数据结构见下章节
2.1)如果NALU长度小于MTU,则将该NALU打包为一个RTP包
2.2)否则,将NALU按照MTU大小进行分片(本文只用FU-A分片格式)
5、抓包分析
FU indicator(0x5C)
0....... = F:禁止为,0表示正常,1表示错误,一般都是0
.10..... = NRI:重要级别,11表示非常重要。
...11100 = TYPE:28,即FU-A
FU Header(0x41)
0....... = S: 1 bit 当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
.1...... = E: 1 bit 当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的 FU荷载不是分片NAL单元的最后分片,结束位设置为0。
..0..... = R:1 bit 保留位必须设置为0,接收者必须忽略该位
...00001 = TYPE:1,即帧类型为 P帧