H.266/VVC代码学习笔记14:xCheckRDCostMerge2Nx2N()函数

近期学习了一下xCheckRDCostMerge2Nx2N()函数,这是编码端帧间预测中非常重要的一个函数,该函数类似于帧内的xCheckRDCostIntra()函数,是对三种Merge模式:regular_Merge、CIIPMerge、MMVD_Merge;进行率失真代价的比较,选出代价最小也就是最优的一种Merge候选。大体上看明白了该函数的流程,简要的讲一下该函数的具体的流程,如下:
1、首先进行一些初始化的操作:初始化结构信息,Merge列表的定义,PU、CU的获取等一系列初始化操作。

2、构造常规的Merge列表以及紧接着构造MMVD的Merge列表,选出MMVD的两个起始点。关于常规的Merge列表的构造我在之前的博客已经详细讲过了。这里直接给出链接:
H.266/VVC代码学习笔记13:getInterMergeCandidates()函数

   PU::getInterMergeCandidates(pu, mergeCtx
      , 0
    );//获取帧间的Merge候选列表
    PU::getInterMMVDMergeCandidates(pu, mergeCtx);//构造MMVDMerge 的候选列表

3、定义关于预测值存储的一些缓存变量如下:

  PelUnitBuf                                  acMergeBuffer[MRG_MAX_NUM_CANDS];//保存所有Merge候选运动补偿出来的预测值
#if JVET_O0108_DIS_DMVR_BDOF_CIIP
  PelUnitBuf                                  acMergeTmpBuffer[MRG_MAX_NUM_CANDS];//保存所有Merge候选运动补偿出来的预测值临时变量
#endif
  PelUnitBuf                                  acMergeRealBuffer[MMVD_MRG_MAX_RD_BUF_NUM];//保存所有Merge候选运动补偿出来的真实的预测值
  PelUnitBuf *                                acMergeTempBuffer[MMVD_MRG_MAX_RD_NUM];//保存所有Merge候选运动补偿出来的预测值临时缓存的地址
  PelUnitBuf *                                singleMergeTempBuffer;//单Merge列表候选的预测值缓存

4、关于Merge模式的一些定义以及初始化操作:

struct ModeInfo
  {
    uint32_t mergeCand;//Merge候选数。
    bool     isRegularMerge;//是否是常规Merge
    bool     isMMVD;//是否是MMVD模式
    bool     isCIIP;//是否是CIIP
    ModeInfo() : mergeCand(0), isRegularMerge(false), isMMVD(false), isCIIP(false) {}//regular模式和MMVD模式以及CIIP模式都初始化为不可用,Merge候选数量初始化为0
    ModeInfo(const uint32_t mergeCand, const bool isRegularMerge, const bool isMMVD, const bool isCIIP) :
      mergeCand(mergeCand), isRegularMerge(isRegularMerge), isMMVD(isMMVD), isCIIP(isCIIP) {}
  };

5、对常规的Merge候选循环遍历进行SAD的率失真代价比较,选出代价较小的几个候选,并随时更新列表。此时的循环是循环regular模式的Merge列表,此时当前CU默认是regular_Merge模式,MMVD以及CIIP都是在Regular_Merge的Merge列表SAD率失真代价比较完之后的基础上再去改进的。这里详细的流程如下:
①将遍历到的当前Merge候选的运动信息放入到当前pu中去,然后进行相应的运动补偿(MC)得到当前pu的预测值存入缓存区singleMergeTempBuffer中。
②计算完预测值之后对双向MV进行MV的细化操作(即DMVR),虽然DMVR叫做解码端运动矢量细化,但还是要在编码端默认进行该操作,否则将会导致编解码不一致的问题。
③MV细化完后,计算SAD的RDCost,然后更新RDcost的模式列表RdModeList,以及候选的代价列表candCostList,列表的长度最多为uiNumMrgSATDCand

6、在常规的Merge列表粗选完之后,再对CIIP模式进行粗选;首先从上述RDCost列表中选出前四个候选进行CIIP的预测。对四个较优的RDlist中的候选分别循环进行各候选MC的预测,然后与亮度分量Planar帧内模式的预测值进行加权,选出SAD下的RDcost最优的一种CIIP的组合,也即为CIIP模式在4个Merge候选中最好的一种模式。同样更新RdModeList以及candCostList

7、CIIP选择完之后再对MMVD的64种模式进行RDcost的比较。同样更新RdModeList以及candCostList。关于MMVD的技术细节参考博客:H.266/VVC相关技术学习笔记:帧间预测技术中的MMVD技术(Merge mode with MVD)

8、使用一个阈值去限制上述代价比较完后代价候选列表的数量

9、如果CIIP模式可用,先对之前选中的最优的CIIP组合进行正式的三个分量的intra预测值的计算。为后面在HAD细选中CIIP加权预测做准备。

10、对前面通过SADCost粗选后的RdModeList列表中的所有候选进行HAD(SATD)细选,选出最优的一种候选,可能是regular模式、CIIPMerge或者MMVDMerge中的一种。(HAD就是带有哈达吗变换的SAD,即SATD。是一种更为精确的绝对误差的比较方法。相比于SAD的比较更为细致)。这里的详细过程如下:
①初始化的操作;
②判断当前候选是三种Merge模式中的哪一种,为相应的模式做预测的准备;

 //判断当前候选是三种Merge模式中的哪一种,为相应的模式做预测的准备
      if (uiNoResidualPass == 0 && RdModeList[uiMrgHADIdx].isCIIP)//CIIP模式
      {
        cu.mmvdSkip = false;
        mergeCtx.setMergeInfo(pu, uiMergeCand);//为当前pu设置基础的Merge信息
        pu.mhIntraFlag = true;//将当前pu设置为CIIP模式
        pu.regularMergeFlag = false;
        pu.intraDir[0] = PLANAR_IDX;//帧内亮度模式采用Planar模式
        CHECK(pu.intraDir[0]<0 || pu.intraDir[0]>(NUM_LUMA_MODE - 1), "out of intra mode");
        pu.intraDir[1] = DM_CHROMA_IDX;//帧内色度模式采用DM模式
      }
      else if (RdModeList[uiMrgHADIdx].isMMVD)
      {
        cu.mmvdSkip = true;//将当前pu设置为MMVDSkip模式
#if JVET_O0249_MERGE_SYNTAX
        pu.regularMergeFlag = true;
#endif
        mergeCtx.setMmvdMergeCandiInfo(pu, uiMergeCand);
      }
      else//当前pu为常规Merge模式
      {
        cu.mmvdSkip = false;
#if JVET_O0249_MERGE_SYNTAX
        pu.regularMergeFlag = true;
#endif
        mergeCtx.setMergeInfo(pu, uiMergeCand);
      }
      PU::spanMotionInfo( pu, mergeCtx );//为当前pu设置基础的运动信息

