编解码原理(一):H264

1. 介绍

国际上制定视频编解码技术的组织有两个,一个是"国际电联(ITU-T)",它制定的标准有H.261、H.263、H.263+等,另一个是"国际标准化组织(ISO)"它制定的标准有MPEG-1MPEG-2MPEG-4等。

H.264是国际标准化组织(ISO)和国际电信联盟(ITU)共同提出的继MPEG4之后的新一代数字视频压缩格式。H.264是ITU-T以H.26x系列为名称命名的视频编解码技术标准之一。

H.264是由两个组织联合组建的联合视频组(JVT)共同制定的新数字视频编码标准,所以它既是ITU-T的H.264,又是ISO/IEC的MPEG-4高级视频编码(Advanced Video Coding,AVC)的第10 部分。因此,不论是MPEG-4 AVC、MPEG-4 Part 10,还是ISO/IEC 14496-10,都是指H.264。

以上内容摘自《H264 百度百科》,简单地介绍了我们今天所谈论的H264标准的由来,需要理解的是:H264、AVC、MPEG-4 Part10、ISO/IEC 14496-10,这些都是同一份视频编码标准。

2. 相关概念解析

2.1 H264中的图像层次结构

H264标准中,将需要编码的图像划分为序列、图像、片、宏块、子块几个层次。

2.1.1 序列(sequence)

由于视频的图像内容具有时间冗余性的特点,一段时间内相邻的图像中,像素、亮度与色温等都差别较小。所以对于一段连续时间内的图像,我们没有必要去对每一帧图像进行完整的编码,而是可以选择这段时间内的第一张图像进行完整编码,而下一张图可以记录下与第一帧完整编码图像的像素、亮度、色温等的差别,由此类推循环下去,减少记录的数据量。

上述中在一段时间段内,图像变化不大的图像集,我们可以称之为一个序列。

序列可以理解为有相同特点的一系列图像数据。但是编码过程中,如果某张图像与之前的图像差别很大,难以参考之前的帧来生成新的帧,那么就结束上一个序列,开始下一个序列。

2.1.2 图像(picture)

图像是指一组视频序列中的一帧画面,这个比较直观。当然,如果是说帧(frame),也是一样的概念。

2.1.3 片(slice)

一帧图像可以划分成一个或多个"片",每片包含整数个"宏块"。

一般在编码图像时,编码器会按照"之"字形光栅扫描顺序处理一个个16x16的宏块。一个片就是一系列连续的宏块,编码器会决定片的划分和其中的宏块数量(至少一个,最多可以包含整个画面的宏块)。

独立编解码数据传输错误恢复的最小单元。

  1. 独立性

    (1) 一个片在编码时,不能依赖本帧内其他片的数据(宏块),更精确地说,宏块进行帧内预测时,只能依赖同一个片的其他宏块,不能跨跃片进行参考。所以同一帧图像的多个片之间没有编码的依赖性

    (2) 由于多个片之间没有依赖性,所以这使编解码并行化成为可能,理论上CPU可以同时处理不同的片,提高编解码效率,因此片可以作为并行处理的单位。

  2. 传输的单元 在传输层面上,一个片通常被直接打包成一个网络数据包。这么做的目的是为了让数据包的大小匹配网络的MTU(最大传输单元),避免IP数据包分片,减少延迟和丢包风险。

  3. 错误恢复

    (1) 一个片中包含了重建自身所需的所有数据头信息(如片头,包含了帧号、量化参数等关键信息)。

    (2) 由于片的独立性和自身可重建,所以可以限制误码的扩散和传输。如果因为网络丢包,使得一个片出现错误,解码器仍然可以解码出同一帧中其他完好的片,不会因为错误扩散导致整帧报废。

片的设计主要服务于传输层需要的功能。

2.1.4 宏块(MarcoBlock)

一个宏块是由一个16x16的YUV数据(16x16亮度,8x8Cb, 8x8Cr)组成。若干个宏块被排列成片的形式。

I片只包含I宏块,P片可以包含P宏块和I宏块,B片可以包含B和I宏块。

I宏块利用从当前片中已经解码的像素作为参考进行帧内预测,不能取其他片中的已解码像素作为参考进行帧内预测。

