(本文基本逻辑:视频编码的理论基础是什么 → H.264 视频编码的基本概念、编码工具、编码流程及码流结构 → H.265 的编码工具及改进 → H.266 的编码工具及改进)

跟音频编码一样,视频编码最重要的目的也是为了进行数据压缩,以此来降低数据传输和存储成本

以一路分辨率 720x1280(常说的 720P),帧率为 30 fps 的视频为例,如果不经过编码压缩,直接传输或存储原始的 RGB 数据,对应的码率是:720 * 1280 * 3 * 8 * 30 = 632.8125 Mbps (宽 * 高 * 像素字节数 * 字节比特数 * 帧数)。一分钟的时间所需要的数据量是:632.8125 Mbps * 60s = 4.63 GB。

这个数据量很大,需要很高的存储或传输成本,因此需要采用编码压缩技术以减少码率。

通常,对信息进行压缩,可以从这几个方面着手:

要对视频进行编码,则主要是研究视频数据中的冗余信息,并对其进行压缩。视频信息主要包括这几个方面的冗余:

现在常见的视频编码格式有 3 个大的系列,分别由不同的组织主导制定:

1)ISO-MPEG/ITU-T 系列:由国际标准组织机构(ISO)下属的运动图象专家组(MPEG)和国际电传视讯联盟远程通信标准化组织(ITU-T)开发的系列编码标准。

2)AOM 系列:前身是由 Google 主导的 VPx 系列的编码标准。后续由多家公司组件成立了开放媒体联盟(Alliance for Open Media,AOM)继续开发新的编码标准。

3)AVS 系列:AVS(Audio Video coding Standard)是中国具备自主知识产权的系列编码标准。

这里我们只对 H.264、H.265、H.266 做一下介绍。

1、H.264 编码

1.1、基本概念

1.1.1、句法元素分层结构

H.264 中,句法元素可以分为『序列』、『图像』、『片』、『宏块』、『子宏块』五个层次。

音基础H.264H.265(音基础H.264H.265)(1)

在 H.264 中,分层结构相较之前最大的不同是取消了序列层和图像层,并将原本属于序列和图像头部的大部分句法元素游离出来形成序列和图像两级参数集,其余的部分则放入片层。在这种机制下,由于参数集是独立的,可以被多次重发或者采用特殊技术加以保护。参数集与参数集外部的句法元素处于不同信道中,这是 H.264 的一个建议,我们可以使用更安全但成本更昂贵的通道来传输参数集。

音基础H.264H.265(音基础H.264H.265)(2)

领取音视频开发资料:音视频流媒体高级开发FFmpegWebRTCRTMPRTSPHLSRTP播放器

音基础H.264H.265(音基础H.264H.265)(3)

企鵝君羊994289133领取资料

音基础H.264H.265(音基础H.264H.265)(4)

1.1.2、软件和硬件编解码

编解码分为软编软解和硬编硬解

软编用的是 CPU 处理,优点是调节能力比较强,相对于硬编,软编可以通过参数调整可以在同一码率下编码出清晰度更高的视频,此外软编可以兼容性更好,可以适配所有设备,但缺点是性能可能比较差,不如硬编速度快、功耗低。

软解相对于硬解,则是性能可能较差,不如硬解功耗低,但是兼容性更好,适配性更好。

目前移动应用大部分业务场景采用的编码策略是:手机端尽量采用硬编编码出一路高清的视频,将高清视频发送给服务器,由服务器再进行软编转码为多路码率的视频,再通过 CDN 分发给观看端。另外,安卓的一些低端机可能由于硬件问题对硬编支持不完善,这时候可以使用软编,或者硬编出错的情况可以切换为软编来兜底。当然有时候,对于一些性能优越的高端机型或者编码时长不多的业务场景也可以优先用软编,例如录制 15 秒短视频的场景,首先时间比较短并且机器性能高不怕 CPU 消耗,这样相同码率可以再提高清晰度。

对于大部分的应用场景的解码策略则主要采用硬解,用软解作为兜底。此外,对于一些硬解不支持的编码类型,可以使用软解,比如有的机型不支持 H.265 解码,则只能使用软解。

