Android音视频H264码流结构

2021-11-24 13:47:25 浏览数 (1)

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

代表的意义

forbidden_zero_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的起始码为0x0000010x00000001,但是有一种在NALU的内部也有0x0000010x00000001的数据怎么办?H264采用了一种方法如果NALU内部出现了编码器就在最后一个字节前,插入一个新的字节:0x03。做了如下4种情况的处理:

代码语言:txt复制
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)

0 人点赞