文章

04.视频编码之H.264

04.视频编码之H.264

视频编码

为什么需要编码?

摄像头采集画面直接写入到文件中时,我们会发现没一会文件已经非常大了。这导致很不适合保存和传输,所以需要编码,把画面数据进行压缩。视频编码标准有很多,而我们这里讲的是 H.264 编码,其他编码格式见:视频编码标准汇总及比较-CSDN博客

H.264 编码

制订 H.264 的主要目标有两个:

  • 视频编码层 (VCL,全称:Video Coding Layer):得到高的视频压缩比。
  • 网络提取层 (NAL,全称:Network Abstraction Layer):具有良好的网络亲和性,即可适用于各种传输网络。而 NAL 则是以 NALU(NAL Unit)为单元来支持编码数据在基于包交换技术网络中传输的。

H.264 编码器

![image.png700](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240602232942.png)

上面是 H264 的编码器原理图,编码时进行 帧内编码帧间编码。用相机录像举例,当相机录像过程帧出现一帧帧画面,没有压缩之间都是可以单独作为一张完整的照片,经过 H264 编码器后会出现:

  • 1.SPS、PPS:生成图片的一些参数信息,比如宽高等。一般是开启编码器后的第一次 I 帧前输出一次。
  • 2.I 帧:编码后第一个完整帧,包含一张图片的完整信息,但是也是压缩的。主要是进行帧内压缩,把一帧图片划分为很多 宏块,如:16x16、8x16、8x8、4x4 等等,记录宏块顶部和左侧像素点数据,以及预测的方向。
  • 3.B 帧:叫双向预测帧。编码器遇到下一帧画面与前面帧变化非常非常小(相似度在 95% 之内),比如在录像中的人在发呆,当遇到变化略微有点大的 P 帧才停止,这会出现多张 B 帧,为了尽可能的存储更少的信息,将参考前面的 I 和后面的 P 帧,把中间变化的数据存储下来,这样编码记录运动矢量和残差数据即可。所以编码器当遇到这些帧时,先等待 P 帧编码结束后才进行编码 B 帧,因此输出顺序在 P 帧之后。
  • 4.P 帧:有了 I 帧作为基础,P 就有参考的对象。跟前面 I 帧的变化非常少(相似度 70% 之内)。
  • 5.GOF:按上面说录像人在发呆场景时,将一组数据相差较小的图片进行编码,这就有了 GOF,即:group of picture,即一组图片,也叫一个场景的一组图片。是从一个 I 帧开始到下一个 I 帧前的一组数据。接着编码就是这样的一个个 GOF 的轮回。

把他看成一个 “ 黑盒 “ 的话,编码成了:

![image.png700](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240602233348.png)

解码成了:

![image.png700](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240602233401.png)

编码的输入与输出

一张张画面通过以 H.264 编码标准的编码器 (如 x264) 编码后,输出一段包含 N 个 NALU 的数据,每个 NALU 之间通过 起始码 来分隔,如图:

![image.png700](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240602233547.png)
  • 起始码: 0x00 00 01 或者 0x00 00 00 01。

在网络传输(如 RTMP)或者一些容器中(如 FLV),通常会把 NALU 整合到视频区域的数据中。如下图的 flv 格式:

![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240602233639.png)

NALU(NAL 单元)

NALU(NAL Unit,NAL 单元) 的组成部分如下图。其中,f(0) 占 1bit,u(2) 占 2bit,u(5) 占 5bit,文中如有出现类似描述符请看 H.264描述符 。

![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240602233720.png)

forbidden_zero_bit:禁止位,初始为 0。当客户端接受到该位为 1 时,则会丢弃该 NAL 单元的解码,表示异常数据。

nal_ref_idc:优先级,越大优先级越高。比如:I 帧优先级大于 B 帧,DPS 芯片会优先解码重要性高的。

从上图可以看出来,当前 NAL 单元属于什么样的类型,这取决于 RBSP 具体是什么样的类型,而 RBSP 的类型是根据 nal_unit_type 的值来定义的。 ①当 nal_unit_type 为 1~5 时:RBSP 为切片类型(有 5 种切片类型);整个 NAL 单元类型为 VCL NAL 单元,VCL 是上面说的视频编码层,里面有编码后画面数据。 ②当 nal_unit_type 为其他时:RBSP 为序列参数集类型、图像参数集类型等等;整个 NAL 单元类型为非 VCL NAL 单元。 具体的 nal_unit_type 所对应的 RBSP 类型如下表所示:

nal_unit_typeNAL 单元和 RBSP 语法结构的内容
0未指定
1一个非 IDR 图像的编码条带
slice_layer_without_partitioning_rbsp( )
2编码条带数据分割块 A
slice_data_partition_a_layer_rbsp( )
3编码条带数据分割块 B
slice_data_partition_b_layer_rbsp( )
4编码条带数据分割块 C
slice_data_partition_c_layer_rbsp( )
5IDR 图像的编码条带
slice_layer_without_partitioning_rbsp( )
6辅助增强信息 (SEI)
sei_rbsp( )
7序列参数集(SPS)
seq_parameter_set_rbsp( )
8图像参数集 (PPS)
pic_parameter_set_rbsp( )
9访问单元分隔符
access_unit_delimiter_rbsp( )
10序列结尾
end_of_seq_rbsp( )
11流结尾
end_of_stream_rbsp( )
12填充数据
filler_data_rbsp( )
13序列参数集扩展
seq_parameter_set_extension_rbsp( )
14..18保留
19未分割的辅助编码图像的编码条带
slice_layer_without_partitioning_rbsp( )
20..23保留
24..31未指定

SPS(序列参数集)

SPS 全称 Sequence parameter set(序列参数集),当 nal_unit_type=7 时,RBSP 就是 SPS 类型,也可以说 NAL 单元为 SPS 的 NAL 单元。SPS 主要包含的是针对一连续编码视频序列的参数,如帧数、图像尺寸等;

序列参数集 RBSP 语法

![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240602234032.png)

PPS(图像参数集)

PPS 全称 picture parameter set(图像参数集),当 nal_unit_type=8 时,RBSP 就是 PPS 类型,也可以说 NAL 单元为 SPS 的 NAL 单元。一个序列中某一幅图像或者某几幅图像,其参数如标识符 pic_parameter_set_id、可选的 seq_parameter_set_id、熵编码模式选择标识、片组数目、初始量化参数和去方块滤波系数调整标识等;详见下表 图像参数集 RBSP 语法

![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240602234114.png)

X264

这是国际好评的 H.264 协议标准的编码工具,这里简单介绍一下如何使用。

(1)下载:https://www.videolan.org/developers/x264.html

(2)编译(Android 的交叉编译,平台:Mac)

X264 简单使用

最简单的视频编码器:基于libx264(编码YUV为H.264)-CSDN博客

本文由作者按照 CC BY 4.0 进行授权