HEVC的基本概念以及特性

作者:马雅狄

HEVC,即H.265,是现行的先进视频编解码标准H.264/MPEG-4 AVC的后继与发展。由ISO/IEC Moving Picture Experts Group(MPEG)和ITU-T Video Coding Experts Group(VCEG)两个组织联合成立的Joint Collaborative Team on Video Coding (JCT-VC)开发。该项目被称为ISO/IEC 23008-2 MPEG-H Part2 或 ITU-T H.265。与他的前辈H.264相比,H.265具有更高的(接近于两倍)压缩比率,在同码率下具有更佳的视频质量,而且支持8k UHD超高清视频。根据最新消息,在2013年的1月,该标准已经进入最终稿状态(Final Draft),这意味着新一代的视频编解码标准的制定已经接近完成,电影电视和视频领域的新时代已经on the way。

1、HEVC的特征

HEVC旨在允许运算复杂度提升的前提下,在相似视频质量情况下比H.264节 省一半的比特率。依据不同应用场合的需求,HEVC编码器可以在压缩率、运算复杂度、抗误码性和编码延迟方面进行取舍和折中。相对于H.264,HEVC 具有两大改进,即支持更高分辨率的视频以及改进的并行处理模式。HEVC的目标是应用与下一代高清电视和摄像系统,这些系统的特征有逐行扫描和从QVGA 到4320P的多种分辨率;此外还要在噪声强度、全色度和动态范围情况下提升视频质量。

2、视频编码层
HEVC使用了其他标准广泛应用的混合编码方法,即采用帧内、帧间预测和二维变 换编码。HEVC编码器首先将第一帧或随机存取点的某一I帧分割成多个块区域。当像素块仅依据当前帧的数据进行编码时,编码称作帧内编码。对于其他帧,使 用了参考帧信息的编码方法称为帧间编码。预测运算和环路滤波结束后,重建图像储存于解码缓存中,可作为其他帧的参考帧。

HEVC特别为逐行扫描视频信号设计,并未专门研究隔行扫描信息。为此,HEVC发送“元流数据”来解释隔行信息如何传输:或者两场数据分别作为一帧编码,或者将两场数据合并成一帧进行编码传输。这样HEVC就不需要专门设计隔行扫描的解码器。

3、编码工具

(1)预测块尺寸

HEVC放弃了当前各种编码标准中“宏块”的概念,而是采用了一种最大可达64×64,可以根据内容进一步分解为多种尺寸的结构。HEVC首先将树形编码单元(coding tree units,CTUs),而后进一步对亮度和色度分量分解为树形编码块(coding tree blocks,CTBs)。CTB通常为64×64, 32×32或16×16,通常分块越大,编码效率越高。CTB被分割为编码单元(coding units,CUs),CTB内CU的组织结构为四叉树结构,即一次分割产生四个小块。CUs又可进一步分割为预测单元(prediction units,PUs),该结果的属性或为帧内或为帧间,大小从64×64到4×4不等。PU使用两个参考块进行编码,即双预测编码,大小限制为8×4或4×8以节省存储带宽。预测残差由变换单元(transform units,TUs)进行编码,进行空域块变换和量化。TU的尺寸可能为32×32, 16×16, 8×8或4×4。

在2012年7月HEVC会议上,依据提案 JCTVC-J0334,HEVC Level 5及以上等级要求CTB大小至少为32×32或64×64。该决定被加入国际标准草案中作为变量Log2MaxCtbSize的限制(2012年10月会议上更名为CtbSizeY)。

(2)Internal bit depth increase (IBDI):(没看懂⋯⋯明白的哪位能给我解释一下?)

(3)并行处理工具

“马赛克”结构一帧数据被分割成多个矩形像素块,可以独立进行编码/解码。该结构的主要目的就是可以让像素块进行并行处理,甚至可以对视频流中的某一个像素块进行随机存取。

波前编码(Wavefront parallel processing ,WPP)应用于条带被分割微CTUs的行结构,且除第一行外,其余各行的解码均需要前一行的信息的情况。波前编码使熵编码器使用上一行ctus的信息,可能达到比马赛克结构更高的压缩性能。

条带slice结构在大多数情况下可以进行独立解码,而预测算法的范围通常不会超越条带的边界。这样在一定程度上改善了视频流中丢失数据的影响。slice是以光栅结构扫描的ctus集合,不同slice可以采用如ibp等不同的编码类型。

如果必须解码整个条带,则依赖型条带可以使与马赛克或WPP相关的数据处理更快。其主要目的是减少编码延迟。

(4).熵编码

