本文继续讨论编码器的初始化过程,即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,大家可以按照相同的思路进行分析,这里就不再重复上述过程了。