参考自 H.266/VVC-VTM代码学习19-CU层确定测试模式函数initCULevel, VVC中CTU的编码,块划分以及qp的决策 - 知乎 (zhihu.com),本文主要讲解VTM18.0中的EncModeCtrlMTnoRQT::initCULevel
的流程,该函数在EncCu::xCompressCU
的开始被调用,将需要测试的划分模式和编码模式加入堆栈。主要步骤:
1、确定最大和最小搜索深度,如果开启Large CTU
,会根据周围块的深度进行调整
2、根据baseQP设置附近的minQP和maxQP,并在添加后续模式时遍历可能的QP
3、添加划分模式,默认添加顺序是QT、TTV、TTH、BTV 、BTH和Non-split,因为是保存在堆栈中,测试顺序相反。值得注意的是,当current CU的 QtDepth 小于左侧和上侧相邻CU的 QtDepth 时,或者 current CU的决策深度较浅时,会将QT的测试顺序放在BT之前。此外,QT和TT共用一套QP range,BT单独使用一套QP range
4、添加测试模式,此时的QP range是Non-split的,包括 intra、inter、IBC 和 palette等模式
该函数进一步调用canSplit
确定划分模式是否合法,
// VTM18.0
void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStructure& cs )
{
// 该函数主要完成编码端RD cost的模式筛选,并加入各种划分模式和inter intra模式等至RDO列表
// Min/max depth
unsigned minDepth = 0;
unsigned maxDepth = floorLog2(cs.sps->getCTUSize()) - floorLog2(cs.sps->getMinQTSize( m_slice->getSliceType(), partitioner.chType ));
// 最大搜索深度 = log(CTU_Size / minQTSize), by default log(128 / 4) = 5
if( m_pcEncCfg->getUseFastLCTU() )
{
if( auto adPartitioner = dynamic_cast( &partitioner ) )
{
// LARGE CTU
// 根据当前块的临近块拓展maxDepth和minDepth
adPartitioner->setMaxMinDepth( minDepth, maxDepth, cs );
}
}
// 写入最大和最小深度
m_ComprCUCtxList.push_back( ComprCUCtx( cs, minDepth, maxDepth, NUM_EXTRA_FEATURES ) );
const CodingUnit* cuLeft = cs.getCU( cs.area.blocks[partitioner.chType].pos().offset( -1, 0 ), partitioner.chType );
const CodingUnit* cuAbove = cs.getCU( cs.area.blocks[partitioner.chType].pos().offset( 0, -1 ), partitioner.chType );
// 如果左侧CU和上方CU存在,而且存在CU的qtDepth大于当前CU的QT深度,则在BT之前尝试QT划分(默认顺序是non-split, BTH, BTV, TTH, QT)
// 如果都不存在,Width >= 32 * 2^depth and Width > minQTSize * 2, 也将QT提前到BT之前
const bool qtBeforeBt = ( ( cuLeft && cuAbove && cuLeft ->qtDepth > partitioner.currQtDepth && cuAbove->qtDepth > partitioner.currQtDepth )
|| ( cuLeft && !cuAbove && cuLeft ->qtDepth > partitioner.currQtDepth )
|| ( !cuLeft && cuAbove && cuAbove->qtDepth > partitioner.currQtDepth )
|| ( !cuAbove && !cuLeft && cs.area.lwidth() >= ( 32 << cs.slice->getDepth() ) ) )
&& ( cs.area.lwidth() > ( cs.pcv->getMinQtSize( *cs.slice, partitioner.chType ) << 1 ) );
// set features
ComprCUCtx &cuECtx = m_ComprCUCtxList.back();
cuECtx.set( BEST_NON_SPLIT_COST, MAX_DOUBLE );
cuECtx.set( BEST_VERT_SPLIT_COST, MAX_DOUBLE );
cuECtx.set( BEST_HORZ_SPLIT_COST, MAX_DOUBLE );
cuECtx.set( BEST_TRIH_SPLIT_COST, MAX_DOUBLE );
cuECtx.set( BEST_TRIV_SPLIT_COST, MAX_DOUBLE );
cuECtx.set( DO_TRIH_SPLIT, 1 );
cuECtx.set( DO_TRIV_SPLIT, 1 );
cuECtx.set( BEST_IMV_COST, MAX_DOUBLE * .5 );
cuECtx.set( BEST_NO_IMV_COST, MAX_DOUBLE * .5 );
cuECtx.set( QT_BEFORE_BT, qtBeforeBt );
cuECtx.set( DID_QUAD_SPLIT, false );
cuECtx.set( IS_BEST_NOSPLIT_SKIP, false );
cuECtx.set( MAX_QT_SUB_DEPTH, 0 );
// QP
int baseQP = cs.baseQP; // Slice级别的QP
// SepTree 包括 帧内的Dual tree,以及帧间的local dual Tree
if (!partitioner.isSepTree(cs) || isLuma(partitioner.chType)) // joint Tree 或者 luma 分量
{
if (m_pcEncCfg->getUseAdaptiveQP())
{
// baseQP 取 -(6 * (m_bitDepth[CHANNEL_TYPE_LUMA] - 8) 和 63 和 sliceQP + delta 中的中值
baseQP = Clip3(-cs.sps->getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, baseQP + xComputeDQP(cs, partitioner));
}
#if ENABLE_QPA_SUB_CTU
else if (m_pcEncCfg->getUsePerceptQPA() && !m_pcEncCfg->getUseRateCtrl() && cs.pps->getUseDQP() && cs.slice->getCuQpDeltaSubdiv() > 0)
{
const PreCalcValues &pcv = *cs.pcv;
if ((partitioner.currArea().lwidth() < pcv.maxCUWidth) && (partitioner.currArea().lheight() < pcv.maxCUHeight) && cs.picture)
{
const Position &pos = partitioner.currQgPos;
const unsigned mtsLog2 = (unsigned)floorLog2(std::min (cs.sps->getMaxTbSize(), pcv.maxCUWidth));
const unsigned stride = pcv.maxCUWidth >> mtsLog2;
baseQP = cs.picture->m_subCtuQP[((pos.x & pcv.maxCUWidthMask) >> mtsLog2) + stride * ((pos.y & pcv.maxCUHeightMask) >> mtsLog2)];
}
}
#endif
#if SHARP_LUMA_DELTA_QP
if (m_pcEncCfg->getLumaLevelToDeltaQPMapping().isEnabled())
{
if (partitioner.currQgEnable())
{
m_lumaQPOffset = calculateLumaDQP (cs.getOrgBuf (clipArea (cs.area.Y(), cs.picture->Y())));
}
baseQP = Clip3 (-cs.sps->getQpBDOffset (CHANNEL_TYPE_LUMA), MAX_QP, baseQP - m_lumaQPOffset);
}
#endif
if (m_pcEncCfg->getSmoothQPReductionEnable())
{
int smoothQPoffset = 0;
if (partitioner.currQgEnable())
{
// enable smooth QP reduction on selected frames
bool checkSmoothQP = false;
if (m_pcEncCfg->getSmoothQPReductionPeriodicity() != 0)
{
checkSmoothQP = ((m_pcEncCfg->getSmoothQPReductionPeriodicity() == 0) && cs.slice->isIntra()) || (m_pcEncCfg->getSmoothQPReductionPeriodicity() == 1) || ((cs.slice->getPOC() % m_pcEncCfg->getSmoothQPReductionPeriodicity()) == 0);
}
else
{
checkSmoothQP = ((m_pcEncCfg->getSmoothQPReductionPeriodicity() == 0) && cs.slice->isIntra());
}
if (checkSmoothQP)
{
bool isIntraSlice = cs.slice->isIntra();
if (isIntraSlice)
{
smoothQPoffset = calculateLumaDQPsmooth(cs.getOrgBuf(clipArea(cs.area.Y(), cs.picture->Y())), baseQP, m_pcEncCfg->getSmoothQPReductionThresholdIntra(), m_pcEncCfg->getSmoothQPReductionModelScaleIntra(), m_pcEncCfg->getSmoothQPReductionModelOffsetIntra(), m_pcEncCfg->getSmoothQPReductionLimitIntra());
}
else
{
smoothQPoffset = calculateLumaDQPsmooth(cs.getOrgBuf(clipArea(cs.area.Y(), cs.picture->Y())), baseQP, m_pcEncCfg->getSmoothQPReductionThresholdInter(), m_pcEncCfg->getSmoothQPReductionModelScaleInter(), m_pcEncCfg->getSmoothQPReductionModelOffsetInter(), m_pcEncCfg->getSmoothQPReductionLimitInter());
}
}
}
baseQP = Clip3(-cs.sps->getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, baseQP + smoothQPoffset);
}
}
// 以下是在base QP附近设定minQP和maxQP,再RD 测试两者之间的所有取值,确定最佳的minQP和maxQP
int minQP = baseQP;
int maxQP = baseQP;
xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, CU_QUAD_SPLIT ); // 针对四叉树和三叉树
//色度分量不检查IBC模式
bool checkIbc = true;
if (partitioner.chType == CHANNEL_TYPE_CHROMA)
{
checkIbc = false;
}
// Add coding modes here
// NOTE: Working back to front, as a stack, which is more efficient with the container
// NOTE: First added modes will be processed at the end.
// 注意:划分模式是先入后出
//
// Add unit split modes
// 添加划分模式进入测试列表
if( !cuECtx.get( QT_BEFORE_BT ) )
{
for( int qp = maxQP; qp >= minQP; qp-- ) // 遍历可能的QP参数
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_QT, ETO_STANDARD, qp } ); // if not qt_before_bt, then push back QT_mode
}
}
if( partitioner.canSplit( CU_TRIV_SPLIT, cs ) )
{
// add split modes
for( int qp = maxQP; qp >= minQP; qp-- )
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_TT_V, ETO_STANDARD, qp } ); // 三叉树竖直划分
}
}
if( partitioner.canSplit( CU_TRIH_SPLIT, cs ) )
{
// add split modes
for( int qp = maxQP; qp >= minQP; qp-- )
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_TT_H, ETO_STANDARD, qp } ); // 三叉树水平划分
}
}
int minQPq = minQP;
int maxQPq = maxQP;
xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, CU_BT_SPLIT ); // 针对二叉树
if( partitioner.canSplit( CU_VERT_SPLIT, cs ) )
{
// add split modes
for( int qp = maxQP; qp >= minQP; qp-- )
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_BT_V, ETO_STANDARD, qp } ); // 二叉树竖直划分
}
m_ComprCUCtxList.back().set( DID_VERT_SPLIT, true );
}
else
{
m_ComprCUCtxList.back().set( DID_VERT_SPLIT, false );
}
if( partitioner.canSplit( CU_HORZ_SPLIT, cs ) )
{
// add split modes
for( int qp = maxQP; qp >= minQP; qp-- )
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_BT_H, ETO_STANDARD, qp } ); // 二叉树水平划分
}
m_ComprCUCtxList.back().set( DID_HORZ_SPLIT, true );
}
else
{
m_ComprCUCtxList.back().set( DID_HORZ_SPLIT, false );
}
if( cuECtx.get( QT_BEFORE_BT ) )
{
for( int qp = maxQPq; qp >= minQPq; qp-- )
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_QT, ETO_STANDARD, qp } ); // QT优先于BT划分
}
}
m_ComprCUCtxList.back().testModes.push_back( { ETM_POST_DONT_SPLIT } ); // 不划分
xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, CU_DONT_SPLIT ); // 设置不划分的QP,即当前块的intra inter IBC等模式需要test的QP range
int lowestQP = minQP;
//
// Add unit coding modes: Intra, InterME, InterMerge ...
// 添加单元编码模式
bool tryIntraRdo = true; // 是否尝试Intra mode RDO
bool tryInterRdo = true; // 是否尝试Inter mode RDO
bool tryIBCRdo = true;
if( partitioner.isConsIntra() )
{
tryInterRdo = false;
}
else if( partitioner.isConsInter() )
{
tryIntraRdo = tryIBCRdo = false;
}
checkIbc &= tryIBCRdo;
for( int qpLoop = maxQP; qpLoop >= minQP; qpLoop-- )
{
const int qp = std::max( qpLoop, lowestQP );
#if REUSE_CU_RESULTS
const bool isReusingCu = isValid( cs, partitioner, qp );
cuECtx.set( IS_REUSING_CU, isReusingCu );
if( isReusingCu )
{
m_ComprCUCtxList.back().testModes.push_back( {ETM_RECO_CACHED, ETO_STANDARD, qp} );
}
#endif
// add intra modes
if( tryIntraRdo )
{
if (cs.slice->getSPS()->getPLTMode()
&& (partitioner.treeType != TREE_D || cs.slice->isIntra()
|| (cs.area.lwidth() == 4 && cs.area.lheight() == 4))
&& getPltEnc())
{
m_ComprCUCtxList.back().testModes.push_back({ ETM_PALETTE, ETO_STANDARD, qp }); // paletee mode, 屏幕内容编码的一种模式
}
m_ComprCUCtxList.back().testModes.push_back({ ETM_INTRA, ETO_STANDARD, qp });
if (cs.slice->getSPS()->getPLTMode() && partitioner.treeType == TREE_D && !cs.slice->isIntra()
&& !(cs.area.lwidth() == 4 && cs.area.lheight() == 4) && getPltEnc())
{
m_ComprCUCtxList.back().testModes.push_back({ ETM_PALETTE, ETO_STANDARD, qp });
}
}
// add ibc mode to intra path
if (cs.sps->getIBCFlag() && checkIbc)
{
m_ComprCUCtxList.back().testModes.push_back({ ETM_IBC, ETO_STANDARD, qp });
if (partitioner.chType == CHANNEL_TYPE_LUMA)
{
m_ComprCUCtxList.back().testModes.push_back({ ETM_IBC_MERGE, ETO_STANDARD, qp });
}
}
}
// add first pass modes
if ( !m_slice->isIntra() && !( cs.area.lwidth() == 4 && cs.area.lheight() == 4 ) && tryInterRdo )
{
for( int qpLoop = maxQP; qpLoop >= minQP; qpLoop-- )
{
const int qp = std::max( qpLoop, lowestQP );
if (m_pcEncCfg->getIMV())
{
m_ComprCUCtxList.back().testModes.push_back({ ETM_INTER_ME, EncTestModeOpts( 4 << ETO_IMV_SHIFT ), qp });
}
if( m_pcEncCfg->getIMV() || m_pcEncCfg->getUseAffineAmvr() )
{
int imv = m_pcEncCfg->getIMV4PelFast() ? 3 : 2;
m_ComprCUCtxList.back().testModes.push_back( { ETM_INTER_ME, EncTestModeOpts( imv << ETO_IMV_SHIFT ), qp } );
m_ComprCUCtxList.back().testModes.push_back( { ETM_INTER_ME, EncTestModeOpts( 1 << ETO_IMV_SHIFT ), qp } );
}
// add inter modes
if( m_pcEncCfg->getUseEarlySkipDetection() )
{
if( cs.sps->getUseGeo() && cs.slice->isInterB() )
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_MERGE_GEO, ETO_STANDARD, qp } );
}
m_ComprCUCtxList.back().testModes.push_back( { ETM_MERGE_SKIP, ETO_STANDARD, qp } );
if (cs.sps->getUseAffine() || (cs.sps->getSbTMVPEnabledFlag() && cs.slice->getPicHeader()->getEnableTMVPFlag()))
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_AFFINE, ETO_STANDARD, qp } );
}
m_ComprCUCtxList.back().testModes.push_back( { ETM_INTER_ME, ETO_STANDARD, qp } );
}
else
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_INTER_ME, ETO_STANDARD, qp } );
if( cs.sps->getUseGeo() && cs.slice->isInterB() )
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_MERGE_GEO, ETO_STANDARD, qp } );
}
m_ComprCUCtxList.back().testModes.push_back( { ETM_MERGE_SKIP, ETO_STANDARD, qp } );
if (cs.sps->getUseAffine() || (cs.sps->getSbTMVPEnabledFlag() && cs.slice->getPicHeader()->getEnableTMVPFlag()))
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_AFFINE, ETO_STANDARD, qp } );
}
}
if (m_pcEncCfg->getUseHashME())
{
int minSize = min(cs.area.lwidth(), cs.area.lheight());
if (minSize < 128 && minSize >= 4)
{
m_ComprCUCtxList.back().testModes.push_back({ ETM_HASH_INTER, ETO_STANDARD, qp });
}
}
}
}