H.266/VVC-VTM代码学习-帧内预测11-编码端亮度块模式选择estIntraPredLumaQT函数

H.266/VVC专栏传送

上一篇:H.266/VVC-VTM代码学习-帧内预测10-色度预测的CCLM模式(2)xGetLMParameters函数求解CCLM参数α和β
下一篇:H.266/VVC-VTM代码学习-帧内预测12-编码端获取MPM列表getIntraMPMs函数

前言

VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。

本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。

VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)

本文涉及的代码主要存在于工程下的Lib\EncoderLib\IntraSearch.cpp文件中。

一、函数代码

bool IntraSearch::estIntraPredLumaQT(CodingUnit &cu, Partitioner &partitioner, const double bestCostSoFar, bool mtsCheckRangeFlag, int mtsFirstCheckId, int mtsLastCheckId, bool moreProbMTSIdxFirst, CodingStructure* bestCS)
{
/*****************************************************/
/******************* 初始化参数 **********************/
/*****************************************************/

  //获取当前编码块的编码结构
  CodingStructure       &cs            = *cu.cs;
  //获取该编码结构的语法元素
  const SPS             &sps           = *cs.sps;
  //亮度块宽取对数(即亮度块宽需要多少bits)
  const uint32_t             uiWidthBit    = floorLog2(partitioner.currArea().lwidth() );
  //亮度块高取对数(即亮度块高需要多少bits)
  const uint32_t             uiHeightBit   =                   floorLog2(partitioner.currArea().lheight());

  //Lambda calculation at equivalent Qp of 4 is recommended because at that Qp, the quantization divisor is 1.
  //建议在等效Qp(量化参数)为4时进行lambda的计算,因为在此时,用于量化的除数为1
  const double sqrtLambdaForFirstPass = m_pcRdCost->getMotionLambda( ) * FRAC_BITS_SCALE;

  //===== loop over partitions =====
  //===== 循环分区 =====
  
  //上下文模型的开始
  const TempCtx ctxStart          ( m_CtxCache, m_CABACEstimator->getCtx() );
  //MIP模式的flag上下文模型
  const TempCtx ctxStartMipFlag    ( m_CtxCache, SubCtx( Ctx::MipFlag,          m_CABACEstimator->getCtx() ) );
  //ISP模式的上下文
  const TempCtx ctxStartIspMode    ( m_CtxCache, SubCtx( Ctx::ISPMode,          m_CABACEstimator->getCtx() ) );
  //Planar模式的flag上下文
  const TempCtx ctxStartPlanarFlag ( m_CtxCache, SubCtx( Ctx::IntraLumaPlanarFlag, m_CABACEstimator->getCtx() ) );
  //其他帧内模式的上下文
  const TempCtx ctxStartIntraMode(m_CtxCache, SubCtx(Ctx::IntraLumaMpmFlag, m_CABACEstimator->getCtx()));
  //多参考行索引上下文
  const TempCtx ctxStartMrlIdx      ( m_CtxCache, SubCtx( Ctx::MultiRefLineIdx,        m_CABACEstimator->getCtx() ) );

  CHECK( !cu.firstPU, "CU has no PUs" );
  //variables for saving fast intra modes scan results across multiple LFNST passes
  //用于保存对多个LFNST快速帧内模式扫描结果的变量。
  //这两个变量主要用于使用LFNST时,只需要进行一遍一轮和二轮的SATD扫描。  
  //当LfnstIdx==0时,不使用LFNST。此时使用LFNSTSaveFlag将一轮和二轮扫描结果保存下来。
  //当LfnstIdx==1、2时,使用LFNST,分别索引变换矩阵集中的第一个矩阵和第二个矩阵。此时使用LFNSTLoadFlag直接加载一轮和二轮的扫描结果

  //SPS层开启LFNST,使用LFNST(此时加载一轮和二轮的扫描结果)
  bool LFNSTLoadFlag = sps.getUseLFNST() && cu.lfnstIdx != 0;
  //SPS层开启LFNST,且不使用LFNST(此时将一轮和二轮扫描结果保存)
  bool LFNSTSaveFlag = sps.getUseLFNST() && cu.lfnstIdx == 0;

  //若SPS开启帧内MTS,判断当前是否检查的是DCT-Ⅱ;
  //若SPS没有开启帧内MTS,则保持原SaveFlag
  LFNSTSaveFlag &= sps.getUseIntraMTS() ? cu.mtsFlag == 0 : true;

  //LFNST索引
  const uint32_t lfnstIdx = cu.lfnstIdx;
  //帧间CU的cost
  double costInterCU = findInterCUCost( cu );

  //当前亮度CU的宽
  const int width  = partitioner.currArea().lwidth();
  //当前亮度CU的高
  const int height = partitioner.currArea().lheight();

  //Marking MTS usage for faster MTS
  //标记MTS的使用情况以加快MTS
  //0: MTS is either not applicable for current CU (cuWidth > MTS_INTRA_MAX_CU_SIZE or cuHeight > MTS_INTRA_MAX_CU_SIZE), not active in the config file or the fast decision algorithm is not used in this case
  //0:MTS不适用于当前CU(cuWidth>MTS_INTRA_MAX_CU_SIZE或cuHeight>MTS_INTRA_MAX_CU_SIZE),在配置文件中不激活,或者不使用快速决策算法的情况
  //1: MTS fast algorithm can be applied for the current CU, and the DCT2 is being checked
  //1:当前CU可以采用MTS快速算法,正在检查DCT2
  //2: MTS is being checked for current CU. Stored results of DCT2 can be utilized for speedup
  //2:正在检查当前CU的MTS,DCT2的存储结果可用于加速
  uint8_t mtsUsageFlag = 0;
  const int maxSizeEMT = MTS_INTRA_MAX_CU_SIZE;
  //若SPS开启LFNST,且一次变换不为DCT-2(mtsFlag=0时为DCT-2),则mtsUsageFlag为2;否则mtsUsageFlag为1
  if( width <= maxSizeEMT && height <= maxSizeEMT && sps.getUseIntraMTS() )
  {
    mtsUsageFlag = ( sps.getUseLFNST() && cu.mtsFlag == 1 ) ? 2 : 1;
  }

  //宽度*高度<64,且没有开启LFNST快速算法,则mtsUsageFlag为0
  if( width * height < 64 && !m_pcEncCfg->getUseFastLFNST() )
  {
    mtsUsageFlag = 0;
  }

  //是否启用ACT变换
  const bool colorTransformIsEnabled = sps.getUseColorTrans() && !CS::isDualITree(cs);
  //是否是第一种颜色空间
  const bool isFirstColorSpace       = colorTransformIsEnabled && ((m_pcEncCfg->getRGBFormatFlag() && cu.colorTransform) || (!m_pcEncCfg->getRGBFormatFlag() && !cu.colorTransform));
  //是否是第二种颜色空间
  const bool isSecondColorSpace      = colorTransformIsEnabled && ((m_pcEncCfg->getRGBFormatFlag() && !cu.colorTransform) || (!m_pcEncCfg->getRGBFormatFlag() && cu.colorTransform));

  //定义当前最优代价
  double bestCurrentCost = bestCostSoFar;
  //是否可用ISP(SPS确定使用ISP,且MTSFLAG为0(当前正在检查DCT-2变换),且lfnstIdx == 0(没有检查LFNST),且CU可以使用ISP(当前块尺寸符合ISP尺寸限制))
  bool ispCanBeUsed   = sps.getUseISP() && cu.mtsFlag == 0 && cu.lfnstIdx == 0 && CU::canUseISP(width, height, cu.cs->sps->getMaxTbSize());
  //为ISP保存数据(使用ISP,且不启用ACT变换或者是第一种颜色空间)
  bool saveDataForISP = ispCanBeUsed && (!colorTransformIsEnabled || isFirstColorSpace);
  //测试ISP(使用ISP,且不启用ACT变换)
  bool testISP        = ispCanBeUsed && (!colorTransformIsEnabled || !cu.colorTransform);

  //为ISP保存数据
  if ( saveDataForISP )
  {
    //reset the intra modes lists variables
    //重置ISP帧内模式列表变量
    m_ispCandListHor.clear();
    m_ispCandListVer.clear();
  }
  
  //测试ISP
  if( testISP )
  {
    //reset the variables used for the tests
    //重置ISP测试需要用到的变量
    m_regIntraRDListWithCosts.clear();//选择的常规帧内模式
    //水平方向分块的总数
    int numTotalPartsHor = (int)height >> floorLog2(CU::getISPSplitDim(width, height, TU_1D_HORZ_SPLIT));
    //垂直方向分块的总数
    int numTotalPartsVer = (int)width  >> floorLog2(CU::getISPSplitDim(width, height, TU_1D_VERT_SPLIT));
    //载入分区数
    m_ispTestedModes[0].init( numTotalPartsHor, numTotalPartsVer );
    //the total number of subpartitions is modified to take into account the cases where LFNST cannot be combined with ISP due to size restrictions
    //考虑到由于尺寸限制无法将LFNST与ISP组合的情况,修改子分区的总数
    //水平分区数
    numTotalPartsHor = sps.getUseLFNST() && CU::canUseLfnstWithISP(cu.Y(), HOR_INTRA_SUBPARTITIONS) ? numTotalPartsHor : 0;
    //垂直分区数
    numTotalPartsVer = sps.getUseLFNST() && CU::canUseLfnstWithISP(cu.Y(), VER_INTRA_SUBPARTITIONS) ? numTotalPartsVer : 0;

    //LFNST有4个set 每个set有2个变换核
    //set0是为planar(0)和DC(1)准备的
    //set1是为 mode<0 和 2 <= mode <= 12 准备的(左下方)
    //set2是为 13 <= mode <=23 和 45 <= mode <= 55 准备的(近水平和近垂直)
    //set3是为 24 <= mode <= 44 准备的(右上方)
    //NUM_LFNST_NUM_PER_SET = 3
    for (int j = 1; j < NUM_LFNST_NUM_PER_SET; j++)
    {
      //根据ISP分块,初始化m_ispTestedModes[1]和m_ispTestedModes[2]
      m_ispTestedModes[j].init(numTotalPartsHor, numTotalPartsVer);
    }
  }
  //BDPCM测试标志
  //SPS中BDPCM标志位有效,且当前CU块允许BDPCM,且cu.mtsFlag == 0 (当前正在检查DCT-2变换),且cu.lfnstIdx == 0(没有检查LFNST)
  const bool testBDPCM = sps.getBDPCMEnabledFlag() && CU::bdpcmAllowed(cu, ComponentID(partitioner.chType)) && cu.mtsFlag == 0 && cu.lfnstIdx == 0;
  //哈达玛变换(即SATD)粗选后的候选列表
  static_vector<ModeInfo, FAST_UDI_MAX_RDMODE_NUM> uiHadModeList;
  //候选代价列表
  static_vector<double, FAST_UDI_MAX_RDMODE_NUM> CandCostList;
  //哈达玛变换(SATD/HAD)后的候选Had列表
  static_vector<double, FAST_UDI_MAX_RDMODE_NUM> CandHadList;

  auto &pu = *cu.firstPU;
  bool validReturn = false;
  //代码块开始
  {
    CandHadList.clear();
    CandCostList.clear();
    uiHadModeList.clear();

    CHECK(pu.cu != &cu, "PU is not contained in the CU");

    //===== determine set of modes to be tested (using prediction signal only) ===== 
    //确定要测试的模式集(仅使用预测信号)
    //传统帧内模式的数量(67),不包括MIP模式
    int numModesAvailable = NUM_LUMA_MODE; 
    //是否进行MIP快速算法
    const bool fastMip    = sps.getUseMIP() && m_pcEncCfg->getUseFastMIP();
    //MIP允许使用标志(SPS中允许使用MIP,且当前为亮度块,且lfnstIdx == 0(没有检查LFNST)或当前块大小允许LFNST with MIP)
    const bool mipAllowed = sps.getUseMIP() && isLuma(partitioner.chType) && ((cu.lfnstIdx == 0) || allowLfnstWithMip(cu.firstPU->lumaSize()));
    //MIP测试标志(允许MIP,且宽高比不能超过1/8或8/1)
    const bool testMip = mipAllowed && !(cu.lwidth() > (8 * cu.lheight()) || cu.lheight() > (8 * cu.lwidth()));
    //MIP支持最大块尺寸为64*64
    const bool supportedMipBlkSize = pu.lwidth() <= MIP_MAX_WIDTH && pu.lheight() <= MIP_MAX_HEIGHT;

    static_vector<ModeInfo, FAST_UDI_MAX_RDMODE_NUM> uiRdModeList;
    //最终RDcost环节最优模式的数量
    int numModesForFullRD = 3;
    //根据CU的尺寸选择模式数量
    numModesForFullRD = g_aucIntraModeNumFast_UseMPM_2D[uiWidthBit - MIN_CU_LOG2][uiHeightBit - MIN_CU_LOG2];
    
#if INTRA_FULL_SEARCH
    //如果是帧内预测全搜索则将最优模式数量设置为可用模式数量
    numModesForFullRD = numModesAvailable;
#endif

    //第二种颜色空间
    if (isSecondColorSpace)
    {
      uiRdModeList.clear();
      //如果第一个颜色空间的模式列表不为空,直接从第一个颜色空间的模式列表中加载测试的模式列表
      if (m_numSavedRdModeFirstColorSpace[m_savedRdModeIdx] > 0)
      {
        for (int i = 0; i < m_numSavedRdModeFirstColorSpace[m_savedRdModeIdx]; i++)
        {
          uiRdModeList.push_back(m_savedRdModeFirstColorSpace[m_savedRdModeIdx][i]);
        }
      }
      //第一颜色空间模式列表为空
      else
      {
        return false;
      }
    }
    //不是第二种颜色空间
    else
    {
      //mtsUsageFlag = 0 or 1,此时一次变换为DCT-2
      if (mtsUsageFlag != 2)
      {
        CHECK(!pu.Y().valid(), "PU is not valid");
        //是否是CTU的第一行
        bool isFirstLineOfCtu     = (((pu.block(COMPONENT_Y).y) & ((pu.cs->sps)->getMaxCUWidth() - 1)) == 0);
        //使用的扩展参考行数量(若为CTU第一行或者SPS层关闭了MRL,则最多只能用1行,否则最多可以用3行)
        int  numOfPassesExtendRef = ((!sps.getUseMRL() || isFirstLineOfCtu) ? 1 : MRL_NUM_REF_LINES);
        pu.multiRefIdx            = 0;

        //如果最终RDcost环节最优模式的数量不等于传统帧内模式数量(67)(正常都是这个情况,所以这里是主要的RD入口)
        if (numModesForFullRD != numModesAvailable)
        {
          CHECK(numModesForFullRD >= numModesAvailable, "Too many modes for full RD search");

          //获取当前 luma PU 的区域
          const CompArea &area = pu.Y();

          //获取当前 luma PU 的亮度原始值
          PelBuf piOrg  = cs.getOrgBuf(area);
          //获取当前 luma PU 的亮度预测值
          PelBuf piPred = cs.getPredBuf(area);

          //定义一个使用SAD的失真参数
          DistParam distParamSad;
          //定义一个使用HAD/SATD(加了哈达玛变换)的失真参数
          DistParam distParamHad;
          
          //Luma mapping with chroma scaling (LMCS)
          if (cu.slice->getLmcsEnabledFlag() && m_pcReshape->getCTUFlag())
          {
            CompArea tmpArea(COMPONENT_Y, area.chromaFormat, Position(0, 0), area.size());
            PelBuf   tmpOrg = m_tmpStorageLCU.getBuf(tmpArea);
            tmpOrg.copyFrom(piOrg);
            tmpOrg.rspSignal(m_pcReshape->getFwdLUT());
            // Use SAD cost 设置SAD参数
            m_pcRdCost->setDistParam(distParamSad, tmpOrg, piPred, sps.getBitDepth(CHANNEL_TYPE_LUMA), COMPONENT_Y,
                                     false);   
            // Use HAD (SATD) cost  设置SATD参数
            m_pcRdCost->setDistParam(distParamHad, tmpOrg, piPred, sps.getBitDepth(CHANNEL_TYPE_LUMA), COMPONENT_Y,
                                     true);
          }
          else
          {
            // Use SAD cost 设置SAD参数
            m_pcRdCost->setDistParam(distParamSad, piOrg, piPred, sps.getBitDepth(CHANNEL_TYPE_LUMA), COMPONENT_Y,
                                     false);
            // Use HAD (SATD) cost 设置SATD参数
            m_pcRdCost->setDistParam(distParamHad, piOrg, piPred, sps.getBitDepth(CHANNEL_TYPE_LUMA), COMPONENT_Y,
                                     true);
          }

          distParamSad.applyWeight = false;
          distParamHad.applyWeight = false;

          //若MIP测试标志有效,且为MIP支持尺寸,则将MIP加入候选模式中
          if (testMip && supportedMipBlkSize)
          {
            numModesForFullRD += fastMip
                                   ? std::max(numModesForFullRD, floorLog2(std::min(pu.lwidth(), pu.lheight())) - 1)
                                   : numModesForFullRD;
          }
          //定义SATD变换候选的数量
          const int numHadCand = (testMip ? 2 : 1) * 3;

          //以下是使用哈达玛变换的推导(常规)角度候选
          /*****************************************************/
	      /****************** 常规角度候选 *********************/
          /*****************************************************/
          cu.mipFlag = false;

          //===== init pattern for luma prediction =====
          //初始化亮度预测模式
          initIntraPatternChType(cu, pu.Y(), true);
          //用于存放对应模式是否被检查
          bool bSatdChecked[NUM_INTRA_MODE];
          memset(bSatdChecked, 0, sizeof(bSatdChecked));

          //若LFNSTLoadFlag无效,即不使用LFNST,不能直接加载一轮和二轮的扫描结果
          if (!LFNSTLoadFlag)
          {
            //首先对67种传统的亮度帧内模式进行第一轮SATD循环粗选
            //遍历传统帧内模式(67)
            for (int modeIdx = 0; modeIdx < numModesAvailable; modeIdx++)
            {
              uint32_t   uiMode    = modeIdx;
              Distortion minSadHad = 0;

              /********************* 第一轮SATD粗选 ************************/
              // Skip checking extended Angular modes in the first round of SATD
              //在第一轮SATD中跳过检查扩展角模式(即主要检查0、1、2、4、6...66这些角度模式【即对Planar、DC、HEVC中的角度模式】)
              //跳过扩展角度模式
              if (uiMode > DC_IDX && (uiMode & 1))
              {
                continue;
              }

			  //标记当前模式被检查过
              bSatdChecked[uiMode] = true;

              //该数组中存放的是当前CU最终选中的预测模式
              pu.intraDir[0] = modeIdx;

              //初始化帧内预测参数
              initPredIntraParams(pu, pu.Y(), sps);
              //传统角度预测模式函数入口,进行传统的角度预测
              predIntraAng(COMPONENT_Y, piPred, pu);
              
              //Use the min between SAD and HAD as the cost criterion 
              //使用SAD和HAD之间的最小值作为代价标准(HAD即SATD,是加了哈达玛变换后的SAD)
              //SAD is scaled by 2 to align with the scaling of HAD
              //SAD的缩放比例为2,与HAD的缩放比例一致
              minSadHad += std::min(distParamSad.distFunc(distParamSad) * 2, distParamHad.distFunc(distParamHad));

              //NB xFracModeBitsIntra will not affect the mode for chroma that may have already been pre-estimated.
              //NB xFracModeBitsIntra不会影响可能已经预先估计的色度模式

              //载入MIP Flag
              m_CABACEstimator->getCtx() = SubCtx( Ctx::MipFlag, ctxStartMipFlag );
              //载入ISP Mode
              m_CABACEstimator->getCtx() = SubCtx( Ctx::ISPMode, ctxStartIspMode );
              //载入PLANAR Flag
              m_CABACEstimator->getCtx() = SubCtx(Ctx::IntraLumaPlanarFlag, ctxStartPlanarFlag);
              //载入MPM Flag
              m_CABACEstimator->getCtx() = SubCtx(Ctx::IntraLumaMpmFlag, ctxStartIntraMode);
              //载入MRL Idx
              m_CABACEstimator->getCtx() = SubCtx( Ctx::MultiRefLineIdx, ctxStartMrlIdx );

              //估计编码帧内模式所需比特数
              uint64_t fracModeBits = xFracModeBitsIntra(pu, uiMode, CHANNEL_TYPE_LUMA);

              //计算每种模式的cost = minSadHad + 当前模式所需比特数 * lambda
              double cost = (double) minSadHad + (double) fracModeBits * sqrtLambdaForFirstPass;

              DTRACE(g_trace_ctx, D_INTRA_COST, "IntraHAD: %u, %llu, %f (%d)\n", minSadHad, fracModeBits, cost, uiMode);

              //更新SAD之后的RD候选模式列表以及代价列表
              updateCandList(ModeInfo(false, false, 0, NOT_INTRA_SUBPARTITIONS, uiMode), cost, uiRdModeList,
                             CandCostList, numModesForFullRD);               
              //更新SATD之后的uiHadModeList、CandHadList列表
              updateCandList(ModeInfo(false, false, 0, NOT_INTRA_SUBPARTITIONS, uiMode), double(minSadHad),
                             uiHadModeList, CandHadList, numHadCand);
            }//遍历完67种传统角度模式
            
            //如果SPS层关闭MIP,且需要为LFNST保存粗选结果
            if (!sps.getUseMIP() && LFNSTSaveFlag)
            {
              // save found best modes
              //保存找到的最佳模式
              m_uiSavedNumRdModesLFNST = numModesForFullRD;
              m_uiSavedRdModeListLFNST = uiRdModeList;
              m_dSavedModeCostLFNST    = CandCostList;
              // PBINTRA fast
              m_uiSavedHadModeListLFNST = uiHadModeList;
              m_dSavedHadListLFNST      = CandHadList;
              LFNSTSaveFlag             = false;
            }
          }//if (!LFNSTLoadFlag)   
          
          //如果SPS层关闭MIP,且使用LFNST,即可为LFNST直接加载之前的粗选结果
          if (!sps.getUseMIP() && LFNSTLoadFlag)
          {
            // restore saved modes
            numModesForFullRD = m_uiSavedNumRdModesLFNST;
            uiRdModeList      = m_uiSavedRdModeListLFNST;
            CandCostList      = m_dSavedModeCostLFNST;
            // PBINTRA fast
            uiHadModeList = m_uiSavedHadModeListLFNST;
            CandHadList   = m_dSavedHadListLFNST;
          }

          //如果SPS层关闭MIP,或者不使用LFNST(不能为LFNST加载粗选结果)
          if (!(sps.getUseMIP() && LFNSTLoadFlag))
          {
            //将第一轮选出的最优模式放入父模式中
            static_vector<ModeInfo, FAST_UDI_MAX_RDMODE_NUM> parentCandList = uiRdModeList;

            /******************** 第二轮对扩展角度模式进行SATD粗选 ***********************/
            //Second round of SATD for extended Angular modes
            //开始第二轮对剩下的扩展角度模式的SATD粗选
            //遍历第一轮选出的最优模式列表
            for (int modeIdx = 0; modeIdx < numModesForFullRD; modeIdx++)
            {
              //定义父模式,父模式放入的是第一轮选出的最优模式列表
              unsigned parentMode = parentCandList[modeIdx].modeId;
              if (parentMode > (DC_IDX + 1) && parentMode < (NUM_LUMA_MODE - 1))
              {
                //从扩展模式开始循环处理 模式号3-65
                for (int subModeIdx = -1; subModeIdx <= 1; subModeIdx += 2)
                {
                  //父模式减一和加一,检查第一轮非扩展模式相邻的扩展模式
                  unsigned mode = parentMode + subModeIdx;

                  //如果当前模式未经历第一轮SATD粗选
                  if (!bSatdChecked[mode])
                  {
                    pu.intraDir[0] = mode;

                    //初始化帧内预测参数
                    initPredIntraParams(pu, pu.Y(), sps);
                    //传统角度预测模式函数入口,进行传统的角度预测
                    predIntraAng(COMPONENT_Y, piPred, pu);

                    //Use the min between SAD and SATD as the cost criterion
                    //使用SAD和SATD之间的最小值作为代价标准
                    //SAD is scaled by 2 to align with the scaling of HAD
                    //SAD的缩放比例为2,与HAD的缩放比例一致
                    Distortion minSadHad =
                      std::min(distParamSad.distFunc(distParamSad) * 2, distParamHad.distFunc(distParamHad));

                    //NB xFracModeBitsIntra will not affect the mode for chroma that may have already been pre-estimated.
                    //NB xFracModeBitsIntra不会影响可能已经预先估计的色度模式
                    m_CABACEstimator->getCtx() = SubCtx(Ctx::MipFlag, ctxStartMipFlag);
                    m_CABACEstimator->getCtx() = SubCtx(Ctx::ISPMode, ctxStartIspMode);
                    m_CABACEstimator->getCtx() = SubCtx(Ctx::IntraLumaPlanarFlag, ctxStartPlanarFlag);
                    m_CABACEstimator->getCtx() = SubCtx(Ctx::IntraLumaMpmFlag, ctxStartIntraMode);
                    m_CABACEstimator->getCtx() = SubCtx(Ctx::MultiRefLineIdx, ctxStartMrlIdx);

                    //估计编码帧内模式所需比特数
                    uint64_t fracModeBits = xFracModeBitsIntra(pu, mode, CHANNEL_TYPE_LUMA);

                    //计算每种模式的cost = minSadHad + 当前模式所需比特数 * lambda
                    double cost = (double) minSadHad + (double) fracModeBits * sqrtLambdaForFirstPass;

                    //更新SAD之后的RD候选模式列表以及代价列表
                    updateCandList(ModeInfo(false, false, 0, NOT_INTRA_SUBPARTITIONS, mode), cost, uiRdModeList,
                                   CandCostList, numModesForFullRD);
                    //更新SATD之后的uiHadModeList、CandHadList列表
                    updateCandList(ModeInfo(false, false, 0, NOT_INTRA_SUBPARTITIONS, mode), double(minSadHad),
                                   uiHadModeList, CandHadList, numHadCand);

                    //记录当前模式已被检查过
                    bSatdChecked[mode] = true;
                  }//if (!bSatdChecked[mode])
                }//for (int subModeIdx = -1; subModeIdx <= 1; subModeIdx += 2)
              }//if (parentMode > (DC_IDX + 1) && parentMode < (NUM_LUMA_MODE - 1))
            }//for (int modeIdx = 0; modeIdx < numModesForFullRD; modeIdx++)
            
            //为之后测试ISP保存粗选出的传统帧内模式
            if (saveDataForISP)
            {
              //we save the regular intra modes list
              //保存常规帧内模式列表
              m_ispCandListHor = uiRdModeList;
            }
            pu.multiRefIdx    = 1;
            //定义最可能模式的数量,即MPM列表大小(设置为6)
            const int numMPMs = NUM_MOST_PROBABLE_MODES;
            //定义多参考行的MPM列表
            unsigned  multiRefMPM[numMPMs];
            //获取MPM列表的函数入口
            PU::getIntraMPMs(pu, multiRefMPM);
            //遍历多参考行
            /*****************************************************/
	        /*********** 遍历MPM中的模式进行多参考行候选 **********/
            /*****************************************************/
            for (int mRefNum = 1; mRefNum < numOfPassesExtendRef; mRefNum++)
            {
              //当前多参考行索引
              int multiRefIdx = MULTI_REF_LINE_IDX[mRefNum];

              //设置为当前遍历到的多参考行索引
              pu.multiRefIdx = multiRefIdx;
              {
                //获取参考像素长度、获取参考像素、参考像素滤波
                initIntraPatternChType(cu, pu.Y(), true);
              }
              //对MPM列表中的6个模式进行SATD比较并选择
              for (int x = 1; x < numMPMs; x++)
              {
                uint32_t mode = multiRefMPM[x];
                {
                  pu.intraDir[0] = mode;
                  //初始化帧内预测参数
                  initPredIntraParams(pu, pu.Y(), sps);

                  //传统角度预测模式函数入口,进行传统的角度预测
                  predIntraAng(COMPONENT_Y, piPred, pu);

                  //Use the min between SAD and SATD as the cost criterion
                  //使用SAD和SATD之间的最小值作为代价标准
                  //SAD is scaled by 2 to align with the scaling of HAD 
                  //SAD的缩放比例为2,与HAD的缩放比例一致
                  Distortion minSadHad =
                    std::min(distParamSad.distFunc(distParamSad) * 2, distParamHad.distFunc(distParamHad));

                  //NB xFracModeBitsIntra will not affect the mode for chroma that may have already been pre-estimated.
                  //NB xFracModeBitsIntra不会影响可能已经预先估计的色度模式
                  m_CABACEstimator->getCtx() = SubCtx(Ctx::MipFlag, ctxStartMipFlag);
                  m_CABACEstimator->getCtx() = SubCtx(Ctx::ISPMode, ctxStartIspMode);
                  m_CABACEstimator->getCtx() = SubCtx(Ctx::IntraLumaPlanarFlag, ctxStartPlanarFlag);
                  m_CABACEstimator->getCtx() = SubCtx(Ctx::IntraLumaMpmFlag, ctxStartIntraMode);
                  m_CABACEstimator->getCtx() = SubCtx(Ctx::MultiRefLineIdx, ctxStartMrlIdx);

                  //估计编码帧内模式所需比特数
                  uint64_t fracModeBits = xFracModeBitsIntra(pu, mode, CHANNEL_TYPE_LUMA);

                  //计算每种模式的cost = minSadHad + 当前模式所需比特数 * lambda
                  double cost = (double) minSadHad + (double) fracModeBits * sqrtLambdaForFirstPass;
                  //更新SAD之后的RD候选模式列表以及代价列表
                  updateCandList(ModeInfo(false, false, multiRefIdx, NOT_INTRA_SUBPARTITIONS, mode), cost, uiRdModeList,
                                 CandCostList, numModesForFullRD);
                  //更新SATD之后的uiHadModeList、CandHadList列表
                  updateCandList(ModeInfo(false, false, multiRefIdx, NOT_INTRA_SUBPARTITIONS, mode), double(minSadHad),
                                 uiHadModeList, CandHadList, numHadCand);
                }
              }//for (int x = 1; x < numMPMs; x++)
            }//for (int mRefNum = 1; mRefNum < numOfPassesExtendRef; mRefNum++)
            CHECKD(uiRdModeList.size() != numModesForFullRD, "Error: RD mode list size");

            //save a different set for the next run
            //为下次运行保存不同的集
            //如果LFNST保存标志和MIP测试标志同时有效,且允许LFNST与MIP
            if (LFNSTSaveFlag && testMip
                && !allowLfnstWithMip(cu.firstPU->lumaSize()))
            {
              // save found best modes
              //保存找到的最优模式
              m_uiSavedRdModeListLFNST = uiRdModeList;
              m_dSavedModeCostLFNST    = CandCostList;
              // PBINTRA fast
              m_uiSavedHadModeListLFNST = uiHadModeList;
              m_dSavedHadListLFNST      = CandHadList;
              m_uiSavedNumRdModesLFNST =
                g_aucIntraModeNumFast_UseMPM_2D[uiWidthBit - MIN_CU_LOG2][uiHeightBit - MIN_CU_LOG2];
              m_uiSavedRdModeListLFNST.resize(m_uiSavedNumRdModesLFNST);
              m_dSavedModeCostLFNST.resize(m_uiSavedNumRdModesLFNST);
              // PBINTRA fast
              m_uiSavedHadModeListLFNST.resize(3);
              m_dSavedHadListLFNST.resize(3);
              LFNSTSaveFlag = false;
            }
            //*** Derive MIP candidates using Hadamard
            //在两轮传统角度模式的SATD以及MPM的SATD结束以后,对使用哈达玛的MIP模式单独进行候选模式的推导
            /*****************************************************/
	        /****************** MIP模式候选 **********************/
            /*****************************************************/
            //如果MIP测试标志有效,且当前块大小不支持MIP
            if (testMip && !supportedMipBlkSize)
            {
              //avoid estimation for unsupported blk sizes
              //避免对不支持的块大小进行估计
              //根据当前块sizeId,获取对应MIPmode的数量  0:16 1:8 2:6
              const int transpOff    = getNumModesMip(pu.Y());
              //包括转置的总模式数(即transpOff的两倍)
              const int numModesFull = (transpOff << 1);
              //遍历所有MIPmode(包括转置)
              for (uint32_t uiModeFull = 0; uiModeFull < numModesFull; uiModeFull++)
              {
                //所有MIPmode中(包括转置),前transOff个为转置的模式,后面为非转置模式
                const bool     isTransposed = (uiModeFull >= transpOff ? true : false);
                //当前真实MIPmode号
                const uint32_t uiMode       = (isTransposed ? uiModeFull - transpOff : uiModeFull);

                //最终RDcost环节最优模式的数量 + 1
                numModesForFullRD++;
                //将MIP模式加入最优模式列表
                uiRdModeList.push_back(ModeInfo(true, isTransposed, 0, NOT_INTRA_SUBPARTITIONS, uiMode));
                //加入对应代价为0
                CandCostList.push_back(0);
              }
            }
            //如果MIP测试标志有效,且当前块大小支持MIP
            else if (testMip)
            {
              cu.mipFlag     = true;
              pu.multiRefIdx = 0;

              double mipHadCost[MAX_NUM_MIP_MODE] = { MAX_DOUBLE };

              //获取参考像素长度、获取参考像素、参考像素滤波
              initIntraPatternChType(cu, pu.Y());
              //调用函数初始化MIP(求参考像素下采样的向量)
              initIntraMip(pu, pu.Y());

              const int transpOff    = getNumModesMip(pu.Y());
              const int numModesFull = (transpOff << 1);
              //循环遍历当前sizeId对应的全部MIPmode(包括转置)
              for (uint32_t uiModeFull = 0; uiModeFull < numModesFull; uiModeFull++)
              {
                //判断是否转置
                const bool     isTransposed = (uiModeFull >= transpOff ? true : false);
                //当前MIPmode
                const uint32_t uiMode       = (isTransposed ? uiModeFull - transpOff : uiModeFull);

                pu.mipTransposedFlag           = isTransposed;
                pu.intraDir[CHANNEL_TYPE_LUMA] = uiMode;
                //MIP预测函数入口,进行MIP预测
                predIntraMip(COMPONENT_Y, piPred, pu);

                // Use the min between SAD and HAD as the cost criterion 
                //使用SAD和SATD之间的最小值作为代价标准
                // SAD is scaled by 2 to align with the scaling of HAD 
                //SAD的缩放比例为2,与HAD的缩放比例一致
                Distortion minSadHad =
                  std::min(distParamSad.distFunc(distParamSad) * 2, distParamHad.distFunc(distParamHad));

                m_CABACEstimator->getCtx() = SubCtx(Ctx::MipFlag, ctxStartMipFlag);

                //估计编码帧内模式所需比特数
                uint64_t fracModeBits = xFracModeBitsIntra(pu, uiMode, CHANNEL_TYPE_LUMA);

                //计算每种模式的cost = minSadHad + 当前模式所需比特数 * lambda
                double cost            = double(minSadHad) + double(fracModeBits) * sqrtLambdaForFirstPass;
                mipHadCost[uiModeFull] = cost;
                DTRACE(g_trace_ctx, D_INTRA_COST, "IntraMIP: %u, %llu, %f (%d)\n", minSadHad, fracModeBits, cost,
                       uiModeFull);

                //更新SAD之后的RD候选模式列表以及代价列表
                updateCandList(ModeInfo(true, isTransposed, 0, NOT_INTRA_SUBPARTITIONS, uiMode), cost, uiRdModeList,
                               CandCostList, numModesForFullRD + 1);
                //更新SATD之后的uiHadModeList、CandHadList列表
                updateCandList(ModeInfo(true, isTransposed, 0, NOT_INTRA_SUBPARTITIONS, uiMode),
                               0.8 * double(minSadHad), uiHadModeList, CandHadList, numHadCand);
              }//for (uint32_t uiModeFull = 0; uiModeFull < numModesFull; uiModeFull++)
              

              //在总共四轮的STAD之后,最终更新RDcost列表之后,还要对该列表进行缩减
              const double thresholdHadCost = 1.0 + 1.4 / sqrt((double) (pu.lwidth() * pu.lheight()));
              //缩减HAD候选列表
              reduceHadCandList(uiRdModeList, CandCostList, numModesForFullRD, thresholdHadCost, mipHadCost, pu,
                                fastMip);
            }//else if (testMip)
            
            
            //如果SPS层开启MIP,且要为LFNST保存粗选结果
            if (sps.getUseMIP() && LFNSTSaveFlag)
            {
              // save found best modes
              m_uiSavedNumRdModesLFNST = numModesForFullRD;
              m_uiSavedRdModeListLFNST = uiRdModeList;
              m_dSavedModeCostLFNST    = CandCostList;
              // PBINTRA fast
              m_uiSavedHadModeListLFNST = uiHadModeList;
              m_dSavedHadListLFNST      = CandHadList;
              LFNSTSaveFlag             = false;
            }
          }//if (!(sps.getUseMIP() && LFNSTLoadFlag))
          
          //if( sps.getUseMIP() && LFNSTLoadFlag)
          //如果SPS层开启MIP,且需要为LFNST加载粗选结果
          else   
          {
            // restore saved modes
            numModesForFullRD = m_uiSavedNumRdModesLFNST;
            uiRdModeList      = m_uiSavedRdModeListLFNST;
            CandCostList      = m_dSavedModeCostLFNST;
            // PBINTRA fast
            uiHadModeList = m_uiSavedHadModeListLFNST;
            CandHadList   = m_dSavedHadListLFNST;
          }


          //获取FastUDI的MPM列表,如果满足条件也将其加入RD候选列表
          if (m_pcEncCfg->getFastUDIUseMPMEnabled())
          {
            const int numMPMs = NUM_MOST_PROBABLE_MODES;
            unsigned  uiPreds[numMPMs];//MPM list

            pu.multiRefIdx = 0;
            /******** 获取MPM列表中最可能的模式,只对MPM中最可能的几个模式进行SATD粗选,其余模式不进行 ********/
            //MPM中的候选个数
            const int numCand = PU::getIntraMPMs(pu, uiPreds);

            //循环遍历MPM list
            for (int j = 0; j < numCand; j++)
            {
              bool     mostProbableModeIncluded = false;
              ModeInfo mostProbableMode( false, false, 0, NOT_INTRA_SUBPARTITIONS, uiPreds[j] );

              //遍历RdModeList,判断最可能模式是否存在
              for (int i = 0; i < numModesForFullRD; i++)
              {
                mostProbableModeIncluded |= (mostProbableMode == uiRdModeList[i]);
              }
              //如果最可能模式不存在于RdModeList,添加最可能模式
              if (!mostProbableModeIncluded)
              {
                numModesForFullRD++;
                uiRdModeList.push_back(mostProbableMode);
                CandCostList.push_back(0);
              }
            }
            //为ISP保存数据
            if (saveDataForISP)
            {
              // we add the MPMs to the list that contains only regular intra modes 
              //我们将MPMs添加到仅包含常规帧内模式的列表中
              //循环遍历MPM
              for (int j = 0; j < numCand; j++)
              {
                bool     mostProbableModeIncluded = false;
                ModeInfo mostProbableMode(false, false, 0, NOT_INTRA_SUBPARTITIONS, uiPreds[j]);

                //遍历候选列表检查最可能模式是否包含
                for (int i = 0; i < m_ispCandListHor.size(); i++)
                {
                  mostProbableModeIncluded |= (mostProbableMode == m_ispCandListHor[i]);
                }
                //若当前最可能模式未被候选列表包含,则添加到候选列表中
                if (!mostProbableModeIncluded)
                {
                  m_ispCandListHor.push_back(mostProbableMode);
                }
              }
            }
          }//if (m_pcEncCfg->getFastUDIUseMPMEnabled())  获取FastUDI可能的MPM列表,如果满足条件也将其加入RD候选列表
        }//if (numModesForFullRD != numModesAvailable)//如果最终RDcost环节最优模式的数量不等于传统帧内模式数量(67)(正常都是这个情况,所以这里是主要的RD入口)
        
        //numModesForFullRD == numModesAvailable
        //即如果最终RDcost环节最优模式的数量等于传统帧内模式数量(67)
        else
        {
          THROW("Full search not supported for MIP");
        }
        
        //若SPS层可用LFNST,且当前CU可用MTS
        //为进行MTS变换检查保存RD模式列表
        if (sps.getUseLFNST() && mtsUsageFlag == 1)
        {
          // Store the modes to be checked with RD
          //存储要进行RD cost 检查的模式
          //最终RD cost 最优模式的数量
          m_savedNumRdModes[lfnstIdx] = numModesForFullRD;
          //保存RD模式列表
          std::copy_n(uiRdModeList.begin(), numModesForFullRD, m_savedRdModeList[lfnstIdx]);
        }
      }//if (mtsUsageFlag != 2)
      
      // mtsUsage = 2 (here we potentially reduce the number of modes that will be full-RD checked) 
      //我们可能减少将进行全RD检查的模式数量
      else   
      {
        //LFNST快速算法或者不是I帧(是B帧或P帧),且最佳模式cost有效
        if ((m_pcEncCfg->getUseFastLFNST() || !cu.slice->isIntra()) && m_bestModeCostValid[lfnstIdx])
        {
          numModesForFullRD = 0;

          //跳过检查模式的cost与最佳模式cost倍数的阈值
          double thresholdSkipMode = 1.0 + ((cu.lfnstIdx > 0) ? 0.1 : 1.0) * (1.4 / sqrt((double) (width * height)));

          // Skip checking the modes with much larger R-D cost than the best mode 
          //跳过检查RD cost比最佳模式大得多的模式,
          //遍历RD cost所有模式,将mtsUsage=1时与最优模式相近的候选模式放入当前候选列表
          for (int i = 0; i < m_savedNumRdModes[lfnstIdx]; i++)
          {
            //若当前遍历模式cost未大于阈值倍最佳模式cost,则检查
            if (m_modeCostStore[lfnstIdx][i] <= thresholdSkipMode * m_bestModeCostStore[lfnstIdx])
            { //这里的m_modeCostStore和m_bestModeCostStore是之前mtsUsage=1时保存的候选模式和最优模式
              //将当前模式加入RdModeList
              uiRdModeList.push_back(m_savedRdModeList[lfnstIdx][i]);
              numModesForFullRD++;
            }
          }
        }
        
        // this is necessary because we skip the candidates list calculation, since it was already obtained for the DCT-II. Now we load it 
        //这里的操作是必要的,因为已经从DCT-2中获得了计算数据,因此这里跳过了候选列表的计算,现在直接加载它
        //是I帧,或最佳模式cost无效
        else   
        {
          // Restore the modes to be checked with RD
          numModesForFullRD = m_savedNumRdModes[lfnstIdx];
          uiRdModeList.resize(numModesForFullRD);
          //直接加载候选列表
          std::copy_n(m_savedRdModeList[lfnstIdx], m_savedNumRdModes[lfnstIdx], uiRdModeList.begin());
          CandCostList.resize(numModesForFullRD);
        }
      }// mtsUsage = 2

      CHECK(numModesForFullRD != uiRdModeList.size(), "Inconsistent state!");

      // after this point, don't use numModesForFullRD
      //在这一点后,不再使用numModesForFullRD

      // PBINTRA fast 
      //快速帧内模式
      //如果设置使用快速帧内模式,且不是I帧(B帧或P帧),且RdModeList长度小于67,且SATD对RD可用,且mstUsageFlag != 2 || lfnstIdx > 0(使用LFNST)
      if (m_pcEncCfg->getUsePbIntraFast() && !cs.slice->isIntra() && uiRdModeList.size() < numModesAvailable
          && !cs.slice->getDisableSATDForRD() && (mtsUsageFlag != 2 || lfnstIdx > 0))
      {
        double   pbintraRatio = (lfnstIdx > 0) ? 1.25 : PBINTRA_RATIO;
        int      maxSize      = -1;
        ModeInfo bestMipMode;
        int      bestMipIdx = -1;
        //遍历MPM,找到最优MIP模式
        for (int idx = 0; idx < uiRdModeList.size(); idx++)
        {
          if (uiRdModeList[idx].mipFlg)
          {
            //记录最优MIP模式
            bestMipMode = uiRdModeList[idx];
            //记录最优MIP模式在MPM中的索引
            bestMipIdx  = idx;
            break;
          }
        }
        const int numHadCand = 3;
        //遍历HAD候选列表
        for (int k = numHadCand - 1; k >= 0; k--)
        {
          //候选Had列表尺寸 >= k,或CandHadList[k] > cs.interHad * pbintraRatio
          if (CandHadList.size() < (k + 1) || CandHadList[k] > cs.interHad * pbintraRatio)
          {
            //记录候选Had列表最大尺寸
            maxSize = k;
          }
        }
        //若候选Had列表最大尺寸不为0
        if (maxSize > 0)
        {
          //MPM长度设置为原长度和候选Had列表最大尺寸中的较小值
          uiRdModeList.resize(std::min<size_t>(uiRdModeList.size(), maxSize));
          //如果MPM中有最佳Mip模式
          if (bestMipIdx >= 0)
          {
            if (uiRdModeList.size() <= bestMipIdx)
            {
              //若当前MPM尺寸未包括最佳MIP模式,则将最佳MIP模式加入MPM中
              uiRdModeList.push_back(bestMipMode);
            }
          }
          //如果为ISP保存数据
          if (saveDataForISP)
          {
            //ISP候选列表长度设置为原长度和Had列表最大尺寸中的较小值
            m_ispCandListHor.resize(std::min<size_t>(m_ispCandListHor.size(), maxSize));
          }
        }
        //若候选Had列表最大尺寸为0
        if (maxSize == 0)
        {
          cs.dist     = std::numeric_limits<Distortion>::max();
          cs.interHad = 0;

          //===== reset context models ===== 
          //重置上下文模型
          m_CABACEstimator->getCtx() = SubCtx(Ctx::MipFlag, ctxStartMipFlag);
          m_CABACEstimator->getCtx() = SubCtx(Ctx::ISPMode, ctxStartIspMode);
          m_CABACEstimator->getCtx() = SubCtx(Ctx::IntraLumaPlanarFlag, ctxStartPlanarFlag);
          m_CABACEstimator->getCtx() = SubCtx(Ctx::IntraLumaMpmFlag, ctxStartIntraMode);
          m_CABACEstimator->getCtx() = SubCtx(Ctx::MultiRefLineIdx, ctxStartMrlIdx);

          return false;
        }
      }//if (m_pcEncCfg->getUsePbIntraFast() && !cs.slice->isIntra() && uiRdModeList.size() < numModesAvailable && !cs.slice->getDisableSATDForRD() && (mtsUsageFlag != 2 || lfnstIdx > 0))
    }//不是第二种颜色空间

    //非ISP模式的数量
    int numNonISPModes = (int)uiRdModeList.size();

    //若测试ISP
    if ( testISP )
    {
      // we reserve positions for ISP in the common full RD list
      //为ISP在通用的完整RD列表中保留位置
      //ISP在RD列表中的最大模式数量(使用LFNST时:16*3,不使用LFNST:16)
      const int maxNumRDModesISP = sps.getUseLFNST() ? 16 * NUM_LFNST_NUM_PER_SET : 16;
      m_curIspLfnstIdx = 0;
      //将ISP模式加入候选模式列表
      for (int i = 0; i < maxNumRDModesISP; i++)
      {
        uiRdModeList.push_back( ModeInfo( false, false, 0, INTRA_SUBPARTITIONS_RESERVED, 0 ) );
      }
    }

    //===== check modes (using r-d costs) =====
    //使用RDCost检查预测模式
    //定义当前PU最优的亮度帧内预测模式
    ModeInfo       uiBestPUMode;
    //最佳BDPCM模式
    int            bestBDPCMMode = 0;
    //不使用BDPCM时的最优代价
    double         bestCostNonBDPCM = MAX_DOUBLE;

    //临时缓存CS
    CodingStructure *csTemp = m_pTempCS[gp_sizeIdxInfo->idxFrom( cu.lwidth() )][gp_sizeIdxInfo->idxFrom( cu.lheight() )];
    //最好的模式缓存
    CodingStructure *csBest = m_pBestCS[gp_sizeIdxInfo->idxFrom( cu.lwidth() )][gp_sizeIdxInfo->idxFrom( cu.lheight() )];CS

    csTemp->slice = cs.slice;
    csBest->slice = cs.slice;
    csTemp->initStructData();
    csBest->initStructData();
    csTemp->picture = cs.picture;
    csBest->picture = cs.picture;

    // just to be sure
    //RdModeList中的模式数量
    numModesForFullRD = ( int ) uiRdModeList.size();
    TUIntraSubPartitioner subTuPartitioner( partitioner );
    //如果测试ISP
    if ( testISP )
    {
      //先设置最优ISP cost 为正无穷
      m_modeCtrl->setIspCost( MAX_DOUBLE );
      //先设置无ISP的MTS最优cost为正无穷
      m_modeCtrl->setMtsFirstPassNoIspCost( MAX_DOUBLE );
    }
    int bestLfnstIdx = cu.lfnstIdx;
    /******** 进入最终对RD候选列表中的模式进一步精选RDcost ********/
    //遍历RD候选列表
    for (int mode = isSecondColorSpace ? 0 : -2 * int(testBDPCM); mode < (int)uiRdModeList.size(); mode++)
    {
      // set CU/PU to luma prediction mode
      //设置亮度CU/PU的预测模式
      ModeInfo uiOrgMode;
      //若使用了ColorTrans,且不是RGB形式,且是第二色彩空间,且当前模式在RD候选列表中下标不为0
      if (sps.getUseColorTrans() && !m_pcEncCfg->getRGBFormatFlag() && isSecondColorSpace && mode)
      {
        continue;//跳过当前候选模式
      }

      //若当前模式在RD候选列表中下标小于0,或是第二色彩空间且在第一色彩空间保存了BDPCM模式   即如果是BDPCM模式
      if (mode < 0 || (isSecondColorSpace && m_savedBDPCMModeFirstColorSpace[m_savedRdModeIdx][mode]))
      {
        //记录BDPCM模式,若模式号小于0则记录绝对值,否则记录对应第一色彩空间的BDPCM模式
        cu.bdpcmMode = mode < 0 ? -mode : m_savedBDPCMModeFirstColorSpace[m_savedRdModeIdx][mode];
        uiOrgMode = ModeInfo( false, false, 0, NOT_INTRA_SUBPARTITIONS, cu.bdpcmMode == 2 ? VER_IDX : HOR_IDX );
      }
      
      //不是BDPCM模式
      else
      {
        cu.bdpcmMode = 0;
        uiOrgMode = uiRdModeList[mode];
      }
      //若不适用BDPCM,且当前模式ISP的模式为4  即如果是ISP模式
      if (!cu.bdpcmMode && uiRdModeList[mode].ispMod == INTRA_SUBPARTITIONS_RESERVED)
      {
        // the list needs to be sorted only once
        //列表只需要排序一次
        //若当前模式号为非ISP模式的数量(即候选列表中第一个ISP模式)
        if (mode == numNonISPModes)   
        {
          //若使用快速ISP
          if (m_pcEncCfg->getUseFastISP())
          {
            m_modeCtrl->setBestPredModeDCT2(uiBestPUMode.modeId);
          }
          //排列ISP模式列表
          if (!xSortISPCandList(bestCurrentCost, csBest->cost, uiBestPUMode))
          {
            break;
          }
        }
        //获取下一个ISP模式
        xGetNextISPMode(uiRdModeList[mode], (mode > 0 ? &uiRdModeList[mode - 1] : nullptr), Size(width, height));
        //若当前模式的ISP模式是4
        if (uiRdModeList[mode].ispMod == INTRA_SUBPARTITIONS_RESERVED)
        {
          continue;
        }
        cu.lfnstIdx = m_curIspLfnstIdx;
        uiOrgMode   = uiRdModeList[mode];
      }
      //当前候选模式是否是MIP
      cu.mipFlag                     = uiOrgMode.mipFlg;
      //当前候选模式是否是MIP转置模式
      pu.mipTransposedFlag           = uiOrgMode.mipTrFlg;
      //当前候选模式是否是ISP
      cu.ispMode                     = uiOrgMode.ispMod;
      //当前候选模式是否使用MRL
      pu.multiRefIdx                 = uiOrgMode.mRefId;
      //当前候选模式放入intraDir
      pu.intraDir[CHANNEL_TYPE_LUMA] = uiOrgMode.modeId;

      CHECK(cu.mipFlag && pu.multiRefIdx, "Error: combination of MIP and MRL not supported");
      CHECK(pu.multiRefIdx && (pu.intraDir[0] == PLANAR_IDX), "Error: combination of MRL and Planar mode not supported");
      CHECK(cu.ispMode && cu.mipFlag, "Error: combination of ISP and MIP not supported");
      CHECK(cu.ispMode && pu.multiRefIdx, "Error: combination of ISP and MRL not supported");
      CHECK(cu.ispMode&& cu.colorTransform, "Error: combination of ISP and ACT not supported");

      pu.intraDir[CHANNEL_TYPE_CHROMA] = cu.colorTransform ? DM_CHROMA_IDX : pu.intraDir[CHANNEL_TYPE_CHROMA];

      // set context models
      //设置上下文模型
      m_CABACEstimator->getCtx() = ctxStart;

      // determine residual for partition
      //确定分区的残差
      cs.initSubStructure( *csTemp, partitioner.chType, cs.area, true );

      bool tmpValidReturn = false;
      //如果是ISP模式
      if( cu.ispMode )
      {
        //如果使用快速ISP
        if ( m_pcEncCfg->getUseFastISP() )
        {
          //记录ISP已被测试
          m_modeCtrl->setISPWasTested(true);
        }
        //记录ISP是否有效
        tmpValidReturn = xIntraCodingLumaISP(*csTemp, subTuPartitioner, bestCurrentCost);
        //如果TU未分块
        if (csTemp->tus.size() == 0)
        {
          // no TUs were coded
          //没有TU被编码
          csTemp->cost = MAX_DOUBLE;
          continue;
        }
        // we save the data for future tests
        //保存数据以备将来的测试
        m_ispTestedModes[m_curIspLfnstIdx].setModeResults((ISPType)cu.ispMode, (int)uiOrgMode.modeId, (int)csTemp->tus.size(), csTemp->cus[0]->firstTU->cbf[COMPONENT_Y] ? csTemp->cost : MAX_DOUBLE, csBest->cost);
        csTemp->cost = !tmpValidReturn ? MAX_DOUBLE : csTemp->cost;
      }
      //不是ISP模式
      else
      {
        //如果colorTransform有效,做ACT变换
        if (cu.colorTransform)
        {
          tmpValidReturn = xRecurIntraCodingACTQT(*csTemp, partitioner, mtsCheckRangeFlag, mtsFirstCheckId, mtsLastCheckId, moreProbMTSIdxFirst);
        }
        //colorTransform无效
        else
        {
          //使用选中的预测模式,通过调用xIntraCodingTUBlock计算相应的失真,从而计算RD Cost,从而选出最佳变换。
          tmpValidReturn = xRecurIntraCodingLumaQT(
            *csTemp, partitioner, uiBestPUMode.ispMod ? bestCurrentCost : MAX_DOUBLE, -1, TU_NO_ISP,
            uiBestPUMode.ispMod, mtsCheckRangeFlag, mtsFirstCheckId, mtsLastCheckId, moreProbMTSIdxFirst);
        }
      }

      if (!cu.ispMode && !cu.mtsFlag && !cu.lfnstIdx && !cu.bdpcmMode && !pu.multiRefIdx && !cu.mipFlag && testISP)
      {
        m_regIntraRDListWithCosts.push_back( ModeInfoWithCost( cu.mipFlag, pu.mipTransposedFlag, pu.multiRefIdx, cu.ispMode, uiOrgMode.modeId, csTemp->cost ) );
      }

      //如果当前CU是ISP模式,且第一块TU的CBF为0(无残差)
      if( cu.ispMode && !csTemp->cus[0]->firstTU->cbf[COMPONENT_Y] )
      {
        csTemp->cost = MAX_DOUBLE;
        csTemp->costDbOffset = 0;
        tmpValidReturn = false;
      }
      validReturn |= tmpValidReturn;

      //如果SPS层开启LFNST,且可以使用MTS并正在检查DCT-2,且不用ISP。且当前模式号>=0 
      if( sps.getUseLFNST() && mtsUsageFlag == 1 && !cu.ispMode && mode >= 0 )
      {
        //将当前RD cost保存以在检查MTS时使用快速算法
        m_modeCostStore[lfnstIdx][mode] = tmpValidReturn ? csTemp->cost : (MAX_DOUBLE / 2.0); //(MAX_DOUBLE / 2.0) ??
      }

      DTRACE(g_trace_ctx, D_INTRA_COST, "IntraCost T [x=%d,y=%d,w=%d,h=%d] %f (%d,%d,%d,%d,%d,%d) \n", cu.blocks[0].x,
             cu.blocks[0].y, (int) width, (int) height, csTemp->cost, uiOrgMode.modeId, uiOrgMode.ispMod,
             pu.multiRefIdx, cu.mipFlag, cu.lfnstIdx, cu.mtsFlag);

      //如果是有效返回值,比较最优模式代价和当前模式代价
      if( tmpValidReturn )
      {
        //如果是第一色彩空间
        if (isFirstColorSpace)
        {
          //如果是RGB格式,或者不是ISP模式
          if (m_pcEncCfg->getRGBFormatFlag() || !cu.ispMode)
          {
            //对RdModeList排序
            sortRdModeListFirstColorSpace(uiOrgMode, csTemp->cost, cu.bdpcmMode, m_savedRdModeFirstColorSpace[m_savedRdModeIdx], m_savedRdCostFirstColorSpace[m_savedRdModeIdx], m_savedBDPCMModeFirstColorSpace[m_savedRdModeIdx], m_numSavedRdModeFirstColorSpace[m_savedRdModeIdx]);
          }
        }
        // check r-d cost
        //检查RD cost
        //如果当前模式的cost小于目前最优模式cost
        if( csTemp->cost < csBest->cost )
        {
          //当前模式成为新的最优模式
          std::swap( csTemp, csBest );

          uiBestPUMode  = uiOrgMode;
          bestBDPCMMode = cu.bdpcmMode;
          //如果使用LFNST
          if( sps.getUseLFNST() && mtsUsageFlag == 1 && !cu.ispMode )
          {
            m_bestModeCostStore[ lfnstIdx ] = csBest->cost; //cs.cost;
            m_bestModeCostValid[ lfnstIdx ] = true;
          }
          if( csBest->cost < bestCurrentCost )
          {
            bestCurrentCost = csBest->cost;
          }
          //如果是ISP模式
          if ( cu.ispMode )
          {
            m_modeCtrl->setIspCost(csBest->cost);
            bestLfnstIdx = cu.lfnstIdx;
          }
          //如果使用ISP,且不启用ACT变换
          else if ( testISP )
          {
            m_modeCtrl->setMtsFirstPassNoIspCost(csBest->cost);
          }
        }//if( csTemp->cost < csBest->cost )
        //如果不是ISP模式,且不是BDPCM模式,且当前cost小于不用BDPCM的最佳cost
        if( !cu.ispMode && !cu.bdpcmMode && csBest->cost < bestCostNonBDPCM )
        {
          //更新不用BDPCM的最佳cost
          bestCostNonBDPCM = csBest->cost;
        }
      }//if( tmpValidReturn )

      //清空CU,PU,TU
      csTemp->releaseIntermediateData();
      if( m_pcEncCfg->getFastLocalDualTreeMode() )
      {
        if( cu.isConsIntra() && !cu.slice->isIntra() && csBest->cost != MAX_DOUBLE && costInterCU != COST_UNKNOWN && mode >= 0 )
        {
          if( m_pcEncCfg->getFastLocalDualTreeMode() == 2 )
          {
            //Note: only try one intra mode, which is especially useful to reduce EncT for LDB case (around 4%)
            //仅尝试一种帧内模式,这对于减少LDB情况下的EncT尤其有用(大约4%)
            break;
          }
          else
          {
            if( csBest->cost > costInterCU * 1.5 )
            {
              break;
            }
          }
        }
      }
      //启用ACT变换
      if (sps.getUseColorTrans() && !CS::isDualITree(cs))
      {
        if ((m_pcEncCfg->getRGBFormatFlag() && !cu.colorTransform) && csBest->cost != MAX_DOUBLE && bestCS->cost != MAX_DOUBLE && mode >= 0)
        {
          if (csBest->cost > bestCS->cost)
          {
            break;
          }
        }
      }
    } // Mode loop RD候选模式的循环遍历结束,选出最优模式
    //设置最优模式
    cu.ispMode = uiBestPUMode.ispMod;
    cu.lfnstIdx = bestLfnstIdx;

    if( validReturn )
    {
      //如果色彩转换
      if (cu.colorTransform)
      {
        cs.useSubStructure(*csBest, partitioner.chType, pu, true, true, KEEP_PRED_AND_RESI_SIGNALS, KEEP_PRED_AND_RESI_SIGNALS, true);
      }
      else
      {
        cs.useSubStructure(*csBest, partitioner.chType, pu.singleChan(CHANNEL_TYPE_LUMA), true, true, KEEP_PRED_AND_RESI_SIGNALS,
                           KEEP_PRED_AND_RESI_SIGNALS, true);
      }
    }
    csBest->releaseIntermediateData();
    if( validReturn )
    {
      //=== update PU data ==== 
      //更新PU数据
      cu.mipFlag = uiBestPUMode.mipFlg;
      pu.mipTransposedFlag             = uiBestPUMode.mipTrFlg;
      pu.multiRefIdx = uiBestPUMode.mRefId;
      pu.intraDir[ CHANNEL_TYPE_LUMA ] = uiBestPUMode.modeId;
      cu.bdpcmMode = bestBDPCMMode;
      if (cu.colorTransform)
      {
        CHECK(pu.intraDir[CHANNEL_TYPE_CHROMA] != DM_CHROMA_IDX, "chroma should use DM mode for adaptive color transform");
      }
    }
  }

  //===== reset context models =====
  //重置上下文模型
  m_CABACEstimator->getCtx() = ctxStart;

  return validReturn;
}