P宏块利用当前已编码的图像作为参考图像进行帧间预测。

B宏块则使用双向的参考图像(当前和未来的已编码的图像帧)进行帧间预测。

宏块是所有编码工具编解码算法直接操作的最小逻辑单元。

  1. 预测(Prediction)的单位:
  • 帧间预测(运动估计/补偿): 编码器会为每个宏块(或更小的子块)在参考帧中寻找最佳匹配块,并为其计算一个运动矢量。这是压缩中最耗计算资源的步骤。
  • 帧内预测: 编码器为每个宏块(或子块)选择一种预测模式(如利用上方或左侧的像素来预测当前块)。
  1. 变换(Transform)和量化(Quantization)的组织单位:
  • 预测后得到的残差数据(原始图像 - 预测图像),会被分割成更小的块(如4x4或8x8),然后对每个块独立进行DCT变换,将空域信号转换为频域信号。
  • 变换后的频域系数会使用一个量化参数(QP) 进行量化,这个QP通常可以以宏块为单位进行微调,实现局部的码率控制。(量化的作用相当于是将数据映射至更小的表达范围)
  1. 熵编码(Entropy Coding)的容器单位:
  • 在CABAC等熵编码中,语法元素(如运动矢量、模式标志)的概率模型更新是基于宏块及其周边宏块的信息进行的。
  1. 码率控制的单位
  • 在量化时,编码器可以通过为每个宏块调整量化参数(QP)来精细控制码率。这个QP通常用来进行码率控制,所以宏块也是码率控制的基本单元。

2.1.5 子块(Sub-MacroBlock)

子块(或子宏块)是宏块内部再进行细分后得到更小的、用于预测和变换的像素块,其目的是为了实现更精细、更准确的预测,从而大幅提升压缩效率。

2.2 图像方面的概念

2.2.1 GOP(画面组)

GOP概念和前面提及的"序列"的概念类似,是指一段时间内变化不大的图像集,很多文章会把两者概念混合在一起,但还是有差别,我觉得更准确的比喻应该是GOP是序列中以I帧开始的每一小节。

  1. 序列,一个序列内可以有多个GOP,有自己对应的SPS/PPS参数集(这是最重要的区别,GOP不涉及SPS/PPS的概念)。
  2. 序列一定是以IDR帧开始,会清空解码区的参考帧缓冲区。而GOP不一定是以IDR帧开始(具体有闭合GOP、开放GOP)。

闭合GOP:GOP始于IDR帧,帧间预测严格限制在本GOP内(【IDR、P、B、P...】),这是最常用的方式。 开放GOP:始于一个非IDR帧的I帧,其后B帧可以跨越GOP边界,参考前一个GOP的帧,从而提升压缩效率。(【...P】【I、P、B、P、P】,其中B帧可以参考前面一个GOP中的P帧)

序列是一个更大的、完全独立的容器,是包含了完整参数信息的、更完整的逻辑单元,用于描述码流的独立解码单元。而GOP是序列内部用于组织预测结构的一个或多个子单元,用于描述帧间的参考关系。

我们使用ffmpeg -g来"调节GOP"时,实际上调节的是IDR帧的间隔帧数,这样的处理更符合实际的需要。所以很多场景下,GOP的概念会泛化成"IDR帧的间隔",需要注意区分和理解。

2.2.2 帧类型 I/P/B/SI/SP

H264中支持以下几种帧类型:

(1)I帧:I帧是帧内预测帧,它不依赖其他参考帧,只依赖自身图像像素。在码流中支持随机访问,但压缩率最低。

  • IDR帧:一种特殊的I帧,解码器在遇到IDR帧时,会清空帧缓冲区。

(2)P帧:P帧是前向参考帧,它需要参考前面一帧已解码的I帧或P帧的数据。其压缩率比I帧高。

(3)B帧:B帧是双向参考帧,它需要参考(其显示时间)之前的帧和之后的帧,其压缩率最高。但由于参考依赖会使得码流的pts!=dts,需要按照解码时间对帧进行重排序,增加了编解码延时。

(4)SI/SP帧:SI帧和SP帧用于码流切换,常用于自适应码率流媒体(如Dash、HLS)。

