[置顶] HEVC学习(二十一) —— 熵编码之二

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





你可能感兴趣的:(hm,HEVC,CABAC)