/H264 Encode

Created Tue, 23 Jan 2024 01:05:20 +0800 Modified Sat, 27 Apr 2024 06:25:32 +0000
13713 Words 62 min

编码框架

编码器包含两个方向的码流分支:

image-20240123002928114

从左到右的前向码流分支为编码过程;

从右到左的反向码流分支为重建过程。

前向编码分支

以 16x16 像素的 MB 为单位进行处理,首先从当前输入的视频图像(Frame or Field)中取一个待编码宏块$F_n$,该宏块以帧内或者帧间的模式进行编码,生成一个预测宏块$P$。

如果是帧内编码,$P$由当前 Slice 里面已经编码、解码、重构并且还没进行去块滤波的宏块 $μF_n’$ 使用帧内预测得到。当前宏块 $μF_n’$ 减去预测宏块 $P$,得到残差块$D_n$,对残差块 $D_n$ 进行整数变换(一般是 4x4,或者 8x8)、量化后得到一组系数 $X$ ,再对 $X$ 进行重排序和熵编码,就完成了一个宏块的编码过程。对于 P 帧和 B 帧,如果 ME 时候找不到最佳匹配块那也会使用帧内预测编码。

经过熵编码的码流加上宏块解码所需的一些信息,如预测模式、量化步长、描述宏块运动预测补偿的运动矢量信息等,就组成了该宏块压缩后的码流,Slice 中所有 MB 的码流加上 Slice 头信息就组成了 Slice 的编码码流,再通过 NAL 层进行传输或存储。图像参数集 PPS 和序列参数集 SPS 则由 NAL 单独进行传输。

后向重建分支

在后向重建分支中,对量化后的宏块系数 $X$ 进行解码从而得到重建宏块,后续宏块进行编码需要从已重建的宏块中寻找参考块。宏块重建过程如下: 宏块系数 $X$ 经过反量化和反变换之后,得到残差宏块 $D_n$ 的近似值 $D_n’$ ,预测块 $P$ 加上 $D_n’$ 得到未滤波的重构宏块 $μF_n’$ ,再做环路滤波来减少块效应,即得到了最终的重构宏块 $F_n’$ ,当图像中所有宏块都重建完成后,就形成了重建图像。

后向重建分支其实就是包含在编码中的完整解码流程,与真正解码器的唯一区别是: 其预测块 P 直接从前向编码分支中得到,而真正的解码器需要利用码流中解出的预测块信息获得预测块 P。当前图像的已重建宏块会被用做帧内预测的参考,而完整的重建图像会被加入参考帧列表,作为未来编码图像帧预测的参考图像。

预测编码

预测编码(Prediction Coding)利用相邻像素之间的空间和时间相关性,用已传输的像素对当前正在编码的像素进行预测,然后对预测值和真实值的差值——预测误差进行编码和传输。

消除空间冗余:利用一帧图像已经编码的部分来预测还没有被编码的部分

消除时间冗余:利用之前编码过的图像来预测当前图像需要编码的部分

通过预测可以得到预测值,预测值通常不等于实际值,所以用实际值减去预测值可以得到预测残差。预测方法越好,残差越小,因而对残差进行编码得到的码流要比对实际值直接进行编码的码流要小

在解码端可以解码出残差,使用与编码端相同的预测过程来获取预测值,加上残差便可以得到相应的实际值。

目前使用较广的预测方法是线性预测。用已传像素的线性组合对正在编码的像素进行预测。

编码器输出预测值和实际值之间的差值,选择最优的预测系数,使预测误差的分布在 0 附近。经过非均匀量化之后,最终传输的是量化之后的预测误差。

重建的图像与编码前的图像之间的区别是量化带来的。

如果取消量化操作,那么预测编码和解码就是一个无失真的编解码系统,但是这样的压缩率要远远低于使用量化的压缩率。

量化的事实依据是图像中存在人眼感知并不明显的区域,通过非均匀量化可以过滤掉这部分数据(也被称为量化噪声),能达到提高压缩率但不降低主观质量的效果。

帧间预测

帧间预测消除的是时间冗余,主要利用的是运动估计和运动补偿。

运动估计 ME 的过程其实就是计算运动向量 MV 的过程:

寻找当前编码的块在已编码图像中的最佳对应快,并且计算出对应块的偏移量:运动向量

假设当前编码块是 $B$ ,在参考帧 $P_r$ 中寻找与 $B$ 块相减残差最小的块 $B_r$ , $B_r$ 就是 $B$ 块的最佳匹配块。

$$ MV=B_r-B=(x_r - x,y_r - y) $$

$B_r$ 块就是 $B$ 块的参考块, $B_r$ 的像素值就作为 $B$ 块像素值的预测值。 运动向量 MV 也需要用合适的方式编码到码流中。

运动补偿 MC:根据 MV 和帧间预测方法,求出当前帧的估计值的过程。

当前帧的估计值是对当前图像的描述,用来说明当前图像的每一个像素怎么由它的参考图像的像素块得到。

ME 是动态的过程,MC 是静态的描述。

  • ME 设计很多算法和技巧。

  • MC 可以看作是索引表,一个描述像素块的最佳匹配块分布情况的索引表。