但因为计算复杂,在实际应用中并不常见,更加常见的用法是缩短IDR帧间隔来快速切换(虽然可能会导致码率变大,延迟高)。

2.2.3 SI/SP帧的应用原理

问题背景:在播放时有两条码流,码流1(高码率)和码流2(低码率),当想在 码流1 切换至 码流2 时,如果没有S帧,解码器必须等到码流B中出现一个I帧时才能开始解码。但I帧很大,且出现频率低(如2s一个),这使得切换需要很长的响应时间,用户体验差。我们希望能在任何一帧都能立即切换,而不是等待I帧来临时进行切换。

2.2.3.1 SP帧的应用

为了让随时从不同码流切换成为可能,需要如此处理:

  1. 编码器在对码流1、码流2进行编码时,对于两条码流中的第N帧,并不是将其编码为P帧,而是主动编码成SP帧,如图中所示的 S1,n 和 S2,n ,这两帧被称为"主SP帧",相当于是在码流中主动加入的切换点。当单独使用一条码流时,"主SP帧"与P帧的处理并无区别,依旧使用前一帧作为参考帧。
  2. 当客户端请求从码流1切换到码流2时,它正处在码流1中的任意一帧位置,如在N-3帧处。此时服务器需要立即响应,让它能在对应的S帧处无缝从码流1切换至码流2。此时服务器会实时生成一个"辅SP帧"S12,n ,并将其发送给客户端。
  3. 客户端当前还处于码流1,当收到"辅SP帧"S12,n 后,解码器使用码流1的N-1帧作为参考帧,使用辅SP帧可以解出码流2的第N帧,从而实现码流的切换,之后就可以继续解码码流2中的后续帧流。
2.2.3.2 SI帧的应用

上述SP帧的例子提及的是同一图像序列,不同编码参数的,码流流间切换的场景,这种情况码流其实来自于同一信源,相互之间相关性较大。

如果是不同信源情况下的码流切换,如不同视角的多台摄像机的输出码流间切换、电视节目的插入广告等,此时涉及不同图像序列生成码流的问题,如果仅在切换点使用帧间预测的辅SP帧,编码就不会那么有效,此时应该采用空间预测的SI帧。

使用SI帧的例子如图所示,编码阶段还是需要设置主SP帧,但请求切换的场景,服务器生成的帧为"辅帧"SI2,n 。客户端在收到"辅帧"后,不依赖其他参考帧,可以解出码流2的第N帧,之后切换至码流2,码流中的第N+1帧,可以参考"辅帧"SI2,n,解出后续的帧。

综上所述,SP帧与SI帧均可用于流间切换。当视频流的内容相同,编码参数不同时,采用SP帧;而当视频流的内容相差较大,采用SI帧将更加有效。

2.3 Profile(档次)和Level(级别)

H264的Profile(可译为"档次"或"配置集")是标准定义的一组技术特性功能 的集合,用于约束编解码器的复杂度和应用场景。Level则是定义了对应Profile下编解码器所支持的输入视频的分辨率帧率码率等的数据指标,特定性能的设备上流畅解码和播放。

简而言之,Profile限定了编解码器所能使用的编码工具、算法、支持的数据片类型等,描述了其功能集合。Level限定了编解码器在对应工具集(Profile)下的处理能力。两者结合使用确保了视频的编码能力和播放兼容性。

在实际应用中,设备厂商会标明支持的组合(如 High Profile @ Level 4.1),而我们可以根据这些参数,了解到目标播放设备的兼容情况,设置对应的转码参数。

H264主要支持以下四种Profile(不完全):

  1. Baseline,基本档次。支持I片/P片,仅支持Progressive无交错扫描和CAVLC。主要应用于移动设备,无线通信、直播等实时视频,要求低延迟通信的场景。
  2. Extended,扩展/进阶档次。支持I/P/B/SP/SI片,只支持无交错(Progressive)和CAVLC。SP/SI片的引入,支持码流之间的切换(但不常用)。
  3. Main, 主要档次。支持I/P/B片,加权预测的帧间编码(Weighted Prediction),支持CABAC,支持Progressive无交错扫描和Interlaced交错扫描。
  4. High,高级档次。在main Profile 的基础上增加了8x8内部预测、自定义量化、 无损视频编码和更多的YUV格式;(一般有10bit色深的编码需求,如HDR视频,则必须使用High 10 Profile)