HEVC使用上下文自适应的二进制算数编码(CABAC)。该算法与H.264中的CABAC基本类似。与H.264不同的是,HEVC仅采用CABAC这一种熵编码方法。

(5).帧内预测

HEVC指定了33种帧内预测方向模式,而H.264仅有8种。同时,HEVC也指定了DC和平面模式。帧内预测使用相邻像素块已经解码完成的数据进行预测。

(6).运动补偿

在运动补偿中,HEVC使用半像素和1/4像素精度。HEVC中的加权预测可只用一个预测块做单预测,也可以使用两个预测块进行双预测。

(7).反变换

HEVC指定了四种尺寸的变换单元——4×4, 8×8, 16×16和32×32——来编码预测残差。一个CTB递归地分割成4个或更多个TU,使用类DCT变换。另外,对于属于帧内预测模式的4×4亮度块,使用类DST变换。

(8).环路滤波器

HEVC指定了两种环路滤波器,即去块滤波器(deblocking filter,DBF)和采样自适应偏移滤波器(sample adaptive offset,SAO),均在帧间预测环路中进行。

去块滤波器:HEVC中的去块滤波器与H.264中的DBF类似,但是设计更简单,对并行设计支持更好。HEVC只采用8*8结构,而H.264采用4*4结构。HEVC的DBF滤波强度范围为0~2三级,要求先对垂直边缘进行水平滤波,然后再对水平边缘进行垂直滤波。

采样自适应偏移滤波器:SAO用于DBF之后,使用传输的查阅表进行更好地原始信号重建。对于每个CTB,SAO可以使用/禁用一种或者两种模式:边缘偏移模式或带偏移模式。边缘偏移模式将采样像素值利用四种方向梯度模式与8个边缘像素中的两个比较,并对像素进行分类:minimum,两种edges, maximum,或者neither。对于前四种模式,滤波器进行偏移操作。带偏移模式采用的偏移量基于某个单一采样值的幅度,依据该幅度分为32个不同 的band。四个连续的band指定一个偏移量。SAO旨在提高图像的质量,减少带状干扰和边缘震荡效应。

4、高层语法架构
HEVC的新设计特性增强了在多种应用和网络环境下操作的灵活性以及对数据损失的鲁棒性。H.264大部分高层语法架构得到保留,具体如下:

(1)参数集合:参数集合包含可在解码视频的不同区域分享的信息,为传递解码关键数据提供了高鲁棒机制。新的视频参数集(VPS)的概念对序列和图像参数的概念进行了增强。

(2)NAL基本语法结构:每个语法结构放入网络提取层单元这一逻辑数据包中。利用2字节的NAL单元头,容易识别携带数据的内容类型。

(3)条带:条带是可独立于当前帧的其他条带进行解码的数据结构,其熵编码、预测和残差信号重建均独立。其主要目的之一是在数据丢失情况下进行重新同步。在分组发送中,条带中的最大负载比特数常常受限,CTU的数量不定,为了在限制数据包大小的前提下将分组数量最小化。

(4)补充增强信息和视频可用性信息元数据:提供视频图像的定时、颜色空间解释、3D视频信息填充等信息。

5、并行解码语法和改进的条带结构
HEVC引入了三种新技术来增强并行处理能力和条带结构的封装性。

(1)Tiles:将图像分割为矩形区域的选项。其主要目的增强并行处理性能而非容错性。Tiles是图像中共享头信息编码但是可以独立解码的区域,并可 在空间域作为图像的随机接入点。通常,每一个tile中包含的CTU的数目是接近于相等的。Tile提供了一种简单的并行处理模式,不需要复杂的线程同 步。

(2)波前并行处理:该模式中,条带被分割成CTU的行。第一行按正常处理,第二行在第一行两个CTU完成后开始处理,第三行在第二行的两个CTU处理完成后开始处理,以此类推。通常该方法的压缩性能高于tiles。

(3)依赖性条带分割:允许依赖于某一特定波前入口点或者tile的数据在一个独立的NAL单元中携带传输。只有在其他条带的部分数据解码完成后,才能解码一个依赖性条带的波前入口点。

6、高层语法
包含类似H.264中网络提取层的元素,用于联系视频编码层和网络传输(如数据包封装等),支持的新功能描述如下:

6.1 随机接入和比特流条带特征

H.264采用可包含独立解码图像的IDR接入单元实现随机接入。

HEVC中的新结构:clean random access (CRA)。支持开放GOP操作。基本概念:random access skipped leading (RASL) pictures,因为包含帧间信息而不能解码的图像;broken link access (BLA) pictures:位于不同比特流连接点的图像;random access decodable leading (RADL) picture:在随机接入点后,可以按顺序解码的图像。随机接入点的图像可能是IDR/CRA/BLA的图像。leading pictures (LPs):RASL和RADL的总称。trailing pictures:随机接入点后按解码和输出顺序的图像。

