【H264】码流结构详解

无论是解析视频文件或者通过网络传输,其实都是一串字节序列。H264 码流就是按照一定的规则组织排列的字节串。

一、码流的组织形式

在 H264 中完全没有 I 帧、P 帧、B 帧、IDR 帧的概念,之所以沿用这些说法是为了表明数据的编码模式。H264 码流的组织形式从大到小排序是:视频序列(video sequence)、图像(frame/field-picture)、片组(slice group)、片(slice)、宏块(macroblock)、子块(sub-block)、像素(pixel)。
H264_Struct_A.png

二、码流功能的角度

从码流功能的角度可以分为两层:视频编码层(VCL)和网络提取层(NAL)

VCL:进行视频编解码,包括预测(帧内预测和帧间预测),DCT 变化和量化,熵编码和切分数据等功能,是为了实现更高的视频压缩比。
NAL:负责以网络所要求的恰当的方式对 VCL 数据进行打包和传送。

VCL 是管理 H264 的视频数据层,是为了实现更高的视频压缩比,那 VCL 究竟是怎么管理 H264 视频数据的呢?抛开 H264 压缩算法细节来看就 3 步:

压缩:预测(帧内预测和帧间预测)-> DCT 变化和量化 -> 比特流编码;
切分数据,主要为了第三步。这里一点,网上看到的“切片(slice)”、“宏块(macroblock)”是在VCL 中的概念,一方面提高编码效率和降低误码率、另一方面提高网络传输的灵活性。
压缩切分后的 VCL 数据会包装成为 NAL 中的一部分。

下面要重点讲解下 NAL。

三、网络提取层(NAL)

NAL,英文全称为 Network Abstraction Layer,这块和 H264 压缩算法无关,涉设计出 NAL 的目的就是为了获得 “network-friendly”,即为了实现良好的网络亲和性,即可适用于各种传输网络。

终于要讲 NAL 了,但是,我们需要先看 NAL的组成单元 – NALU

3.1 NAL单元 – NALU

NALU 的格式如下图(引用H264 PDF)所示:

FFmpeg_H264_NALU.png

很明显,NALU 由身体两个部分组成:

头:一般存储标志信息,譬如 NALU 的类型。前面讲过 NAL 会打包 VCL 数据,但是这并不意味着所有的 NALU 负载的都是 VCL,也有一些 NALU 仅仅存储了和编解码信息相关的数据;
身体:存储了真正的数据。但实际上,这块也会相对比较复杂,前面其实也提到过 H264 的一个目的是“网络友好性”,说白了就是能够很好地适配各种传输格式。所以根据实际采用数据传输流格式,也会对这部分数据格式再进行处理。

3.2 NALU Header

首先,NALU Header只占 1 个字节,即 8 位,其组成如下图所示:

H264_Struct_B.jpg

forbidden_zero_bit

在网络传输中发生错误时,会被置为 1,告诉接收方丢掉该单元;否则为 0。

nal_ref_idc
用于表示当前NALU的重要性,值越大,越重要。
解码器在解码处理不过来的时候,可以丢掉重要性为 0 的 NALU。

nal_unit_type
表示 NALU 数据的类型,有以下几种:

H264_Struct_C.png

其中比较注意的应该是以下几个:

1-4:I/P/B帧,如果 nal_ref_idc 为 0,则表示 I 帧,不为 0 则为 P/B 帧。
5:IDR帧,I 帧的一种,告诉解码器,之前依赖的解码参数集合(接下来要出现的 SPSPPS 等)可以被刷新了。
6:SEI,英文全称 Supplemental Enhancement Information,翻译为“补充增强信息”,提供了向视频码流中加入额外信息的方法。
7:SPS,全称 Sequence Paramater Set,翻译为“序列参数集”。SPS 中保存了一组编码视频序列(Coded Video Sequence)的全局参数。因此该类型保存的是和编码序列相关的参数。
8: PPS,全称 Picture Paramater Set,翻译为“图像参数集”。该类型保存了整体图像相关的参数。
9:AU 分隔符,AU 全称 Access Unit,它是一个或者多个 NALU 的集合,代表了一个完整的帧,有时候用于解码中的帧边界识别。

特殊的 NALU 类型:SPS和PPS

SPSPPS 存储了编解码需要一些图像参数,SPS,PPS 需要在 I 帧前出现,不然解码器没法解码。而 SPS,PPS 出现的频率也跟不同应用场景有关,对于一个本地 h264 流,可能只要在第一个 I 帧前面出现一次就可以,但对于直播流,每个 I 帧前面都应该插入 sps 或 pps,因为直播时客户端进入的时间是不确定的。

3.3 NALU Payload

很少有资料会称身体部分为 Payload,绝大部分资料对 NALU 组成的定义是这样子的:

NALU = NALU Header + SODB // 定义1
NALU = NALU Header + RBSP // 定义2
NALU = NALU Header + EBSP // 定义3

