H.264码流格式
h264的有两种码流格式:字节流格式和RTP包格式。
字节流格式
`Annex-B Byte stream
format,这个是官方h264协议文档中规定的格式,所以它是大多数编码器默认的编码后的输出格式。它的基本数据单位为NAL单元,简称
NALU`(Network
Abstraction Layer
Unit)。每个NALU的前面加上起始码:0x000001
(3个字节)或0x00000001
(4个字节)用于分割,后面会介绍。
RTP包格式
这种格式没有在h264中规定,这种格式不需要起始码分割NALU,而是在NALU开始的几个字节代表NALU的长度。这个我没有过多研究,应该是不常用的。
所以我们这里主要介绍的就是字节流格式的h264裸流。所谓的裸流就是经编码器编码后输出的数据,而没有经过传输协议(比如flv)封装的数据,这样的数据就叫做裸流。
H.264结构
码流分层
如上所说h264码流是由一个接一个的 NALU组成的,但是它按照功能分为
视频编码层:VCL(Video Coding Layer),编码器压缩处理后的压缩视频数据序列。
网络抽象层:NAL(Network Abstraction
Layer),负责以网络要求的格式对数据进行打包和传送,是传输层。不管是本地保存还是在网络上传送,都需要通过这一层来传输。
也就是视频编码数据(VCL)在传输或存储(保存到文件)之前,会先被封装进NAL(也就是NALU)单元才可以。
NALU(NAL单元)
h264码流是一系列的NALU组成,用起始码分割每个。所以整体看码流的格式就是:
H264码流 = …Start_Code_Prefix NALU Start_Code_Prefix NALU …
Start_Code_Prefix
标示的就是起始码,起始码为:0x000001
(3个字节)或0x00000001
(4个字节),起始码中间的部分就是NALU
的部分。
我们看下我们从抖音/快手提取的h264文件的开始部分(因为h264格式开始有SPS,PPS,SEI 分割较多,你可以搜索一下文件后后面的数据流也有):
起始码.png
NALU的主体是:NALU=NALU Header EBSP
NALU的主体有细分:分别为EBSP、RBSP和SODB。其中EBSP完全等价于NALU主体,而且它们三个的结构关系为:
EBSP包含RBSP,RBSP包含SODB。
EBSP名字叫:扩展字节序列载荷(Encapsulated Byte Sequence Payload)
RBSP名字叫:原始字节序列载荷(Raw Byte Sequence Payload)
SODB(String Of Data Bits)就是最原始的编码数据。
后续介绍,先有个大概的概念区分,真的是概念非常多。
NALU Header
NALU Header 在每个的NALU中,占据一个字节也就是8位。分三部分,如下:
名称 | 占据位数bit | 代表的意义 |
---|---|---|
| 1bit |
h264文档规定,这个值应该为0,当它不为0时,表示网络传输过程中,当前NALU中可能存在错误,解码器可以考虑不对这个NALU进行解码
nal_ref_idc
| 2bit | 取值0~3,代表当前这个NALU的重要性,取值越大,代表当前NALU越重要
nal_unit_type
| 5bit | NALU的数据类型,比如是sps,pps,sei,idr等
我们主要看一下nal_unit_type
在h264协议中定义如下:
nal_unit_type.png
nal_unit_type
=1-5是VCL(视频编码层)单元。
6-代表当前NALU为辅助增强信息(SEI)。一般会埋入视频版权等信息。
7-代表当前NALU为序列参数集SPS,包括一个图像序列的所有信息,即两个 IDR 图像间的所有图像信息,如图像尺寸、视频格式等
8-代表当前NALU为图像参数集PPS,包括一个图像的所有分片的所有相关信息, 包括图像类型、序列号等
一般h264的码流最开始都是SEI,SPS,PPS,IDR(I帧)...,SPS,PPS,IDR(I帧).
一般在IDR(I帧)前有SPS,PPS,也就是每一组图像(GOP序列,图片组)都给予了图像参数集(PPS)和这个序列参数集SPS(SPS)。我们看下最开始提取的抖音的h264文件(也就是上面启始码的后一字节)。
代码语言:txt复制// 这里只贴了关键字节,省略其它的
代码语言:txt复制// 16进制打开,每2位数是一个字节byte=8位(bit)
代码语言:txt复制// 1F的二进制位的后五位为:11111
代码语言:txt复制0000 0001 0605 ffff e1dc 45e9 bde6 d948 SEI 06&1F取该字节的后五位=6
代码语言:txt复制3d31 3a31 2e30 3000 8000 0000 0167 6400 SPS 67&1F取该字节的后五位=7
代码语言:txt复制0303 c0f1 8319 a000 0000 0168 e978 b2c8 PPS 68&1F取该字节的后五位=8
代码语言:txt复制b000 0001 6588 8400 4ffe 841f c0a5 9f35 IDR 65&1F取该字节的后五位=5
代码语言:txt复制71b9 4cd3 13c1 0000 0001 419a 246c 47ff slice(片) 41&1F取该字节的后五位=1
视频的宽高就是在SPS中取出来的。
EBSP和RBSP
NALU的起始码为0x000001
或0x00000001
,但是有一种在NALU的内部也有0x000001
或0x00000001
的数据怎么办?H264采用了一种方法如果NALU内部出现了编码器就在最后一个字节前,插入一个新的字节:0x03。做了如下4种情况的处理:
0x000000 插入x03 0x00000300
代码语言:txt复制0x000001 插入x03 0x00000301
代码语言:txt复制0x000002 插入x03 0x00000302
代码语言:txt复制0x000003 插入x03 0x00000303
0x000003
是为了防止NALU内部本来就有0x000003
这样的数据。
所以说EBSP相较于RBSP,多了防止冲突的一个字节:0x03
。当使用EBSP时,就需要检测EBSP内是否有序列:0x000003
,如果有,则去掉其中的0x03
。这样一来,我们就能得到原始字节序列载荷:RBSP。
我们用提取的抖音的h264文件找下:
代码语言:txt复制3d31 3a31 2e30 3000 8000 0000 0167 6400
代码语言:txt复制1fac d980 b40a 1b01 1000 0003 0010 0000
代码语言:txt复制// 比如67=SPS 的NALU就有一个0303
代码语言:txt复制0303 c0f1 8319 a000 0000 0168 e978 b2c8
代码语言:txt复制b000 0001 6588 8400 4ffe 841f c0a5 9f35
代码语言:txt复制11fe 06cb d3bf 26e6 9d1f ff2c e1b1 aaf2
RBSP和SODB
原始编码数据SODB(String Of Data Bits)他们2个的关系是:
RBSP = SODB RBSP尾部
RBSP尾部
H264协议文档中有两种尾部表示,如下:
RBSP尾部.png
尾部特RBSP语法
RBSP最后一个字节的最后一个比特为rbsp_stop_one_bit
,其值为1,并且当rbsp_stop_one_bit
不是最后一个比特时,用一个或多个rbsp_alignment_zero_bit
,其值为0,补齐以形成一个字节对齐。
条带RBSP尾部
当
nal_unit_type
等于1~5时采用这种尾部。在尾部特RBSP语法的基础上,如果当entropy_coding_mode_flag
值为1,也即当前采用的熵编码为CABAC
,而且more_rbsp_trailing_data
返回为true,也即RBSP中有更多数据时,添加一个或多个0x0000
。
H264的码流结构
所以整体H.264的Annex-B码流格式从概念上来看就是,SODB里就是原始的编码数据。
H.264 Annex-B 码流格式.png
如有描述不准确欢迎指正。
H.264的协议文档
http://www.itu.int/rec/T-REC-H.264(https://links.jianshu.com/go?to=http://www.itu.int/rec/T-
REC-H.264-200503-S/en)
http://www.itu.int/rec/T-REC-H.264-200503-S/en(https://links.jianshu.com/go?to=http://www.itu.int/rec/T-
REC-H.264-200503-S/en)