1.1.3、序列

H.264 编码的方式可以这样理解:在视频中,一段时间内相邻的图像的像素、亮度与色温的差别通常很小。所以没必要去对一段时间内的每一幅图像都进行完整一帧的编码,而是可以选取这段时间的第一帧图像进行完整编码,而下一幅图像只记录与第一帧完整编码图像的像素、亮度与色温等特征的差别即可,以此类推循环下去。

什么叫序列呢?上述的这段时间内图像变化不大的图像集就可以称之为一个序列。序列可以理解为有相同特点的一段图像数据。但是如果某个图像与之前的图像变换很大,很难参考之前的帧来生成新的帧,那么就结束上一个序列,开始下一个序列。重复上述做法,生成新的一段序列。

1.1.4、帧类型

H.264 结构中,一幅视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成,一个宏块由 16x16 的 YUV 数据组成。宏块是 H.264 编码的基本单位。

在 H.264 协议内定义了三种帧,分别是 I 帧、B 帧与 P 帧。

1)I 帧

I 帧,即帧内编码图像帧,不参考其他图像帧,只利用本帧的信息进行编码。

I 帧的特点:

I 帧编码流程:

2)P 帧

P 帧,即预测编码图像帧,利用之前的 I 帧或 P 帧,采用运动预测的方式进行帧间预测编码。

P 帧的预测与重构:P 帧是以 I 帧为参考帧,在 I 帧中找出 P 帧『某点』的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运动矢量从 I 帧中找出 P 帧『某点』的预测值并与差值相加以得到 P 帧『某点』样值,从而可得到完整的 P 帧。

P 帧特点:

P 帧编码的基本流程:

3)B 帧

B 帧,即双向预测编码图像帧,提供最高的压缩比,它既需要之前的图像帧(I 帧或 P 帧),也需要后来的图像帧(P 帧),采用运动预测的方式进行帧间双向预测编码。

B 帧的预测与重构:B 帧以前面的 I 或 P 帧和后面的 P 帧为参考帧,找出 B 帧『某点』的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运动矢量在两个参考帧中找出预测值并与差值求和,得到 B 帧『某点』样值,从而可得到完整的 B 帧。

B 帧特点:

B 帧编码的基本流程:

率失真函数的相关简介:

有损压缩算法,性能由编码输出的比特率失真共同决定。

编码的目的:就是在保证一定视频质量的条件下尽量减少编码比特率,或在一定编码比特率限制条件下尽量地减小编码失真。

编码器的工作:根据以上率失真准则找到最佳编码参数。

信息论中率失真概念:在允许一定程度失真的条件下,能够把信源信息压缩到什么程度,即最少需要多少比特数才能描述信源。由此得到率失真函数:R(D) = min I(X, Y),它给出了限定失真条件下信息压缩允许的下界。但其在视频编码中难以应用,因为各种概率和条件概率未知,只能作为理论值。

视频编码中的率失真曲线:为了研究视频码率与视频质量的平衡。由于系统性,不能达到理论上的 R(D) 值,只能由不同的编码参数(如 QP 和选择的模式)得到有限的 (R, D) 可操作点,形成凸包络。

视频编码中的率失真优化(RDO):遍历所有的参数候选模式对视频进行编码,满足码率限制的失真最小的一组参数集作为最优的视频编码参数。每一层级都找出,最终使整体系统性能最优。这里假设了无相关性的独立优化,如相关性较强则共同优化。

音基础H.264H.265(音基础H.264H.265)(5)

1.1.5、DTS 和 PTS

DTS、PTS 的概念如下所述:

需要注意的是:虽然 DTS、PTS 是用于指导播放端的行为,但它们是在编码的时候由编码器生成的。

当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是一致的。但如果有 B 帧时,就回到了我们前面说的问题:解码顺序和播放顺序不一致了。

比如一个视频中,帧的显示顺序是:I B B P,现在我们需要在解码 B 帧时知道 P 帧中信息,因此这几帧在视频流中的顺序可能是:I P B B,这时候就体现出每帧都有 DTS 和 PTS 的作用了。DTS 告诉我们该按什么顺序解码这几帧图像,PTS 告诉我们该按什么顺序显示这几帧图像。顺序大概如下:

Stream: I P B B DTS: 1 2 3 4 PTS: 1 4 2 3

1.1.6、GOP

GOP(Group Of Pictures)是图像组的概念,它指的是视频编码序列中两个 I 帧之间的距离。通常意义上的 GOP 由 I 帧开始,到下一个 I 帧之前的帧结束。严格意义上讲,这个 I 帧是一个 IDR 帧。

H.264 使用的是封闭 GOP(Closed GOP),即在一个 GOP 中所有帧的解码不依赖该 GOP 外的其他帧,除了第一帧必须是 I 帧,其他帧可以是 P 帧或 B 帧。

音基础H.264H.265(音基础H.264H.265)(6)

上图中是一个 GOP 为 15 帧的例子,如果视频的帧率是 15 fps,那么这个 GOP 就是 1s 时长。

关键帧的间隔调节会影响 GOP 的长度,进而影响到读取 GOP 的速度,为防止运动变化,一个 GOP 组内帧数不宜取多。如果关键帧的间隔设置过大的话(GOP 长度过大),在必须用到关键帧的场合就可能被迫使用 B/P 帧来代替,这就会降低画面质量。

1.1.7、IDR 帧

IDR 帧全称叫做 Instantaneous Decoder Refresh,是 I 帧的一种。IDR 帧的作用是立刻刷新,重新算一个新的序列开始编码,使错误不致传播。I 帧有被跨帧参考的可能,但 IDR 帧不会。

比如:

IDR1 P2 B3 B4 P5 B6 B7 I8 B9 B10 P11 B12 B13 P14 B15 B16

这里的 B8 可以跨过 I8 去参考 P7。

IDR1 P2 B3 B4 P5 B6 B7 IDR8 B9 B10 P11 B12 B13 P14 B15 B16

这里的 B9 就不可以参考 IDR8 前面的帧。

H.264 引入 IDR 帧是为了解码的重同步,当解码器解码到 IDR 帧时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现错误,在这里可以获得重新同步的机会,不会将错误传导下去。IDR 帧之后的帧永远不会使用 IDR 帧之前的帧来解码。

所以总结下来,IDR 帧有如下特性:

1.1.8、压缩方式

H.264 采用的核心算法是『帧内压缩』和『帧间压缩』,帧内压缩是生成 I 帧的算法,帧间压缩是生成 B 帧和 P 帧的算法。

帧内压缩也称为空间压缩。当压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息,这实际上与静态图像压缩类似。帧内一般采用有损压缩算法,由于帧内压缩是编码一个完整的图像,所以可以独立的解码、显示。帧内压缩一般达不到很高的压缩率,跟编码 JPEG 差不多。

帧间压缩的原理是:相邻几帧的数据有很大的相关性,或者说前后两帧信息变化很小的特点。也即连续的视频其相邻帧之间具有冗余信息,根据这一特性,压缩相邻帧之间的冗余量就可以进一步提高压缩量,减小压缩比。帧间压缩也称为时间压缩,它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的。帧差值算法是一种典型的时间压缩法,它通过比较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值,这样可以大大减少数据量。

编码压缩的步骤大致如下:

1.2、分层结构

H.264 的主要目标是为了有高的视频压缩比和良好的网络亲和性,为了达成这两个目标,H.264 的解决方案是将系统框架分为两个层面:『视频编码层面(VCL)』和『网络抽象层面(NAL)』。

音基础H.264H.265(音基础H.264H.265)(7)

从视频编码层(VCL)角度去看 H.264 的结构:

音基础H.264H.265(音基础H.264H.265)(8)

从网络适配层(NAL)角度去看 H.264 的结构:

音基础H.264H.265(音基础H.264H.265)(9)

1.3、编码工具