于是新的问题来了:SODB,RBSP和EBSP都是什么东西呢?这块概念,在博客NALU详解二(EBSP、RBSP与SODB)中介绍得非常清楚,总结来说就是:

SODB

英文全称 String Of Data Bits,称原始数据比特流,就是最原始的编码/压缩得到的数据。

RBSP

英文全称 Raw Byte Sequence Payload,又称原始字节序列载荷。和 SODB 关系如下:

RBSP = SODB + RBSP Trailing Bits(RBSP尾部补齐字节)

引入 RBSP Trailing Bits 做 8 位字节补齐。

EBSP

英文全称 Encapsulated Byte Sequence Payload,称为扩展字节序列载荷。和 RBSP 关系如下:

EBSP :RBSP插入防竞争字节(`0x03`)

这里说明下防止竞争字节(0x03):读者可以先认为 H264 会插入一个叫做 StartCode 的字节串来分割 NALU,于是问题来了,如果 RBSP 中也包括了 StartCode(0x000001 或 0x00000001)怎么办呢?所以,就有了防止竞争字节(0x03)

编码时,扫描 RBSP,如果遇到连续两个 0x00 字节,就在后面添加 防止竞争字节(0x03);解码时,同样扫描 EBSP,进行逆向操作即可。

最后,以一幅图总结 NALU 这段内容:

H264_Struct_D.jpg

四、码流解析的角度

H264 码流实际可以理解为由一个一个的 NALU 单元组成。(下图中的 RBSP 类似 NALU Payload)

H264_Struct_E.png

前面提到的一帧图像(I 帧, P 帧, B 帧)就是一个 NALU 单元,NALU 单元除了代表图像外还能包含其他类型的数据,如 PPS 和 SPS。

五、H264更详细的分层结构

H264_Struct_F.png

第一层:比特流。该层有两种格式:Annexb 格式和 RTP 格式。
第二层:NAL Unit 层。包含了 NAL Header 和 NAL Body 信息。
第三层:Slice 层。一帧视频图像可编码成一个或者多个片,每片包含整数个宏块,即每片至少 一个宏块,最多时包含整个图像的宏块。
第四层:Slice data 层。Slice 由宏块(macro block, MB)组成。宏块是编码处理的基本单元。
第五层:PCM 类。
第六层:残差层。

片的目的:

为了限制误码的扩散和传输,使编码片相互间保持独立。片共有 5 种类型: I 片(只包含 I 宏块)、P 片(P 和 I 宏块)、B 片(B 和 I 宏块)、SP 片(用于不同编码流之 间的切换)和 SI 片(特殊类型的编码宏块)。

六、扩展:怎么区分 NALU 的边界?

了解了 NALU 之后,关于 H264 格式,还有一个问题:解码器怎么知道一个 NALU 要结束了?或者说它怎么区分 NALU 的边界?

要回答这个问题,就必须了解 H264 的打包方式,通俗来说是H264 如何组织一连串的 NALU 为完整的 H264 码流。目前 H264 主流的两种格式:

Annex-B:本文关于 NALU 的很多细节介绍都是 Annex-B,它依靠前文提到的 Start Code 来分隔 NALU,打包方式如下:

[start code]--[NALU]--[start code]--[NALU]...

AVCC:笔者对这个格式了解的不多,从网上找到很多资料知道以下几点:

NALUextradata/sequence header 组成,由于在 extradata/sequence header 中存储了 NALU 的长度,因此 NALU Payload 不需要做字节对齐,不过防竞争字节还是有的;

SPSPPS 被放在了 extradata/sequence header

打包方式如下:

[SIZE (4 bytes)]--[NAL]--[SIZE (4 bytes)]--[NAL]... // 请注意,SIZE一般为4字节,但是具体以实际为准

至于为什么要有这两类格式,还需要查阅更多的资料。不过 StackOverflow 上关于Possible Locations for Sequence/Picture Parameter Set(s) for H.264 Stream的回答可以帮助深入了解这两种格式,推荐阅读。

下面是一个 H264 码流,可以看到每个 NALU 前有一个 StartCode(0x000001 或 0x00000001),作为 NALU 的分割符:

H264_Struct_G.jpg

分析其中比较有代表性的3帧:

00 00 00 01 67
00 00 00 01 是一个 NALU 开始,67 是Header, 二进制为 0110 0111, nal_unit_type 为00111,即7为 SPS 帧。
00 00 00 01 68
68 二进制为 0110 1000,nal_unit_type 为00111,即 8 为 SPS 帧。
00 00 00 01 65
65 二进制为 0110 0101,nal_unit_type 为 00101, 即 5 为 IDR 帧。

参考:

H264系列–码流组成和分层结构

知乎 – 视频和视频帧:H264编码格式整理

(十三) h264标准解析

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注