音视频学习笔记|(一)H.264 基础原理介绍

一、视频压缩基础

1.1 压缩基本概念

  • 编码的目的是为了压缩,编码是一种压缩技术方法;

  • 编码的作用就是压缩数据和统一格式;

  • 视频压缩原理——数据冗余

    • 空间冗余:图像相邻像素之间有较强的相关性
    • 时间冗余:相邻前后帧图像内容相似
    • 视觉冗余:人眼对某些细节不敏感,对图像中高频信息的敏感度小于低频信息,故可以适当剔除高频信息
    • 编码冗余(信息熵冗余):一幅图像中不同像素出现的概率不同,对出现次数较多的像素用少的位数编码以此来减小编码大小(哈夫曼编码)

1.2 封装格式

  • 封装格式也叫容器,就是将已经编码压缩好的视频轨和音频轨按照一定的格式放到一个文件中;
  • 他是一个外壳容器,常见的格式(后缀名)有 mp4、mov、avi、mkv 等;

1.3 常见视频压缩算法

二、H264 编码概述

2.1 YUV 图像

YUV 是一种色彩编码模型,也叫做 YCbCr,其中 “Y” 表示明亮度(Luminance),“U” 和 “V” 分别表示色度(Chrominance)和浓度(Chroma)

其实亮度 Y 也可以理解成 RGB 图像中的灰度值。YUV 颜色空间主要在多媒体流中使用较多。YUV 空间最大的特点就是图像的亮度 Y 和色度 UV 是分离的。通常人对色度 UV 的敏感性要小于对亮度 Y 的敏感性。所以通常都会对 UV 进行压缩,甚至没有UV分量一样可以显示完整的图像。当只有 Y 分量的时候,图像表示为灰度图。

2.2 I 帧 P 帧 B 帧

在 H264 协议中定义了三类帧

I 帧:关键帧(包含完整的画面数据),仅由帧内预测的宏块组成,采用 帧内压缩 技术。
P 帧:预测帧,向前参考帧,在压缩时,只参考前面已经处理的帧。
B 帧:双向参考帧,在压缩时,它即参考前而的帧,又参考它后面的帧。采用帧间压缩技术。

2.3 GOP 图像组

GOP(Group of picture)是指两个 IDR 帧之间的一组画面组。

两个 I 帧之间是一个图像序列,在一个图像序列中只有一个I帧。如下图所示:

2.4 IDR 立即刷新图像

在 H.264 中,图像以序列为单位进行组织。一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR 图像之后的图像永远不会使用IDR之前的图像的数据来解码。IDR 图像一定是 I 图像,但 I 图像不一定是 IDR 图像。I 帧之后的图像有可能会使用I帧之前的图像做运动参考。

IDR 帧的性质是,比如第 1000 帧是 IDR 帧,那么这一帧相当于一个分水岭,从 1001 帧开始,所有的帧都不能再参照1000帧之前的帧。

视频开头的 I 帧一定是 IDR 帧

GOP 越大,编码的 I 帧就越少,相对而言 P、B 帧的压缩效率就越高。

2.5 H264 画质级别

H.264有四种画质级别,分别是baseline, extended, main, high:
  1、Baseline Profile:基本画质。支持I/P 帧,只支持无交错(Progressive)和CAVLC;
  2、Extended profile:进阶画质。支持I/P/B/SP/SI 帧,只支持无交错(Progressive)和CAVLC;(用的少)
  3、Main profile:主流画质。提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced),
    也支持CAVLC 和CABAC 的支持;
  4、High profile:高级画质。在main Profile 的基础上增加了8x8内部预测、自定义量化、 无损视频编码和更多的YUV 格式;
H.264 Baseline profile、Extended profile和Main profile都是针对8位样本数据、4:2:0格式(YUV)的视频序列。在相同配置情况下,High profile(HP)可以比Main profile(MP)降低10%的码率。
根据应用领域的不同,Baseline profile多应用于实时通信领域,Main profile多应用于流媒体领域,High profile则多应用于广电和存储领域。

