资讯详情

HM块划分有关的函数

最近需要研究HM函数与块划分有关(TLibEncoder\TEncCu),但是网上参考资料很少,这部分代码复杂,所以本文是基于最新的16.20版,试着分析实现原理,以后会更新x265对应部分的源码剖析。

HM15.本部分的代码分析可参考[本博客](https://blog.csdn.net/qq_21880777/article/details/78827285)。

CTU层次

基本上分为两个步骤。首先,压缩CTU,然后编码CTU

TEncCu::compressCtu压缩CTU

  1. 顶层的初始化CTU数据,m_ppcBestCU[0]->initCtu()m_ppcTempCU[0]->initCtu()
  2. 递归的调用xCompressCU()压缩CTU

TEncCu::encodeCtu编码CTU

  1. 根据Slice的设置初始化QP的参数
  2. 递归调用xEncodeCU()编码CTU

CU层次

xCompressCU( )

从前面的x可见这种方法是一种protected成员函数的类型,根据xCheckRDCostMerge2Nx2NxCheckRDCostInter根据定位帧间预测部分,xCheckRDCostIntra定位帧中的预测部分。( 补充:关于HM命名规则和项目结构可以参考我之前的博客。

传入参数为rpcBestCU,rpcTempCU,uiDepth,其中uiDepth即为当前CU深度(0、1、2)。

详细步骤如下

1. 参数初始化(读取BestCU用于计算数据和参数BestCU RD cost的量化参数QP)

参数初始化分为两部分,读取第一部分rpcBestCU所代表CU的YUV存储在数据、坐标信息和有效组件中的数量m_ppcOriginYuv[yiDepth]uiLPelX~uiBelYnumberValidComponents有效组件的类型为枚举类型:

| 枚举类型 | 值 |

| :---------------: | :--: |

| COMPONENT_Y | 0 |

| COMPONENT_Cb | 1 |

| COMPONENT_Cr | 2 |

| MAX_NUM_COMPONENT | 3 |

第二部分,解析图片参数集pps(picture parameter set)和 序列参数集sps(sequence parameter set),量化参数主要集中在分析参数上QP的设置,QP分别由四个参数控制iBaseQPiMinQPiMaxQPisAddLowestQP

首先xComputeQP()根据 输入父CUrpcBestCU和 目前深度划分uiDepth计算iBaseQP,然后用四种方法确定iMinQP和`iMaxQP:

  • 默认(可以是一个范围)
  • 由亮度等级决定QP(value)
  • 确定目标码率QP(value,万帅)
  • TQB模式(value)
// ToDo: 根据第一个注释中的参数值CU确定运行情况 // iMinQP = 上下限截断(iBaseQP-idQP),  iMaxQP = 上下限截断(iBaseQP idQP)  // 方法1 (默认,依据pps和sps设置QP最大值和最小值), idQP if( uiDepth <= pps.getMaxCuDQPDepth() )   {     Int idQP = m_pcEncCfg->getMaxDeltaQP(); // 0     iMinQP = Clip3( -sps.getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, iBaseQP-idQP ); // clip3(minVal,maxVal,a),MAX_QP=51,-sps.getQpBDOffset(CHANNEL_TYPE_LUMA)=51     iMaxQP = Clip3( -sps.getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, iBaseQP idQP ); // 查看 iBaseQP 和 idQP的值   }   else   {     // 默认该QP最大值与最小值相同,此时QP只能选择`rpcTempCU->getQP(0)`     iMinQP = rpcTempCU->getQP(0);     iMaxQP = rpcTempCU->getQP(0);   }    // 方法2:根据luma level计算QP offset   // 如果能将亮度等级转化为QP等级(`m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled()==True`),则进行转化   if ( m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() )   {     if ( uiDepth <= pps.getMaxCuDQPDepth() )     {       // keep using the same m_QP_LUMA_OFFSET in the same CTU       m_lumaQPOffset = calculateLumaDQP(rpcTempCU, 0, m_ppcOrigYuv[uiDepth]);     }     iMinQP = Clip3(-sps.getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, iBaseQP - m_lumaQPOffset);     iMaxQP = iMinQP; // force encode choose the modified QO   }    // 方法3:根据目标码率确定QP值(万帅,P335)   if ( m_pcEncCfg->getUseRateCtrl() )   {     iMinQP = m_pcRateCtrl->getRCQP();     iMaxQP = m_pcRateCtrl->getRCQP();   }    // 方法4: TQB   // transquant-bypass (TQB) processing loop variable initialisation    // TQB 跳过伸缩、变换和环路滤波过程,由`pps.getTransquantBypassEnabledFlag()`决定   // 后续由isAddLowestQP判断是否是TQB模式    const Int lowestQP = iMinQP; // For TQB, use this QP which is the lowest non TQB QP tested (rather than QP'=0) - that way delta QPs are smaller, and TQB can be tested at all CU levels.    if ( (pps.getTransquantBypassEnabledFlag()) )   {     isAddLowestQP = true; // mark that the first iteration is to cost TQB mode.     iMinQP = iMinQP - 1;  // increase loop variable range by 1, to allow testing of TQB mode along with other QPs     if ( m_pcEncCfg->getCUTransquantBypassFlagForceValue() )     {       iMaxQP = iMinQP; // 此时的QP最小,失真也最少     }   } 

2. 计算BestCU的RD cost

首先,获得当前CU所在的slice,然后判断当前CU是否在Slice判断标识的边界为bBoundary,如果当前CU在Slice边界,直接跳过第二步,进入第三步划分成子CU(为了控制错误传播,编码单元不能跨越Slice,一般来说,它不会跨越Slice,相当于进一步检查)。

2.1 初始化TempCU,计算帧间模式RD cost

,针对QP调整不同的初始化方法iQP之后,,如果当前CU帧不是I帧(即表示当前帧(即表示当前帧)CU是帧间模式),根据帧间编码计算RD cost:

// 遍历QP,初始化TempCU for (Int iQP=iMinQP; iQP<=iMaxQP; iQP  ) {   // 针对TQB调整iQP   const Bool bIsLosslessMode = isAddLowestQP && (iQP == iMinQP);   if (bIsLosslessMode
  {
    iQP = lowestQP;
  }
  // 针对上述的方法2(根据Luma level初始化QP)调整PD cost的拉格朗日系数lambda
  if ( m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() && uiDepth <= pps.getMaxCuDQPDepth() )
  {
    getSliceEncoder()->updateLambda(pcSlice, iQP);
  }
  // 针对色度块调整QP,首次运行跳过(此处luma block和chroma block的QP offset不同,详见万帅6.2)
  m_cuChromaQpOffsetIdxPlus1 = 0;
  if (pcSlice->getUseChromaQpAdj())
  {
    /* Pre-estimation of chroma QP based on input block activity may be performed
     * here, using for example m_ppcOrigYuv[uiDepth] */
    /* To exercise the current code, the index used for adjustment is based on
     * block position
     */
    Int lgMinCuSize = sps.getLog2MinCodingBlockSize() +
                      std::max<Int>(0, sps.getLog2DiffMaxMinCodingBlockSize()-Int(pps.getPpsRangeExtension().getDiffCuChromaQpOffsetDepth()));
    m_cuChromaQpOffsetIdxPlus1 = ((uiLPelX >> lgMinCuSize) + (uiTPelY >> lgMinCuSize)) % (pps.getPpsRangeExtension().getChromaQpOffsetListLen() + 1);
  }

  // 针对每个iQP创建新的TempCU
  rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
  // 计算帧间模式的RD cost
  // 调用xCheckRDCostInter、xCheckRDCostMerge2Nx2N
  // do inter modes, SKIP and 2Nx2N
  if( rpcBestCU->getSlice()->getSliceType() != I_SLICE )
  {
    // 2Nx2N
    if(m_pcEncCfg->getUseEarlySkipDetection())
    {
      xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) );
      rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );//by Competition for inter_2Nx2N
    }
    // SKIP
    xCheckRDCostMerge2Nx2N( rpcBestCU, rpcTempCU DEBUG_STRING_PASS_INTO(sDebug), &earlyDetectionSkipMode );//by Merge for inter_2Nx2N
    rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );

    if(!m_pcEncCfg->getUseEarlySkipDetection())
    {
      // 2Nx2N, NxN
      xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) );
      rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
      if(m_pcEncCfg->getUseCbfFastMode())
      {
        doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;
      }
    }
  }
  // Restore loop variable if lossless mode was searched.
  if (bIsLosslessMode)
  {
    iQP = iMinQP;
  }
}

2.2 计算帧间和帧内模式的PD cost

首先判断是不是early skip mode,如果是提前跳过模式,转第三步,否则继续执行。

遍历QP begin

  1. 调整iQP,初始化rpcTempCU
  2. 计算的RD cost,分为2Nx2N,NxN,Nx2N,2NxN 和 AMP五种模式,每个模式用到的核心函数只有两个xCheckRDCostInter()rpcTempCU->initEstData()
  3. 计算的RD cost
  4. 读取CBF配置,若CU的预测模式为MODE_INTER,其对应的预测残差不包含非零的变换系数,则跳过该CU的其他候选模式,即当前模式为CU的最优模式,直接进行下一步的四叉树划分
  5. 调用xCheckRDCostIntra(),对2Nx2N的CU进行预测,并计算RD Cost 调用rpcTempCU->initEstData()初始化TempCU
  6. PCM模式下执行和上一步类似的操作
  7. 汇总当前的RD cost
   if( rpcBestCU->getTotalCost()!=MAX_DOUBLE ) // MAX_DOUBLE = 1.7e+308
   {
     m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[uiDepth][CI_NEXT_BEST]);
     m_pcEntropyCoder->resetBits();
     m_pcEntropyCoder->encodeSplitFlag( rpcBestCU, 0, uiDepth, true );
     rpcBestCU->getTotalBits() += m_pcEntropyCoder->getNumberOfWrittenBits(); // split bits
     rpcBestCU->getTotalBins() += ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded();
     rpcBestCU->getTotalCost()  = m_pcRdCost->calcRdCost( rpcBestCU->getTotalBits(), rpcBestCU->getTotalDistortion() );
     m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[uiDepth][CI_NEXT_BEST]);
   }

遍历QP end

3. 计算BestCU划分后的子CU的RD cost(递归调用,并保存RD cost最优的CU)

首先,PCM模式下将原始的YUV数据拷贝到PCM缓存区,然后,再次调整QP的最大值和最小值,同步骤一参数初始化中一样,分为四种方式;

bSubBraunch判断是否继续划分,如果继续划分,遍历QP:

  1. 初始化,这里的变量命名方式和上面类似,但是都加上了Sub,例如pcSubBestPartCUpcSubTempPartCU指向下一层
  2. 遍历划分后的四个子CU,分别调用四次xCompressCU函数,计算子CU的RD cost,并将RD cost存放到rpcTempCU
  3. 汇总遍历划分的子CU的RD cost
  4. 比较结果,判断是否划分

比较rpcBestCUrpcTempCU的RD Cost决定是否对16x16CU进行进一步的划分,即比较rpcBestCU->getTotalCost()rpcTempCU->getTotalCost(),如果划分后的RD cost更低,则rpcTemp会替换掉rpcBestCU。可见,该过程不仅有划分前后的RD cost的比较,也有划分不同模式的RD cost的比较。

   xCheckBestMode( rpcBestCU, rpcTempCU, uiDepth DEBUG_STRING_PASS_INTO(sDebug) DEBUG_STRING_PASS_INTO(sTempDebug) DEBUG_STRING_PASS_INTO(false) ); 
   // RD compare current larger prediction

该部分的源码:

//*******************递归划分子CU,记录最优RDcost对应的划分模式*******************//
const Bool bSubBranch = bBoundary || !( m_pcEncCfg->getUseEarlyCU() && rpcBestCU->getTotalCost()!=MAX_DOUBLE && rpcBestCU->isSkipped(0) );
// true = false || !(false && ture)
// bBoudary==true 说明该CU跨过了Slice,因此必须继续划分
// 后面三项只有一项为false即可

if( bSubBranch && uiDepth < sps.getLog2DiffMaxMinCodingBlockSize() && (!getFastDeltaQp() || uiWidth > fastDeltaQPCuMaxSize || bBoundary))
{
// 进一步划分
Double splitTotalCost = 0;
// 遍历QP
for (Int iQP=iMinQP; iQP<=iMaxQP; iQP++)
{
  const Bool bIsLosslessMode = false; 
  // False at this level. Next level down may set it to true.

  // 初始化Temp,存放此次划分模式
  rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );

  UChar       uhNextDepth         = uiDepth+1;
  TComDataCU* pcSubBestPartCU     = m_ppcBestCU[uhNextDepth]; // 在TEncCU::create()中被创建
  TComDataCU* pcSubTempPartCU     = m_ppcTempCU[uhNextDepth]; // 同上
  DEBUG_STRING_NEW(sTempDebug)
  // 遍历划分后的四个子区域
  // for循环中调用四次xCompressCU函数,计算子CU的RDcost,并将四个子CU的RDcost存放到rpcTempCU中
  for ( UInt uiPartUnitIdx = 0; uiPartUnitIdx < 4; uiPartUnitIdx++ )
  {
    //初始化下一层BestCU和TempCU,存放划分后的一个子CU
    pcSubBestPartCU->initSubCU( rpcTempCU, uiPartUnitIdx, uhNextDepth, iQP );          
    pcSubTempPartCU->initSubCU( rpcTempCU, uiPartUnitIdx, uhNextDepth, iQP );         
    //判断子CU的位置是否出界,并加载RD coder
    if( ( pcSubBestPartCU->getCUPelX() < sps.getPicWidthInLumaSamples() ) && ( pcSubBestPartCU->getCUPelY() < sps.getPicHeightInLumaSamples() ) )
    {
      // 如果是第一个子CU,需初始化先前深度的buffer中的RD coder
      if ( 0 == uiPartUnitIdx) //initialize RD with previous depth buffer
      {
        m_pppcRDSbacCoder[uhNextDepth][CI_CURR_BEST]->load(m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST]);
      }
       // 其他三个子CU继承前面的RD coder
      else
      {
        m_pppcRDSbacCoder[uhNextDepth][CI_CURR_BEST]->load(m_pppcRDSbacCoder[uhNextDepth][CI_NEXT_BEST]);
      }
      // 递归调用xCompressCU函数
#if AMP_ENC_SPEEDUP
      DEBUG_STRING_NEW(sChild)
      if ( !(rpcBestCU->getTotalCost()!=MAX_DOUBLE && rpcBestCU->isInter(0)) )
      {
        xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth DEBUG_STRING_PASS_INTO(sChild), NUMBER_OF_PART_SIZES );
      }
      else
      {
        xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth DEBUG_STRING_PASS_INTO(sChild), rpcBestCU->getPartitionSize(0) );
      }
      DEBUG_STRING_APPEND(sTempDebug, sChild)
#else
      xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth );
#endif
      // 将最佳部分数据`pcSubBestPartCU`保留为父CU的临时数据`rpcTempCU`中
      rpcTempCU->copyPartFrom( pcSubBestPartCU, uiPartUnitIdx, uhNextDepth );  
      // 保存YUV数据
      xCopyYuv2Tmp( pcSubBestPartCU->getTotalNumPart()*uiPartUnitIdx, uhNextDepth );
      // 累加子CU的RD cost
      if ( m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() && pps.getMaxCuDQPDepth() >= 1 )
      {
        splitTotalCost += pcSubBestPartCU->getTotalCost();
      }
    }
    else
    {
      pcSubBestPartCU->copyToPic( uhNextDepth );
      rpcTempCU->copyPartFrom( pcSubBestPartCU, uiPartUnitIdx, uhNextDepth );
    }// 结束位置判断
  }

  // 汇总四个子CU的RD cost
  m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[uhNextDepth][CI_NEXT_BEST]);
  if( !bBoundary )
  {
    m_pcEntropyCoder->resetBits();
    m_pcEntropyCoder->encodeSplitFlag( rpcTempCU, 0, uiDepth, true );
    if ( m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() && pps.getMaxCuDQPDepth() >= 1 )
    {
      Int splitBits = m_pcEntropyCoder->getNumberOfWrittenBits();
      Double splitBitCost = m_pcRdCost->calcRdCost( splitBits, 0 );
      splitTotalCost += splitBitCost;
    }

    rpcTempCU->getTotalBits() += m_pcEntropyCoder->getNumberOfWrittenBits(); // split bits
    rpcTempCU->getTotalBins() += ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded();
  }

  if ( m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled() && pps.getMaxCuDQPDepth() >= 1 )
  {
    rpcTempCU->getTotalCost() = splitTotalCost;
  }
  else
  {
    rpcTempCU->getTotalCost()  = m_pcRdCost->calcRdCost( rpcTempCU->getTotalBits(), rpcTempCU->getTotalDistortion() );
  }

  if( uiDepth == pps.getMaxCuDQPDepth() && pps.getUseDQP())
  {
    Bool hasResidual = false;
    for( UInt uiBlkIdx = 0; uiBlkIdx < rpcTempCU->getTotalNumPart(); uiBlkIdx ++)
    {
      if( (     rpcTempCU->getCbf(uiBlkIdx, COMPONENT_Y)
            || (rpcTempCU->getCbf(uiBlkIdx, COMPONENT_Cb) && (numberValidComponents > COMPONENT_Cb))
            || (rpcTempCU->getCbf(uiBlkIdx, COMPONENT_Cr) && (numberValidComponents > COMPONENT_Cr)) ) )
      {
        hasResidual = true;
        break;
      }
    }

    if ( hasResidual )
    {
      m_pcEntropyCoder->resetBits();
      m_pcEntropyCoder->encodeQP( rpcTempCU, 0, false );
      rpcTempCU->getTotalBits() += m_pcEntropyCoder->getNumberOfWrittenBits(); // dQP bits
      rpcTempCU->getTotalBins() += ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded();
      rpcTempCU->getTotalCost()  = m_pcRdCost->calcRdCost( rpcTempCU->getTotalBits(), rpcTempCU->getTotalDistortion() );

      Bool foundNonZeroCbf = false;
      rpcTempCU->setQPSubCUs( rpcTempCU->getRefQP( 0 ), 0, uiDepth, foundNonZeroCbf );
      assert( foundNonZeroCbf );
    }
    else
    {
      rpcTempCU->setQPSubParts( rpcTempCU->getRefQP( 0 ), 0, uiDepth ); // set QP to default QP
    }
  }

  m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[uiDepth][CI_TEMP_BEST]);

  // If the configuration being tested exceeds the maximum number of bytes for a slice / slice-segment, then
  // a proper RD evaluation cannot be performed. Therefore, termination of the
  // slice/slice-segment must be made prior to this CTU.
  // This can be achieved by forcing the decision to be that of the rpcTempCU.
  // The exception is each slice / slice-segment must have at least one CTU.
  if (rpcBestCU->getTotalCost()!=MAX_DOUBLE)
  {
    const Bool isEndOfSlice        =    pcSlice->getSliceMode()==FIXED_NUMBER_OF_BYTES
                                     && ((pcSlice->getSliceBits()+rpcBestCU->getTotalBits())>pcSlice->getSliceArgument()<<3)
                                     && rpcBestCU->getCtuRsAddr() != pcPic->getPicSym()->getCtuTsToRsAddrMap(pcSlice->getSliceCurStartCtuTsAddr())
                                     && rpcBestCU->getCtuRsAddr() != pcPic->getPicSym()->getCtuTsToRsAddrMap(pcSlice->getSliceSegmentCurStartCtuTsAddr());
    const Bool isEndOfSliceSegment =    pcSlice->getSliceSegmentMode()==FIXED_NUMBER_OF_BYTES
                                     && ((pcSlice->getSliceSegmentBits()+rpcBestCU->getTotalBits()) > pcSlice->getSliceSegmentArgument()<<3)
                                     && rpcBestCU->getCtuRsAddr() != pcPic->getPicSym()->getCtuTsToRsAddrMap(pcSlice->getSliceSegmentCurStartCtuTsAddr());
                                         // Do not need to check slice condition for slice-segment since a slice-segment is a subset of a slice.
    if(isEndOfSlice||isEndOfSliceSegment)
    {
      rpcBestCU->getTotalCost()=MAX_DOUBLE;
    }
  }

  //*****************比较,决定是否进一步划分***********//
  // 比较rpcBestCU, rpcTempCU的RD Cost决定是否对16x16CU进行进一步的划分,即`rpcBestCU->getTotalCost()`和`rpcTempCU->getTotalCost()`
  // 如果需要进一步划分,则将m_ppcRecoYuvTemp[depth]中的重建YUV拷贝到m_ppcRecoYuvBest[depth]中
  xCheckBestMode( rpcBestCU, rpcTempCU, uiDepth DEBUG_STRING_PASS_INTO(sDebug) DEBUG_STRING_PASS_INTO(sTempDebug) DEBUG_STRING_PASS_INTO(false) ); // RD compare current larger prediction
}
}

4. 递归终止条件

// Assert if Best prediction mode is NONE
  // Selected mode's RD-cost must be not MAX_DOUBLE.
  assert( rpcBestCU->getPartitionSize ( 0 ) != NUMBER_OF_PART_SIZES       );
  assert( rpcBestCU->getPredictionMode( 0 ) != NUMBER_OF_PREDICTION_MODES );
  assert( rpcBestCU->getTotalCost     (   ) != MAX_DOUBLE                 );

说了这么多,还不是如一张流程图直观:

标签: dqp9051多参变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台