HEVC代码学习5:cross-component prediction代码实现2

时间:2021-08-07 11:29:07

在之前的学习中,主要对CCP的整体代码思路进行了学习,下面将继续对TEncSearch::xIntraCodingTUBlock中CCP的参数和CCP其它相关函数进行研究。


一、CCP参数

1、 m_crossComponentPredictionEnabledFlag:CCP可用的标识。

TAppEncCfg::parseCfg会将读入的cfg文件中的CrossComponentPrediction的值赋给m_crossComponentPredictionEnabledFlag,默认情况下m_crossComponentPredictionEnabledFlag是false。


TAppEncCfg::xCheckParameter会对其进行检测,只有当YUV444时,m_crossComponentPredictionEnabledFlag才可以为真。

  if(m_crossComponentPredictionEnabledFlag && (m_chromaFormatIDC != CHROMA_444))
{
fprintf(stderr, "****************************************************************************\n");
fprintf(stderr, "** WARNING: Cross-component prediction is specified for 4:4:4 format only **\n");
fprintf(stderr, "****************************************************************************\n");

m_crossComponentPredictionEnabledFlag = false;
}


有两个相关的函数,分别实现对m_crossComponentPredictionEnabledFlag的取值和赋值操作。

  Bool      getCrossComponentPredictionEnabledFlag     ()                const { return m_crossComponentPredictionEnabledFlag;   }
Void setCrossComponentPredictionEnabledFlag (const Bool value) { m_crossComponentPredictionEnabledFlag = value; }

在运行CCP之前会通过pcCU->getSlice()->getPPS()->getPpsRangeExtension().getCrossComponentPredictionEnabledFlag(),来获取m_crossComponentPredictionEnabledFlag进行判断,只有当值为true时,CCP才可用。另外在反变换TComTrQuant::invRecurTransformNxN、CCP编码TEncSbac::codeCrossComponentPrediction、重构色度TEncSearch::xRecurIntraCodingLumaQT、解码器分析CCP信息TDecSbac::parseCrossComponentPrediction等函数中都会对m_crossComponentPredictionEnabledFlag进行判断。


2、bUseCrossCPrediction:也是CCP的可用标识。

与m_crossComponentPredictionEnabledFlag的区别在于,它是计算得到的,如Void TEncSearch::xIntraCodingTUBlock中,通过如下计算定义bUseCrossCPrediction。
 const Bool           bUseCrossCPrediction                 = isChroma(compID) && (uiChPredMode == DM_CHROMA_IDX) && checkCrossCPrediction;


3、m_reconBasedCrossCPredictionEstimate:指示使用重构亮度残差还是原始亮度残差预测。

取值为true时,使用重构亮度残差预测;取值为false时,使用原始亮度残差预测。

TAppEncCfg::parseCfg会将读入的cfg文件中的ReconBasedCrossCPredictionEstimate的值赋给m_reconBasedCrossCPredictionEstimate,默认情况下m_crossComponentPredictionEnabledFlag是false。


有两个相关的函数,分别实现对m_reconBasedCrossCPredictionEstimate的取值和赋值操作。

  Bool      getUseReconBasedCrossCPredictionEstimate ()                const { return m_reconBasedCrossCPredictionEstimate;  }
Void setUseReconBasedCrossCPredictionEstimate (const Bool value) { m_reconBasedCrossCPredictionEstimate = value; }


4、bUseReconstructedResidualForEstimate:指示使用重构亮度残差还是原始亮度残差。

直接由getUseReconBasedCrossCPredictionEstimate获取m_reconBasedCrossCPredictionEstimate来赋值,然后决定lumaResidualForEstimate的值,进而决定由重构亮度残差还是原始亮度残差计算α。

  Pel *const     lumaResidualForEstimate              = bUseReconstructedResidualForEstimate ? reconstructedLumaResidual : encoderLumaResidual;