二、逻辑结构

1.[2]-[183]:初始化相关参数(包括确认lambda、加载上下文、确定LFNST标志、确定MTS标志等)

2.[185]-[201]:若当前为ACT变换后的颜色空间,则直接从原颜色空间的模式列表中加载测试的模式列表

3.[202]-[851]:若当前不是ACT变换后的颜色空间,则开始选择测试模式

(1) [206]-[732]:mtsUsageFlag != 2

若帧内全搜索,则提示MIP不支持帧内全搜索。若非帧内全搜索,则:

  1. [208]-[268]:检查确定相关参数,定位当前区域,并设置SAD、HAD参数,若测试MIP,则在最终RD cost列表中预留对应数量位置。
  2. [284]-[362]:先对非扩展角度模式进行第一轮粗选([288]-[347]),若不使用MIP且LFNST保存标志有效,则将粗选结果保存([350]-[361])。
  3. [365]-[374]:若SPS层不使用MIP且LFNST加载标志有效,则直接加载之前保存的粗选结果。
  4. [377]-[642]:若SPS层不使用MIP,或者LFNST加载标志无效,则先对第一轮选出的候选角度模式的扩展角度模式进行第二轮粗选[386]-[441])。再判断为ISP保存数据的标志位,若有效,则将此时候选列表保存([444]-[449])。再对候选列表中的模式遍历多参考行,更新候选模式列表[461]-[512])。若LFNST保存标志和MIP测试标志同时有效,且不允许LFNST与MIP同时存在,则保存候选列表([518]-[536])。下面开始添加MIP模式:若MIP测试标志有效,但当前块大小不支持MIP,则直接将所有MIP模式加入候选模式列表([543]-[566])。若需要测试MIP模式(块大小也支持MIP),则遍历所有可能MIP模式,更新候选模式列表([568]-[627])。若SPS允许使用MIP,且LFNST保存标志有效,则保存候选列表([631]-[641])。
  5. [646]-[655]:若SPS层使用MIP,且需要为LFNST加载候选,则直接加载候选模式列表。
  6. [659]-[711] :获取FASTUDI的MPM列表,MPM中的模式若不存在于候选模式列表,则添加。同样为ISP添加候选模式。
  7. [723]-[731]:若SPS层可用LFNST,且当前CU可用MTS,为进行MTS变换检查保存RD模式列表。
