关于HEVC的前25篇博文全文发表在新浪博客,地址为:http://blog.sina.com.cn/s/articlelist_1376260467_0_1.html。从第26篇开始博客全文发在CSDN,新浪同步更新摘要和链接地址。
在第13篇博文中贴出了我们在调试代码时所采用的二进制码流的开头一部分数据,并根据这些数据进行了NAL Header解析、参数集合解析和条带头解析等信息的分析。今后的博文中如无特殊情况依然会采用这些数据作为学习材料。
通过对这段码流进行分析,我们可以看出,在对于一个slice进行解码的过程中,对slice header和slice data采取了不同的熵解码方法:slice header的语法元素采用了变长码(主要是指数哥伦布编码),而slice data的语法元素采用的是算术编码CABAC。由于对CABAC的基本概念不是非常熟悉,之前一段时间一直在回顾H.264中CABAC相关的知识,现在来看HEVC重的算术编码相对于H.264做了什么样的改变。
1、数据准备
同JM86中在每一个slice中为算术编码进行数据准备不同,HM 10.0 decoder在程序的一开始就为CABAC解码的过程在做准备。在main函数中的最开始会定义一个解码器应用的实例:
TAppDecTop cTAppDecTop;
在TAppDecTop这个类的声明中可以看出,该类包含一个TDecTop类型的成员m_cTDecTop,这个对象将实现对编码码流的解码。在TDecTop这一层还将实现针对不同类型的NAL,如VPS, SPS, PPS, SEI和slice数据等类型的NAL进行分别处理等操作,在TDecTop::decode函数实现。
在TDecTop的声明中找到了一个TDecSbac类型的成员m_cSbacDecoder,根据TDecSbac.cpp开头的简介提示可知,这个类实现的就是上下文自适应的熵解码器类。并且在类的声明中可以得知,该类对码流中的多种不同语法元素定义了相应的解析方法,如parseMVPIdx(), parseSaoMaxUvlc()等。除了这些解析方法之外,TDecSbac中还定义了一个数组存放上下文模型:ContextModel
m_contextModels[MAX_NUM_CTX_MOD];一共512个元素。这些元素的类型ContextModel是专门用来定义上下文模型的类,其成员包括了预定义的LPS和MPS的状态索引m_aucNextStateMPS[ 128 ]、m_aucNextStateLPS[ 128 ]以及表示当前状态的变量m_ucState。在该类的实现函数中,上述两个数组被分别赋予相应的初始值。
2、SbacDecoder的初始化
SbacDecoder类的构造函数内容几乎就是空的,但是该构造函数同时调用了SbacDecoder类成员的构造函数对其进行了初始化,其中数量最多的就是多个ContextModel3DBuffer类型的成员变量分别用于不同类型的语法元素的上下文模型。从命名来看就可以得知这是一个保存上下文模型的三维缓存,其中的成员变量也包含一个指向ContextModel类的指针m_contextModel和三个维度变量m_sizeX、m_sizeXY和m_sizeXYZ。在ContextModel3DBuffer的构造函数里将指定的上下文模型的指针指向m_contextModels[]的某个成员。这些ContextModel3DBuffer类型的语法元素上下文模型都将在TDecSbac的构造函数调用时进行初始化。这一步将完成上下文模型的初始化操作。
3、其他的初始化流程
在主函数decmain.cpp中,调用cTAppDecTop.decode()函数;该函数调用xInitDecLib();该函数会调用m_cTDecTop.init()函数实现TDecTop的初始化;TDecTop::init()函数分别调用了initROM()和m_cGopDecoder、m_cSliceDecoder和m_cEntropyDecoder的init函数进行初始化。在TGopGop的声明中可以看到该类声明了TDecEntropy、TDecSbac等类型的指针。m_cGopDecoder.init()函数的作用就是将TGopGop的各种指针指向了TDecTop的各个实例成员。
4、解码过程
对条带头的解析在TDecEntropy::xDecodeSlice()中实现,在其中调用parseSliceHeader()函数。
码流的实际解析过程在TDecGop::decompressSlice()中实现。decompressSlice函数中,m_pcBinCABAC指向的就是TDecTop中定义的TDecSbac的实例m_cSbacDecoder;另外在对m_pcEntropyDecoder、m_pcSbacDecoder、m_pcSbacDecoders、m_pcBinCABACs完成设置之后,TDecGop::decompressSlice()调用了TDecSlice::decompressSlice()函数进行解码slice_segment_data部分。
slice_segment_data由多个coding_tree_unit( )组成,在色度或亮度sao flag设为true时,coding_tree_unit( )的第一个语法元素为sao(),解析过程由pcSbacDecoder->parseSaoOneLcuInterleaving()实现。该函数第一个实际调用的函数是TDecSbac::parseSaoTypeIdx(),其中进行cabac解码的函数为TDecBinCABAC::decodeBin()函数。
TDecBinCABAC::decodeBin()函数的实现可以参考标准文档的9.3.4.3.2部分。
TDecBinCABAC::decodeBin( UInt& ruiBin, ContextModel &rcCtxModel ) { //实现导出ivlLpsRange值的过程:通过选定的上下文模型获取当前状态,通过计算ivlCurrRange的值计算得到qRangeIdx(qRangeIdx =( ivlCurrRange >> 6 ) & 3); UInt uiLPS = TComCABACTables::sm_aucLPSTable[ rcCtxModel.getState() ][ ( m_uiRange >> 6 ) - 4 ]; m_uiRange -= uiLPS; //m_uiRange(标准文档中描述为ivlCurrRange)初始化为510,并变更为ivlCurrRange − ivlLpsRange UInt scaledRange = m_uiRange << 7;//计算更新的ivlOffset,这个值是表示算数解码器引擎状态的变量之一,在解码引擎初始化的时候进行赋值 if( m_uiValue < scaledRange )//根据ivlOffset与ivlCurrRange的对比判断当前属于MPS还是LPS { // MPS path ruiBin = rcCtxModel.getMps();//直接将valMps返回给输出值binVal rcCtxModel.updateMPS();//更新MPS的上下文索引,通过查询表m_aucNextStateMPS[]获得 if ( scaledRange >= ( 256 << 7 ) ) { return; } m_uiRange = scaledRange >> 6; m_uiValue += m_uiValue; if ( ++m_bitsNeeded == 0 ) { m_bitsNeeded = -8; m_uiValue += m_pcTComBitstream->readByte(); } } else { // LPS path Int numBits = TComCABACTables::sm_aucRenormTable[ uiLPS >> 3 ]; m_uiValue = ( m_uiValue - scaledRange ) << numBits; m_uiRange = uiLPS << numBits; ruiBin = 1 - rcCtxModel.getMps();//输出值valMps设为1-valMps rcCtxModel.updateLPS();//更新LPS的上下文模型索引,通过查询表m_aucNextStateLPS[]获得。 m_bitsNeeded += numBits; if ( m_bitsNeeded >= 0 ) { m_uiValue += m_pcTComBitstream->readByte() << m_bitsNeeded; m_bitsNeeded -= 8; } } }