实际计算过程中会对树状结构分块的 8 种模式都尝试一遍。 H264 支持亮度 1/4 像素、色度 1/8 像素的运动估计,在亚像素 ME 之前需要先用插值法得到亚像素值。

  • 在噪声大的视频中,提高搜索精度没法提高预测精确度。

  • 在噪声小的视频中,1/4 像素精度可以达到比较好的预测效果。

  • 视频会议里 1/2 像素精度基本可以满足需求。

运动估计算法

假设匹配误差随着离全局误差最小点的距离增加而单调增加,从原点开始,采用固定的搜索模板和搜索策略得到最佳匹配块。

理论最优的算法是全搜索法,但是计算量巨大,效率低, 一般作为其他搜索算法的一种效率参考。

还有其他的快速法:X264 里面主要使用的是钻石搜索法 DIA、六边形搜索法 HEX 和 UMH。

钻石搜索法以搜索起点为中心,先用 LDSP 进行搜索,直到最佳匹配点位于大菱形的中心位置,然后再用小菱形搜索,直至最佳匹配点位于小菱形的中心位置。

六边形搜索法采用 1 个大模板(六边形模板)和 2 个小模板(小菱形模板和小正方形模板)。

  • 步骤 1:以搜索起点为中心,采用图中左边的六边形模板进行搜索。计算区域中心及周围 6 个点处的匹配误差并比较,如最小 MBD 点位于模板中心点,则转至步骤 2;否则以上一次的 MBD 点作为中心点,以六边形模板为模板进行反复搜索。
  • 步骤 2:以上一次的 MBD 点为中心点,采用小菱形模板搜索,计算各点的匹配误差,找到 MBD 点。然后以 MBD 点为中心点,采用小正方形模板搜索,得到的 MBD 点就是最优匹配点。

UMH 是基于 MV 具有时空相关性,所以可以结合上一帧和上一步中 MV 的方向和角度,来修改多层六边形的形状,UMH 算法包含四中搜索模式:不均匀交叉搜索、多六边形网格搜索、迭代六边形搜索、菱形搜索。

  • 步骤 1:进行一次小菱形搜索,根据匹配误差值和两个门限值(对于一种尺寸的宏块来说是固定大小的 threshold1 和 threshold2)之间的关系作相应的处理,可能用到中菱形模板或者正八边形模板,也有可能直接跳到步骤 1。
  • 步骤 2:使用非对称十字模板搜索。“非对称”的原因是一般水平方向运动要比垂直方向运动剧烈,所以将水平方向搜索范围定为 W,垂直方向搜索范围定为 W/2。
  • 步骤 3:使用 5x5 逐步搜索模板搜索。
  • 步骤 4:使用大六边形模板搜索。
  • 步骤 5:使用六边形搜索算法找到最优匹配点。

码率不变的前提下,“Dia”、“HEX”、“UMH”编码获得的质量依次提高,速度依次降低。快速算法(“Dia”、“HEX”、“UMH”)的编码质量比全搜索算法低不了太多,但是速度却高了很多倍。

多参考帧预测

编码端存储参考帧的缓冲区就是 DPB,主要有三种参考帧:短期参考帧、长期参考帧和非参考帧。

  • 短期参考帧就是和当前帧相邻的帧,按照从近到远的顺序排序。
  • 长期参考帧是较早之前的帧,按照从远到近的方式排列。
  • 不使用的参考帧是因为某些原因废弃了的参考帧,并且没有被新的参考帧替换掉。比如遇到 IDR 帧的时候 DPB 里面的所有参考帧都会标记成非参考状态。所以 I 帧之后的 P 帧也可以参考这个 I 帧之前的图像。

MV 预测和 Skip 模式

需要对 MV 进行压缩,方式是使用临近分块 MV 之前的相关性对当前块的 MV 进行预测,只对预测残差 MVD 进行编码。

Skip 模式:Skip 模式只针对宏块编码,也就是完全不用编码只需要在码流里面标明是 SKIP 宏块就行。P_Skip 宏块就是 COPY 宏块,既没有 MVD,也不编码量化残差,解码时候直接用 MVp 作为运动向量得到像素的预测值作为像素重建值。B_Skip 宏块也是既没有 MVD 也没有量化残差,解码时候通过 Direct 预测模式计算出前向和后向 MV,然后得到像素预测值作为重建值。

  • P_Skip:最佳模式是 Inter16x16、参考帧是 List0 里面的第一个参考帧、MVD=0、变换系数被量化成 0,或者在 RDO 模型中被抛弃。

  • B_Skip:最佳模式是 B_direct_16x16,变换系数要么全是 0 要么被算法抛弃。

加权预测

用来应对明暗/亮度变化的场景,使用两种预测模式:显式模式(P 帧、B 帧)和隐式模式(B 帧)。

  • 显式模式:加权系数由编码器决定并且在 Slice Header 里面传输。
  • 隐式模式:加权系数由参考图像的时间位置推算,越接近当前图像,加权系数越大。