三、H264 编码流程

3.1 宏块划分/ 扫描

  • 视频以宏块为单元,宏块越大视频文件越小;
  • 视频的本质是宏块的运动;
  • 在H.264 中,句法元素共被组织成 序列、图像、片、宏块、子宏块五个层次

对一个 YUV 图像来说,可以划分为一个个 16x16 的宏块。

H264 默认是使用 16X16 大小的区域作为一个宏块,也可以划分成 8X8 大小。

划分好宏块后,计算宏块的象素值

划分子块

H264对比较平坦的图像使用 16X16 大小的宏块。但为了更高的压缩率,还可以在 16X16 的宏块上更划分出更小的子块。子块的大小可以是 8X16、 16X8、 8X8、 4X8、 8X4、 4X4非常的灵活。

3.2 帧分组

1、分组:把几帧图像分为一组(GOP,也就是一个序列),为防止运动变化,帧数不宜取多
2、定义帧:将每组内各帧图像定义为三种类型,即 I 帧、B帧和P帧
3、预测帧:以帧作为基础帧,以帧预测P帧,再由 I 帧和P帧预测B帧
4、数据传输:最后将 I 帧数据与预测的差值信息进行存储和传输

通过宏块扫描与宏块搜索可以发现这两个帧的关联度是非常高的。进而发现这一组帧的关联度都是非常高的。因此,上面这几帧就可以划分为一组。其算法是:在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,我们认为这样的图可以分到一组。

在这样一组帧中,经过编码后,我们只保留第一帖的完整数据,其它帧都通过参考上一帧计算出来。我们称第一帧为IDR/I帧,其它帧我们称为P/B帧,这样编码后的数据帧组我们称为 GOP

3.3 帧间压缩(运动估计与补偿)

帧间压缩(Interframe compression)也称为时间压缩(Temporal_compression),是基于许多视频或动画的连续前后两帧具有很大的相关性(即连续的视频其相邻帧之间具有冗余信息)的特点来实现的;通过比较时间轴上不同帧之间的数据实施压缩,进一步提高压缩比。

在视频中,帧内压缩就是压缩 GOP 图像组中的 B 帧与 P 帧。

H264 编码器首先按顺序从缓冲区头部取出两帧视频数据,然后进行宏块扫描。当发现其中一幅图片中有物体时,就在另一幅图的邻近位置(搜索窗口中)进行搜索。如果此时在另一幅图中找到该物体,那么就可以计算出物体的运动矢量了。

运动矢量计算出来后,将相同部分(也就是绿色部分)减去,就得到了补偿数据。我们最终只需要将补偿数据进行压缩保存,以后在解码时就可以恢复原图了。压缩补偿后的数据只需要记录很少的一点数据。

我们把运动矢量与补偿称为帧间压缩技术,它解决的是视频帧在时间上的数据冗余。除了帧间压缩,帧内也要进行数据压缩,帧内数据压缩解决的是空间上的数据冗余。

帧间压缩的主要过程先进行宏块查找,寻找出残差值,进行运动矢量计算,最后通过残差值和运动矢量推算出下一帧的数据。

残差值是指帧之间有差别的部分;

帧间压缩 (Interframe compression) 也称为时间压缩 (Temporal_compression),是基于许多视频或动画的连续前后两帧具有很大的相关性(即连续的视频其相邻帧之间具有冗余信息)的特点来实现的;通过比较时间轴上不同帧之间的数据实施压缩,进一步提高压缩比。

3.4 帧内预测

人眼对图象都有一个识别度,对低频的亮度很敏感,对高频的亮度不太敏感。所以基于一些研究,可以将一幅图像中人眼不敏感的数据去除掉。这样就提出了帧内预测技术。

H264的帧内压缩与JPEG很相似。一幅图像被划分好宏块后,对每个宏块可以进行 9 种模式的预测。找出与原图最接近的一种预测模式。

帧内(Intraframe)压缩也称为空间压缩(Spatial compression)。当压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息,这实际上与静态图像压缩类似。帧内一般采用有损压缩算法,达不到很高的压缩比。