(2)[736]-[773]:mtsUsageFlag == 2 时
  1. [739]-[759]:LFNST快速算法或者不是I帧(是B帧或P帧),且最佳模式cost有效时,检查RD候选模式中,与根据LFNSTIndex索引的最优模式代价比小于阈值的模式,将其加入RD候选模式中。
  2. [764]-[772]:是I帧,或最佳模式cost无效时,直接复制之前保存的RD候选模式列表。
(3) [783]-[850]:快速帧内模式(PBINTRA fast)
  1. [791]-[801]:遍历RDmodelist找到最优MIP模式。
  2. [804]-[850]:记录HAD候选列表最大长度。最大长度不为0时,将RDmodelist长度设置为原长度和HAD候选列表最大长度中的较小值,再判断最佳MIP模式是否包含在缩小后的RDmodelist中,若不,则添加进RDmodelist。若为ISP保存数据,则将ISP候选列表也设置为原长度和HAD候选列表最大长度中的较小值。重置上下文模型。

4.[857]-[869]:为ISP在RD列表中保留位置

5.[907]-[1141]:遍历RD列表精选,选出最佳模式

6.[1143]-[1180]:结尾工作(设置最优模式、更新PU参数、重置上下文模型等)

上一篇:H.266/VVC-VTM代码学习-帧内预测10-色度预测的CCLM模式(2)xGetLMParameters函数求解CCLM参数α和β
下一篇:H.266/VVC-VTM代码学习-帧内预测12-编码端获取MPM列表getIntraMPMs函数

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值