想要说明H.264 HP与H.264 MP的区别就要讲到H.264的技术发展了。JVT于2003年完成H.264基本部分标准制定工作,包含Baseline profile、Extended profile和Main profile,分别包括不同的编码工具。之后JVT又完成了H.264 FRExt(即:Fidelity Range Extensions)扩展部分(Amendment)的制定工作,包括High profile(HP)、High 10 profile(Hi10P)、High 4:2:2 profile(Hi422P)、High 4:4:4 profile(Hi444P)4个profile。

H.264 Baseline profile、Extended profile和Main profile都是针对8位样本数据、4:2:0格式的视频序列,FRExt将其扩展到8~12位样本数据,视频格式可以为4:2:0、4:2:2、4:4:4,设立了High profile(HP)、High 10 profile(Hi10P)、High 4:2:2 profile(Hi422P)、High 4:4:4 profile(Hi444P) 4个profile,这4个profile都以Main profile为基础。

在相同配置情况下,High profile(HP)可以比Main profile(MP)节省10%的码流量,比MPEG-2 MP节省60%的码流量,具有更好的编码性能。

profile:

level:

2.4 其他概念

2.4.1 CAVLC(基于上下文的自适应变长编码)和CABAC(基于上下文的自适应二进制算术编码)

​特性​ ​CAVLC​ ​CABAC​
​压缩效率​ 中等(码率较高) 高(节省 10%~30% 码率)
​计算复杂度​ 低(适合实时编码) 高(需更多 CPU/GPU 资源)
​硬件支持​ 简单,广泛兼容 需要较强算力
​Profile 支持​ Baseline/Main/High Main/High(Baseline 不支持)
​适用场景​ 移动设备、低延迟直播 高清电影、高码率存储

3. H264编解码的主要流程

其实H264的编解码流程,网上有很多介绍该部分的内容,尤其是这张图,但我觉得这张图不太好,因为它杂糅了编码和解码的部分,尤其是在说编码过程时喜欢贴上这张图(但其实它包含了解码部分)。当然,我也不想再画一张,所以我打算将这张图P一下拆分来进行叙述。

3.1 编码流程

编码的流程如上图所示,我们也按照这个图来梳理一下编码的流程。当然,细节上可能很复杂,但能有个大概的认识也挺好。

编码器将原始视频帧(YUV)压缩成H264码流,主要包括以下的步骤:

  1. 帧类型划分
    这一步主要是确定图像流里面的编码帧类型,主要是I/P/B帧的划分。在图中没有直接画出来,但是确实是编码的前提。
  2. 宏块划分
    将每帧图像划分成不同的片,并将片划分成16x16的宏块,如果有需要,进一步划分成8x8或4x4的子宏块。宏块是编码的基本单位。这一步也没有在图中直接画出来,但是需要知道,经过这一步,可以得到图中的输入 <math xmlns="http://www.w3.org/1998/Math/MathML"> F n F_{n} </math>Fn。
  3. 预测
    这一步主要是按据帧的类型,进行帧内预测(I帧),或帧间预测(P/B帧),通过减少帧内或帧间的冗余信息来降低数据量。
  • 帧内预测(Intra Prediction)
    用于I帧,利用当前帧内相邻的像素预测当前块的值,消除空间冗余。
  • 帧间预测(Inter Prediction)
    用于P/B帧,通过运动估计(Motion Estimation, ME)和运动补偿(Motion Compensation, MC)减少时间冗余。
    • 运动估计:在当前帧和参考帧之间搜索最匹配的块。
    • 运动补偿:计算运动向量(MV)和残差(Difference)。
      图中 <math xmlns="http://www.w3.org/1998/Math/MathML"> F n F_{n} </math>Fn的两条流向就分别表示帧内预测和帧间预测两种情况,帧间预测还需要参考之前的帧,因此之前的参考帧也会作为输入。
  1. 频域变换(Transform,转换) 这一步主要是通过DCT(离散余弦变换),将残差数据(原始图像 - 预测图像)(帧内预测和帧间预测都有)信号,从空间域(Spatial Domain)变换至频域(Frequency Domain)。这一步是图中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> T T </math>T。
  2. 量化
    量化(Quantization)是一个关键的有损压缩步骤,其核心作用是通过降低数据的精度来大幅减少编码所需的比特数,将数据映射至更小的表达范围 ,同时平衡重构视频的质量与码率。这一步是图中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> Q Q </math>Q
  3. 重排序
    重排序(Reordering)主要用于优化残差系数的表示方式,将量化后二维的频域系数块排序成一维系数序列,以提高后续熵编码(如CAVLC或CABAC)的压缩效率。图中是Reorder。
  4. 熵编码
    熵编码主要是用两种算法CAVLC/CABAC,对残差数据进行bit流编码。CAVLC计算复杂度低,压缩效率适中,适合对实时性或兼容性有要求的情况下使用。CABAC计算复杂度高,具有更高的压缩率,生成的码率更低,更适合如高清视频存储的情况下使用。这一步在图中是"Entropy encode"。
  5. 码率封装
    这一步主要是将编码后的数据,按照NALU进行组织。