③对当前候选进行MV的细化操作,MV细化之后再去重新求出当前PU对应Merge模式的预测值。对于CIIP模式,进行三个分量的加权操作;对于MMVD模式,重新计算MMVD模式预测值(通过MC);对于regular模式,直接拷贝上面已经计算好的预测值,因为在上面已经对regular模式下的MV细化过了。

11、调用xEncodeInterResidual()函数,编码当前Merge候选模式的inter预测值的残差,并在该函数中完成HAD的比较,最终选出HAD最小的那个Merge模式

12、结束HAD细选的循环之后,选出三种Merge中最优的一种Merge候选,并计算出相应的预测值以及残差,最后计算HAD下的RDcost,再去和其余的Merge模式(TPM、Affine之类的模式)做SATD的RDcsot的比较。

最后,附上整个xCheckRDCostMerge2Nx2N()函数的代码,并且代码里有详细的注释,由于某些地方看的不是很细,也可能看的不是太明白,或许有出错的地方,欢迎大家指正,不过总体的流程应该没啥大问题。

//对Merge列表检查RDcost,这是Merge和Skip模式的第一主函数入口
void EncCu::xCheckRDCostMerge2Nx2N( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode )
{
  const Slice &slice = *tempCS->slice;

  CHECK( slice.getSliceType() == I_SLICE, "Merge modes not available for I-slices" );
  //初始化结构信息
  tempCS->initStructData( encTestMode.qp, encTestMode.lossless );

  MergeCtx mergeCtx;//Merge的上下文模型
  const SPS &sps = *tempCS->sps;

  if( sps.getSBTMVPEnabledFlag() )//子块Merge模式是否使用
  {
    Size bufSize = g_miScaling.scale( tempCS->area.lumaSize() );
    mergeCtx.subPuMvpMiBuf    = MotionBuf( m_SubPuMiBuf,    bufSize );
  }

  Mv   refinedMvdL0[MAX_NUM_PARTS_IN_CTU][MRG_MAX_NUM_CANDS];
  setMergeBestSATDCost( MAX_DOUBLE );//设置Merge中最优的SATDCost
  {
    // first get merge candidates获取第一个Merge候选
    CodingUnit cu( tempCS->area );
    cu.cs       = tempCS;
    cu.predMode = MODE_INTER;//预测模式为帧间模式
    cu.slice    = tempCS->slice;
    cu.tileIdx  = tempCS->picture->brickMap->getBrickIdxRsMap( tempCS->area.lumaPos() );

    PredictionUnit pu( tempCS->area );
    pu.cu = &cu;
    pu.cs = tempCS;
    pu.shareParentPos = tempCS->sharedBndPos;
    pu.shareParentSize = tempCS->sharedBndSize;
    PU::getInterMergeCandidates(pu, mergeCtx
      , 0
    );//获取帧间的Merge候选列表
    PU::getInterMMVDMergeCandidates(pu, mergeCtx);//构造MMVDMerge 的候选列表
    pu.regularMergeFlag = true;//常规Merge的flag,初始化为可用,一开始默认当前PU为regular模式
  }
  bool candHasNoResidual[MRG_MAX_NUM_CANDS + MMVD_ADD_NUM];
  for (uint32_t ui = 0; ui < MRG_MAX_NUM_CANDS + MMVD_ADD_NUM; ui++)
  {
    candHasNoResidual[ui] = false;
  }

  bool                                        bestIsSkip = false;
  bool                                        bestIsMMVDSkip = true;
  PelUnitBuf                                  acMergeBuffer[MRG_MAX_NUM_CANDS];//保存所有Merge候选运动补偿出来的预测值
#if JVET_O0108_DIS_DMVR_BDOF_CIIP
  PelUnitBuf                                  acMergeTmpBuffer[MRG_MAX_NUM_CANDS];//保存所有Merge候选运动补偿出来的预测值临时变量
#endif
  PelUnitBuf                                  acMergeRealBuffer[MMVD_MRG_MAX_RD_BUF_NUM];//保存所有Merge候选运动补偿出来的真实的预测值
  PelUnitBuf *                                acMergeTempBuffer[MMVD_MRG_MAX_RD_NUM];//保存所有Merge候选运动补偿出来的预测值临时缓存的地址
  PelUnitBuf *                                singleMergeTempBuffer;//单Merge列表候选的预测值缓存
  int                                         insertPos;//插入的位置
  unsigned                                    uiNumMrgSATDCand = mergeCtx.numValidMergeCand + MMVD_ADD_NUM;//需要进行SATD比较的所有候选的数量:regular_Merge列表中的有效候选以及64个MMVD的候选

  struct ModeInfo
  {
    uint32_t mergeCand;//Merge候选数。
    bool     isRegularMerge;//是否是常规Merge
    bool     isMMVD;//是否是MMVD模式
    bool     isCIIP;//是否是CIIP
    ModeInfo() : mergeCand(0), isRegularMerge(false), isMMVD(false), isCIIP(false) {}//regular模式和MMVD模式以及CIIP模式都初始化为不可用,Merge候选数量初始化为0
    ModeInfo(const uint32_t mergeCand, const bool isRegularMerge, const bool isMMVD, const bool isCIIP) :
      mergeCand(mergeCand), isRegularMerge(isRegularMerge), isMMVD(isMMVD), isCIIP(isCIIP) {}
  };

  static_vector<ModeInfo, MRG_MAX_NUM_CANDS + MMVD_ADD_NUM>  RdModeList;//RDCost的模式列表
  bool                                        mrgTempBufSet = false;

  for (int i = 0; i < MRG_MAX_NUM_CANDS + MMVD_ADD_NUM; i++)//常规的6个Merge候选+MMVD的64种Merge候选
  {
    if (i < mergeCtx.numValidMergeCand)
    {
      RdModeList.push_back(ModeInfo(i, true, false, false));//往需要RDCost的模式列表中Push各种可用的Merge候选
    }
    else
    {
      RdModeList.push_back(ModeInfo(std::min(MMVD_ADD_NUM, i - mergeCtx.numValidMergeCand), false, true, false));
    }
  }

  const UnitArea localUnitArea(tempCS->area.chromaFormat, Area(0, 0, tempCS->area.Y().width, tempCS->area.Y().height));
  for (unsigned i = 0; i < MMVD_MRG_MAX_RD_BUF_NUM; i++)
  {
    acMergeRealBuffer[i] = m_acMergeBuffer[i].getBuf(localUnitArea);//初始化每一个候选的预测值缓存
    if (i < MMVD_MRG_MAX_RD_NUM)
    {
      acMergeTempBuffer[i] = acMergeRealBuffer + i;
    }
    else
    {
      singleMergeTempBuffer = acMergeRealBuffer + i;
    }
  }

  bool isIntrainterEnabled = sps.getUseMHIntra();//从高层语法获取CIIP模式是否可用(是否可用和当前CU是否是CIIP模式不是一个概念)

  //如果块尺寸小于64、宽和高大于最大CU边长(128)时。CIIP模式不可用
  if (bestCS->area.lwidth() * bestCS->area.lheight() < 64 || bestCS->area.lwidth() >= MAX_CU_SIZE || bestCS->area.lheight() >= MAX_CU_SIZE)
  {
    isIntrainterEnabled = false;
  }
  bool isTestSkipMerge[MRG_MAX_NUM_CANDS]; // record if the merge candidate has tried skip mode
                                           //如果Merge候选尝试采用SKip,则记录下来 

  for (uint32_t idx = 0; idx < MRG_MAX_NUM_CANDS; idx++)
  {
    isTestSkipMerge[idx] = false;
  }
  if( m_pcEncCfg->getUseFastMerge() || isIntrainterEnabled)//CIIP模式可用或者使用快速Merge模式
  {
    uiNumMrgSATDCand = NUM_MRG_SATD_CAND;//参与SATD选择的Merge候选数量为4
    if (isIntrainterEnabled)
    {
      uiNumMrgSATDCand += 1;//如果CIIP模式可用,则/参与SATD选择的Merge候选数量+1
    }
    bestIsSkip       = false;//最好模式是否是Skip

    if( auto blkCache = dynamic_cast< CacheBlkInfoCtrl* >( m_modeCtrl ) )
    {
      if (slice.getSPS()->getIBCFlag())//SPS层标志IBC模式可用
      {
        ComprCUCtx cuECtx = m_modeCtrl->getComprCUCtx();
        bestIsSkip = blkCache->isSkip(tempCS->area) && cuECtx.bestCU;
      }
      else
      bestIsSkip = blkCache->isSkip( tempCS->area );//最优的模式是否是Skip
      bestIsMMVDSkip = blkCache->isMMVDSkip(tempCS->area);//最优的模式是否是MMVDSkip
    }

    if (isIntrainterEnabled) // always perform low complexity check
    {
      bestIsSkip = false;//最优的模式不是SKip
    }

    static_vector<double, MRG_MAX_NUM_CANDS + MMVD_ADD_NUM> candCostList;//候选的代价列表(最多构造6+64个)

    // 1. Pass: get SATD-cost for selected candidates and reduce their count
    //1、比较STADcost,选择最优的候选,并减少数量
    if( !bestIsSkip )//最好的模式不是Skip
    {
      RdModeList.clear();//清除RD候选模式列表
      mrgTempBufSet       = true;
      const TempCtx ctxStart(m_CtxCache, m_CABACEstimator->getCtx());

      CodingUnit &cu      = tempCS->addCU( tempCS->area, partitioner.chType );//是当前CU的引用变量,意味着对里面的值修改也就改变了外层的CU相关的参数
      const double sqrtLambdaForFirstPassIntra = m_pcRdCost->getMotionLambda(cu.transQuantBypass) * FRAC_BITS_SCALE;
      partitioner.setCUData( cu );
      cu.slice            = tempCS->slice;
      cu.tileIdx          = tempCS->picture->brickMap->getBrickIdxRsMap( tempCS->area.lumaPos() );
      cu.skip             = false;//当前CU不为Skip模式
      cu.mmvdSkip = false;//当前CU不为MMVDSkip模式
      cu.triangle         = false;//当前CU不为TPM模式
    //cu.affine
      cu.predMode         = MODE_INTER;//当前CU为帧间预测模式
    //cu.LICFlag
      cu.transQuantBypass = encTestMode.lossless;
      cu.chromaQpAdj      = cu.transQuantBypass ? 0 : m_cuChromaQpOffsetIdxPlus1;
      cu.qp               = encTestMode.qp;
    //cu.emtFlag  is set below

      PredictionUnit &pu  = tempCS->addPU( cu, partitioner.chType );//当前pu的引用变量

      DistParam distParam;//失真参数
      const bool bUseHadamard = !encTestMode.lossless && !tempCS->slice->getDisableSATDForRD();//是否使用哈达吗变换

      //设置失真参数
      m_pcRdCost->setDistParam (distParam, tempCS->getOrgBuf().Y(), m_acMergeBuffer[0].Y(), sps.getBitDepth (CHANNEL_TYPE_LUMA), COMPONENT_Y, bUseHadamard);

      //当前编码区域
      const UnitArea localUnitArea( tempCS->area.chromaFormat, Area( 0, 0, tempCS->area.Y().width, tempCS->area.Y().height) );

      //对有效的Merge候选循环遍历进行SAD的率失真代价比较,选出代价较小的几个候选
      //此时的循环是循环regular模式的Merge列表,此时当前CU默认是regular_Merge模式,MMVD以及CIIP都是在Regular_Merge的Merge列表SAD率失真代价比较完之后的基础上再去改进的
      for (uint32_t uiMergeCand = 0; uiMergeCand < mergeCtx.numValidMergeCand; uiMergeCand++)
      {
        mergeCtx.setMergeInfo(pu, uiMergeCand);//设置当前pu的运动信息,将候选中的各种运动信息全部复制到当前pu中

        PU::spanMotionInfo(pu, mergeCtx);//扩展当前pu的运动信息
        pu.mvRefine = true;//当前pu的MV可被细化
        distParam.cur = singleMergeTempBuffer->Y();//当前CU的亮度失真参数,是从单Merge列表候选的预测缓存中得到的
#if JVET_O0108_DIS_DMVR_BDOF_CIIP       
        acMergeTmpBuffer[uiMergeCand] = m_acMergeTmpBuffer[uiMergeCand].getBuf(localUnitArea);//对acMergeTmpBuffer用m_acMergeTmpBuffer进行初始化为0BUFF
        m_pcInterSearch->motionCompensation(pu, *singleMergeTempBuffer, REF_PIC_LIST_X, true, true, &(acMergeTmpBuffer[uiMergeCand]));//运动补偿得到帧间预测值,将预测值暂时保存在singleMergeTempBuffer中
#else
        m_pcInterSearch->motionCompensation(pu, *singleMergeTempBuffer);
#endif
        acMergeBuffer[uiMergeCand] = m_acRealMergeBuffer[uiMergeCand].getBuf(localUnitArea);//用m_acRealMergeBuffer对其进行初始化,因为m_acRealMergeBuffer里面的值为NULL,是不带BIO的预测缓存,默认为0
        acMergeBuffer[uiMergeCand].copyFrom(*singleMergeTempBuffer);//将MC之后的预测值singleMergeTempBuffer复制到acMergeBuffer中
        pu.mvRefine = false;

        //对于双向的预测候选,直接进行MV的细化(即DMVR)
        if (mergeCtx.interDirNeighbours[uiMergeCand] == 3 && mergeCtx.mrgTypeNeighbours[uiMergeCand] == MRG_TYPE_DEFAULT_N)
        {
          mergeCtx.mvFieldNeighbours[2 * uiMergeCand].mv = pu.mv[0];
          mergeCtx.mvFieldNeighbours[2 * uiMergeCand + 1].mv = pu.mv[1];
          {
            int dx, dy, i, j, num = 0;
            dy = std::min<int>(pu.lumaSize().height, DMVR_SUBCU_HEIGHT);
            dx = std::min<int>(pu.lumaSize().width, DMVR_SUBCU_WIDTH);
            if (PU::checkDMVRCondition(pu))//检查该PU是否使用DMVR技术
            {
              for (i = 0; i < (pu.lumaSize().height); i += dy)
              {
                for (j = 0; j < (pu.lumaSize().width); j += dx)
                {
                  refinedMvdL0[num][uiMergeCand] = pu.mvdL0SubPu[num];
                  num++;
                }
              }
            }
          }
        }

        Distortion uiSad = distParam.distFunc(distParam);
        m_CABACEstimator->getCtx() = ctxStart;
        uint64_t fracBits = m_pcInterSearch->xCalcPuMeBits(pu);
        double cost = (double)uiSad + (double)fracBits * sqrtLambdaForFirstPassIntra;//计算SAD下的RDCost
        insertPos = -1;
        //更新在当前块为regularMerge 模式下的Merge候选代价列表
        updateCandList(ModeInfo(uiMergeCand, true, false, false), cost, RdModeList, candCostList, uiNumMrgSATDCand, &insertPos);
        if (insertPos != -1)
        {
          if (insertPos == RdModeList.size() - 1)
          {
            swap(singleMergeTempBuffer, acMergeTempBuffer[insertPos]);
          }
          else
          {
            for (uint32_t i = uint32_t(RdModeList.size()) - 1; i > insertPos; i--)
            {
              swap(acMergeTempBuffer[i - 1], acMergeTempBuffer[i]);
            }
            swap(singleMergeTempBuffer, acMergeTempBuffer[insertPos]);//将singleMergeTempBuffer和acMergeTempBuffer进行交换,也就是将帧间预测值放入到acMergeTempBuffer中去。
          }
        }
        CHECK(std::min(uiMergeCand + 1, uiNumMrgSATDCand) != RdModeList.size(), "");
      }


     
      if (isIntrainterEnabled)//CIIP模式可用
      {
        // prepare for Intra bits calculation
        //为帧内比特计算做准备
        pu.mhIntraFlag = true;//自行将mhIntraFlag设为真,进行CIIP的率失真选择,选出最好的inter预测值,也即最好的Merge候选

        // save the to-be-tested merge candidates
        //保存已经测试过的Merge候选
        uint32_t MHIntraMergeCand[NUM_MRG_SATD_CAND];//CIIP的Merge候选列表


        //最多从RDCost列表中选出前四个候选进行CIIP的预测。
        for (uint32_t mergeCnt = 0; mergeCnt < std::min(NUM_MRG_SATD_CAND, (const int)mergeCtx.numValidMergeCand); mergeCnt++)
        {
          MHIntraMergeCand[mergeCnt] = RdModeList[mergeCnt].mergeCand;//之前已经RD过的Merge候选列表复制给CIIP的候选列表
        }

        //这里对四个较优的RDlist中的候选分别循环进行各候选MC的预测,然后与亮度分量Planar帧内模式的预测值进行加权,选出SAD下的RDcost最优的一种CIIP的组合,也即为CIIP模式在4个Merge候选中最好的一种模式
        for (uint32_t mergeCnt = 0; mergeCnt < std::min(std::min(NUM_MRG_SATD_CAND, (const int)mergeCtx.numValidMergeCand), 4); mergeCnt++)
        {
          uint32_t mergeCand = MHIntraMergeCand[mergeCnt];//CIIP的Merge候选索引
#if JVET_O0108_DIS_DMVR_BDOF_CIIP
          acMergeTmpBuffer[mergeCand] = m_acMergeTmpBuffer[mergeCand].getBuf(localUnitArea);//再一次对acMergeTmpBuffer进行初始化。为0BUFF
#else
          acMergeBuffer[mergeCand] = m_acRealMergeBuffer[mergeCand].getBuf(localUnitArea);
#endif

          // estimate merge bits//估计Merge的比特
          mergeCtx.setMergeInfo(pu, mergeCand);
#if CIIP_B_TO_P
          
          acMergeTmpBuffer[mergeCand] = m_acMergeTmpBuffer[mergeCand].getBuf(localUnitArea);//对acMergeTmpBuffer用m_acMergeTmpBuffer进行初始化为0BUFF
          m_pcInterSearch->motionCompensation_X(pu, *singleMergeTempBuffer, REF_PIC_LIST_X, true, true);//运动补偿得到帧间预测值

          acMergeBuffer[mergeCand] = m_acRealMergeBuffer[mergeCand].getBuf(localUnitArea);//用m_acRealMergeBuffer对其进行初始化,因为m_acRealMergeBuffer里面的值为NULL,是不带BIO的预测缓存,默认为0
          acMergeBuffer[mergeCand].copyFrom(*singleMergeTempBuffer);//将MC之后的预测值singleMergeTempBuffer复制到acMergeBuffer中
          acMergeTempBuffer[mergeCand]->copyFrom(*singleMergeTempBuffer);

#endif
          // first round第一轮的计算去计算帧内预测值
          pu.intraDir[0] = PLANAR_IDX;//帧内亮度只用Planar模式去预测
          uint32_t intraCnt = 0;//帧内数量为0
          // generate intrainter Y prediction生成内部的亮度预测值
          if (mergeCnt == 0)//如果Merge候选数量为0,即为第一轮进入帧内预测模式,这里只对Y分量进行预测
          {
            m_pcIntraSearch->initIntraPatternChType(*pu.cu, pu.Y());//帧内角度预测函数入口初始化帧内分量类型
            m_pcIntraSearch->predIntraAng(COMPONENT_Y, pu.cs->getPredBuf(pu).Y(), pu);//帧内角度预测函数入口
            m_pcIntraSearch->switchBuffer(pu, COMPONENT_Y, pu.cs->getPredBuf(pu).Y(), m_pcIntraSearch->getPredictorPtr2(COMPONENT_Y, intraCnt));
          }
#if JVET_O0108_DIS_DMVR_BDOF_CIIP
          pu.cs->getPredBuf(pu).copyFrom(acMergeTmpBuffer[mergeCand]);//将之前得到的每个Merge候选的inter预测值acMergeTmpBuffer[mergeCand]通过getPredBuf放入到对应分量的帧间BUFF中
#else
          pu.cs->getPredBuf(pu).copyFrom(acMergeBuffer[mergeCand]);
#endif
          if (pu.cs->slice->getLmcsEnabledFlag() && m_pcReshape->getCTUFlag())
          {
            pu.cs->getPredBuf(pu).Y().rspSignal(m_pcReshape->getFwdLUT());
          }

          //该函数即对帧内帧间的预测值进行加权预测(同样只针对Y分量)
          m_pcIntraSearch->geneWeightedPred(COMPONENT_Y, pu.cs->getPredBuf(pu).Y(), pu, m_pcIntraSearch->getPredictorPtr2(COMPONENT_Y, intraCnt));

          // calculate cost计算代价
          if (pu.cs->slice->getLmcsEnabledFlag() && m_pcReshape->getCTUFlag())
          {
            pu.cs->getPredBuf(pu).Y().rspSignal(m_pcReshape->getInvLUT());
          }
          distParam.cur = pu.cs->getPredBuf(pu).Y();
          Distortion sadValue = distParam.distFunc(distParam);
          if (pu.cs->slice->getLmcsEnabledFlag() && m_pcReshape->getCTUFlag())
          {
            pu.cs->getPredBuf(pu).Y().rspSignal(m_pcReshape->getFwdLUT());
          }
          m_CABACEstimator->getCtx() = ctxStart;
#if JVET_O0249_MERGE_SYNTAX
          pu.regularMergeFlag = false;
#endif
          uint64_t fracBits = m_pcInterSearch->xCalcPuMeBits(pu);
          double cost = (double)sadValue + (double)fracBits * sqrtLambdaForFirstPassIntra;//最终的代价函数
          insertPos = -1;

          //重新更新候选列表,更新当前pu为CIIP模式下的Merge代价列表
          updateCandList(ModeInfo(mergeCand, false, false, true), cost, RdModeList, candCostList, uiNumMrgSATDCand, &insertPos);
          if (insertPos != -1)
          {
            for (int i = int(RdModeList.size()) - 1; i > insertPos; i--)
            {
              swap(acMergeTempBuffer[i - 1], acMergeTempBuffer[i]);
            }
            swap(singleMergeTempBuffer, acMergeTempBuffer[insertPos]);//得到CIIP的代价最小的预测值(即得到了CIIP的最优的Merge候选),存下来在后面和其他Merge模式去竞争
          }
        }

        pu.mhIntraFlag = false;//CIIP选出最佳的组合以后,将当前pu的CIIPFlag设置为false,再对后面的Merge模式进行预测,
      }

      if ( pu.cs->sps->getUseMMVD() )//sps层MMVD模式可用
      {
        cu.mmvdSkip = true;//自行设置cu.mmvdSkip为真,进行MMVD的率失真选择
#if JVET_O0249_MERGE_SYNTAX
        pu.regularMergeFlag = true;//自行设置pu.regularMergeFlag ,因为MMVD的Merge候选也是需要在regular模式的基础上得到的
#endif
        const int tempNum = (mergeCtx.numValidMergeCand > 1) ? MMVD_ADD_NUM : MMVD_ADD_NUM >> 1;
        //对MMVD候选循环遍历,选出SAD下RDCost最优的一种MMVD的组合
        for (int mmvdMergeCand = 0; mmvdMergeCand < tempNum; mmvdMergeCand++)//循环64遍,前32种得到起始点1以及对应的8个步长
                                                                             //因为对于每种MV扩展都可以得到4*8种组合,因此两个起始点总共是64种组合
        {
          int baseIdx = mmvdMergeCand / MMVD_MAX_REFINE_NUM;//候选基MV(即初始MV)索引,要么0 要么1,(这里就是两个初始MV的起点,每个初始MV有32种4*8的步长+方向的组合)
          int refineStep = (mmvdMergeCand - (baseIdx * MMVD_MAX_REFINE_NUM)) / 4;//表示8种步长
          if (refineStep >= m_pcEncCfg->getMmvdDisNum())
            continue;
          //设置MMVD候选的信息,得到每个扩展MV具体每个方向以及对应的步长
          mergeCtx.setMmvdMergeCandiInfo(pu, mmvdMergeCand);

          PU::spanMotionInfo(pu, mergeCtx);
          pu.mvRefine = true;
          distParam.cur = singleMergeTempBuffer->Y();
          pu.mmvdEncOptMode = (refineStep > 2 ? 2 : 1);
          CHECK(!pu.mmvdMergeFlag, "MMVD merge should be set");
          // Don't do chroma MC here

          //计算MMVD的运动补偿预测值
          m_pcInterSearch->motionCompensation(pu, *singleMergeTempBuffer, REF_PIC_LIST_X, true, false);
          pu.mmvdEncOptMode = 0;
          pu.mvRefine = false;
          Distortion uiSad = distParam.distFunc(distParam);//失真函数

          m_CABACEstimator->getCtx() = ctxStart;
          uint64_t fracBits = m_pcInterSearch->xCalcPuMeBits(pu);//计算码率
          double cost = (double)uiSad + (double)fracBits * sqrtLambdaForFirstPassIntra; //计算RDcost
          insertPos = -1;

          //再重新更新候选列表,更新当前pu为MMVD模式下的Merge代价列表
          updateCandList(ModeInfo(mmvdMergeCand, false, true, false), cost, RdModeList, candCostList, uiNumMrgSATDCand, &insertPos);
          if (insertPos != -1)
          {
            for (int i = int(RdModeList.size()) - 1; i > insertPos; i--)
            {
              swap(acMergeTempBuffer[i - 1], acMergeTempBuffer[i]);
            }
            swap(singleMergeTempBuffer, acMergeTempBuffer[insertPos]);//将代价更小的MMVD候选预测出的预测值放入到acMergeTempBuffer[insertPos]中
          }
        }
      }
      // Try to limit number of candidates using SATD-costs
      //使用一个阈值去限制SATD列表的数量
      for( uint32_t i = 1; i < uiNumMrgSATDCand; i++ )
      {
        if( candCostList[i] > MRG_FAST_RATIO * candCostList[0] )
        {
          uiNumMrgSATDCand = i;
          break;
        }
      }

      setMergeBestSATDCost( candCostList[0] );//设置最优的一个MergeRDcost,选出最好的一个Merge候选(可能是regular,也可能是MMVDMerge,也可能是CIIPmerge)

      if (isIntrainterEnabled)//CIIP模式可用,对之前选中的最优的CIIP组合进行正式的三个分量的intra预测值的计算
      {
        pu.mhIntraFlag = true;//当前pu的模式为CIIP

        //对每个Merge候选循环
        for (uint32_t mergeCnt = 0; mergeCnt < uiNumMrgSATDCand; mergeCnt++)
        {
          if (RdModeList[mergeCnt].isCIIP)//当前模式是CIIP模式,则对当前块进行帧内预测
          {
            pu.intraDir[0] = PLANAR_IDX;//亮度帧内模式使用Planar
            pu.intraDir[1] = DM_CHROMA_IDX;//色度帧内模式用DM模式
            uint32_t bufIdx = 0;

            //Cb分量
            m_pcIntraSearch->initIntraPatternChType(*pu.cu, pu.Cb());
            m_pcIntraSearch->predIntraAng(COMPONENT_Cb, pu.cs->getPredBuf(pu).Cb(), pu);
            m_pcIntraSearch->switchBuffer(pu, COMPONENT_Cb, pu.cs->getPredBuf(pu).Cb(), m_pcIntraSearch->getPredictorPtr2(COMPONENT_Cb, bufIdx));
            //Cr分量
            m_pcIntraSearch->initIntraPatternChType(*pu.cu, pu.Cr());
            m_pcIntraSearch->predIntraAng(COMPONENT_Cr, pu.cs->getPredBuf(pu).Cr(), pu);
            m_pcIntraSearch->switchBuffer(pu, COMPONENT_Cr, pu.cs->getPredBuf(pu).Cr(), m_pcIntraSearch->getPredictorPtr2(COMPONENT_Cr, bufIdx));
          }
        }
        pu.mhIntraFlag = false;
      }

      tempCS->initStructData( encTestMode.qp, encTestMode.lossless );
      m_CABACEstimator->getCtx() = ctxStart;
    }
    else//如果是Skip模式
    {
      if (bestIsMMVDSkip)//如果最优模式是MMVDSkip模式
      {
        uiNumMrgSATDCand = mergeCtx.numValidMergeCand + ((mergeCtx.numValidMergeCand > 1) ? MMVD_ADD_NUM : MMVD_ADD_NUM >> 1);
        //uiNumMrgSATDCand的数量等于常规Merge列表中可用候选的数量+MMVD候选的数量(64/32)
      }
      else
      {
        uiNumMrgSATDCand = mergeCtx.numValidMergeCand;
      }
    }
  }
  m_bestModeUpdated = tempCS->useDbCost = bestCS->useDbCost = false;
  uint32_t iteration;
  uint32_t iterationBegin = 0;
  if (encTestMode.lossless)//如果是残差为零,即无损编码,则只迭代一次
  {
    iteration = 1;
  }
  else//否则迭代两次
  {
    iteration = 2;
  }

//HAD就是带有哈达吗变换的SAD,即SATD。是一种更为精确的绝对误差的比较方法。相比于SAD的比较更为细致

  for (uint32_t uiNoResidualPass = iterationBegin; uiNoResidualPass < iteration; ++uiNoResidualPass)
  {
    //对前面通过SADCost粗选后的RdModeList列表中的所有候选进行HAD(SATD)细选,选出最优的一种候选,可能是regular模式、CIIPMerge或者MMVDMerge中的一种
    for( uint32_t uiMrgHADIdx = 0; uiMrgHADIdx < uiNumMrgSATDCand; uiMrgHADIdx++ )
    {
      uint32_t uiMergeCand = RdModeList[uiMrgHADIdx].mergeCand;

      if (uiNoResidualPass != 0 && RdModeList[uiMrgHADIdx].isCIIP) // CIIP不支持Skip模式
      {
        if (isTestSkipMerge[uiMergeCand])
        {
          continue;
        }
      }

      if (((uiNoResidualPass != 0) && candHasNoResidual[uiMrgHADIdx])
       || ( (uiNoResidualPass == 0) && bestIsSkip ) )
      {
        continue;
      }

      // first get merge candidates
      //首选获取Merge候选们
      CodingUnit &cu      = tempCS->addCU( tempCS->area, partitioner.chType );//定义当前CU的引用变量

      partitioner.setCUData( cu );
      cu.slice            = tempCS->slice;
      cu.tileIdx          = tempCS->picture->brickMap->getBrickIdxRsMap( tempCS->area.lumaPos() );
      cu.skip             = false;//Skip模式默认关闭
      cu.mmvdSkip = false;//MMVDSkip模式默认关闭
      cu.triangle         = false;//TPM模式默认关闭
    //cu.affine
      cu.predMode         = MODE_INTER;
    //cu.LICFlag
      cu.transQuantBypass = encTestMode.lossless;
      cu.chromaQpAdj      = cu.transQuantBypass ? 0 : m_cuChromaQpOffsetIdxPlus1;
      cu.qp               = encTestMode.qp;
      PredictionUnit &pu  = tempCS->addPU( cu, partitioner.chType );//定义当前pu的引用变量


      //判断当前候选是三种Merge模式中的哪一种,为相应的模式做预测的准备
      if (uiNoResidualPass == 0 && RdModeList[uiMrgHADIdx].isCIIP)//CIIP模式
      {
        cu.mmvdSkip = false;
        mergeCtx.setMergeInfo(pu, uiMergeCand);//为当前pu设置基础的Merge信息
        pu.mhIntraFlag = true;//将当前pu设置为CIIP模式
        pu.regularMergeFlag = false;
        pu.intraDir[0] = PLANAR_IDX;//帧内亮度模式采用Planar模式
        CHECK(pu.intraDir[0]<0 || pu.intraDir[0]>(NUM_LUMA_MODE - 1), "out of intra mode");
        pu.intraDir[1] = DM_CHROMA_IDX;//帧内色度模式采用DM模式
      }
      else if (RdModeList[uiMrgHADIdx].isMMVD)
      {
        cu.mmvdSkip = true;//将当前pu设置为MMVDSkip模式
#if JVET_O0249_MERGE_SYNTAX
        pu.regularMergeFlag = true;
#endif
        mergeCtx.setMmvdMergeCandiInfo(pu, uiMergeCand);
      }
      else//当前pu为常规Merge模式
      {
        cu.mmvdSkip = false;
#if JVET_O0249_MERGE_SYNTAX
        pu.regularMergeFlag = true;
#endif
        mergeCtx.setMergeInfo(pu, uiMergeCand);
      }
      PU::spanMotionInfo( pu, mergeCtx );//为当前pu设置基础的运动信息



      if( m_pcEncCfg->getMCTSEncConstraint() )
      {
        bool isDMVR = PU::checkDMVRCondition( pu );
        if( ( isDMVR && MCTSHelper::isRefBlockAtRestrictedTileBoundary( pu ) ) || ( !isDMVR && !( MCTSHelper::checkMvBufferForMCTSConstraint( pu ) ) ) )
        {
          // Do not use this mode
          tempCS->initStructData( encTestMode.qp, encTestMode.lossless );
          continue;
        }
      }

      //对当前候选进行MV的细化操作
      if( mrgTempBufSet )
      {
        {
          int dx, dy, i, j, num = 0;
          dy = std::min<int>(pu.lumaSize().height, DMVR_SUBCU_HEIGHT);
          dx = std::min<int>(pu.lumaSize().width, DMVR_SUBCU_WIDTH);
          //对当前候选运用DMVR技术
          if (PU::checkDMVRCondition(pu))
          {
            for (i = 0; i < (pu.lumaSize().height); i += dy)
            {
              for (j = 0; j < (pu.lumaSize().width); j += dx)
              {
                pu.mvdL0SubPu[num] = refinedMvdL0[num][uiMergeCand];
                num++;
              }
            }
          }
        }

        //MV细化之后再去重新求出当前PU对应Merge模式的预测值
        if (pu.mhIntraFlag)//CIIP模式可用
        {
          uint32_t bufIdx = 0;
          PelBuf tmpBuf = tempCS->getPredBuf(pu).Y();//获取亮度块的帧间预测信号
#if JVET_O0108_DIS_DMVR_BDOF_CIIP
          tmpBuf.copyFrom(acMergeTmpBuffer[uiMergeCand].Y());
#else
          tmpBuf.copyFrom(acMergeBuffer[uiMergeCand].Y());
#endif
          if (pu.cs->slice->getLmcsEnabledFlag() && m_pcReshape->getCTUFlag())
          {
            tmpBuf.rspSignal(m_pcReshape->getFwdLUT());
          }

          //计算帧内帧间加权(对亮度做)
          m_pcIntraSearch->geneWeightedPred(COMPONENT_Y, tmpBuf, pu, m_pcIntraSearch->getPredictorPtr2(COMPONENT_Y, bufIdx));
          tmpBuf = tempCS->getPredBuf(pu).Cb();
#if JVET_O0108_DIS_DMVR_BDOF_CIIP
          tmpBuf.copyFrom(acMergeTmpBuffer[uiMergeCand].Cb());
#else
          tmpBuf.copyFrom(acMergeBuffer[uiMergeCand].Cb());
#endif
          //计算帧内帧间加权(对Cb分量做)
          m_pcIntraSearch->geneWeightedPred(COMPONENT_Cb, tmpBuf, pu, m_pcIntraSearch->getPredictorPtr2(COMPONENT_Cb, bufIdx));
          tmpBuf = tempCS->getPredBuf(pu).Cr();
#if JVET_O0108_DIS_DMVR_BDOF_CIIP
          tmpBuf.copyFrom(acMergeTmpBuffer[uiMergeCand].Cr());
#else
          tmpBuf.copyFrom(acMergeBuffer[uiMergeCand].Cr());
#endif
          //计算帧内帧间加权(帧间为Affine模式,对Cr分量做)
          m_pcIntraSearch->geneWeightedPred(COMPONENT_Cr, tmpBuf, pu, m_pcIntraSearch->getPredictorPtr2(COMPONENT_Cr, bufIdx));
        }
        else
        {
          if (RdModeList[uiMrgHADIdx].isMMVD)//重新计算MMVD模式预测值
          {
            pu.mmvdEncOptMode = 0;
            m_pcInterSearch->motionCompensation(pu);
          }
          else if (uiNoResidualPass != 0 && RdModeList[uiMrgHADIdx].isCIIP)//CIIP不支持Skip模式
          {
            tempCS->getPredBuf().copyFrom(acMergeBuffer[uiMergeCand]);
          }
          else//regular模式
          {
            tempCS->getPredBuf().copyFrom(*acMergeTempBuffer[uiMrgHADIdx]);
          }
        }
      }
      else//如果mrgTempBufSet为false,强制开启MV细化功能
      {
        pu.mvRefine = true;
        m_pcInterSearch->motionCompensation( pu );
        pu.mvRefine = false;
      }

      //如果当前候选不是MMVD也不是CIIP模式
      if (!cu.mmvdSkip && !pu.mhIntraFlag && uiNoResidualPass != 0)
      {
        CHECK(uiMergeCand >= mergeCtx.numValidMergeCand, "out of normal merge");
        isTestSkipMerge[uiMergeCand] = true;
      }

      //编码当前Merge候选模式的inter预测值的残差,并在该函数中完成HAD的比较,最终选出HAD最小的那个Merge模式
      xEncodeInterResidual( tempCS, bestCS, partitioner, encTestMode, uiNoResidualPass, uiNoResidualPass == 0 ? &candHasNoResidual[uiMrgHADIdx] : NULL );

      if( m_pcEncCfg->getUseFastDecisionForMerge() && !bestIsSkip && !pu.mhIntraFlag)
      {
        bestIsSkip = !bestCS->cus.empty() && bestCS->getCU( partitioner.chType )->rootCbf == 0;
      }
      tempCS->initStructData( encTestMode.qp, encTestMode.lossless );
    }// end loop uiMrgHADIdx  结束HAD细选的循环

    if( uiNoResidualPass == 0 && m_pcEncCfg->getUseEarlySkipDetection() )
    {
      const CodingUnit     &bestCU = *bestCS->getCU( partitioner.chType );
      const PredictionUnit &bestPU = *bestCS->getPU( partitioner.chType );

      if( bestCU.rootCbf == 0 )
      {
        if( bestPU.mergeFlag )
        {
          m_modeCtrl->setEarlySkipDetected();
        }
        else if( m_pcEncCfg->getMotionEstimationSearchMethod() != MESEARCH_SELECTIVE )
        {
          int absolute_MV = 0;

          for( uint32_t uiRefListIdx = 0; uiRefListIdx < 2; uiRefListIdx++ )
          {
            if( slice.getNumRefIdx( RefPicList( uiRefListIdx ) ) > 0 )
            {
              absolute_MV += bestPU.mvd[uiRefListIdx].getAbsHor() + bestPU.mvd[uiRefListIdx].getAbsVer();
            }
          }

          if( absolute_MV == 0 )
          {
            m_modeCtrl->setEarlySkipDetected();
          }
        }
      }
    }
  }


  //经过上面的HAD细选流程后选出三种Merge中最优的一种Merge候选,并计算出相应的预测值以及残差,最后计算HAD下的RDcost,再去和其余的Merge模式(TPM、Affine之类的模式)做SATD的RDcsot的比较
  if ( m_bestModeUpdated && bestCS->cost != MAX_DOUBLE )
  {
    xCalDebCost( *bestCS, partitioner );
  }
}

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值