一、函数简述
xCompressCU这个函数通过递归的调用自己来完成对整个CTU的划分和每个CU的模式选择。与HEVC中该函数不同的是,VVC中加入了很多新的模式,比如帧间Affine模式,帧内IBC模式等。
亮度块最大允许尺寸为128×128,并且划分方式采用多类型树结构,CU可以进行四叉划分,水平二叉划分,垂直二叉划分,水平三叉划分,垂直三叉划分。最优划分模式的确定在xCheckModeSplit函数中,该函数会继续调用xCompressCU对划分的小CU块进行模式的选择和进一步划分。所以模式的选择和CU的划分会一直在这两个函数之间跳转。看的时候很容易懵逼= =!
二、函数流程
void EncCu::xCompressCU( CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner )
{
if (m_shareState == NO_SHARE)
{
tempCS->sharedBndPos = tempCS->area.Y().lumaPos();//CU块的位置信息
tempCS->sharedBndSize.width = tempCS->area.lwidth();//CU的宽
tempCS->sharedBndSize.height = tempCS->area.lheight();//CU的高
bestCS->sharedBndPos = bestCS->area.Y().lumaPos();
bestCS->sharedBndSize.width = bestCS->area.lwidth();
bestCS->sharedBndSize.height = bestCS->area.lheight();
}
#if ENABLE_SPLIT_PARALLELISM//并行划分
CHECK( m_dataId != tempCS->picture->scheduler.getDataId(), "Working in the wrong dataId!" );
if( m_pcEncCfg->getNumSplitThreads() != 1 && tempCS->picture->scheduler.getSplitJobId() == 0 )
{
if( m_modeCtrl->isParallelSplit( *tempCS, partitioner ) )
{
m_modeCtrl->setParallelSplit( true );
xCompressCUParallel( tempCS, bestCS, partitioner );
return;
}
}
#endif
Slice& slice = *tempCS->slice;//slice
const PPS &pps = *tempCS->pps;//图像参数集
const SPS &sps = *tempCS->sps;//序列参数集
const uint32_t uiLPelX = tempCS->area.Y().lumaPos().x;//获取当前块的左上x坐标
const uint32_t uiTPelY = tempCS->area.Y().lumaPos().y;//获取当前块的左上y坐标
const UnitArea currCsArea = clipArea( CS::getArea( *bestCS, bestCS->area, partitioner.chType ), *tempCS->picture );//当前区域
m_modeCtrl->initCULevel( partitioner, *tempCS );//CU的初始化,添加各种模式
if( partitioner.currQtDepth == 0 && partitioner.currMtDepth == 0 && !tempCS->slice->isIntra() && ( sps.getUseSBT() || sps.getUseInterMTS() ) )
{
auto slsSbt = dynamic_cast<SaveLoadEncInfoSbt*>( m_modeCtrl );
int maxSLSize = sps.getUseSBT() ? tempCS->slice->getSPS()->getMaxSbtSize() : MTS_INTER_MAX_CU_SIZE;
slsSbt->resetSaveloadSbt( maxSLSize );
#if ENABLE_SPLIT_PARALLELISM
CHECK( tempCS->picture->scheduler.getSplitJobId() != 0, "The SBT search reset need to happen in sequential region." );
if (m_pcEncCfg->getNumSplitThreads() > 1)
{
for (int jId = 1; jId < NUM_RESERVERD_SPLIT_JOBS; jId++)
{
auto slsSbt = dynamic_cast<SaveLoadEncInfoSbt *>(m_pcEncLib->getCuEncoder(jId)->m_modeCtrl);
slsSbt->resetSaveloadSbt(maxSLSize);
}
}
#endif
}
m_sbtCostSave[0] = m_sbtCostSave[1] = MAX_DOUBLE;
m_CurrCtx->start = m_CABACEstimator->getCtx();
m_cuChromaQpOffsetIdxPlus1 = 0;
//色度QP调整
if( slice.getUseChromaQpAdj() )
{
// TODO M0133 : double check encoder decisions with respect to chroma QG detection and actual encode
int lgMinCuSize = sps.getLog2MinCodingBlockSize() +
std::max<int>( 0, sps.getLog2DiffMaxMinCodingBlockSize() - int( pps.getPpsRangeExtension().getCuChromaQpOffsetSubdiv()/2 ) );
m_cuChromaQpOffsetIdxPlus1 = ( ( uiLPelX >> lgMinCuSize ) + ( uiTPelY >> lgMinCuSize ) ) % ( pps.getPpsRangeExtension().getChromaQpOffsetListLen() + 1 );
}
if( !m_modeCtrl->anyMode() )//测试模式为空集则停止对该CU的划分和模式的选择
{
m_modeCtrl->finishCULevel( partitioner );
return;
}
DTRACE_UPDATE( g_trace_ctx, std::make_pair( "cux", uiLPelX ) );
DTRACE_UPDATE( g_trace_ctx, std::make_pair( "cuy", uiTPelY ) );
DTRACE_UPDATE( g_trace_ctx, std::make_pair( "cuw", tempCS->area.lwidth() ) );
DTRACE_UPDATE( g_trace_ctx, std::make_pair( "cuh", tempCS->area.lheight() ) );
DTRACE( g_trace_ctx, D_COMMON, "@(%4d,%4d) [%2dx%2d]\n", tempCS->area.lx(), tempCS->area.ly(), tempCS->area.lwidth(), tempCS->area.lheight() );
int startShareThisLevel = 0;
m_pcInterSearch->resetSavedAffineMotion();
//开始进行各种模式的检测,包括对CU的划分
do
{
EncTestMode currTestMode = m_modeCtrl->currTestMode();//当前测试模式
if (pps.getUseDQP() && CS::isDualITree(*tempCS) && isChroma(partitioner.chType))//如果使用DQP,亮度色度分离,是色度通道
{
const Position chromaCentral(tempCS->area.Cb().chromaPos().offset(tempCS->area.Cb().chromaSize().width >> 1, tempCS->area.Cb().chromaSize().height >> 1));//色度中心
const Position lumaRefPos(chromaCentral.x << getComponentScaleX(COMPONENT_Cb, tempCS->area.chromaFormat), chromaCentral.y << getComponentScaleY(COMPONENT_Cb, tempCS->area.chromaFormat));
const CodingStructure* baseCS = bestCS->picture->cs;
const CodingUnit* colLumaCu = baseCS->getCU(lumaRefPos, CHANNEL_TYPE_LUMA);
if (colLumaCu)
{
currTestMode.qp = colLumaCu->qp;
}
}
#if SHARP_LUMA_DELTA_QP || ENABLE_QPA_SUB_CTU
if (partitioner.currQgEnable() && (
#if SHARP_LUMA_DELTA_QP
(m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled()) ||
#endif
#if ENABLE_QPA_SUB_CTU
(m_pcEncCfg->getUsePerceptQPA() && !m_pcEncCfg->getUseRateCtrl() && pps.getUseDQP())
#else
false
#endif
))
{
#if ENABLE_SPLIT_PARALLELISM
CHECK( tempCS->picture->scheduler.getSplitJobId() > 0, "Changing lambda is only allowed in the master thread!" );
#endif
if (currTestMode.qp >= 0)
{
updateLambda (&slice, currTestMode.qp, CS::isDualITree (*tempCS) || (partitioner.currDepth == 0));//更新lambda
}
}
#endif
if( currTestMode.type == ETM_INTER_ME )//帧间运动估计
{
if( ( currTestMode.opts & ETO_IMV ) != 0 )
{
tempCS->bestCS = bestCS;
xCheckRDCostInterIMV( tempCS, bestCS, partitioner, currTestMode );
tempCS->bestCS = nullptr;
}
else
{
tempCS->bestCS = bestCS;
xCheckRDCostInter( tempCS, bestCS, partitioner, currTestMode );
tempCS->bestCS = nullptr;
}
}
else if (currTestMode.type == ETM_HASH_INTER)//帧间哈希
{
xCheckRDCostHashInter( tempCS, bestCS, partitioner, currTestMode );
}
else if( currTestMode.type == ETM_AFFINE )//仿射模式
{
xCheckRDCostAffineMerge2Nx2N( tempCS, bestCS, partitioner, currTestMode );
}
#if REUSE_CU_RESULTS
else if( currTestMode.type == ETM_RECO_CACHED )
{
xReuseCachedResult( tempCS, bestCS, partitioner );
}
#endif
else if( currTestMode.type == ETM_MERGE_SKIP )//Merge模式
{
xCheckRDCostMerge2Nx2N( tempCS, bestCS, partitioner, currTestMode );
CodingUnit* cu = bestCS->getCU(partitioner.chType);
if (cu)
cu->mmvdSkip = cu->skip == false ? false : cu->mmvdSkip;
}
else if( currTestMode.type == ETM_MERGE_TRIANGLE )
{
xCheckRDCostMergeTriangle2Nx2N( tempCS, bestCS, partitioner, currTestMode );
}
else if( currTestMode.type == ETM_INTRA )
{
xCheckRDCostIntra( tempCS, bestCS, partitioner, currTestMode );//帧内入口函数
}
else if( currTestMode.type == ETM_IPCM )
{
xCheckIntraPCM( tempCS, bestCS, partitioner, currTestMode );
}
else if (currTestMode.type == ETM_IBC)//IBC模式
{
xCheckRDCostIBCMode(tempCS, bestCS, partitioner, currTestMode);
}
else if (currTestMode.type == ETM_IBC_MERGE)//IBC合并模式
{
xCheckRDCostIBCModeMerge2Nx2N(tempCS, bestCS, partitioner, currTestMode);
}
else if( isModeSplit( currTestMode ) )
{
xCheckModeSplit( tempCS, bestCS, partitioner, currTestMode );//此函数完成CU的划分,并且内部会递归的调用xCompressCU
}
else
{
THROW( "Don't know how to handle mode: type = " << currTestMode.type << ", options = " << currTestMode.opts );
}
} while( m_modeCtrl->nextMode( *tempCS, partitioner ) );//检测下一个模式
if(startShareThisLevel == 1)
{
m_shareState = NO_SHARE;
m_pcInterSearch->setShareState(m_shareState);
setShareStateDec(m_shareState);
}
//
// Finishing CU
#if ENABLE_SPLIT_PARALLELISM
if( bestCS->cus.empty() )
{
CHECK( bestCS->cost != MAX_DOUBLE, "Cost should be maximal if no encoding found" );
CHECK( bestCS->picture->scheduler.getSplitJobId() == 0, "Should always get a result in serial case" );
m_modeCtrl->finishCULevel( partitioner );
return;
}
#endif
// set context states 设置上下文状态
m_CABACEstimator->getCtx() = m_CurrCtx->best;
// QP from last processed CU for further processing
bestCS->prevQP[partitioner.chType] = bestCS->cus.back()->qp;
if ((!slice.isIntra() || slice.getSPS()->getIBCFlag())
&& partitioner.chType == CHANNEL_TYPE_LUMA
&& bestCS->cus.size() == 1 && (bestCS->cus.back()->predMode == MODE_INTER || bestCS->cus.back()->predMode == MODE_IBC)
&& bestCS->area.Y() == (*bestCS->cus.back()).Y()
)
{
const CodingUnit& cu = *bestCS->cus.front();
const PredictionUnit& pu = *cu.firstPU;
if (!cu.affine && !cu.triangle)
{
MotionInfo mi = pu.getMotionInfo();
mi.GBiIdx = (mi.interDir == 3) ? cu.GBiIdx : GBI_DEFAULT;
cu.cs->addMiToLut(CU::isIBC(cu) ? cu.cs->motionLut.lutIbc : cu.cs->motionLut.lut, mi);
}
}
bestCS->picture->getPredBuf(currCsArea).copyFrom(bestCS->getPredBuf(currCsArea));
bestCS->picture->getRecoBuf( currCsArea ).copyFrom( bestCS->getRecoBuf( currCsArea ) );
m_modeCtrl->finishCULevel( partitioner );
#if ENABLE_SPLIT_PARALLELISM
if( tempCS->picture->scheduler.getSplitJobId() == 0 && m_pcEncCfg->getNumSplitThreads() != 1 )
{
tempCS->picture->finishParallelPart( currCsArea );
}
#endif
// Assert if Best prediction mode is NONE
// Selected mode's RD-cost must be not MAX_DOUBLE.
CHECK( bestCS->cus.empty() , "No possible encoding found" );
CHECK( bestCS->cus[0]->predMode == NUMBER_OF_PREDICTION_MODES, "No possible encoding found" );
CHECK( bestCS->cost == MAX_DOUBLE , "No possible encoding found" );
}