3.2 解码流程

解码部分其实在图上反映的更有问题,因为想复用编码部分的内容,但我觉得画成了"四不像",所以我决定自己画一张。

解码过程是编码过程的逆过程,主要包括以下步骤:

  1. NALU解析
    从NALU流中解析出SPS、PPS、不同的I/P/B帧Slice等数据。
  2. 熵解码
    主要是CAVLC/CABAC的解码过程,还原量化后的数据。这一步在图中是"Entropy encode (decoding)"
  3. 重排序 重排序的作用与编码端的重排序相反,目的是将熵解码后的一维系数序列还原为二维的频域系数块,以便后续进行反量化和逆变换进行。
  4. 反量化
    反量化(Inverse Quantization)是将量化后的频域系数重新缩放为变换系数(近似于原始的DCT系数)的步骤。这一过程是编码端量化的逆操作,旨在恢复数据的数值范围。这一步在途中是 <math xmlns="http://www.w3.org/1998/Math/MathML"> Q − 1 Q^{-1} </math>Q−1
    (由于量化过程本身也是有损的过程,所以这一步还原得到的数据,也不是和之前的编码时输入DCT数据完全一致)
  5. 反变换
    反变换则是将反量化后得到的频域数据,重新转换回空间域,得到残差数据。这一步在图中是 <math xmlns="http://www.w3.org/1998/Math/MathML"> Q − 1 Q^{-1} </math>Q−1。
  6. 预测帧重建
    主要是根据帧的类型,对帧的数据进行重建。
  • 帧内预测重建:用于I帧,根据当前残差数据还原完整的块。
  • 帧间预测重建:用于P/B帧,依赖之前已解码帧数据,根据"运动向量(MV)"从参考帧中获取预测块。通过残差和预测块,还原出重建块。
    这一步在图中,是开关的部分,分别有两条路径,分别是帧内和帧间预测重建过程。
  1. 去块滤波
    去块滤波(Deblocking Filter),主要在宏块边界进行平滑滤波,消除块效应(Blocking Artifacts),提高视觉质量。这一步在图中是Filter
  2. 图像输出 重建后的YUV数据进行输出。

4. 数据组成

4.1 NAL、VCL

H264按照功能划分,主要分成两个层次:

  1. VCL层(Video Coding Layer,视频编码层)
    VCL层是H264中对于视频编码的核心层,其主要内容包括对算法核心引擎功能,以及块、宏块、片等语法元素的定义,负责有效表示视频数据的内容,其输出为编码后的原始数据流SODB(String of Data Bits,数据比特串),表示最原始,未经过处理的编码数据。
  2. NAL层(Network Abstraction Layer,网络抽象层)
    NAL层为VCL产生的原始数据提供"打包"和"传输"服务。其定义了片级以上的语法元素,负责以网络所要求的恰当方式来格式化编码数据,提供头信息,以确保数据可以在各种信道和存储介质上运输。其主要功能包括:
    (1)将VCL层中的视频数据封装为NAL单元,并添加了必要的控制信息,如该NAL单元属于哪个序列、帧的类型信息,QoS信息等。
    (2)对不同NALU进行分类和重组,使其达到以包的形式进行传输和存储的目的,从而在网络中被快速传输和解码。

