近期學習了一下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 );
}
}