在视频中,帧内压缩就是压缩 GOP 图像组中的 I 帧/ IDR帧(属于I帧)。

帧内压缩是空域, 在空间XY轴进行压缩,进行压缩参考本帧数据,压缩率较小。

帧内预测后的图像与原始图像的对比如下:

然后,将原始图像与帧内预测后的图像相减得残差值。

再将我们之前得到的预测模式信息一起保存起来,这样我们就可以在解码时恢复原图了。效果如下:

经过帧内与帧间的压缩后,虽然数据有大幅减少,但还有优化的空间。

帧内压缩与帧间压缩都属于有损压缩。

视频花屏的产生原因:一组 GOP 中有帧丢失,主要是 P 帧和 B 帧的丢失,这样残差值和运动矢量也会丢失,造成了当前帧解码失败,就会出现花屏。

视频卡顿的原因:当有帧丢失,就丢弃 GOP 内所有帧,直到下一个 I 帧出现,重新刷新图像。I 帧是 GOP 的第一帧,时间周期可能长,等待下一个 I 帧来之前的这段时间,就会出现卡顿。

这两者是不能同时兼顾的,比如微信视频通话没有花屏,但是会出现卡顿。一般视频录制的时候一般会有花屏,但不会出现卡顿。

3.5 DCT 和量化

对残差数据做 DCT

可以将残差数据做整数离散余弦变换,去掉数据的相关性,进一步压缩数据

将残差数据宏块进行 DCT 转换。

变换是视频、图像编码的核心部分。目前所采用的变换算法都是从傅里叶变换演变而来。

单纯的变换并不会导致视频(图像)的码率变小,反而会增大。

但是非常巧妙的一点是:变换把图像从空域转换成的时域,把由色块组成的图像变为由基准色调与图像细节组成;低频代表图片的基准色调,高频代表图像细节,类比电路中的基频与谐波。变换会使得图像的低频系数集中于某一点(左上角),频率向右下角递增。

一般来说,4x4大小的图像大多只是颜色平缓的色块,不会有太多的细节,因此低频系数会较大,而高频系数较小。另外,人的眼睛对于高频系数,即图像细节,并不会特别敏感。因此,通过量化可以去掉很大一部分小的高频系数。

量化是使数据比特率下降的有效工具。量化过程的输入值动态范围很大,需要较多的比特才能表示一个数值,量化后的输出则只需要较小比特表示。
量化是不可逆过程,处理过程中有信息丢失,存在量化误差。

H.264采用标量量化技术,它将每个图像样点编码映射成较小的数值。一般标量量化器的原理为:
F Q = r o u n d ( y / Q P ) FQ=round(y/QP)FQ=round(y/QP)
其中,y 为输入样本点编码,QP 为量化步长,FQ 为 y 的量化值,round()为取整函数(其输出为与输入实数最近的整数)

在量化和反量化过程中,量化步长 QP 决定量化器的编码压缩率及图像精度。如果 QP 比较大,则量化值 FQ 动态范围较小,其相应的编码长度较小,数据压缩率高但会损失较多的图像细节信息;如果QP 比较小,则 FQ 动态范围较大,相应的编码长度也较大,但图像细节信息损失较少。编码器根据图像值实际动态范围自动改变 QP 值,在编码码率和图像精度之间折衷,达到整体最佳效果。

3.6 熵编码

经过 ZigZag 扫描后,一连串的数字的最后大部分为 0,以及一些 +1,-1。针对这一系列的数字,从概率的角度,再进行一次编码,这个过程称之为熵编码。

熵编码主要分为 CAVLC,和 CABAC,分别代表基于上下文的自适应可变长编码和基于上下文的自适应二进制算术编码。

3.6.1 CAVLC

请参考下述文章