步骤:

  1. 亮度变化检测:

    使用直方图计算前后两幅图像在各个灰度级别 SAD,然后采用阈值法判断有没有发生亮度变化。

  2. 计算加权系数: 第一种方法:计算参考帧和当前帧的亮度均值比值,利用比值作为加权系数(全局加权补偿的效果有限)

    第二种方法:全局补偿,但是使用使前后图像 MSE 和偏移量最小的加权系数:使 MSE 的表达式(加权系数$W_1$和偏移量$W_2$)分别基于$W_1$和$W_2$的偏微分等于 0。

  3. 亮度补偿:

    原则上需要做到宏块级别的亮度补偿,但是为了降低复杂度只做帧级亮度补偿。

    根据求出的加权系数和偏移量进行全局亮度补偿得到新图像。

    把新图像存到一个列表中作为带亮度补偿运动估计的参考帧。

帧内预测

帧内预测实质是消除空间冗余,利用已编码的块的像素的来预测未编码的像素值。

未编码的块像素的实际值-预测值=残差,传输只需要传输残差。

H264 引入了基于空域的帧内预测技术,在空域中利用当前块的相邻像素直接对每个像素做预测,并对预测残差进行变换、量化。

H264 帧内预测中,色度和亮度信息是被分开预测的。

  • 对于亮度待编码块,可以按照 4x4 块方式预测(I4MB)或 16x16 宏块方式预测(I16MB)。
    • 4x4 预测时有 9 种模式(水平、垂直、DC、6 个方向),用于图像细节部分的预测。
    • 16x16 预测时有 4 种模式(水平、垂直、DC、平面),用于图像平坦区域的预测。
  • 对于色度待编码块,基于 8x8 块进行预测。
    • 8x8 预测有 4 种模式(水平、垂直、DC、平面)。

亮度和色度的最佳帧内预测模式相互独立:

  • 色度的只需要比较 4 种模式的代价,选择最小的。

  • 亮度的需要:算出代价最小的 Intra4x4 模式、算出代价最小的 Intra16x16 模式、取两者最小的。

    • Intra4x4:用 RDO 模型也就是拉格朗日模型计算代价。
    • Intra16x16:用 SATD,变换使用哈达马变换(看作是简单的时频变换,可以反映生成码流的大小)

变换编码

变换编码(Transform Coding)将空间域描述的图像,经过某种变换形成变换域中的数据,达到改变数据分布,减少有效数据量的目的。

变换编码中主要使用方式是正交变换。正交变换不会改变信源的熵值,变换之后图像的信息量并没有损失,完全可以通过反变换得到原来的图像值。

正交变换可以改变数据的分布,将数据集中分布之后就可以使用进一步的量化操作来去除大部分的 0 值和接近 0 的值。

正交变换中的理论最优变换是 K-L 变换。

实际中常用的正交变换有 DCT 变换,DFT 变换(离散傅里叶变换),Hadamard 变换。

因为 DCT 系数主要集中在低频区域,越是高频区域系数值越小,通过设置不同的视觉阈值的量化电平,将许多能量较小的高频系数量化为 0,可以增加变换系数中 0 的个数,同时保留能量较大的系数分量,对量化之后的系数进行熵编码可以获得进一步的压缩。

H264 使用的是整数变换,变换核只用加减法和左移操作实现,不需要乘法器。

各种变换的比较:

  • 压缩比和重建质量:
    • 较小分块时,DCT 的 MSE 接近 K-L 变换。
    • 块大小超过 16x16 时,除傅里叶变换之外,其他几种变换的 MSE 下降很慢。
    • 大方块尺寸时,傅里叶变换趋向于 K-L 变换。
  • 计算复杂度:
    • Hadamard 计算复杂度最小。
    • 其次是 DFT 和 DCT,具有固定的核函数。
    • K-L 变换的核函数与输入相关,计算量很大,不实用。

变换编码中的失真还是由量化器引起,正反变换和变长编解码都是无损处理。

DCT 变换

离散余弦变换与 DFT 变换相似,但是 DCT 变换只使用实数。

DCT 变换具有能量集中性,大多数的声音和图像信号的能量都集中在 DCT 变换之后的低频部分。

当信号具有接近马尔可夫过程的统计特性时,DCT 变换的去相关性接近于 K-L 变换。

图像处理领域中,DCT 变换的效果要强于 DFT 变换,因而图像处理中更多应用的是 DCT 变换。

DCT 产生的系数很容易被量化,因而可以获得较好的块压缩。

DCT 算法的性能好,有快速算法,采用快速傅里叶变换可以进行高效的运算。

量化

量化的思想就是映射一个输入间隔到一个整数,减少信源编码的 bit 数。

量化器的设计就是率失真优化问题,在允许一定失真的条件下,获得尽可能高的压缩比。

量化步长决定量化器的编码压缩率和图像精度。

量化最简单的方法就是均匀(线性)量化,但均匀量化的效果往往并不好,因为它没有考虑到量化对象的概率分布。

对 DCT 系数这样的数据而言,其分布大部分集中在直流和低频附近,如果采用非均匀量化,对低频区域进行细量化,对高频区域进行粗量化,在相同的量化步长的条件下,非均匀量化比均匀量化所造成的量化误差要小得多。

量化之后,熵编码之前,可以根据从高到低的统计特性,对系数进行 Zigzag 锯齿扫描和游程长度编码。这样做的原因在于:量化之后的 DCT 系数更为稀疏,只有少数的 AC 系数不为 0,Zigzag 扫描能增加连 0 的长度,减少统计事件的个数,从而进一步增加对 DCT 系数熵编码的压缩率。

