xRecurIntraChromaCodingQT在estIntraPredChromaQT函数中被调用,用来计算每个帧内色度预测模式时的cost。
流程:
首先判断tu是否需要划分,若tu划分时,对每个划分出的小tu递归调用本函数,计算每个小tu的在chromaIntra模式下的最优的cost,最后求和;
如果tu不划分,分别对Cb和Cr通道进行处理。对于每个通道,是/否TS模式+是/否跨组件预测可以构成4种模式,分别对4种模式调用xIntraCodingTUBlock函数进行预测变换量化反量化反变换,得到reco像素和cost,按照cost选取TU的最优模式。选择的最优cost即当前test的chromaIntra模式的最优的cost。
本函数和VTM1中没有太大区别:
VTM1.0代码阅读:xRecurIntraChromaCodingQT函数
函数中调用的xIntraCodingTUBlock函数在帧内亮度预测时已经看过:
VTM3.0代码阅读:xIntraCodingTUBlock函数
ChromaCbfs IntraSearch::xRecurIntraChromaCodingQT(CodingStructure &cs, Partitioner& partitioner)
{
UnitArea currArea = partitioner.currArea();
const bool keepResi = cs.sps->getSpsNext().getUseLMChroma() || KEEP_PRED_AND_RESI_SIGNALS;
if( !currArea.Cb().valid() ) return ChromaCbfs( false );
//色度tu、色度pu、lumaTU
TransformUnit &currTU = *cs.getTU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
const PredictionUnit &pu = *cs.getPU( currArea.chromaPos(), CHANNEL_TYPE_CHROMA );
const TransformUnit &currTULuma = CS::isDualITree( cs ) ? *cs.picture->cs->getTU( currArea.lumaPos(), CHANNEL_TYPE_LUMA ) : currTU;
//CS::isDualITree()为true时,亮度和色度域有不同的划分树,就必须特别的getLumaTU;如果不是DualITree,直接用currTU
uint32_t currDepth = partitioner.currTrDepth; //tu的划分深度
const PPS &pps = *cs.pps;
ChromaCbfs cbfs ( false );
if (currDepth == currTU.depth) //tu划分深度已达到partitioner.currTrDepth,不再划分
{
if (!currArea.Cb().valid() || !currArea.Cr().valid()) //Cb/Cr分量可用
{
return cbfs;
}
bool checkTransformSkip = pps.getUseTransformSkip(); //Cb是否TS模式
checkTransformSkip &= TU::hasTransformSkipFlag( *currTU.cs, partitioner.currArea().Cb() );
if( m_pcEncCfg->getUseTransformSkipFast() ) //Y的TS模式
{
checkTransformSkip &= TU::hasTransformSkipFlag( *currTU.cs, partitioner.currArea().Y() );
if( checkTransformSkip && cs.pcv->noChroma2x2 )
{
int nbLumaSkip = currTULuma.transformSkip[0] ? 1 : 0;
{
// the chroma blocks are co-located with the last luma block, so backwards references are needed
nbLumaSkip += cs.getTU( currTULuma.Y().topLeft().offset( -1, 0 ), partitioner.chType )->transformSkip[0] ? 1 : 0;
nbLumaSkip += cs.getTU( currTULuma.Y().topLeft().offset( -1, -1 ), partitioner.chType )->transformSkip[0] ? 1 : 0;
nbLumaSkip += cs.getTU( currTULuma.Y().topLeft().offset( 0, -1 ), partitioner.chType )->transformSkip[0] ? 1 : 0;
}
checkTransformSkip &= ( nbLumaSkip > 0 );
}
}
CodingStructure &saveCS = *m_pSaveCS[1]; //saveCS保存临时数据
saveCS.pcv = cs.pcv;
saveCS.picture = cs.picture;
saveCS.area.repositionTo( cs.area );
saveCS.initStructData( -1, false, true );
TransformUnit &tmpTU = saveCS.addTU(currArea, partitioner.chType); //saveCS的tmpTU保存临时tu数据
cs.setDecomp(currArea.Cb(), true); // set in advance (required for Cb2/Cr2 in 4:2:2 video)
const unsigned numTBlocks = ::getNumberValidTBlocks( *cs.pcv ); //4:0:0采样为1,其它为3
for( uint32_t c = COMPONENT_Cb; c < numTBlocks; c++) //Cb、Cr两个通道分别计算最优模式及最优模式下的信息
{
const ComponentID compID = ComponentID(c);
const CompArea& area = currTU.blocks[compID];
double dSingleCost = MAX_DOUBLE; //保存最优cost等信息
int bestModeId = 0; //最优变换预测模式的模式idx
Distortion singleDistC = 0;
Distortion singleDistCTmp = 0;
double singleCostTmp = 0; //临时的失真和cost
//跨组件预测CCLM
const bool checkCrossComponentPrediction = PU::isChromaIntraModeCrossCheckMode( pu ) && pps.getPpsRangeExtension().getCrossComponentPredictionEnabledFlag() && TU::getCbf( currTU, COMPONENT_Y );
const int crossCPredictionModesToTest = checkCrossComponentPrediction ? 2 : 1; //跨组件预测
const int transformSkipModesToTest = checkTransformSkip ? 2 : 1; //TransformSkip模式
const int totalModesToTest = crossCPredictionModesToTest * transformSkipModesToTest; //总的test模式数
const bool isOneMode = (totalModesToTest == 1);
int currModeId = 0;
int default0Save1Load2 = 0;
TempCtx ctxStart ( m_CtxCache );
TempCtx ctxBest ( m_CtxCache );
if (!isOneMode)
{
ctxStart = m_CABACEstimator->getCtx(); //保存上下文模型
}
//是/否TS模式 + 是/否跨组件预测,最多构成4种模式
for (int transformSkipModeId = 0; transformSkipModeId < transformSkipModesToTest; transformSkipModeId++) //是/否TransformSkip模式
{
for (int crossCPredictionModeId = 0; crossCPredictionModeId < crossCPredictionModesToTest; crossCPredictionModeId++) //是/否跨组件预测模式CCLM
{
currTU.compAlpha [compID] = 0;
currTU.transformSkip[compID] = transformSkipModeId;
currModeId++;
const bool isFirstMode = (currModeId == 1);
const bool isLastMode = (currModeId == totalModesToTest); // currModeId is indexed from 1 //是否为最后一个需要test的模式
if (isOneMode) //如果只有一个模式
{
default0Save1Load2 = 0;
}
else if (!isOneMode && (transformSkipModeId == 0) && (crossCPredictionModeId == 0))
{ //当前为第一个test的模式,并且之后还有需要test的模式
default0Save1Load2 = 1; //save prediction on first mode //此时下面的xIntraCodingTUBlock函数中需要保存预测像素
}
else //有多个test的模式,并且当期已经不是第一个模式
{
default0Save1Load2 = 2; //load it on subsequent modes //此时下面的xIntraCodingTUBlock函数中预测像素,用上面保存的
}
if (!isFirstMode) // if not first mode to be tested
{
m_CABACEstimator->getCtx() = ctxStart;
}
singleDistCTmp = 0;
//当前tu采用currModeId号变换模式,然后按照本函数要计算的chromaIntra模式,进行帧内预测变换量化反量化反变换
//从而得到reco像素,计算得到失真singleDistCTmp,返回
xIntraCodingTUBlock( currTU, compID, crossCPredictionModeId != 0, singleDistCTmp, default0Save1Load2 );
if( ( ( crossCPredictionModeId == 1 ) && ( currTU.compAlpha[compID] == 0 ) ) || ( ( transformSkipModeId == 1 ) && !TU::getCbf( currTU, compID ) ) ) //In order not to code TS flag when cbf is zero, the case for TS with cbf being zero is forbidden.
{ //为了在cbf为0时不对transformSkip标志进行编码,cbf为0时的TS模式被禁止
singleCostTmp = MAX_DOUBLE; //cost设为最大,不会选上TS模式,即禁止
}
else if( !isOneMode )
{ //如果不只一个test模式,由上面的失真,得到cost
uint64_t fracBitsTmp = xGetIntraFracBitsQTChroma( currTU, compID );
singleCostTmp = m_pcRdCost->calcRdCost( fracBitsTmp, singleDistCTmp );
}
if( singleCostTmp < dSingleCost ) //当前test模式为最优时,保存最优信息
{
dSingleCost = singleCostTmp;
singleDistC = singleDistCTmp;
bestModeId = currModeId;
if( !isLastMode ) //如果不是最后一个test的模式
{
#if KEEP_PRED_AND_RESI_SIGNALS
saveCS.getPredBuf (area).copyFrom(cs.getPredBuf (area));
saveCS.getOrgResiBuf(area).copyFrom(cs.getOrgResiBuf(area));
#endif
if( keepResi ) //保存resi、reco像素信息
{
saveCS.getResiBuf (area).copyFrom(cs.getResiBuf (area));
}
saveCS.getRecoBuf (area).copyFrom(cs.getRecoBuf (area));
tmpTU.copyComponentFrom(currTU, compID); //saveCS中的tmpTU拷贝tu的信息
ctxBest = m_CABACEstimator->getCtx(); //最优上下文模型
}
}
}
} // loop结束
if (bestModeId < totalModesToTest) //将最优的信息由saveCS拷贝给cs
{
#if KEEP_PRED_AND_RESI_SIGNALS
cs.getPredBuf (area).copyFrom(saveCS.getPredBuf (area)); //resi、reco、tu、ctxBest等信息
cs.getOrgResiBuf(area).copyFrom(saveCS.getOrgResiBuf(area));
#endif
if( keepResi )
{
cs.getResiBuf (area).copyFrom(saveCS.getResiBuf (area));
}
cs.getRecoBuf (area).copyFrom(saveCS.getRecoBuf (area));
currTU.copyComponentFrom(tmpTU, compID);
m_CABACEstimator->getCtx() = ctxBest;
}
cs.picture->getRecoBuf(area).copyFrom(cs.getRecoBuf(area)); //reco像素保存到picture
cbfs.cbf(compID) = TU::getCbf(currTU, compID);
cs.dist += singleDistC; //单通道的失真加到cs的失真上。在下面的TU划分递归调用的情况时会用到
} Cb/Cr两个通道loop结束
}
else //tu划分深度还未达到partitioner.currTrDepth,继续划分
{
unsigned numValidTBlocks = ::getNumberValidTBlocks( *cs.pcv );
ChromaCbfs SplitCbfs ( false );
if( partitioner.canSplit( TU_MAX_TR_SPLIT, cs ) )
{
partitioner.splitCurrArea( TU_MAX_TR_SPLIT, cs ); //当前tu划分为小tu
}
else
THROW( "Implicit TU split not available" );
//do-while循环对每个划分的小tu递归调用本函数进行处理,得到每个小tu在Cb/Cr通道的最优模式信息和reco和cost
do
{
ChromaCbfs subCbfs = xRecurIntraChromaCodingQT( cs, partitioner ); //xRecurIntraChromaCodingQT
for( uint32_t ch = COMPONENT_Cb; ch < numValidTBlocks; ch++ )
{
const ComponentID compID = ComponentID( ch );
SplitCbfs.cbf( compID ) |= subCbfs.cbf( compID );
}
} while( partitioner.nextPart( cs ) );
partitioner.exitCurrSplit();
{
cbfs.Cb |= SplitCbfs.Cb;
cbfs.Cr |= SplitCbfs.Cr;
for( auto &ptu : cs.tus ) //对每个划分的小tu,设置cbf
{
if( currArea.Cb().contains( ptu->Cb() ) || ( !ptu->Cb().valid() && currArea.Y().contains( ptu->Y() ) ) )
{
TU::setCbfAtDepth( *ptu, COMPONENT_Cb, currDepth, SplitCbfs.Cb );
TU::setCbfAtDepth( *ptu, COMPONENT_Cr, currDepth, SplitCbfs.Cr );
}
}
}
}
return cbfs;
}