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