本文继续讨论编码器的初始化过程,即draft 9.3.1.1。
上一篇介绍的是各个context对应到标准相应表格的值,以及相关变量和函数,前面提到initBuffer函数对context进行初始化,但是没有深入解析这个函数的实现,这就是本文的主要任务。
首先介绍下面讨论过程中会涉及到的一个类ContextModel3DBuffer,重点关注该类的构造函数:
/// context model 3D buffer class class ContextModel3DBuffer { protected: ContextModel* m_contextModel; ///< array of context models const UInt m_sizeX; ///< X size of 3D buffer const UInt m_sizeXY; ///< X times Y size of 3D buffer const UInt m_sizeXYZ; ///< total size of 3D buffer public: ContextModel3DBuffer ( UInt uiSizeZ, UInt uiSizeY, UInt uiSizeX, ContextModel *basePtr, Int &count ); //!< 构造函数 ~ContextModel3DBuffer () {} ... ... ... ... // initialization & copy functions Void initBuffer( SliceType eSliceType, Int iQp, UChar* ctxModel ); ///< initialize 3D buffer by slice type & QP ... ... };
ContextModel3DBuffer::ContextModel3DBuffer( UInt uiSizeZ, UInt uiSizeY, UInt uiSizeX, ContextModel *basePtr, Int &count ) : m_sizeX ( uiSizeX ) , m_sizeXY ( uiSizeX * uiSizeY ) , m_sizeXYZ( uiSizeX * uiSizeY * uiSizeZ ) { // allocate 3D buffer m_contextModel = basePtr; //!< m_contextModel由basePtr赋值,即指向指定的context的内存区 count += m_sizeXYZ; //!< count记录的是到目前为止所有context的尺寸 }
下面再看类TEncSbac的构造函数,因为实际熵编码就是该类定义的一个对象来完成的,重点关注该类的私有数据成员是如何初始化的:
TEncSbac::TEncSbac() // new structure here : m_pcBitIf ( NULL ) , m_pcSlice ( NULL ) , m_pcBinIf ( NULL ) , m_uiCoeffCost ( 0 ) , m_numContextModels ( 0 ) //!< context model的计数值,接下来的所有除了assert的语句都是对句法元素对应的context进行初始化 , m_cCUSplitFlagSCModel ( 1, 1, NUM_SPLIT_FLAG_CTX , m_contextModels + m_numContextModels, m_numContextModels ) , m_cCUSkipFlagSCModel ( 1, 1, NUM_SKIP_FLAG_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUMergeFlagExtSCModel ( 1, 1, NUM_MERGE_FLAG_EXT_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUMergeIdxExtSCModel ( 1, 1, NUM_MERGE_IDX_EXT_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUPartSizeSCModel ( 1, 1, NUM_PART_SIZE_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUPredModeSCModel ( 1, 1, NUM_PRED_MODE_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUIntraPredSCModel ( 1, 1, NUM_ADI_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUChromaPredSCModel ( 1, 1, NUM_CHROMA_PRED_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUDeltaQpSCModel ( 1, 1, NUM_DELTA_QP_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUInterDirSCModel ( 1, 1, NUM_INTER_DIR_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCURefPicSCModel ( 1, 1, NUM_REF_NO_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUMvdSCModel ( 1, 1, NUM_MV_RES_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUQtCbfSCModel ( 1, 2, NUM_QT_CBF_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUTransSubdivFlagSCModel ( 1, 1, NUM_TRANS_SUBDIV_FLAG_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUQtRootCbfSCModel ( 1, 1, NUM_QT_ROOT_CBF_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUSigCoeffGroupSCModel ( 1, 2, NUM_SIG_CG_FLAG_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUSigSCModel ( 1, 1, NUM_SIG_FLAG_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCuCtxLastX ( 1, 2, NUM_CTX_LAST_FLAG_XY , m_contextModels + m_numContextModels, m_numContextModels) , m_cCuCtxLastY ( 1, 2, NUM_CTX_LAST_FLAG_XY , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUOneSCModel ( 1, 1, NUM_ONE_FLAG_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUAbsSCModel ( 1, 1, NUM_ABS_FLAG_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cMVPIdxSCModel ( 1, 1, NUM_MVP_IDX_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cCUAMPSCModel ( 1, 1, NUM_CU_AMP_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cSaoMergeSCModel ( 1, 1, NUM_SAO_MERGE_FLAG_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cSaoTypeIdxSCModel ( 1, 1, NUM_SAO_TYPE_IDX_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_cTransformSkipSCModel ( 1, 2, NUM_TRANSFORMSKIP_FLAG_CTX , m_contextModels + m_numContextModels, m_numContextModels) , m_CUTransquantBypassFlagSCModel( 1, 1, NUM_CU_TRANSQUANT_BYPASS_FLAG_CTX, m_contextModels + m_numContextModels, m_numContextModels) { assert( m_numContextModels <= MAX_NUM_CTX_MOD ); }
所有的私有数据成员(指m_cCUSplitFlagSCModel及其接下来的变量)都是ContextModel3DBuffer定义的对象,因此,在TEncSbac类的构造函数的成员初始化列表中,将会调用ContextModel3DBuffer类的构造函数对相应变量初始化。
接下来看initBuffer函数的实现:
/** * Initialize 3D buffer with respect to slicetype, QP and given initial probability table * * \param eSliceType slice type * \param iQp input QP value * \param CtxModel given probability table */ Void ContextModel3DBuffer::initBuffer( SliceType sliceType, Int qp, UChar* ctxModel ) { ctxModel += sliceType * m_sizeXYZ; //!< 根据当前slice的类型(I,P,B)选择对应的context,为什么这么做,下面会解释 for ( Int n = 0; n < m_sizeXYZ; n++ ) { m_contextModel[ n ].init( qp, ctxModel[ n ] ); //!< 完成context的各个状态变量的初始化工作。 m_contextModel[ n ].setBinsCoded( 0 ); } }
接下来进入到m_contextModel[ n ].init( qp, ctxModel[ n ] )中去:
/** - initialize context model with respect to QP and initialization value . \param qp input QP value \param initValue 8 bit initialization value */ Void ContextModel::init( Int qp, Int initValue ) { qp = Clip3(0, 51, qp); //!< 与draft 9.3.1.1基本呈一一对应关系 Int slope = (initValue>>4)*5 - 45; //!< m Int offset = ((initValue&15)<<3)-16; //!< n Int initState = min( max( 1, ( ( ( slope * qp ) >> 4 ) + offset ) ), 126 ); //!< preCtxState UInt mpState = (initState >= 64 ); //!< valMPS m_ucState = ( (mpState? (initState - 64):(63 - initState)) <<1) + mpState; //!< pStateIdx,与(9-5)式略有不同,这里的m_ucState的值实际上是draft中pStateIdx<<1+valMPS,这么做的目的应该是为了节省内存 }
本文至此还剩下最后一个问题,即initBuffer函数中这么一句:
ctxModel += sliceType * m_sizeXYZ;
这句实际上是在根据sliceType计算initType并将context指针移动到正确的位置上,在draft 9.3.1.1有这么一段:
The variable initType in is derived as follows:
if( slice_type = = I )
initType = 0
else if(slice_type = = P )
initType = cabac_init_flag ? 2 : 1
else
initType = cabac_init_flag ? 1 : 2
这个initType用于索引context model,且由slice_type来决定。在HM中,slice_type如下定义:
/// supported slice type enum SliceType { B_SLICE, P_SLICE, I_SLICE };
即B_SLICE,P_SLICE,I_SLICE分别为0,1,2,这个跟上面一段有所区别(I:0, P:2, B:1,cabac_init_flag默认为true)。
方便起见,这里举个实例(split_cu_flag)来说明HM是如何解决这个问题的。
// initial probability for split flag static const UChar INIT_SPLIT_FLAG[3][NUM_SPLIT_FLAG_CTX] = { { 107, 139, 126, }, { 107, 139, 126, }, { 139, 141, 157, }, }; //!< Table 9-7
与draft Table 9-7 对照后可以发现,实际上INIT_SPLIT_FLAG保存的顺序是initType=2,1,0时对应的initValue,这么做以后,对于I_SLICE,ctxModel += 6 (I_SLICE=2, m_sizeXYZ=3),对于P_SLICE,ctxModel += 3(P_SLICE=1, m_sizeXYZ=3),对于B_SLICE,
ctxModel += 0(B_SLICE=0, m_sizeXYZ=3),而ctxModel最初指向的是INIT_SPLIT_FLAG这个数组,通过简单的计算后我们发现,HM的做法保证了最终的结果与draft中的initType是一致的。
其余的context,大家可以按照相同的思路进行分析,这里就不再重复上述过程了。