4.2 数据组成

4.2.1 码流的形式

H264的码流由一个个NAL单元组成(NALU,NAL Unit)。这些NALU具体有不同的类型。

NALU格式中并没有要求字段记录NALU的长度,一般有以下方式,来获得NALU的长度并对其进行分割。

  1. AnnexB格式:在NALU前加入起始码,一般为0x000001(3字节)或者0x00000001(4字节)。此外,对于NALU中的可能存在的0x000001数据, 将其转换成0x00000301记录(防竞争码)。
  2. AVCC格式:在NALU前加上4字节的长度。为简化编解码器的设计还是加入了防竞争码,将NALU内的0x000001转换成0x00000301。

4.2.2 NALU的结构

NALU的结构分为两部分,NAL HeaderNAL Body。其中Header部分1个字节。而body部分,准确上来说承载的是EBSP, 它是SODB经过字节对齐、防竞争码插入后的字节数据。

SODB

SODB,VCL层输出的bit流数据。

RBSP

RBSP(Raw Byte Sequence Payload,原始字节序列载荷),相比于VCL输出的比特流SODB,RBSP在其末尾添加了1bit的"1",和若干bit的"0",用于字节对齐。

EBSP

EBSP(Encapsulated Byte Stream Packet,),上面有提及,为分割NALU,在NALU前,会加入起始码0x000001或0x00000001。为防止NALU中的内容和起始码的模式冲突,需要加入防竞争码。在RBSP的基础上加入防竞争码,即是EBSP。

规则: 遍历整个RBSP字节序列,每当遇到连续的两个0x00字节后面跟着一个0x01、0x02或0x03字节时(即模式 0x000000, 0x000001, 0x000002, 0x000003,这个处理方式,其实cover了2bit,相比直接对比是否为0x000001,更宽泛一点点),就在两个0字节后面插入一个特殊的"预防字节"0x03。

  • 0x000000-> 0x0000**03**00
  • 0x000001-> 0x0000**03**01
  • 0x000002-> 0x0000**03**02
  • 0x000003-> 0x0000**03**03

3.2.2.1 NAL Header

NAL Header由一个字节组成,包括1bit禁止位,2bit重要性指示位,5bit NAL类型位。 图中F表示forbidden_zeror_bit,禁止位,1bit,当网络中发现NALU有比特错误时,会将该bit设置为1,以便接受方可以丢弃该单元。

NRI则表示NAL Reference Indicator,NAL参考指示器,该字段有如下作用:

  1. 指示当前的NALU是否为参考帧。NRI为0,表示该帧不为参考帧(如B帧、非参考性P帧),解码后可以丢弃,不会影响后续帧的解码。若不为0,则表示当前NALU属于参考帧(如IDR帧、I帧、参考性P帧),解码器需要保留此帧数据用于后续帧的预测。
  2. 在网络传输时指示NALU的重要性和优先级,值越大,优先级越高,需优先保障传输。可根据该字段进行QoS控制。

TYPE字段则表示nal unit type,NAL类型, 1~23表示单个NAL包,24~31则表示需要分包或者组合发送。

具体了解一下不同的type,实际情况下开发者更多关注type为1,5,6,7,8的内容。

type1:非IDR帧的普通片,一般情况下的I、P、B片数据。视频流中占比最高。

type5:IDR帧的片。

type6:SEI(补充增强信息单元)片。

type7:SPS(序列参数集)片。

type8:PPS(图像参数集)片。

其中1和5构成视频数据的主体,7,8是视频解码必不可少的参数,6是在一些自定义数据的需求场景下,写入额外的数据。