量化区间上的最优量化值应该是区间的期望值,所以需要知道残差变换系数的统计分布。

引入量化偏移量 f 来进行非均匀量化,在帧内预测时 f=Qstep/3,帧间预测时 f=Qstep/6.

f 可以控制量化死区大小,f 变大,量化死区减少,f 变小,量化死区增加。死区大小直接影响图像的主观质量。

去块滤波(不是很了解细节)

环路滤波器是被放置在编解码的图像重建环路当中。

在启用了环路滤波的编解码环境中,无论是编码器还是解码器,都是在图像被重建后才进行滤波。

在编码器中,滤波后的图像会作为后续编码运动补偿的参考图像;

在解码器中,滤波后的图像会被输出显示并且作为后续图像解码重建的参考图像。

块效应出现的原因

  1. 基于块的量化会破坏相邻块之间的相关性,并且在低码率情况下会放大这种误差。
  2. 运动补偿加剧了由变换量化导致的块效应。因为运动补偿块的匹配不可能绝对准确,各个块的残差大小程度存在差异,尤其是当相邻两个块用的参考帧不同、运动矢量或参考块的差距过大时,块边界上产生的数据不连续就更加明显。

过程

估算边界强度、区分真假边界、滤波运算

熵编码

主要利用信源的统计特性进行码率压缩,是无损压缩编码方法。

H264 支持 CAVLC(变长编码)和 CABAC(二进制算术编码)。

CAVLC 本质是哈夫曼编码,所以必须为所有可能的长度为 N 的序列设计和存储编码表,复杂度随 N 指数增长。

CABAC 的思想是用 0 到 1 区间上的一个数来表示整个字符输入流,而不是为输入流中的每个字符分别指定码字。

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

一般来讲,只要计算量允许,就应该选择使用算术编码。

H264 对不同的数据采用不同的熵编码模式,对于宏块和子块的残差数据经过变换之后的系数采用 CAVLC,对于其他相对重要的语法元素使用指数哥伦布编码。在 CABAC 方案里,对不同的语法元素也使用了不同的编码树结构。

GOP/Frame/Slice/I/B/P

帧 Frame 包含切片 Slice,Slice 包含宏块 Macroblock(MB)。

一个 Frame 至少包含一个 Slice,一个 Slice 至少包含一个 MB。

Slice 是 MB 的载体,出现的原因是为了防止误码的扩散和传播。

每个 Slice 都是互相独立被传输的,某个 Slice 不能以其他 Slice 中的 MB 为参考。

Slice 存储在 NAL 单元 NALU 中,是 NALU 的有效载荷 Payload。

Slice 中包含 Slice Header 和 Slice Payload。

Slice Header 中存放的是 Slice 类型、Slice 中的宏块类型、Slice 属于哪个 Frame、对应的帧的设置和参数等信息,Slice Payload 中存放的是 MB

MB 中包含了 MB 类型、预测类型、Coded Block Pattern(CBP)、量化参数 QP、像素的亮度和色度数据等信息

GOP 由 IDR 帧开始,中间存在多个 P 帧或 B 帧(基本档次 Baseline Profile 不存在)

I 帧只进行帧内编码,IDR 帧是每个 GOP 的第一个帧,IDR 帧是 I 帧,I 帧不一定是 IDR 帧

P 帧参考前面的 I 帧或者 P 帧进行编码;

B 帧参考前向、后向的 I 帧或 P 帧进行编码;

P/B 帧进行编码时候只能参考当前 GOP 的 I/P 帧,不能越过当前 GOP 开始时的 IDR 帧,但是 I 帧之后的 P 帧也可以参考这个 I 帧之前的图像。

SPS/PPS/DTS/PTS

SPS:序列参数集,描述的是整个视频序列的参数信息,如图像的宽度、高度、帧率、色度空间等,一个视频序列只有一个 SPS,用于描述整个视频序列的基本特性。SPS 一般在视频码流的开头发送,是一种全局静态的描述方式。当然,如果编码器在编码过程中改变了 SPS 中描述的参数如分辨率时也需要发送新的 SPS。

PPS:图像参数集,描述的是一个序列中一个或多个图像的参数,例如编码图像的配置、QP 这些,可以在视频序列的任何时刻发送,PPS 相对与 SPS 来说比较灵活动态。

发 I 帧之前至少要发一次 SPS 和 PPS

DTS:解码时间序列,编码帧时的顺序

PTS:展示时间序列,展示帧时的顺序,当有 B 帧时 PTS 不等于 DTS

编码之后的 VCL 数据被封装进 NALU 中,构成了 h264 原始码流

image-20240123003111970

NALU

NALU 是视频编码的基本单位,同时也是后续进行视频传输的基本单位。

不同应用需求采用不同的传输方式,NALU 根据传输方式可以以两种方式应用于传输业务。

  • 一类是字节流,即把 NALU 按照解码顺序生成连续的比特流进行传输和处理。

  • 一类是分组流应用,也是本文使用的实时视频通信的应用场景,网络可以根据不同网络分组的重要性优化视频流的服务质量,分组流通过将编码后得到的 NALU 作为网络传输的载荷。