在TEncSearch::xIntraCodingTUBlock的生成CCP预测中,如果不使用CCP,且bUseReconstructedResidualForEstimate为false时,会对原始亮度残差进行存储。


5、m_crossComponentPredictionAlpha[MAX_NUM_COMPONENT]:存储α值的数组。

有三个相关函数,其中两个函数用于对m_crossComponentPredictionAlpha数组进行取值:

  Char*         getCrossComponentPredictionAlpha( ComponentID compID )             { return m_crossComponentPredictionAlpha[compID];         }
Char getCrossComponentPredictionAlpha( UInt uiIdx, ComponentID compID ) { return m_crossComponentPredictionAlpha[compID][uiIdx]; }


一个函数给m_crossComponentPredictionAlpha写入计算得到的α值。

Void TComDataCU::setCrossComponentPredictionAlphaPartRange( Char alphaValue, ComponentID compID, UInt uiAbsPartIdx, UInt uiCoveredPartIdxes )
{
memset((m_crossComponentPredictionAlpha[compID] + uiAbsPartIdx), alphaValue, (sizeof(Char) * uiCoveredPartIdxes));
}

二、相关函数

之前学习了TEncSearch::xIntraCodingTUBlock下直接调用的TEncSearch::xCalcCrossComponentPredictionAlpha、TComTrQuant::crossComponentPrediction、TEncSearch::xStoreCrossComponentPredictionResult三个函数,下面将对其它与CCP相关的函数进行学习。


1、TDecSbac::parseCrossComponentPrediction

位于解码器中的,用于检测CCP信息,根据二进制比特信息计算α值。由于一直在看编码器端的实现,对于解码器没有接触,因此很多看不懂。如果有错误请指正。

Void TDecSbac::parseCrossComponentPrediction( TComTU &rTu, ComponentID compID )
{
  TComDataCU *pcCU = rTu.getCU();


  if( isLuma(compID) || !pcCU->getSlice()->getPPS()->getPpsRangeExtension().getCrossComponentPredictionEnabledFlag() ) //检测CCP是否可用,不可用直接返回
  {
    return;
  }


  const UInt uiAbsPartIdx = rTu.GetAbsPartIdxTU(); 


  if (!pcCU->isIntra(uiAbsPartIdx) || (pcCU->getIntraDir( CHANNEL_TYPE_CHROMA, uiAbsPartIdx ) == DM_CHROMA_IDX))
  {
    Char alpha  = 0;
    UInt symbol = 0;


    DTRACE_CABAC_VL( g_nSymbolCounter++ )
    DTRACE_CABAC_T("\tparseCrossComponentPrediction()")
    DTRACE_CABAC_T( "\tAddr=" )
    DTRACE_CABAC_V( compID )
    DTRACE_CABAC_T( "\tuiAbsPartIdx=" )
    DTRACE_CABAC_V( uiAbsPartIdx )
#if RExt__DECODER_DEBUG_BIT_STATISTICS
    TComCodingStatisticsClassType ctype(STATS__CABAC_BITS__CROSS_COMPONENT_PREDICTION, (g_aucConvertToBit[rTu.getRect(compID).width] + 2), compID);
#endif
    ContextModel *pCtx = m_cCrossComponentPredictionSCModel.get(0, 0) + ((compID == COMPONENT_Cr) ? (NUM_CROSS_COMPONENT_PREDICTION_CTX >> 1) : 0); 
    m_pcTDecBinIf->decodeBin( symbol, pCtx[0] RExt__DECODER_DEBUG_BIT_STATISTICS_PASS_OPT_ARG(ctype) ); //用symbol记录alpha量化后的值


    if(symbol != 0) //symbol=0即α=0,此时不进行CCP预测
    {
      // Cross-component prediction alpha is non-zero.
      UInt sign = 0; //表示α的符号
      m_pcTDecBinIf->decodeBin( symbol, pCtx[1] RExt__DECODER_DEBUG_BIT_STATISTICS_PASS_OPT_ARG(ctype) );


      if (symbol != 0)
      {
        // alpha is 2 (symbol=1), 4(symbol=2) or 8(symbol=3).
        // Read up to two more bits
        xReadUnaryMaxSymbol( symbol, (pCtx + 2), 1, 2 RExt__DECODER_DEBUG_BIT_STATISTICS_PASS_OPT_ARG(ctype) );
        symbol += 1;
      }
      m_pcTDecBinIf->decodeBin( sign, pCtx[4] RExt__DECODER_DEBUG_BIT_STATISTICS_PASS_OPT_ARG(ctype) );


      alpha = (sign != 0) ? -(1 << symbol) : (1 << symbol); //计算α
    }
    DTRACE_CABAC_T( "\tAlpha=" )
    DTRACE_CABAC_V( alpha )
    DTRACE_CABAC_T( "\n" )


    pcCU->setCrossComponentPredictionAlphaPartRange( alpha, compID, uiAbsPartIdx, rTu.GetAbsPartIdxNumParts( compID ) ); //存储α值
  }
}

