一、函數簡述
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" );
}