编码原理(五)–熵编码–CAVLC

  • 非零系数的数目(TotalCoeffs): 代表 ZigZag 扫描后序列中非 0 值的个数;
  • 拖尾系数的数目(TrailingOnes):指 Z 扫描后,末尾高频信号中出现连续1或-1的个数(中间可以隔任意多个0),拖尾系数最多有5个。当连续1或-1的个数超过3个,只有最后3个1或-1是拖尾系数,其他的当作普通的非零系数。
  • TotalZeros: 最后一个非零系数前零的数目;
  • nC:用来决策 coeff 如何编码的变量;
  • ZerosLeft: 当前系数之前所有的 0 的个数;
  • RunBefore: 紧邻当前系数的0的个数;

利用相邻已编码符号所提供的相关性,为索要编码的符号选择合适的上下文模型。

利用合适的上下文模型,就可以大大降低符号间的冗余度。

TotalCoeffs 和 TrailingOnes 通过查表方式进行编码,H.264 针对 TotalCoeffs 和 TrailingOnes,提供了 4 张变长表和 1 张定长表(部分见附表9-5)。

编码表的选择是由NC确定的,NC的值是由上下文信息确定的。对于色度直流信号,NC=-1;对于其他的NC,根据当前块左边4x4块和上面4x4块的非零系数的个数A和B决定。如表一:其中X表示该块与当前块属于同一slice且可用。根据NC选择编码表的策略如表二:

非零系数主要集中在低频部分,高频系数大部分为零,量化后经过 zig-zag 编码。

非零系数的幅值通常在靠近DC(即直流分量)部分较大,而在高频部分较小;

上下文模型主要体现在:非零系数编码所需表格和拖尾系数后缀长度的更新。

CAVLC 以 zig-zag 顺序用于对变换后的残差块进行编码。

CAVLC 是 CABAC 的替代品,虽然其压缩效率不如 CABAC,但 CAVLC 实现简单,并且在所有的 H.264 profile 中都支持。

通过 根据已编码句法元素的情况动态调整编码中使用的码表,取得了极高的压缩比。

3.6.2 CABAC

四、H.264 码流分层

4.1 VCL & NAL

  • H.264 的功能分为两层,即视频编码层(VCL)和网络提取层(NAL,Network Abstraction

    Layer)。

  • VCL Layer 將 vidoe image 分割為許多的 slices,這此slice在分成許多的NALU傳送出去

  • VCL 数据即编码处理的输出,它表示被压缩编码后的视频数据序列。在 VCL 数据传输或存

    储之前,这些编码的 VCL 数据,先被映射或封装进 NAL 单元中。

    每个 NAL 单元包括一个原始字节序列负荷(RBSP)、一组对应于视频编码数据的 NAL 头信息。

    NAL 单元序列的结构见图 6.6

  • 切片头:包含了一组片的信息,比如片的数量,顺序等等

H.264从层次来看分为两层:视频编码层(VCL, Video Coding Layer)和网络提取层(NAL,Network Abstraction Layer)。VCL输出的是原始数据比特流(SODB,String of data bits),表示H.264的语法元素编码完成后的实际的原始二进制码流。

SODB通常不能保证字节对齐,故需要补齐为原始字节序列负荷(RBSP,Raw Byte Sequence Payload)。NAL层实际上就是最终输出的H.264码流,它是由一个个NALU组成的,每个NALU包括一组对应于视频编码数据的NAL头信息和一个原始字节序列负荷(RBSP,Raw Byte Sequence Payload)。以上名词之间的关系如下:

RBSP = SODB + RBSP trailing bits
NALU = NAL header(1 byte) + RBSP
H.264 = Start Code Prefix(3 bytes) + NALU + Start Code Prefix(3 bytes) + NALU +…

所以H.264码流的结构如下:

NALU (Nal Unit) = NALU头 + RBSP 在 VCL

数据传输或存储之前,这些编码的 VCL 数据,先被映射或封装进 NAL 单元(以下简称 NALU,Nal Unit) 中。每个 NALU 包括一个原始字节序列负荷(RBSP, Raw Byte Sequence Payload)、一组 对应于视频编码的 NALU 头部信息。RBSP 的基本结构是:在原始编码数据的后面填加了结尾 比特。一个 bit“1”若干比特“0”,以便字节对齐。