下图为一个典型的视频编码器。在进行当前信号编码时,编码器首先会产生对当前信号做预测的信号,称作预测信号(Predicted Signal),预测的方式可以是时间上的帧间预测(Inter Prediction),亦即使用先前帧的信号做预测,或是空间上的帧内预测(Intra Prediction),亦即使用同一张帧之中相邻像素的信号做预测。得到预测信号后,编码器会将当前信号与预测信号相减得到残差信号(Residual Signal),并只对残差信号进行编码,如此一来,可以去除一部分时间上或是空间上的冗余信息。接着,编码器并不会直接对残差信号进行编码,而是先将残差信号经过变换(通常为离散余弦变换)然后量化以进一步去除空间上和感知上的冗余信息。量化后得到的量化系数会再透过熵编码,去除统计上的冗余信息。

音基础H.264H.265(音基础H.264H.265)(10)

H.264 的编解码流程如下:

音基础H.264H.265(音基础H.264H.265)(11)

1.3.1、帧内预测

一般来说,对于一帧图像,相邻两个像素的亮度和色度值之间通常是比较接近的,也就是颜色是逐渐变化的,不会一下子突变成完全不一样的颜色。而进行视频编码,目的就是利用这个相关性,来进行压缩。帧内预测就是基于这个原理。

假设现在我们要对一个像素 X 进行编码,在编码这个像素之前,我们找到它临近的像素作为参考像素 X’,根据 X’ 我们经过预测算法得到对像素 X 的预测值 Xp,然后我们再用 X 减去 Xp 得到二者的残差 D,并用这个残差 D 代替 X 进行编码,起到节省码率的作用。最后,我们还用预测值 Xp 和残差 D 相加得到 X’ 用于下一个像素的预测。这个就是我们用帧内预测进行编码压缩的大体思想。

在实际编码中,我们固然可以按像素为单位进行预测,但这样效率比较低,所以在 H.264 标准中提出按照块为单位进行计算。一个宏块是 16x16 像素,它可以分成子块,最小是 4x4 的(这个大小是对于亮度编码而言,至于色度编码,4:2:0 采样格式的色度宏块的长和宽都是亮度宏块的一半),这样能大大提高计算速度。

在帧内预测模式中,预测块是基于已编码重建的块和当前块形成的。对亮度像素而言,预测块用于 4×4 子块或者 16×16 宏块的相关操作。4×4 亮度子块有 9 种可选预测模式,独立预测每一个 4×4 亮度子块,适用于带有大量细节的图像编码;16×16 亮度块有 4 种预测模式,预测整个 16×16 亮度块,适用于平坦区域图像编码;色度块也有 4 种预测模式,类似于 16×16 亮度块预测模式。编码器通常选择使预测块和编码块之间差异最小的预测模式。

1.3.2、帧间预测

帧间预测就是时域预测,旨在消除时域冗余信息,简单点说就是利用之前编码过的图像来预测要编码的图像。其中涉及到两个重要的概念:运动估计和运动补偿。

运动估计是寻找当前编码的块在已编码的图像(参考帧)中的最佳对应块,并且计算出对应块的偏移(运动矢量)。

运动补偿是根据运动矢量和帧间预测方法,求得当前帧的估计值过程。其实就是将运动矢量参数贴到参考帧上获取当前帧。另外运动补偿是一个过程。

H.264 帧间预测是利用已编码视频帧/场和基于块的运动补偿的预测模式。与以往标准帧间预测的区别在于块尺寸范围更广(从 16×16 到 4×4)、亚像素运动矢量的使用(亮度采用 1/4 像素精度 MV)及多参考帧的运用等等。

每个宏块(16×16 像素)可以 4 种方式分割:一个 16×16,两个 16×8,两个 8×16,四个 8×8。其运动补偿也相应有四种。而 8×8 模式的每个子宏块还可以四种方式分割:一个 8×8,两个 4×8 或两个 8×4 及 4 个 4×4。这些分割和子宏块大大提高了各宏块之间的关联性。这种分割下的运动补偿则称为树状结构运动补偿。