NALU 由 NAL Header 和 RBSP 组成,Header 占一个字节,分为 3 个部分。

第 1 个部分是第 0 位,禁止位,值为 0,值为 1 表示语法错误。

第 2 个部分是第 1~2 位,表示当前 NAL 的优先级。值越高表示当前 NAL 越重要,越需要优先保护。SPS/PPS/IDR 帧非常重要,I/P 帧重要,B 帧/SEI 不重要

第 3 个部分是第 3~7 位,表示当前 NALU 的类型:

image-20240123003122907

RBSP:原始字节序列载荷,是 NALU 数据部分的封装格式,封装的数据来自于 SODB(原始数据比特流)。

SODB 是编码后的原始数据。SODB 到 RBSP 的过程: 如果 SODB 是空的,生成的 RBSP 也是空的。 否则: RBSP 的第一个字节直接取自 SODB 的第 1~8 个 bit,(RBSP 字节内的 bit 按照从左到右对应位从高到低的顺序排列),RBSP 其余的每个字节都直接取自 SODB 的相应 bit。RBSP 的最后一个字节包含 SODB 的最后几个 Bit 和 rbsp_trailing_bits() rbsp_trailing_bits 的第一个 bit 是 1,接下来填充 0 直到字节对齐。 最后添加几个 cabac_zero_word,值为 0x0000.

NALU 主要涉及到 SPS/PPS/SEI 和 Slice 这几种类型。

SEI 是补充增强信息,提供了向视频码流中加入额外信息的方法,不是解码过程中的必须选项,可能对解码过程有帮助,集成在视频码流中。

RBSP 字节流加上 0x0300 就得到 NALU 载荷字节流:

image-20240123003131193

Profile/Level

Profile 主要对视频编码的特性做了差异化支持。

image-20240123003137786

H264 中的常用 Profile 有 Baseline,Extended,Main 和 High

Baseline Profile:基本画质,只支持 I/P 帧和 CAVLC 和无交错

Extended Profile:进阶画质,支持 I/P/B/SP/SI,只支持 CAVLC 和无交错

Main Profile:主流画质,支持 I/P/B 帧和,交错和无交错、CAVLC 和 CABAC

High Profile:高级画质:在 Main 的基础上增加了 8x8 内部预测、自定义量化和更多的 yuv 格式。

Baseline Profile 主要用于实时视频通信,Main Profile 和 High Profile 主要用于流媒体领域。

Level 主要根据设备能力来确定编解码时的码率/分辨率上限支持

CBR/VBR/ABR/CRF

CBR:恒定码率,每秒传输的 bit 数固定,每个视频帧都被分配相同数量的 bit,和复杂度无关,适用于要求网络或者存储带宽具有固定容量的场景,但可能导致复杂场景下的帧质量下降。WebRTC 中使用的就是 CBR。

VBR:可变码率,允许每个帧使用不同的比特数,根据图像复杂度和需要进行动态分配。适合于在不同场景下需要保持一致质量的情况,但是消耗的带宽会有较大波动。适合用于视频存储,不适合网络传输。

ABR:平均码率,允许在整个视频序列中有一定的变化,但是在一个时间窗口内保持一定的平均码率。在此时间内,对简单、静态的图像分配低于平均码率的码率,对于复杂的、大量运动的图像分配高于平均码率的码流。码率分配比较均衡,比较适合网络传输。

CRF:恒定质量因子,追求的是恒定的视觉质量,编码器根据图像内容自动调整码率,以保持相对恒定的质量。适合于追求质量而不在乎码率的场景如视频剪辑和存档。

OpenH264 码控

编码第一个 IDR 帧时使用固定的 QP,具体的值使用视频分辨率作为判断依据并查表得出并将其初始化为 initialQP:

image-20240123003154138

将 QP 查表转换为 QStep 完成初始化:

image-20240123003201991

之后进行 IDR 帧的量化及后续编码操作,并得到 frameDqBits(已编码的比特数),由此可以计算 intraCmplx:

image-20240123003208172

对于第一个 PFrame,linearCmplx 也使用下面的公式计算,其中 QStep 使用 initialQP 查表。

image-20240123003217458

之后计算 QStep&QP 时就使用一阶 RQ 模型计算,frameComplexity 在预处理阶段得出。

image-20240123003222850

RTP

RTP(Real-time Transport Protocol,实时传输协议)是一种应用层协议,通常基于 UDP 协议,但也支持 TCP 协议。

它提供了端到端的实时传输数据的功能,但不包含资源预留存 1(resource reservation)、不保证实时传输质量,这些功能都需要 WebRTC 自己实现。

RTP 协议分为两种子协议,分别是 RTP Data Transfer Protocol 和 RTP Control Protocol。

前者顾名思义,是用来传输实时数据的;后者则是我们常说的 RTCP 协议,可以提供实时传输过程中的统计信息(如网络延迟、丢包率等),WebRTC 正是根据这些信息处理丢包。

RT(D)P 包分为两部分,分别是 header 和 payload,header 包含了实时音视频的同步信息(和一些额外参数),payload 则承载了具体的音视频数据。这里我们只需要关注 header 结构就好,payload 是编解码器关心的。

image-20240123003246634