一个原始的 H.264 NALU 单元常由 [StartCode] [NALU Header] [NALU Payload] 三部分组成。

  • StartCode : Start Code 用于标示这是一个NALU 单元的开始,必须是”00 00 00 01” 或”00 00 01”(Annex B码流格式才必须是”00 00 00 01” 或”00 00 01”)

下表为 NAL Header Type

4.2 码流格式

H.264 标准中指定了视频如何编码成独立的包,但如何存储和传输这些包却未作规范,虽然标准中包含了一个 Annex 附件,里面描述了一种可能的格式 Annex B,但这并不是一个必须要求的格式。
为了针对不同的存储传输需求,出现了两种打包方法。一种即 Annex B 格式,另一种称为 AVCC 格式。

  • Annex B
    从上文可知,一个 NALU 中的数据并未包含他的大小(长度)信息,因此我们并不能简单的将一个个 NALU 连接起来生成一个流,因为数据流的接收端并不知道一个 NALU 从哪里结束,另一个 NALU 从哪里开始。
    Annex B格式用起始码(Start Code)来解决这个问题,它在每个 NALU 的开始处添加三字节或四字节的起始码 0x000001 或 0x00000001。通过定位起始码,解码器就可以很容易的识别NALU的边界。
    当然,用起始码定位NALU边界存在一个问题,即NALU中可能存在与起始码相同的数据。为了防止这个问题,在构建 NALU时,需要将数据中的0x000000,0x000001,0x000002,0x000003 中插入防竞争字节(Emulation Prevention Bytes) 0x03,使其变为:
    0x000000 = 0x0000 03 00
    0x000001 = 0x0000 03 01
    0x000002 = 0x0000 03 02
    0x000003 = 0x0000 03 03
    解码器在检测到 0x000003 时,将 0x03 抛弃,恢复原始数据。

由于 Annex B 格式每个 NALU 都包含起始码,所以解码器可以从视频流随机点开始进行解码,常用于实时的流格式。在这种格式中通常会周期性的重复 SPS 和PPS,并且经常时在每一个关键帧之前。

4.3 H264的传输

H264是一种码流 类似与一种不见头,也不见尾的一条河流。如何从和流中取到自己想要的数据呢,

在H264的标砖中有这样的一个封装格式叫做”Annex-B”的字节流格式。 它是H264编码的主要字节流格式。

几乎市面上的编码器是以这种格式进行输出的。起始码 0x 00 00 00 01 或者 0x 00 00 01 作为分隔符

两个 0x 00 00 00 01之间的字节数据 是表示一个NAL Unit

宏快作为压缩视频的最小的一部分,需要被组织,然后在网络之间做相互传输。

如果单纯的用 宏快 来发送数据是 杂乱无章 的,就好像在没有 集装箱 出现之前,货物总是随意被堆放到船上。

上货(编码),下货是非常痛苦的。 当集装箱出现之后,一切都发生了改变,传输效率大大增高。

集装箱可以理解成 H264编码标准,他制定了相互传输的格式,将宏快 有组织,有结构,有顺序的形成一系列的码流。这种码流既可 通过 InputStream 网络流的数据进行传输,也可以封装成一个文件进行保存。

参考引用

[1] YUV图像处理入门1

[2] H264编码(帧内预测)

[3] H.264编码profile & level控制

[4] 帧内压缩与帧间压缩

[5] H.264分层结构与码流结构

[6] H.264中普通I帧和IDR帧究竟有什么区别

[7] 运动补偿与运动估计

[8] Color Formats – equasys GmbH

[9] h264

[10] Introduction to H.264: (2) SODB vs RBSP vs EBSP

[11] H.264基本原理与编码流程

[12] 【知识点】H264, H265硬件编解码基础及码流分析

[13] [Media] 影音傳輸-基礎知識_GOP/视频基础

[14] 深入讲解音视频编码原理,H264码流详解——手写H264编码器

[15] 编码原理(四)—ZIGZAG扫描

[16] 一文搞懂H264量化原理以及计算过程


 目录


# 行动就是意义