6.2 支持时间域分层

在NAL单元头中指定时间识别,表示在分层时间预测中的层次。可以在除了NAL头之外不解析比特流的情况下做到时间可分级。

时间子层接入点temporal sublayer access (TSA);步进式时间子层接入点stepwise TSA (STSA)——比特流中可以由此开始解码更高时域层的位置。

6.3 额外参数集

作为元数据用以描述编码视频序列的整体特征,包括时域子层之间的依赖性。

6.4 参考图像集合和参考图像表

图序计数(picture order count,POC)用以标识解码完成作为参考的图像。这些图像组成参考图像集(reference picture set,RPS)。解码图像缓存中保存两个参考图像表list 0和list 1,,单向预测时,选取二者之一作为参考,双向预测时,两个list中各选择一个作为参考。

7、JCT-VC工作组公布的参考代码HM
I. 将一个yuv测试序列编码为一个.bin格式的二进制码流
(1)下载完成后解压,文件夹中包含下图所示的内容:

不同的开发平台要选择不同的工程文件,苹果的xcode工程文件在HM.xcodeproj中,windows和linux的工程文件在build文件夹中。我所使用的是visual studio 2010,因此选择build文件夹中的HM_vc10.sln,从VS中找到目录文件,打开。

(2)在visual studio 2010中打开后,发现整个solution包含七个工程 (TAppCommon,TAppEncoder,TAppDecoder,TLibCommon,TLibEncoder,TLibDecoder,TLibVideoIO,下图所示)。这些工程分别的功能在以后讨论。今天的目的仅仅是能成功将YUV文件编码成HEVC格式的码流,因此将TAppEncoder设为活动工程(在该工程上由右键->Set as StartUp Project)。

(3)编译整个Solution(F7),等待7个工程编译完成后,根目录下新生成了一个文件夹,路径为:(根目录位置\HM-10.0rc1\bin\vc10\Win32\Debug),其中有成功编译完成的两个可执行文件TAppDecoder.exe和TAppEncoder.exe,以及其他一些副产品。

(4)工程目录中的cfg文件夹中有多个预先设定好的配置文件,分别代表了不同的编码模式。该目录中的pre-sequence文件夹中指定了多个输入测 试序列的配置信息。我们在这里选择encoder_intra_main.cfg作为编码的配置文件,另外用akiyo_qcif.yuv作为测试序列。 新建一个cfg文件命名为akiyo.cfg,内容如下:

#======== File I/O ===============

InputFile : E:\TestSquence\176x144\akiyo_qcif.yuv

InputBitDepth : 8 # Input bitdepth

FrameRate : 30 # Frame Rate per second

FrameSkip : 0 # Number of frames to be skipped in input

SourceWidth : 176 # Input frame width

SourceHeight : 144 # Input frame height

FramesToBeEncoded : 50 # Number of frames to be coded

将两个cfg文件拷贝到(3)中的目录下,待编码序列要依据cfg的路径存放,否则会出现找不到文件的错误。

(5)配置工程属性。

在TAppEncoder工程上右键单击->Properties->Configuration Properties->Debugging。将命令行参数和工作目录改成下图所示:

这一步也可以用命令行完成,效果是一样的。

(6)编译运行。Debug->Start Without Debuging->是,命令行窗口中会显示编码的信息和结果,如下图所示。



II. 将二进制码流进行解码
之前已经讨论过如何运行HM的encoder工程,已经可以成功将一个yuv测试序列编码为一个.bin格式的二进制码流。这里我们再看看它的逆过程,即将二进制码流进行解码。

在整个HM的solution中很明显可以看到解码器的工程TAppDecoder,将这个工程设置为启动工程(在工程目录上右单击->set as startup project),设置工程属性,主要是命令行参数(右键—>Propertiea->configuration properties->debuging),如下图。还有就是别忘了把str.bin这个码流文件拷到指定的工作目录中去。

完成以后直接编译运行就OK了,解码完成后的输出文件默认为dec.yuv。命令行输出信息如图。


解码器的入口点函数在TAppDecoder中decmain.cpp这个文件里。由main函数进入后,新建一个顶层解码器对象cTAppDecTop 之后并输出一些参考解码器版本等等信息之后,cTAppDecTop调用parseCfg()函数来解析命令行参数(在这里也就是上一步设置的“-b str.bin -o dec.yuv”这一行)。