每个分割或子宏块都有一个独立的运动补偿。每个 MV 必须被编码、传输,分割的选择也需编码到压缩比特流中。对大的分割尺寸而言, MV 选择和分割类型只需少量的比特,但运动补偿残差在多细节区域能量将非常高。小尺寸分割运动补偿残差能量低,但需要较多的比特表征 MV 和分割选择。分割尺寸的选择影响了压缩性能。整体而言,大的分割尺寸适合平坦区域,而小尺寸适合多细节区域。

宏块的色度成分(Cr 和 Cb)则为相应亮度的一半(水平和垂直各一半)。色度块采用和亮度块同样的分割模式,只是尺寸减半(水平和垂直方向都减半)。例如,8×16 的亮度块相应色度块尺寸为 4×8,8×4 亮度块相应色度块尺寸为 4×2 等等。色度块的 MV 也是通过相应亮度 MV 水平和垂直分量减半而得。

1.3.3、变换和量化

绝大多数图像都有一个共同的特征:平坦区域和内容缓慢变化区域占据一幅图像的大部分,而细节区域和内容突变区域则占小部分。也可以说,图像中直流和低频区占大部分,高频区占小部分。这样,空间域的图像变换到频域或所谓的变换域,会产生相关性很小的一些变换系数,并可对其进行压缩编码,即所谓的变换编码。

此外,为了减小图像编码的动态范围,一般也会进行量化。

在图像编码中,变换编码和量化从原理上讲是两个独立的过程。但在 H.264 中,将两个过程中的乘法合二为一,并进一步采用整数运算,减少编解码的运算量,提高图像压缩的实时性。

H.264 对图像或预测残差采用了 4×4 整数离散余弦变换(DCT)技术,避免了以往标准中使用的通用 8×8 离散余弦变 换逆变换经常出现的失配问题。量化过程根据图像的动态范围大小确定量化参数,既保留图像必要的细节,又减少码流。

1.3.4、熵编码

熵的大小与信源的概率模型有着密切的关系,各个符号出现的概率不同,信源的熵也不同。当信源中各事件是等概率分布时,熵具有极大值。信源的熵与其可能达到的最大值之间的差值反映了该信源所含有的冗余度。信源的冗余度越小,即每个符号所独立携带的信息量越大,那么传送相同的信息量所需要的序列长度越短,符号位越少。因此,数据压缩的一个基本的途径是去除信源的符号之间的相关性,尽可能地使序列成为无记忆的,即前一符号的出现不影响以后任何一个符号出现的概率。

利用信源的统计特性进行码率压缩的编码就称为熵编码,也叫统计编码。熵编码是无损压缩编码方法,它生成的码流可以经解码无失真地恢复出原数据。熵编码是建立在随机过程的统计特性基础上的。

视频编码常用的有两种:变长编码(哈夫曼编码)、算术编码。

H.264 最后将结果进行熵编码,分为上下文自适应的变长编码(Context-based Adaptive Variable-Length Coding,CAVLC)与上下文自适应的二进制算术编码(Context-based Adaptive Binary Arithmetic Coding,CABAC)。

在 H.264 的 CAVLC(基于上下文自适应的可变长编码)中,通过根据已编码句法元素的情况动态调整编码中使用的码表,取得了极高的压缩比。CAVLC 用于亮度和色度残差数据的编码。残差经过变换量化后的数据表现出如下特性:4×4 块数据经过预测、变换、量化后,非零系数主要集中在低频部分,而高频系数大部分是零;量化后的数据经过 zig-zag 扫描,DC 系数附近的非零系数值较大,而高频位置上的非零系数值大部分是 1 和 -1;相邻的 4×4 块的非零系数的数目是相关的。CAVLC 充分利用残差经过整数变换、量化后数据的特性进行压缩,进一步减少数据中的冗余信息,为 H.264 卓越的编码效率奠定了基础。