如图所示,RT(D)P header 最小为 12 bytes;红色部分为可选字段。字段的含义分别如下:

  • Version 表示 RTP 协议的版本,目前版本为 2。
  • P (Padding) 表示 RT(D)P 包末尾是否有 padding bytes,且 padding bytes 的最后一个 byte 表示 bytes 的数量。Padding 可以被用来填充数据块,比如加密算法可能会用到。
  • X (Extension) 表示是否有头部扩展,头部扩展可以用于存储信息,比如视频旋转角度。
  • CC (CSRC count) 表示红色部分的 CSRC(参见下文)数量,显然最多只能有 15 个 CSRC。
  • M (Marker) 表示当前数据是否与应用程序有某种特殊的相关性。比如传输的是一些私有数据,或者数据中的某些标志位具有特殊的作用。
  • PT (Payload type) 表示 payload 的数据类型,音视频的默认映射格式可参见 RFC 3551。
  • Sequence number 是递增的序列号,用于标记每一个被发送的 RT(D)P 包。接收方可以根据序列号按顺序重新组包,以及识别是否有丢包。序列号的初始值应当是随机的(不可预测的),从而增加明文攻击的难度。
  • Timestamp 即时间戳,接收方根据其来回放音视频。时间戳的间隔由传输的数据类型(或具体的应用场景)确定,比如音频通常以 125µs(8kHz)的时钟频率进行采样,而视频则以 90kHz 的时钟频率采样。这里时间戳的初始值也是随机选取的,是一种相对时间戳。
  • SSRC (Synchronization source) 即同步源标识符。相同 RTP 会话中的 SSRC 是唯一的,且生成的 SSRC 也需要保持随机。尽管多个源选中同一个标识符的概率很低,但具体实现时仍然需要这种情况发生,即避免碰撞。
  • CSRC (Contributing source) 在 MCU 混流时使用,表示混流出的新的音视频流的 SSRC 是由哪些源 SSRC 贡献的。根据上述 CC 得知,我们最多可以同时混 15 路音视频流。
  • Extension header 即头部扩展,包含了音视频的一些额外信息,比如视频旋转角度。

RTP 与 NALU 分组

RFC3984 给出了 3 中不同的 RTP 打包方案:

  1. Single NALU Packet:在一个 RTP 包中只封装一个 NALU,对于小于 1400 字节的 NALU 便采用这种打包方案。
  2. Aggregation Packet:在一个 RTP 包中封装多个 NALU,对于较小的 NALU 可以采用这种打包方案,从而提高传输效率。
  3. Fragmentation Unit:一个 NALU 封装在多个 RTP 包中,在本文中,对于大于 1400 字节的 NALU 便采用这种方案进行拆包处理。

RTCP

RTCP 协议提供实时传输过程中的统计信息,如网络延迟、丢包率等。

在传统的实时通讯过程中,RT(D)P 协议占用偶数位的端口,而 RTCP 协议占用随后的奇数位端口。

不过如果接收方的 SDP 中包含 rtcp-mux 字段 6,即表明接收方支持 RT(D)P 协议和 RTCP 协议共用同一个端口,即多路复用。在 Chrome 57 版本已经强制开启了 rtcp-mux 。

对于 RTCP 包而言,我们不只要关注 header 的结构,还要关注具体的 report block 内容。不过我们先来看一个典型的 RTCP header 结构,如下图所示:

image-20240123003300444

RTCP header 的固定大小为 8 bytes,其中 Version、P、SSRC 的含义同上述 RTP header 相同,在此不与赘述。其他几个字段的含义分别如下: RC (Reception report count) 表示当前 RTCP 包有几个 block,显然最多只能有 32 个。

  • PT (Packet type) 表示 RTCP 包的类型,比如 SR=200、RR=201(SR、RR 参见下文)。
  • Length 等于整个 RTCP 包的长度减一(使得 Length = 0 是合法的),其值包含 header 的长度和所有 padding 占用的空间长度。值的单位是以 32 位字长(32-bit words)描述的。

PSNR/SSIM/VMAF/BD-Rate

PSNR

峰值信噪比,基于均方误差(MSE)计算。

公式:

$$ PSNR = 10 \cdot log_{10}(\frac{MAX_I^2}{MSE}) $$

$$ MSE = \frac{1}{MN}\sum_{i=0}^{m-1}\sum_{j=0}^{n-1}[I(i,j) - K(i,j)]^2 $$

对于用 8bit 表示的视频图像而言,MAX 就是 255

PSNR 的物理含义就是信号的峰值与平均误差的比值,如果误差越小,那么 PSNR 值越高。如果完全没有误差,那么 PSNR 值就是无穷大。

对于图像数据来说,通常有 Y、U、V 三个分量,可以对三个分量各自计算 PSNR。

也可以把三个分量的 PSNR 值以一定的权重加起来作为总的 PSNR 值。

对于整个视频来说,可以计算单帧的 PSNR 值,然后平均,也可以计算整个视频的 Overall PSNR。

  • 一般,psnr 值高于 40dB,表示画面质量极好,(非常接近于原始图像);

  • psnr 值在 30dB-40dB 之间,表示画面质量较好(有失真但可接受);

  • psnr 值在 20dB-30dB 之间,表示画面质量差;

SSIM

