H.266/VVC专栏传送
上一篇:H.266/VVC-VTM代码学习18-自适应QP设置(Adaptive QP)
下一篇:持续创作中…
前言
VTM是H.266/VVC视频编码标准的参考软件,研究VTM代码给研究人员解释了VVC编码标准的详细标准规范与细节。
本文是笔者对VTM代码的一点学习记录,成文于笔者刚开始接触VVC期间,期间很多概念和理论框架还很不成熟,若文中存在错误欢迎批评指正,也欢迎广大视频编码学习者沟通交流、共同进步。
VTM代码的下载及编译请参考博文:
【视频编码学习】H.266/VVC参考软件VTM配置运行(VTM-6.0版本)
一、函数流程
函数 initCULevel 在 VTM10.0 的 EncModeCtrl.cpp 中,主要完成编码端 RD cost 检查的模式筛选。通过了解、调整这部分代码,可以调整 encoder 进行 RD 测试的 mode、split、QP 等,从而实现提升编码性能。
代码的大致流程如下(具体内容见第二部分的代码注释):
- 确定划分最小、最大深度
- 获取 baseQP、minQP、maxQP
- 加入各类划分模式至 RDO 列表
- 加入 Intra、Inter 等模式至 RDO 列表
- 加入其他模式至 RDO 列表
二、函数详解
void EncModeCtrlMTnoRQT::initCULevel( Partitioner &partitioner, const CodingStructure& cs )
{
// Min/max depth
// 确定搜索的最小、最大深度
// 搜索的最小深度为0
unsigned minDepth = 0;
// 搜索的最大深度为 log(CTUSize / minQTSize)
unsigned maxDepth = floorLog2(cs.sps->getCTUSize()) - floorLog2(cs.sps->getMinQTSize( m_slice->getSliceType(), partitioner.chType ));
if( m_pcEncCfg->getUseFastLCTU() )
{
if( auto adPartitioner = dynamic_cast<AdaptiveDepthPartitioner*>( &partitioner ) )
{
// LARGE CTU
// 根据上、左、左上、右上相邻CU深度信息设置最小最大QT深度
adPartitioner->setMaxMinDepth( minDepth, maxDepth, cs );
}
}
// 设置最大、最小深度
m_ComprCUCtxList.push_back( ComprCUCtx( cs, minDepth, maxDepth, NUM_EXTRA_FEATURES ) );
// 默认为关
#if ENABLE_SPLIT_PARALLELISM
if( m_runNextInParallel )
{
for( auto &level : m_ComprCUCtxList )
{
CHECK( level.isLevelSplitParallel, "Tring to parallelize a level within parallel execution!" );
}
CHECK( cs.picture->scheduler.getSplitJobId() == 0, "Trying to run a parallel level although jobId is 0!" );
m_runNextInParallel = false;
m_ComprCUCtxList.back().isLevelSplitParallel = true;
}
#endif
// 当前CU的左侧CU
const CodingUnit* cuLeft = cs.getCU( cs.area.blocks[partitioner.chType].pos().offset( -1, 0 ), partitioner.chType );
// 当前CU的上方CU
const CodingUnit* cuAbove = cs.getCU( cs.area.blocks[partitioner.chType].pos().offset( 0, -1 ), partitioner.chType );
// 若左侧CU与上方CU至少一者存在,且存在的CU深度大于当前QT深度
// 或左侧CU与上方CU均不存在,且当前区域宽度大于(32 * 2^当前深度),且当前区域宽度大于最小QT尺寸 * 2
// 则先做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 );
// baseQP 为 slice 级的 initial QP
int baseQP = cs.baseQP;
// 当前划分树不是separate tree(分离树)是 joint tree(联合树)或当前为亮度分量
if (!partitioner.isSepTree(cs) || isLuma(partitioner.chType))
{
// 使用 adaptive QP
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
// 自适应获得 baseQP 的一种方式
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
// 另一种获得 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 RANDOM_BUFFER
baseQP = baseQP + (rand() % 7) - 3;
#endif // RANDOM_BUFFER
#if TEST_NETWORK
baseQP = baseQP + g_currCtuQP;
#endif // TEST_NETWORK
// -------------------- 以下是在 base QP 附近设定 minQP 和 maxQP 再 RD 测试两者区间内所有取值 -------------
int minQP = baseQP;
int maxQP = baseQP;
// 确定 minQP 与 maxQP
xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, CU_QUAD_SPLIT );
bool checkIbc = true;
// 若为色度分量,则不检查 IBC
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
// 加入 split 模式
// 若BT不先于QT,则加入 QT 四叉树划分
if( !cuECtx.get<bool>( QT_BEFORE_BT ) )
{
for( int qp = maxQP; qp >= minQP; qp-- )
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_QT, ETO_STANDARD, qp } );
}
}
// 三叉树垂直划分
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 );
}
// 若 BT 先于 QT,则在此处加入 QT
if( cuECtx.get<bool>( QT_BEFORE_BT ) )
{
for( int qp = maxQPq; qp >= minQPq; qp-- )
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_SPLIT_QT, ETO_STANDARD, qp } );
}
}
m_ComprCUCtxList.back().testModes.push_back( { ETM_POST_DONT_SPLIT } );
xGetMinMaxQP( minQP, maxQP, cs, partitioner, baseQP, *cs.sps, *cs.pps, CU_DONT_SPLIT );
int lowestQP = minQP;
//
// Add unit coding modes: Intra, InterME, InterMerge ...
// 加入块编码模式
// 是否尝试 Intra RDO
bool tryIntraRdo = true;
// 是否尝试 Inter RDO
bool tryInterRdo = true;
// 是否尝试 IBC(Intra block copy) RDO
bool tryIBCRdo = true;
// 若指定 isConsIntra,则不尝试 Inter RDO
if( partitioner.isConsIntra() )
{
tryInterRdo = false;
}
// 若指定 isConsInter,则不尝试 IBC RDO
else if( partitioner.isConsInter() )
{
tryIntraRdo = tryIBCRdo = false;
}
// 若不尝试 IBC RDO,则不检查 IBC
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 )
{
// 循环将 ETM_RECO_CACHED 加入 RDO
m_ComprCUCtxList.back().testModes.push_back( {ETM_RECO_CACHED, ETO_STANDARD, qp} );
}
#endif
// add intra modes
// 若尝试 Intra RDO
if( tryIntraRdo )
{
if (cs.slice->getSPS()->getPLTMode() && (partitioner.treeType != TREE_D || cs.slice->isIntra() || (cs.area.lwidth() == 4 && cs.area.lheight() == 4)) && getPltEnc())
{
// 加入 palette mode(利用CU内部的样本只是有少数几种典型的颜色组成的特性进行编码) RDO
m_ComprCUCtxList.back().testModes.push_back({ ETM_PALETTE, ETO_STANDARD, qp });
}
// 加入 Intra RDO
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())
{
// 加入 palette mode RDO
m_ComprCUCtxList.back().testModes.push_back({ ETM_PALETTE, ETO_STANDARD, qp });
}
}
// 加入 IBC mode RDO
// 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())
{
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())
{
m_ComprCUCtxList.back().testModes.push_back( { ETM_AFFINE, ETO_STANDARD, qp } );
}
}
// 加入 Hash_Inter RDO
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 });
}
}
}
}
// ensure to skip unprobable modes
// 跳过不可能的模式,调用tryModeMaster()对编码模式进行提前跳过或者终止判定
if( !tryModeMaster( m_ComprCUCtxList.back().testModes.back(), cs, partitioner ) )
{
nextMode( cs, partitioner );
}
m_ComprCUCtxList.back().lastTestMode = EncTestMode();
}
上一篇:H.266/VVC-VTM代码学习18-自适应QP设置(Adaptive QP)
下一篇:持续创作中…