算术编码的思想是用 0 到 1 的区间上的一个数来表示一个字符输入流,它的本质是为整个输入流分配一个码字,而不是给输入流中的每个字符分别指定码字。算术编码是用区间递进的方法来为输入流寻找这个码字的,它从于第一个符号确定的初始区间(0 到 1)开始,逐个字符地读入输入流,在每一个新的字符出现后递归地划分当前区间,划分的根据是各个字符的概率,将当前区间按照各个字符的概率划分成若干子区间,将当前字符对应的子 2 区间取出,作为处理下一个字符时的当前区间。到处理完最后一个字符后,得到了最终区间,在最终区间中任意挑选一个数作为输出。解码器按照和编码相同的方法和步骤工作,不同的是作为逆过程,解码器每划分一个子区间就得到输入流中的一个字符。在实际过程中,输入流中字符的概率分布是动态改变的,这需要维护一个概率表去记录概率变化的信息。在作递进计算时,通过对概率表中的值估计当前字符的概率,当前字符处理后,需要重新刷新概率表。这个过程表现为对输入流字符的自适应。编码器和解码器按照同样的方法估计和刷新 概率表,从而保证编码后的码流能够顺利解码。

用哈夫曼编码,必须为所有可能的长度为 N 的序列设计和存储码书,这样做的复杂度随 N 呈指数增长。用算术编码则不需要预先为每个可能的信源序列指定码书。而是每当所确定区间的下限和上限有公共最高有效位时,就可以连续地得到比特。编码序列的长度可以和信源的长度一样长。因此,实际上,算术编码可以更接近熵率。

算术编码的另一个优点是可以简单地通过更新符号概率表来实现对信源统计特性的自适应。通过对不同上下文用不同的概率表也可以容易地实现条件编码。对于哈夫曼编码,则不得不基于更新的概率表重新设计码书,或对不同的上下文设计多个码表。

由于较高的编码效率和易于自适应,只要所涉及的计算量是能接受的,无疑算术编码比哈夫曼编码是一种更好的选择。

1.4、码流结构

1.4.1、原始码流

H.264 原始码流(又称为裸流),是由一个接一个的 NAL 单元组成的(NAL Header 加上 RBSP 组成一个 NAL 单元),结构如下图所示:

音基础H.264H.265(音基础H.264H.265)(12)

在网络传输的环境下,编码器将每个 NAL 各自独立、完整地放入一个分组,由于分组都有头部,解码器可以很方便地检测出 NAL 的分界,依次取出 NAL 进行解码。为了节省码流,H.264 没有另外在 NAL 的头部设立表示起始的句法元素。但是如果编码数据是储存在介质(如 DVD 光盘)上,由于 NAL 是依次紧密排列,解码器将无法在数据流中分辨每个 NAL 的起始和终止,所以必须要有另外的机制来解决这个问题。

针对这个问题,H.264 草案的附录 B 中指明了一种简单又高效的方案。当数据流是存储在介质上时,在每个 NAL 前添加起始码:0x000001。这就是我们常说的 Annex-b 码流格式

在某些类型的介质上,为了寻址的方便,要求数据流在长度上对齐,或必须是某个常数的倍数。考虑到这种情况,H.264 建议在起始码前添加若干字节的 0 来填充,直到该 NAL 的长度符合要求。在这样的机制下,解码器在码流中检测起始码,作为一个 NAL 的起始标识,当检测到下一个起始码时当前 NAL 结束。H.264 规定当检测到 0x00000001 时也可以表征上一个 NAL 的结束,下一个 NAL 开始,这是因为连着的三个字节的 0 中的任何一个字节的 0 要么属于起始码要么是起始码前面添加的 0。

添加起始码是一个解决问题的很好的方法,但上面关于起始码的介绍还不完整,因为忽略了一个重要的问题:如果在 NAL 内部出现了 0x000001 或是 0x00000001 的序列怎么办?毫无疑问这种情况是致命的,解码器将把这些本来不是起始码的字节序列当作起始码,而错误地认为这里往后是一个新的 NAL 的开始,进而造成解码数据的错位!而我们做的大量实验证明,NAL 内部经常会出现这样的字节序列。因为 0x000001 的情况是覆盖 0x00000001 的情况,所以下面值讨论如何处理 0x000001 即可。