PSNR 指标比较常用,但是不能体现编码前后图像之间的相关性,SSIM 可以从亮度,对比度和结构三个方面来描述编码前后图像之间的相似性。

  • 亮度维度的相似性用均值计算,𝝁𝒙为均值:

    image-20240123003555046

  • 对比度维度的相似性用方差计算,𝝈𝒙 为方差:

    image-20240123003606618

  • 结构维度的相似性用协方差计算, 𝝈𝒙𝒚 表示协方差:

    image-20240123003637110

SSIM 指标和 SSIM 计算公式:

image-20240123003643066

SSIM 性质:

image-20240123003649816

VMAF

VMAF 是一种 Full reference 的视频质量评估方法,适用于视频流媒体质量评估。

主要包括三种指标:视觉信息保真度 (VIF: visualquality fidelity) 、细节损失指标 (DLM: detail loss measure) 、时域运动指标/平均相关位置像素差 (Tl: temporal information) 。

  • 其中 VIF 和 DLM 是空间域的一个画面之内的特征。
  • TI 是时间域的,多个画面之间相关性的特征。

这些特性之间融合计算总分的过程使用了训练好的 SVM 来预测。

VMAF 基于 SVM 的 nuSvr 算法,在运行的过程中,根据事先训练好的 model,赋予每种视频特征以不同的权重。对每一帧画面都生成一个评分。最终以均值算法进行汇总,算出该视频的最终评分

使用方式:

计算时需要保证编码后图像的分辨率与原始图像分辨率一致,如果不一致需要向上或者向下缩放编码图像,而不能缩放原始图像。

BD-Rate

比较两个编码器的 RD 曲线(Rate-Distortion)的差异:

  • 一种是相同质量下的码率差异,指标为 BD-Rate。
  • 一种是相同码率下的质量差异,指标为 BD-PSNR。

BD-Rate 是选取一个范围的的多个采样点(通常是 4 个),然后进行曲线拟合插值,最后计算出平均的指标差异。目前大部分测试数据的对比,都是基于 BDRate 指标的。

视频封装

视频的封装格式定义的是多媒体数据的存储结构,包括如何组织和存储音频、视频、字幕、元数据等信息,以及怎么进行同步和时间标记。

感觉封装其实可以理解为是一个柜子,并且对柜子里的不同抽屉里面能存放哪些数据以及怎么存放数据做了规定。\

常见的封装格式有 Mp4、Mkv,微软的 AVI、苹果的 MOV、Adobe 的 FLV、Google 的 Webm。

不同的封装格式提供了一种统一的方式来存储和传输各种编码格式的多媒体数据。

H264 支持 AnnexB 和 AVCC 两种封装模式。

  • AnnexB 模式是传统模式,有 startcode,SPS 和 PPS 在码流中分别作为一个 NALU。

  • AVCC 模式没有 startcode,SPS 和 PPS 以及其它信息被封装在 container 中,每个 frame 前面 4 个字节是这个 frame 的长度。一般在 mp4 和 mkv 中使用 AVCC。

硬件编码和软件编码

硬编码是通过专用的硬件编码器,比如 GPU 或者专用的视频编码芯片来进行的,硬件编码可以利用专为编码设计的电路从而提供更高的编码速度和更低的功耗,适用于实时性要求较高的应用比如云游戏、视频会议这些场景。硬编码也通常会在移动设备比如手机或者其他的专业视频设备中使用

软编码是通过通用的计算设备,一般来讲就是用 CPU 编码。软编码的性能和功耗一般来讲要比硬件编码差,但是可以跨平台运行,容易升级和更新,编码的画质也会更好。一般用于需要更大灵活性和可定制性的应用,比如短视频录制、非实时的转码等。

码率和分辨率

码率提高和分辨率提高都会增大视频的体积,两者之间需要平衡。

  • 在码率一定的情况下,提高分辨率可能会导致每个像素获得的比特数减少,从而降低整体感知质量。

  • 在分辨率一定的情况下,提高码率可以分配更多的比特给每个像素,从而提高每个像素的清晰度。

高分辨率主要是为了适应更大的屏幕尺寸,或者需要更多图像细节的任务。

其实屏幕尺寸和分辨率之间的关系有点类似于分辨率和码率之间的关系。

一个视频文件能否倒放

一个文件不行,至少需要两个文件才可以。

理论上方式有两种:

  • 第一种是先顺序解码视频到一个 yuv 文件中,然后倒序读入内存进行编码。

  • 第二种是先遍历视频,获取一共有多少个 GOP,跳到最后一个 GOP 的 IDR 帧,对这个 GOP 进行解码输出到 yuv 文件中,再逆序读出这个解码之后的 yuv 文件然后编码,这样最后一个 GOP 就变成了第一个 GOP,按照从后往前的顺序依次类推就可以。

第一种方式简单粗暴好实现,但是对于磁盘存储空间的要求比较高。