2、TEncSbac::codeCrossComponentPrediction

用于编码CCP信息,主要是编码得到二进制化的α。个人对于CABAC不够了解,对该代码的理解程度很差,之后将对CABAC进行学习,然后再回头看这段代码。

Void TEncSbac::codeCrossComponentPrediction( TComTU &rTu, ComponentID compID )
{
  TComDataCU *pcCU = rTu.getCU();


  if( isLuma(compID) || !pcCU->getSlice()->getPPS()->getPpsRangeExtension().getCrossComponentPredictionEnabledFlag() ) //判断CCP是否可用
  {
    return;
  }


  const UInt uiAbsPartIdx = rTu.GetAbsPartIdxTU();


  if (!pcCU->isIntra(uiAbsPartIdx) || (pcCU->getIntraDir( CHANNEL_TYPE_CHROMA, uiAbsPartIdx ) == DM_CHROMA_IDX))
  {
    DTRACE_CABAC_VL( g_nSymbolCounter++ )
    DTRACE_CABAC_T("\tparseCrossComponentPrediction()")
    DTRACE_CABAC_T( "\tAddr=" )
    DTRACE_CABAC_V( compID )
    DTRACE_CABAC_T( "\tuiAbsPartIdx=" )
    DTRACE_CABAC_V( uiAbsPartIdx )


    Int alpha = pcCU->getCrossComponentPredictionAlpha( uiAbsPartIdx, compID ); //取α值
    ContextModel *pCtx = m_cCrossComponentPredictionSCModel.get(0, 0) + ((compID == COMPONENT_Cr) ? (NUM_CROSS_COMPONENT_PREDICTION_CTX >> 1) : 0);
    m_pcBinIf->encodeBin(((alpha != 0) ? 1 : 0), pCtx[0]); //根据α是否等于0确定编码模式


    if (alpha != 0) //α=0时不进行CCP预测
    {
      static const Int log2AbsAlphaMinus1Table[8] = { 0, 1, 1, 2, 2, 2, 3, 3 }; 
      assert(abs(alpha) <= 8); //限定α范围要小于等于8


      if (abs(alpha)>1)  //α大于1和等于1分别编码
      {
        m_pcBinIf->encodeBin(1, pCtx[1]); 
        xWriteUnaryMaxSymbol( log2AbsAlphaMinus1Table[abs(alpha) - 1] - 1, (pCtx + 2), 1, 2 );
      }
      else 
      {
        m_pcBinIf->encodeBin(0, pCtx[1]);
      }
      m_pcBinIf->encodeBin( ((alpha < 0) ? 1 : 0), pCtx[4] );
    }
    DTRACE_CABAC_T( "\tAlpha=" )
    DTRACE_CABAC_V( pcCU->getCrossComponentPredictionAlpha( uiAbsPartIdx, compID ) )
    DTRACE_CABAC_T( "\n" )
  }
}