一开始,这个参数解析函数的内容貌似有些奇怪,后来仔细一看才发现,就如同HM整个solution一样,里面应用了大量的C++面向对象的内容。由于之 前一直是主攻C而且研究的也是用C写的JM/RM等代码,刚接触HM的时候感觉还是非常不习惯。单就这个函数来讲,里面用到了多个C++相对于C的扩展内 容,主要有命名空间、模板类、泛型程序与标准模板库、流等。

命名空间:

命名空间namespace是标准C++引入的关键字,用于控制标识符(包括符号常量、变量、宏、函数、结构、枚举、类和对象等)的作用域,将这些标识符进行本地化,防止命名冲突。如果没有命名空间,则所有标识符都会处于全局空间中,在大型项目的程序开发中很容易出现命名冲突,包括标识符都处于全局空间以及程序使用两个或者多个第三方库的情况等等。

比如若定义一个命名空间:
namespace myNameSpace
{
int nValue1;
int nValue2;

void GetValues(int &_nValue1, int &_nValue2)
{
_nValue1 = nValue1;
-nValue2 = nValue2;
}

Class NSClass
{
private:
int nValueInNSClass;
public:
int ShowNSClassValue()
{
cout< }
}
}

可以看出,在命名空间内部,标识符可以直接使用,如nValue1,nValueInNSClass等。但是若在namespace定义范围之外使用其成 员,则必须使用作用域限定符::。但是如果已经定义了该namespace类型的对象,则利用对象使用成员就不需要作用域限定符了。比如
int _tmain()
{
int a = 1, b = 2, c, d;
myNameSpace::nValue1 = a;
myNameSpace::nValue2 = b;
myNameSpace mNS;
mNS.GetValues(c,d);
}

关于命名空间进一步的讨论可以参考下面的文章:

http://blog.chinaunix.net/uid-26874138-id-3215266.html

http://blog.csdn.net/touzani/article/details/1637776

模板函数和模板类

模板函数是C++中定义的一种函数形式,这种函数不仅仅处理某一种形式的参数,而是可以兼容某一类型的多种参数。通过模板函数,可以实现参数化多态性,即 使用同一段程序用于处理多种不同类型对象。与此类似的是类模板是声明的类的一种形式,该类中的数据成员、成员函数的参数和返回值可以处理多种类型数据,是 一种比类更高层次的抽象,也成为参数化类。模板类的成员函数必须是模板函数。

泛型程序设计

泛型程序设计的目的,是用标准容器和标准算法,以最通用、最有效、最灵活地实现程序的设计,同时不损失效率。泛型程序设计的一个良好典范是C++所提供的标准模板库STL。因为C++模板为泛型程序设计奠定了基础,是所有版本STL的基石。

I/O流

IO流是C++使用的面向对象的输入输出软件包,是C语言的输入输出函数的一种替代产品。通过“流”这一抽象概念,将数据在产生者同使用者(包括磁盘文件和IO设备等)之间进行联系并管理其流动。

关于更详细的解释,相信大家都有各种C++的教材吧,随便一本应该都能讲清楚,自己去找吧,呵呵。

在TAppdecoder中使用了TAppCommon中定义的df,以及在df内部定义的命名空间program_options_lite。这两个命 名空间中定义的多个类、结构和函数用于处理decoder的参数配置信息。TAppDecCfg::parseCfg()这个函数的实现中,先定义了一个 df::program_options_lite类型的命名空间po,并且在po内定义了一个Options对象opts。随后opts调用 addOptions()函数,后面跟着一串由括号引起的四元cfg结构。这里这么写的原因是因为,opts.addOptinos()所返回的是一个指 向OptionSpecific类型的指针,并且OptionSpecific重载了“()”这个运算符,使其也返回一个指向 OptionSpecific类型的指针。这样通过一级一级的括号调用,每次都会调用OptionSpecific的构造函数,再调用其Options成 员parent_的addOption函数解析并添加括号中的四元cfg结构。完成后,这些数据都存储在opts中。

下一个函数po::setDefaults(opts)的目的仅仅是将指定的设置参数数值同制定参数进行赋值而已。后面的几个函数会进行无效参数扫描等操作,大部分这里根本没有执行到,因此暂时忽略之。

III. 代码下载地址
我已将可编译运行的代码放到CSDN的下载区中,地址:http://download.csdn.net/detail/amymayadi/7903385

【参考文献】

1. 【HEVC学习与研究】7.HEVC参考代码HM10.0的使用 http://blog.sina.com.cn/s/blog_520811730101j6jh.html

2. 【HEVC学习与研究】11.HEVC参考解码器的设置及参数解析过程 http://blog.sina.com.cn/s/blog_520811730101k5n6.html

3 . 百度知道

文章来源:amymayadi的专栏