H265(HEVC)比 H264(AVC)做了哪些改进

  • H265 针对编码的各个环节都引入各自对应的单元。

    • 与 H264 中宏块类似的是,在 H.265 里面用的是一系列互不重叠的编码树单元 CTU 处理信息,CTU 内部可以以四叉树结构递归向下划分成更小的正方形编码单元 CU。CU 可以支持最大 64x64 的尺寸,因而可以对高分辨率视频中的平坦和复杂区域做有针对性的 CTU 划分。

    • 预测单元 PU 是定义在 CU 上的一个矩形区域,用来存储和预测相关的所有信息如帧内预测方向、帧间预测的参考帧、和 MV 等。

    • 变换单元 TU 是变换和量化的基本单位,支持 4 种正方形的尺寸大小(4/8/16/32)。变换时采用 RQT 技术,基于四叉树结构进行自适应变换。大块的 TU 模式能够将能量更好地集中,小块的 TU 模式能够保存更多的图像细节。根据当前 CU 内残差特性,自适应选择变换块大小,可以在能量集中和细节保留两者做最优的折中。与传统的固定块大小变换相比,RQT 对编码效率贡献更大。

    • CU 划分成 PU 和 TU,PU 和 TU 之间存在交叉重叠关系,Inter 预测时允许 CU 内的 TU 跨越 PU 边界,Intra 预测时,TU 不能跨越 PU 边界。

  • 在帧内预测模块,H265 支持更多的帧内预测模式。H265 的亮度分量支持 35 种帧内预测模式包括平面模式、DC 模式和 33 种角度模式,色度分量有 5 种帧内预测模式包括平面模式、DC 模式、水平、垂直方向模式和对应于亮度分量的帧内预测模式。

  • 在帧间预测模块,H265 引入了更加复杂的 ME 方式,主要包括 Merge 和 AMPV 以及基于 Merge 的 Skip 模式。

    • Merge:取相邻 PU 的运动参数作为当前 PU 的运动参数(利用空域相关性和时域相关性)
    • AMVP 得到的 MV 一方面为 ME 提供了搜索起点,另一方面也用于预测 MV。AMVP 根据周围块预测 MV,MV=MVP+MVD(矢量差值)
  • H265 把变换和量化模块结合了起来,降低了计算复杂度,支持加权量化矩阵。

  • 在环路滤波模块,H265 新增了采样点自适应偏移滤波 SAO,通过解析去方块滤波后的像素的统计特性,为像素添加相应的偏移值,削弱振铃效应。

  • 因为 H265 的解码要比 h264 的解码复杂很多,所以提供了很多可以并行优化的思路。

SIMD

SIMD 是一种并行计算技术,允许单一指令处理多个数据元素。SIMD 指令集通常由处理器提供,用于加速向量化计算。视频编码中,SIMD 可以用于加速压缩和解压算法。

H264 中的差错控制

Slice 分割、Data Partition (DPA > DPB > DPC)、对 SPS 和 PPS 提供使用高传输优先级、差异化的熵编码(对重要的 SPS 和 PPS 采用指数哥伦布编码)

FMO 通过宏块分配映射把同一帧里的不同宏块划分到不同的 Slice Group 里,在同一个 Slice Group 里的 MB 按照普通的光栅扫描顺序编码。

因为不管是 Intra Coding 还是 Inter Coding 都必须使用同一个 Slice group 的宏块数据,这样当一个 Slice group 里的某一个或者某几个宏块发生错误时候,因为相邻的宏块可能分布在不同的 Slice group,就可以从其他正确接收的 Slice group 里拿到和丢失宏块相邻的宏块信息来进行错误掩盖。

SVC

SVC:(Scalable Video Coding)可伸缩视频编码,编码器产生的码流包含一个或者多个子码流或者层,子码流可以有不同的码流、帧率和分辨率。基本层编码最低层的时域、空域和质量流;增强层以基本层作为起始点,对附加信息进行,从而在解码过程中重构更高层的质量、分辨率和时域层。通过解码基本层和相邻增强层,解码器能生成特定层的视频流。

  • 时域分层:从码流中提取出有不同帧频的码流。

  • 空域分层:从码流中提取出有不同分辨率的码流。

  • 质量分层:从码流中提取出有不同质量的码流。

应用场景

监控:监控视频流产生两路,一路质量好的用于存储,一路低码率的用于预览。

视频会议:会议终端利用 SVC 编出多种分辨率、分层质量的码流,会议中心替代传统的 MCU 二次编解码方法改成视频路由分解转发。也可以在丢包环境下利用时域分级,抛弃一些时域级别实现网络适应性。

流媒体 IPTV:服务器可以根据不同的网络情况丢弃质量层,保证视频的流畅。兼容不同网络环境和终端。

优缺点

优点:分级码流优点是应用非常灵活,因为能根据需要产生不同的码流或者提取出不同的码流。使用 SVC 实现一次分层编码比用 AVC 编多次更高效。 SFU 从发布客户端复制音视频流的信息,然后分发到多个订阅客户端。典型的应用场景是 1 对多的直播服务。SFU 是解决服务端性能问题的好方法,因为它不涉及视频解码和编码的计算费用,用最低的开销来转发各路媒体流,能实现海量的客户端接入。重终端,轻平台。

缺点:因为 SVC 解码控制复杂不利于流式处理,硬件编解码器支持差,协议协商细节复杂,业界标准不统一。

RTC 应用中提高实时性

因为编码主要的时间开销在运动预测过程中,如果在云游戏的场景下,可以得到游戏画面中物体的运动信息然后考虑用来辅助运动预测,或者说对搜索过程进行剪枝。