参考:https://www.it610.com/article/1297657355793670144.htm
https://blog.csdn.net/freeyond/article/details/77918253
H.264原始码流(裸流)是由一个接一个NALU组成,它的功能分为两层,VCL(视频编码层)和 NAL(网络提取层).
(1)NAL层 (视频数据网络抽象层)
因为H264最终还是要在网络上进行传输,在传输的时候,网络包的最大传输单元是1500字节,一个H264的帧往往是大于1500字节的,所以需要将一个帧拆成多个包进行传输。这些拆包、组包等工作都在NAL层去处理。VCL数据要在网络上传输或者存储到磁盘上之前,需要先被封装或映射进NAL单元(NALU)中,每个NAL单元之前需要添加StartCodePrefix,最后形成H.264码流。
(2)VCL层 (视频编码层)
对视频的原始数据进行压缩。VCL数据编码器直接输出的原始数据比特串(SODB),它表示图像被压缩后的编码比特流。
分层图及基本概念
SODB:原始数据比特串(String Of Data Bit)。由编码器直接输出的原始编码数据,即VCL数据。
RBSP:原始字节序列载荷(Raw Byte Sequence Payload)。在SODB的后面增加了若干结尾比特(RBSP trailing bits,1个为’1’的bit和若干为’0’的bit),以使SODB长度为整数字节。
EBSP:扩展字节序列载荷(Extension Byte Sequence Payload)。在RBSP的基础上增加了仿校验字节(0x03)。 增加仿校验字节原因是:将NALU添加到H.264码流上时,需要在每个NALU之前添加开始码(StartCodePrefix)。 添加StartCodePrefix的规则为:如果该NALU对应的slice为一帧的开始则StartCodePrefix为“0x00 0x00 0x00 0x01”这4个字节;否则StartCodePrefix为“0x00 0x00 0x01”这3个字节 。同时H264规定,当检测到0x000000时,也可以表示当前NALU的结束。为了使NALU主体中不包含与开始码相冲突的字节序列,在编码时,每遇到两个字节连续为0x00,就在这两个字节后面插入一个字节的0x03。解码时将0x03去掉,称为脱壳操作。 如下面的图3.3,就是一帧的EBSP数据。
NALU:NAL单元(NAL Unit,简称NALU)。NULU由1个NAL头(NAL Header)和1个RBSP(或EBSP)组成。
H.264码流由NALU序列组成,通过RTP协议封装和传输H.264码流实际上就是对H.264序列中NALU的封装和传输。在介绍RTP打包封装H.264码流之前,我们先来简单了解NALU的结构。
NAL单元(NAL Unit,简称NALU)由1个NAL头(NAL Header)和1个RBSP(或EBSP)组成。
NAL头(NAL Header)长度为1个字节,由“forbidden_zero_bit”、“nal_ref_idc”和“nal_unit_type”三个字段组成。NAL Header结构如果图2-1所示:
F:forbidden_zero_bit,1位,初始为0,当NAL单元在网络传输过程中识别为错误时,可设置该字段为 1,以便接收方纠错或丢掉该单元。
NRI:nal_ref_idc,2 位,用来指示该NALU 的重要性等级。值越大,越重要,解码器在解码处理不过来的时候,可以丢掉重要性为0的NALU。
TYPE:nal_unit_type ,5 位,指出NALU 的类型。
nal_unit_type 是指包含在NALU中的 RBSP 数据结构的类型,取值如下图所示:
图3.1
nal_unit_type的值在1到5之间的NALU称为VCL NALU,其余的称为非VCL NALU。
通过上图可以看到,nal_unit_type的取值除了H.264编码占用了一部分以外(取值为0~23),剩下的一部分(取值为24~31)在RTP打包时会使用。
常见的RBSP数据结构类型有IDR_SLICE、SPS、PPS和SEI,它们的NAL Header取值一般如下:
图3.2
图3.3 ebsp帧数据
RTP包由rtp header和rtp payload组成,RTP包结构如图4.1所示:
图4.1
使用RTP打包H.264码流时,定义了三个不同的载荷结构:单一NAL单元包、组合包和分片单元。 接收者可以通过payload的第一个字节识别载荷结构,我们称它为payload header。
payload header总是被格式化为NALU header,也就是说它和NALU header的结构一致,各字段具有相同意义。
payload header的TYPE字段用于描述payload中NALU的类型,通过图3.1可以看到,RTP打包时使用了24~31之间的TYPE值。
图4.2
载荷结构分为单个NAL单元包、组合包和分片单元三种类型。
需要注意的是载荷结构并不等同于打包模式,它只是打包模式的子集,每个打包模式都支持若干个载荷结构,我们将在下一节描述每种打包模式支持的载荷结构。
4.2.1 单个NAL单元包
单个NAL单元包是指,H.264码流(NALU序列)中的每个NALU都独立封装成一个RTP包,不拆分,不组合。
单个NAL单元包的打包示意图如下:
图 4.2.1 单个NAL单元包
单个NAL单元包必须只包含TYPE为1~23的NAL单元。这意味着组合包和分片单元不可以用在单个NAL单元包中。一个封装单个NAL单元包到RTP的NAL单元流的RTP序号必须符合NAL单元的解码顺序。单个NAL单元包的结构如图3-4所示。
图 4.2.2 单个NAL单元包结构
4.2.2 组合包
组合包是指,H.264码流(NALU序列)中有若干NALU尺寸特别小,因此可以将多个NALU进行组合后封装进一个RTP包。
组合包的打包示意图如下:
图 4.2.3 组合包
4.2.3 分片单元(FUs)
分片单元是指,H.264码流(NALU序列)中NALU长度超过MTU大小限制,因此需要将这样的NALU进行分片。
FU-A分片单元的打包示意图如下:
图 4.2.4 FU-A分片单元
分片只定义于单个NAL单元不用于任何组合包。NAL单元的一个分片由整数个连续NAL单元字节组成。每个NAL单元字节必须正好是该NAL单元一个分片的一部分。相同NAL单元的分片必须使用递增的RTP序号连续顺序发送(第一和最后分片之间没有其他的RTP包)。相似,NAL单元必须按照RTP顺序号的顺序装配。
当一个NAL单元被分片运送在分片单元(FUs)中时,被引用为分片NAL单元。 STAPs,MTAP不可以被分片。FUs不可以嵌套。即,一个FU 不可以包含另一个FU。
运送FU的RTP时戳被设置成被分片NALU的时戳。
图4.2.5表示FU-A的RTP荷载格式。 FU-A由1字节的分片单元指示(FU indicator),1字节的分片单元头(FU Header),和分片单元荷载(FU payload)组成。
图 4.2.5 FU-A分片结构
图 4.2.6 FU indicator结构
FU indicator结构和payload header结构一致,TYPE为28代表FU-A分片,TYPE为29代表FU-B分片。
Fu header结构如下:
图 4.2.7 FU header结构
S:1 bit,开始位
当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。
E:1 bit,结束位
当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。当跟随的FU荷载不是分片NAL单元的最后分片,结束位设置为0。
R:1 bit,保留位
保留位必须设置为0,接收者必须忽略该位。
TYPE:5 bit,NALU类型
与被分片的NALU类型一致。
基于H.264码流的RTP打包模式分为三种:
(1)单NAL单元模式
(2)非交错模式
(3)交错模式
图4.3.1总结了每种打包模式支持的NALU类型:
抓包实例文件下载地址:
FU-A分片实例.7z
文件“FU-A分片实例.7z”中包含两个文件:
(1) FUA_SLICE.pcap,抓取的一段基于H.264码流的RTP包,打包模式为非交错模式,包含两种载荷结构:单个NAL单元包和FU-A分片单元。
(2) frame.bin,从抓包文件中组出一个完整的IDR帧,包括SPS、PPS、SEI和4个IDR_SLICE。
FUA_SLICE.pcap中一个完整的IDR帧包括序列号从23861到23896的RTP包,组帧示意图如下:
图 5.1.0 IDR组帧
从图5.1.0可以看出,一个IDR帧包括了4个IDR_SLICE,每个IDR_SLICE都是完整图像的一部分,可以被独立解码。
frame.bin中包含的就是从RTP包序列号23861到23896组装的完整IDR帧,其NALU序列为:SPS+PPS+SEI+SEI+IDR_SLICE(0)+ IDR_SLICE(1)+ IDR_SLICE(2)+ IDR_SLICE(3),如果5.1.2所示:
图 5.1.2 NALU序列
如上图所示,生成的H264视频帧是由多个切片组成的。一个H264的帧至少由一个切片组成,不能没有切片,可以是一个到多个不能没有。在网络传输的时候一个H264帧可能需要切开去传,一个一次传不完,这就按照切片来切。每一个切片组成一个NAL Unit。
宏块分类 | 意义 |
---|---|
mb_type(宏块类型) | 确定该 MB 是帧内或帧间(P 或 B)编码模式,确定该 MB 分割的尺寸 |
mb_pred(宏块的预测) | 确定帧内预测模式(帧内宏块)确定表 0 或表 1 参考图 像,和每一宏块分割的差分编码的运动矢量(帧间宏块,除 8×8 宏块分割的帧内 MB) |
sub_mb_pred | (只对 8×8MB 分割的帧内 MB)确定每一子宏块的子宏 块分割,每一宏块分割的表 0 和/或表 1 的参考图象;每一 宏块子分割的差分编码运动矢量。 |
coded_block_pattern | 指出哪个 8×8 块(亮度和彩色)包 编码变换系数 |
mb_qp_delta | 量化参数的改变值 |
residual(残差数据) | 预测后对应于残差图象取样的编码变换系数 |
在切片数据中,包含若干个宏块。在一个宏块中,又包含了宏块类型、宏块预测、残差数据。
H264码流的分层结构,如图:
1帧 = n个片
1片 = n个宏块
1宏块 = 16x16yuv数据