于是 H.264 提出了另外一种机制,叫做防止竞争,在编码器编码完一个 NAL 时,应该检测是否出现下表左侧中的四个字节序列,以防止它们和起始码竞争。如果检测到这些序列存在,编码器将在最后一个字节前插入一个新的字节:0x03,从而使它们变成下表右侧的样子。当解码器在 NAL 内部检测到有 0x000003 的序列时,将把 0x03 抛弃,恢复原始数据。

0x000000 → 0x00000300 0x000001 → 0x00000301 0x000002 → 0x00000302 0x000003 → 0x00000303

上表中的前两个序列我们前文中已经提到,第三个 0x000002 是作保留用,而第四个 0x000003是为了保证解码器能正常工作,因为我们刚才提到,解码器恢复原始数据的方法是检测到 0x000003 就抛弃其中的 0x03,这样当出现原始数据为 0x000003 时会破坏数据,所以必须也应该给这个序列插入 0x03。

解码器在逐个字节地读一个 NAL 时并不同时对它解码,而是要通过起始码机制将整个 NAL 读进、计算出长度后再开始解码。

到此,我们就知道如何在原始码流里分割 NAL 单元了。接下来,我们再来了解每个 NAL 单元的结构。

1.4.2、NAL 单元

NAL 单元由 NAL Header 和 RBSP 构成。

NAL Header 的结构如下:

音基础H.264H.265(音基础H.264H.265)(13)

音基础H.264H.265(音基础H.264H.265)(14)

nal_unit_type=5 时,表示当前 NAL 是 IDR 图像的一个片,在这种情况下,IDR 图像中的每个片的 nal_unit_type 都应该等于 5。注意 IDR 图像不能使用片分区。

1.4.3、RBSP

前面也介绍过,RBSP 指原始字节载荷,它是 NAL 单元的数据部分的封装格式,封装的数据来自 SODB(原始数据比特流)。SODB 是编码后的原始数据,SODB 经封装为 RBSP 后放入 NAL 的数据部分。

从 SODB 到 RBSP 的生成过程:

1.4.4、NAL 单元的不同类型

上面讲到了 NAL 单元是有多种类型的,这里我们就其中重要的几种类型做一下讲解:

序列参数集、图像参数集与图像、片之间的关系:

音基础H.264H.265(音基础H.264H.265)(15)

音基础H.264H.265(音基础H.264H.265)(16)

1)序列参数集 SPS

SPS 中保存了一组编码后的图像序列的依赖的全局参数。

SPS 中的信息至关重要,如果其中的数据丢失,解码过程就可能失败。SPS 和 PPS 通常作为解码器的初始化参数。一般情况,SPS 和 PPS 所在的 NAL 单元位于整个码流的起始位置,但是在某些场景下,在码率中间也可能出现这两种结构:

音基础H.264H.265(音基础H.264H.265)(17)

SPS 其中的关键参数包括:

2)图像参数集 PPS

PPS 中保存了每一帧编码后的图像所依赖的参数。

音基础H.264H.265(音基础H.264H.265)(18)

PPS 其中的关键参数包括:

3)补充增强信息 SEI

SEI 即补充增强信息(Supplemental Enhancement Information),属于码流范畴,它提供了向视频码流中加入额外信息的方法,是 H.264 标准的特性之一。

SEI的基本特征如下:

也就是说,视频编码器在输出视频码流的时候,可以不提供 SEI 信息。虽然在视频的传输过程、解封装、解码这些环节,都可能因为某种原因丢弃 SEI 内容,但在视频内容的生成端和传输过程中,是可以插入 SEI 信息的。这些插入的信息,和其他视频内容一同经过传输链路到达消费端。

SEI 是一种 NAL 单元类型。它的结构大致如下:

// H.264 0x06,n 个 FF 字节 1 个非 FF 字节,16 字节 UUID,userData,0x80 或 0x0080

4)片 Slice

一帧图像可编码成一个或者多个片,每片包含整数个宏块,分片的目的是为了限制错误码的扩散和传输,使编码片相互间保持独立。

片的结构:

音基础H.264H.265(音基础H.264H.265)(19)

Slice 中的关键参数包括:

音基础H.264H.265(音基础H.264H.265)(20)

音基础H.264H.265(音基础H.264H.265)(21)

,