type2~4适用于数据分区的情况,最初是为了优化码流的抗误码能力而生,但目前并不常用 。它将单个分片的编码数据分为

  • Partition A(Type 2):包含关键头部信息(如宏块类型、运动矢量)。
  • Partition B(Type 3):帧内预测数据。
  • Partition C(Type 4):残差和变换系数。 通过优先传输 Partition A(比 B/C 更关键),可以在网络丢包时减少画面损坏。但实际应用中由于编码复杂度高,兼容性差,且现代网络传输和错误恢复技术已能更高效地解决丢包问题,因此并不常用
4.2.2.2 H264 Body

我们以上述这张图来说明H264 Body部分的荷载形式。

按照图中的层次,层一是H264比特流的传输/封装形式,这里列举了两种格式:

(1)Annexb格式,一般单独的H264文件格式常用,会在NALU外添加起始码;

(2)RTP格式,这里是指在网络中使用RTP协议数据包来承载NALU数据包。

当然这一层次还有其他形式,如之前提及过的,使用MP4来封装H264的数据,可以使用AVCC格式。这一维度其实算作H264的承载方式,其实不属于H264的标准内。

层二是具体的NALU结构

层三是片 Slice结构,在上面章节中我们有提到过,片的设计主要为传输层服务,一般是作为传输单元。片的语法结构中,片头规定了片的类型(I、P、B、SP、SI片),所属的图形,有关的参考图像等。片的数据部分,主要包含组成片的一系列宏块。

层四是Slice Data部分,包含组成片的一系列的宏块。

层五是宏块 Macroblock结构,上述章节中提及过,宏块是编解码处理的基本单元。这里图中有提及宏块的类型:

(1)I_PCM 格式:它是 H.264 中一种特殊的 无损编码模式 ,用于直接存储图像像素的原始值,而非通过传统的变换(DCT)和量化进行压缩,码率是常规有损压缩的 5-10 倍,一般用不上

(2)子宏块预测格式:将宏块再划分,按照子模块进行预测和编码。

(3)宏块预测格式:以宏块为单位进行预测和编码,与子宏块预测互斥。

层六是Residual,残差层,是像素残差编码的语法结构。

4.2.2.3 参数集SPS、PPS、SEI

我们在上文中提及过,在H.264中,句法元素被组织成 序列、图像、片、宏块、子宏块五个层次(我们可以理解成图像的逻辑结构),VCL层输出的每一bit,均属于这五个层次之一。

早期标准处理

早期标准(如 H.263/MPEG-2)中,这样的分层结构是物理层级划分,码流中直接包含独立的"序列层头部"和"图像层头部"(这里讨论的是数据组织存放的物理结构,类似于网络数据包的层次)。例如:

  • 序列层头部:必须出现在码流开头,包含分辨率、帧率等全局参数。
  • 图像层头部:重复在每个帧头部,携带当前帧的编码参数。 问题:头部频繁重复,冗余大,且头部一旦丢失,后续数据无法解码。
    在这样的结构中,每一层的头部和它的数据部分形成管理与被管理的强依赖关系,头部的句法元素是该层数据的核心,而一旦头部丢失,数据部分的信息几乎不可能再被正确解码出来,尤其在序列层及图像层。
H264中的分层结构

H264摒弃了传统的物理层级划分,转而通过参数集(Parameter Sets)片层(Slice Layer)的逻辑分层重构头部管理,并将原本属于序列和图像头部的大部分句法元素游离出来形成序列和图像两级参数集,其余的部分则放入片层。
参数集是一个独立的数据单位,不依赖于参数集外的其他句法元素。一个参数集不对应某一个特定的图像或序列,同一序列参数集可以被多个图像参数集引用,同理,同一个图像参数集也可以被多个图像引用。只在编码器认为需要更新参数集的内容时,才会发出新的参数集。

使用参数集的优势:

  • 参数复用:SPS/PPS 可被多个帧或片复用,无需重复传输,避免冗余。
  • 动态切换:同一视频流中可动态切换 PPS(如改变熵编码方式)。
  • 鲁棒性提升:参数集可独立传输(如通过带外信道),避免头部丢失导致全局失效。
参数集的内容
SPS

SPS是序列参数集,其结构如下表所示。

SPS中的主要参数:

字段名 说明
profile_idc profile配置,决定能够使用的编码工具集
level_idc level等级,限制分辨率、码率等性能参数
seq_parameter_set_id SPS的唯一标识,PPS可通过该id索引到对应的SPS
chroma_format_idc 色度采样格式(如4:2:0,4:2:2等)
pic_width_in_mbs_minus1 图像分辨率,以宏块为单位计算,width = (pic_width_in_mbs_minus1 + 1) * 16
pic_height_in_map_units_minus1 图像分辨率,以宏块为单位,height = (pic_height_in_map_units_minus1 + 1) * 16
log2_max_frame_num_minus4 可以确定最大的帧编号的范围(也可以理解成GOP大小,因为IDR帧会刷新帧编号)
max_num_ref_frames 解码器必须维护的最大参考帧的数目,如max_num_ref_frames=4,表示当前帧可以从前4帧中选择最佳匹配块,降低残差的码率
vui_parameters 视频可用性信息,包含时序信息(帧率),宽高比、色彩空间等
PPS

PPS是图像参数集,其结构如下。

字段名 说明
pic_parameter_set_id PPS的唯一标识,分片头部可以通过该id索引到对应的PPS
seq_parameter_set_id PPS所引用的SPS id
entropy_coding_mode_flag 熵编码方式,0=CAVLC, 1=CABAC
num_slice_groups_minus1 分片组数量
num_ref_idx_l0_default_active_minus1 定义前向参考帧列表(List0)的实际激活数目,必须 ≤ max_num_ref_frames.
num_ref_idx_l1_default_active_minus1 定义后向参考帧列表(List1)的实际激活数目(仅B帧有效),须 ≤ max_num_ref_frames.
weighted_pred_flag 是否启用加权预测(用于P/B帧的预测补偿)
pic_init_qp_minus26 I/P/B片使用的,初始量化参数QP,该参数作用于所有分片(Slice)的默认量化步长。
pic_init_qs_minus26 专用于SI/SP片,初始量化参数

SEI

SEI(Supplemental Enhancement Information,补充增强信息),是码流范畴里面的概念,提供了向视频码流中加入信息的办法,是H.264/H.265视频压缩标准的特性之一。SEI消息由payloadType (标识类型)和payload (具体数据)组成,不同payloadType对应不同的字段结构。我们可以使用SEI来写入一些自定义信息。

无论哪种SEI类型,其二进制结构均遵循以下规则:

  1. payloadType:变长编码(通常1字节),标识SEI类型。
  2. payloadSize:变长编码,表示后续负载的字节数。
  3. payload:具体数据(字段结构由payloadType定义)。
  4. 结尾标记:0x80(防止与NAL Unit起始码冲突)。

4.3 码流中的NALU顺序

最后,我们通过上图来了解一下码流中的NALU顺序,基本上,H264码流中必不可少的有SPS、PPS、IDR帧、普通帧(I、P、B)。

5.参考资料

  1. H264编码基本原理(一)
  2. H264的NALU结构
  3. h264编码概述四(宏块定义)
  4. SPS PPS详解
  5. H264格式分析
相关推荐
linux开发之路7 小时前
C++ 音视频开发常见面试题及答案汇总
c++·ffmpeg·音视频·流媒体·音视频编解码
默凉3 天前
ffmpeg 安装
ffmpeg
重启的码农3 天前
云游戏技术之高速截屏和GPU硬编码 (5) 色彩空间转换器 (RGBToNV12)
c++·云计算·音视频开发
音视频牛哥4 天前
RTSP流端口占用详解:TCP模式与UDP模式的对比
音视频开发·视频编码·直播
重启的码农4 天前
云游戏技术之高速截屏和GPU硬编码 (4) NVENC 硬件编码 (NvEncoderD3D11)
c++·云计算·音视频开发
重启的码农4 天前
云游戏技术之高速截屏和GPU硬编码 (3) 桌面复制接口 (Desktop Duplication API)
c++·云计算·音视频开发
微瑟秋风4 天前
Python应用——ffmpeg处理音视频的常见场景
python·ffmpeg
WSSWWWSSW4 天前
警告:OPENCV_FFMPEG_READ_ATTEMPTS (current value is 4096)
人工智能·opencv·ffmpeg
A尘埃4 天前
FFmpeg音视频处理解决方案
ffmpeg·音视频