xCheckModeSplit函数主要是根据划分类型对当前CU进行划分,然后调用xCompressCU函数对划分后的子CU进一步划分和模式选择。
首先,根据测试模式(划分模式)得到划分类型(getPartSplit函数)
inline PartSplit getPartSplit( const EncTestMode& encTestmode )
{
switch( encTestmode.type )
{
case ETM_SPLIT_QT : return CU_QUAD_SPLIT;
case ETM_SPLIT_BT_H : return CU_HORZ_SPLIT;
case ETM_SPLIT_BT_V : return CU_VERT_SPLIT;
case ETM_SPLIT_TT_H : return CU_TRIH_SPLIT;
case ETM_SPLIT_TT_V : return CU_TRIV_SPLIT;
default: return CU_DONT_SPLIT;
}
}
根据CU的划分类型,调用partitioner.splitCurrArea对当前CU区域进行划分,之后,遍历全部子区域,对每个子区域调用xCompressCU函数。
特殊情况:(防止出现帧内色度小块)
根据父节点类型和子节点类型判断是否使亮度与色度单独划分
const bool chromaNotSplit = modeTypeParent == MODE_TYPE_ALL && modeTypeChild == MODE_TYPE_INTRA ? true : false;
如果该标志为true,则首先对亮度分量单独调用xCompressCU函数进行子区域划分和模式选择,之后再对色度分量单独调用xCompressCU函数模式选择(不进行划分)。
最后,调用xCheckBestMode函数将tempCS中的内容复制到bestCS中。
这里,传入到xCheckModeSplit函数的tempCS和bestCS是待划分CU对应的CS结构(这里cs包含的区域为CU对应的区域),在xCheckModeSplit函数中调用的xCompressCU函数传递的tempCS和bestCS参数是根据划分后子CU的尺寸从m_pTempCS和m_pBestCS得到的Coding Structure结构(这里的cs中包含的区域为子CU对应区域),要注意区分tempCS和bestCS。
xCheckModeSplit代码及注释如下(基于VTM10.0):
void EncCu::xCheckModeSplit(CodingStructure *&tempCS, CodingStructure *&bestCS, Partitioner &partitioner, const EncTestMode& encTestMode, const ModeType modeTypeParent, bool &skipInterPass )
{
const int qp = encTestMode.qp;
const Slice &slice = *tempCS->slice;
const int oldPrevQp = tempCS->prevQP[partitioner.chType];
const auto oldMotionLut = tempCS->motionLut;
#if ENABLE_QPA_SUB_CTU
const PPS &pps = *tempCS->pps;
const uint32_t currDepth = partitioner.currDepth;
#endif
const auto oldPLT = tempCS->prevPLT;
const PartSplit split = getPartSplit( encTestMode ); //划分类型
const ModeType modeTypeChild = partitioner.modeType;
CHECK( split == CU_DONT_SPLIT, "No proper split provided!" );
tempCS->initStructData( qp );
m_CABACEstimator->getCtx() = m_CurrCtx->start;
const TempCtx ctxStartSP( m_CtxCache, SubCtx( Ctx::SplitFlag, m_CABACEstimator->getCtx() ) );
const TempCtx ctxStartQt( m_CtxCache, SubCtx( Ctx::SplitQtFlag, m_CABACEstimator->getCtx() ) );
const TempCtx ctxStartHv( m_CtxCache, SubCtx( Ctx::SplitHvFlag, m_CABACEstimator->getCtx() ) );
const TempCtx ctxStart12( m_CtxCache, SubCtx( Ctx::Split12Flag, m_CABACEstimator->getCtx() ) );
const TempCtx ctxStartMC( m_CtxCache, SubCtx( Ctx::ModeConsFlag, m_CABACEstimator->getCtx() ) );
m_CABACEstimator->resetBits();
m_CABACEstimator->split_cu_mode( split, *tempCS, partitioner );
m_CABACEstimator->mode_constraint( split, *tempCS, partitioner, modeTypeChild );
const double factor = ( tempCS->currQP[partitioner.chType] > 30 ? 1.1 : 1.075 );
tempCS->useDbCost = m_pcEncCfg->getUseEncDbOpt();
if (!tempCS->useDbCost)
CHECK(bestCS->costDbOffset != 0, "error");
const double cost = m_pcRdCost->calcRdCost( uint64_t( m_CABACEstimator->getEstFracBits() + ( ( bestCS->fracBits ) / factor ) ), Distortion( bestCS->dist / factor ) ) + bestCS->costDbOffset / factor;
m_CABACEstimator->getCtx() = SubCtx( Ctx::SplitFlag, ctxStartSP );
m_CABACEstimator->getCtx() = SubCtx( Ctx::SplitQtFlag, ctxStartQt );
m_CABACEstimator->getCtx() = SubCtx( Ctx::SplitHvFlag, ctxStartHv );
m_CABACEstimator->getCtx() = SubCtx( Ctx::Split12Flag, ctxStart12 );
m_CABACEstimator->getCtx() = SubCtx( Ctx::ModeConsFlag, ctxStartMC );
if (cost > bestCS->cost + bestCS->costDbOffset
#if ENABLE_QPA_SUB_CTU
|| (m_pcEncCfg->getUsePerceptQPA() && !m_pcEncCfg->getUseRateCtrl() && pps.getUseDQP() && (slice.getCuQpDeltaSubdiv() > 0) && (split == CU_HORZ_SPLIT || split == CU_VERT_SPLIT) &&
(currDepth == 0)) // force quad-split or no split at CTU level
#endif
)
{
xCheckBestMode( tempCS, bestCS, partitioner, encTestMode );
return;
}
// 该flag为TRUE时,第一次执行compressCU时,仅对亮度进行划分,第二次执行compressCU时,对色度划分
const bool chromaNotSplit = modeTypeParent == MODE_TYPE_ALL && modeTypeChild == MODE_TYPE_INTRA ? true : false;
if( partitioner.treeType != TREE_D )
{
tempCS->treeType = TREE_L;
}
else
{
if( chromaNotSplit ) // 色度不进行划分
{
CHECK( partitioner.chType != CHANNEL_TYPE_LUMA, "chType must be luma" );
tempCS->treeType = partitioner.treeType = TREE_L;
}
else
{
tempCS->treeType = partitioner.treeType = TREE_D;
}
}
partitioner.splitCurrArea( split, *tempCS ); //根据CU划分模式划分当前区域
bool qgEnableChildren = partitioner.currQgEnable(); // QG possible at children level
m_CurrCtx++;
tempCS->getRecoBuf().fill( 0 );
tempCS->getPredBuf().fill(0);
AffineMVInfo tmpMVInfo;
bool isAffMVInfoSaved;
m_pcInterSearch->savePrevAffMVInfo(0, tmpMVInfo, isAffMVInfoSaved);
BlkUniMvInfo tmpUniMvInfo;
bool isUniMvInfoSaved = false;
if (!tempCS->slice->isIntra())
{
m_pcInterSearch->savePrevUniMvInfo(tempCS->area.Y(), tmpUniMvInfo, isUniMvInfoSaved);
}
do
{
const auto &subCUArea = partitioner.currArea(); //得到划分后的当前子区域
if( tempCS->picture->Y().contains( subCUArea.lumaPos() ) )
{
const unsigned wIdx = gp_sizeIdxInfo->idxFrom( subCUArea.lwidth () );
const unsigned hIdx = gp_sizeIdxInfo->idxFrom( subCUArea.lheight() );
CodingStructure *tempSubCS = m_pTempCS[wIdx][hIdx];
CodingStructure *bestSubCS = m_pBestCS[wIdx][hIdx];
tempCS->initSubStructure( *tempSubCS, partitioner.chType, subCUArea, false );
tempCS->initSubStructure( *bestSubCS, partitioner.chType, subCUArea, false );
tempSubCS->bestParent = bestSubCS->bestParent = bestCS;
double newMaxCostAllowed = isLuma(partitioner.chType) ? std::min(encTestMode.maxCostAllowed, bestCS->cost - m_pcRdCost->calcRdCost(tempCS->fracBits, tempCS->dist)) : MAX_DOUBLE;
newMaxCostAllowed = std::max(0.0, newMaxCostAllowed);
xCompressCU(tempSubCS, bestSubCS, partitioner, newMaxCostAllowed);
tempSubCS->bestParent = bestSubCS->bestParent = nullptr;
if( bestSubCS->cost == MAX_DOUBLE )
{
CHECK( split == CU_QUAD_SPLIT, "Split decision reusing cannot skip quad split" );
tempCS->cost = MAX_DOUBLE;
tempCS->costDbOffset = 0;
tempCS->useDbCost = m_pcEncCfg->getUseEncDbOpt();
m_CurrCtx--;
partitioner.exitCurrSplit();
xCheckBestMode( tempCS, bestCS, partitioner, encTestMode );
if( partitioner.chType == CHANNEL_TYPE_LUMA )
{
tempCS->motionLut = oldMotionLut;
}
return;
}
bool keepResi = KEEP_PRED_AND_RESI_SIGNALS;
tempCS->useSubStructure( *bestSubCS, partitioner.chType, CS::getArea( *tempCS, subCUArea, partitioner.chType ), KEEP_PRED_AND_RESI_SIGNALS, true, keepResi, keepResi, true );
if( partitioner.currQgEnable() )
{
tempCS->prevQP[partitioner.chType] = bestSubCS->prevQP[partitioner.chType];
}
if( partitioner.isConsInter() )
{
for( int i = 0; i < bestSubCS->cus.size(); i++ )
{
CHECK( bestSubCS->cus[i]->predMode != MODE_INTER, "all CUs must be inter mode in an Inter coding region (SCIPU)" );
}
}
else if( partitioner.isConsIntra() )
{
for( int i = 0; i < bestSubCS->cus.size(); i++ )
{
CHECK( bestSubCS->cus[i]->predMode == MODE_INTER, "all CUs must not be inter mode in an Intra coding region (SCIPU)" );
}
}
tempSubCS->releaseIntermediateData();
bestSubCS->releaseIntermediateData();
if( !tempCS->slice->isIntra() && partitioner.isConsIntra() )
{
tempCS->cost = m_pcRdCost->calcRdCost( tempCS->fracBits, tempCS->dist );
if( tempCS->cost > bestCS->cost )
{
tempCS->cost = MAX_DOUBLE;
tempCS->costDbOffset = 0;
tempCS->useDbCost = m_pcEncCfg->getUseEncDbOpt();
m_CurrCtx--;
partitioner.exitCurrSplit();
if( partitioner.chType == CHANNEL_TYPE_LUMA )
{
tempCS->motionLut = oldMotionLut;
}
return;
}
}
}
} while( partitioner.nextPart( *tempCS ) );
partitioner.exitCurrSplit();
m_CurrCtx--;
if( chromaNotSplit )
{
//Note: In local dual tree region, the chroma CU refers to the central luma CU's QP.注:在局部双树区域中,色度CU是指中央luma CU的QP。
//If the luma CU QP shall be predQP (no residual in it and before it in the QG), it must be revised to predQP before encoding the chroma CU
//Otherwise, the chroma CU uses predQP+deltaQP in encoding but is decoded as using predQP, thus causing encoder-decoded mismatch on chroma qp.
if( tempCS->pps->getUseDQP() )
{
//find parent CS that including all coded CUs in the QG before this node
CodingStructure* qgCS = tempCS;
bool deltaQpCodedBeforeThisNode = false;
if( partitioner.currArea().lumaPos() != partitioner.currQgPos )
{
int numParentNodeToQgCS = 0;
while( qgCS->area.lumaPos() != partitioner.currQgPos )
{
CHECK( qgCS->parent == nullptr, "parent of qgCS shall exsit" );
qgCS = qgCS->parent;
numParentNodeToQgCS++;
}
//check whether deltaQP has been coded (in luma CU or luma&chroma CU) before this node
CodingStructure* parentCS = tempCS->parent;
for( int i = 0; i < numParentNodeToQgCS; i++ )
{
//checking each parent
CHECK( parentCS == nullptr, "parentCS shall exsit" );
for( const auto &cu : parentCS->cus )
{
if( cu->rootCbf && !isChroma( cu->chType ) )
{
deltaQpCodedBeforeThisNode = true;
break;
}
}
parentCS = parentCS->parent;
}
}
//revise luma CU qp before the first luma CU with residual in the SCIPU to predQP
if( !deltaQpCodedBeforeThisNode )
{
//get pred QP of the QG
const CodingUnit* cuFirst = qgCS->getCU( CHANNEL_TYPE_LUMA );
CHECK( cuFirst->lumaPos() != partitioner.currQgPos, "First cu of the Qg is wrong" );
int predQp = CU::predictQP( *cuFirst, qgCS->prevQP[CHANNEL_TYPE_LUMA] );
//revise to predQP
int firstCuHasResidual = (int)tempCS->cus.size();
for( int i = 0; i < tempCS->cus.size(); i++ )
{
if( tempCS->cus[i]->rootCbf )
{
firstCuHasResidual = i;
break;
}
}
for( int i = 0; i < firstCuHasResidual; i++ )
{
tempCS->cus[i]->qp = predQp;
}
}
}
assert( tempCS->treeType == TREE_L );
uint32_t numCuPuTu[6];
tempCS->picture->cs->getNumCuPuTuOffset( numCuPuTu );
tempCS->picture->cs->useSubStructure( *tempCS, partitioner.chType, CS::getArea( *tempCS, partitioner.currArea(), partitioner.chType ), false, true, false, false, false );
if (isChromaEnabled(tempCS->pcv->chrFormat))
{
partitioner.chType = CHANNEL_TYPE_CHROMA;
tempCS->treeType = partitioner.treeType = TREE_C;
m_CurrCtx++;
const unsigned wIdx = gp_sizeIdxInfo->idxFrom( partitioner.currArea().lwidth() );
const unsigned hIdx = gp_sizeIdxInfo->idxFrom( partitioner.currArea().lheight() );
CodingStructure *tempCSChroma = m_pTempCS2[wIdx][hIdx];
CodingStructure *bestCSChroma = m_pBestCS2[wIdx][hIdx];
tempCS->initSubStructure( *tempCSChroma, partitioner.chType, partitioner.currArea(), false );
tempCS->initSubStructure( *bestCSChroma, partitioner.chType, partitioner.currArea(), false );
tempCS->treeType = TREE_D;
xCompressCU( tempCSChroma, bestCSChroma, partitioner );
//attach chromaCS to luma CS and update cost
bool keepResi = KEEP_PRED_AND_RESI_SIGNALS;
//bestCSChroma->treeType = tempCSChroma->treeType = TREE_C;
CHECK( bestCSChroma->treeType != TREE_C || tempCSChroma->treeType != TREE_C, "wrong treeType for chroma CS" );
tempCS->useSubStructure( *bestCSChroma, partitioner.chType, CS::getArea( *bestCSChroma, partitioner.currArea(), partitioner.chType ), KEEP_PRED_AND_RESI_SIGNALS, true, keepResi, true, true );
//release tmp resource
tempCSChroma->releaseIntermediateData();
bestCSChroma->releaseIntermediateData();
//tempCS->picture->cs->releaseIntermediateData();
m_CurrCtx--;
}
tempCS->picture->cs->clearCuPuTuIdxMap( partitioner.currArea(), numCuPuTu[0], numCuPuTu[1], numCuPuTu[2], numCuPuTu + 3 );
//recover luma tree status
partitioner.chType = CHANNEL_TYPE_LUMA;
partitioner.treeType = TREE_D;
partitioner.modeType = MODE_TYPE_ALL;
}
// Finally, generate split-signaling bits for RD-cost check
const PartSplit implicitSplit = partitioner.getImplicitSplit( *tempCS );
{
bool enforceQT = implicitSplit == CU_QUAD_SPLIT;
// LARGE CTU bug
if( m_pcEncCfg->getUseFastLCTU() )
{
unsigned minDepth = 0;
unsigned maxDepth = floorLog2(tempCS->sps->getCTUSize()) - floorLog2(tempCS->sps->getMinQTSize(slice.getSliceType(), partitioner.chType));
if( auto ad = dynamic_cast<AdaptiveDepthPartitioner*>( &partitioner ) )
{
ad->setMaxMinDepth( minDepth, maxDepth, *tempCS );
}
if( minDepth > partitioner.currQtDepth )
{
// enforce QT
enforceQT = true;
}
}
if( !enforceQT )
{
m_CABACEstimator->resetBits();
m_CABACEstimator->split_cu_mode( split, *tempCS, partitioner );
partitioner.modeType = modeTypeParent;
m_CABACEstimator->mode_constraint( split, *tempCS, partitioner, modeTypeChild );
tempCS->fracBits += m_CABACEstimator->getEstFracBits(); // split bits
}
}
tempCS->cost = m_pcRdCost->calcRdCost( tempCS->fracBits, tempCS->dist );
// Check Delta QP bits for splitted structure
if( !qgEnableChildren ) // check at deepest QG level only
xCheckDQP( *tempCS, partitioner, true );
// If the configuration being tested exceeds the maximum number of bytes for a slice / slice-segment, then
// a proper RD evaluation cannot be performed. Therefore, termination of the
// slice/slice-segment must be made prior to this CTU.
// This can be achieved by forcing the decision to be that of the rpcTempCU.
// The exception is each slice / slice-segment must have at least one CTU.
if (bestCS->cost != MAX_DOUBLE)
{
}
else
{
bestCS->costDbOffset = 0;
}
tempCS->useDbCost = m_pcEncCfg->getUseEncDbOpt();
if( tempCS->cus.size() > 0 && modeTypeParent == MODE_TYPE_ALL && modeTypeChild == MODE_TYPE_INTER )
{
int areaSizeNoResiCu = 0;
for( int k = 0; k < tempCS->cus.size(); k++ )
{
areaSizeNoResiCu += (tempCS->cus[k]->rootCbf == false) ? tempCS->cus[k]->lumaSize().area() : 0;
}
if( areaSizeNoResiCu >= (tempCS->area.lumaSize().area() >> 1) )
{
skipInterPass = true;
}
}
// RD check for sub partitioned coding structure.
xCheckBestMode( tempCS, bestCS, partitioner, encTestMode );
if (isAffMVInfoSaved)
m_pcInterSearch->addAffMVInfo(tmpMVInfo);
if (!tempCS->slice->isIntra() && isUniMvInfoSaved)
{
m_pcInterSearch->addUniMvInfo(tmpUniMvInfo);
}
tempCS->motionLut = oldMotionLut;
tempCS->prevPLT = oldPLT;
tempCS->releaseIntermediateData();
tempCS->prevQP[partitioner.chType] = oldPrevQp;
}