coding_tree函数通过递归调用,寻址ctu中的所有cu,对每个cu调用coding_unit解码cu信息。
其实,coding_tree函数根据解码得到的QT和MT的flag,逐步递归划分当前区域直到cu,本质就是在解码端重建编码端的划分树,直到叶子节点解码cu。
本函数与VTM1中代码的大致流程一致,最大区别就是对>64的DualITree块进行特别处理:
对于>64的DualITree块,在QT时要对亮度和色度统一进行,保持QT对亮度和色度划分一致。
对于>64的DualITree块,本函数入口参数既有亮度partitioner,又有色度chromaPartitioner。
对于>64的DualITree块的情况时,最后会将luma和chroma的cu拆分为两条,并将chromaCU的头接到lumaCU的尾,有何必要?
bool CABACReader::coding_tree( CodingStructure& cs, Partitioner& partitioner, CUCtx& cuCtx, Partitioner* pPartitionerChroma, CUCtx* pCuCtxChroma)
{
const PPS &pps = *cs.pps;
const UnitArea &currArea = partitioner.currArea(); //当前处理的区域
bool lastSegment = false;
// Reset delta QP coding flag and ChromaQPAdjustemt coding flag
if( pps.getUseDQP() && partitioner.currDepth <= pps.getMaxCuDQPDepth() )
{
cuCtx.isDQPCoded = false;
}
if( cs.slice->getUseChromaQpAdj() && partitioner.currDepth <= pps.getPpsRangeExtension().getDiffCuChromaQpOffsetDepth() )
{
cuCtx.isChromaQpAdjCoded = false;
}
// Reset delta QP coding flag and ChromaQPAdjustemt coding flag
if (CS::isDualITree(cs) && pPartitionerChroma != nullptr)//即:I帧DualITree且CTU为128x128时的情况
{
if (pps.getUseDQP() && pPartitionerChroma->currDepth <= pps.getMaxCuDQPDepth())
{
pCuCtxChroma->isDQPCoded = false;
}
if (cs.slice->getUseChromaQpAdj() && pPartitionerChroma->currDepth <= pps.getPpsRangeExtension().getDiffCuChromaQpOffsetDepth())
{
pCuCtxChroma->isChromaQpAdjCoded = false;
}
}
const PartSplit implicitSplit = partitioner.getImplicitSplit( cs );
// QT
bool canQtSplit = partitioner.canSplit( CU_QUAD_SPLIT, cs ); //当前区域是否满足qt划分条件
if( canQtSplit )
{
// force QT split enabling on the edges and if the current area exceeds maximum transformation size
bool qtSplit = implicitSplit == CU_QUAD_SPLIT;
// split_cu_flag
if( !qtSplit && implicitSplit != CU_QUAD_SPLIT )
{
qtSplit = split_cu_flag( cs, partitioner ); //解码QT划分的flag,当前是否进行qt划分
}
// quad-tree split
if( qtSplit )
{ //下面这个if中,>=64 应该修改为 >64
//MTT在CTU的划分时,首先进行QT划分,QT叶子节点进行BT/TT划分,VVC中CTU一般为128x128
//这一段代码,对I帧DualITree且CTU为128x128时的块进行限制,让亮度和色度在QT划分阶段保持一致
if (CS::isDualITree(cs) && pPartitionerChroma != nullptr && (partitioner.currArea().lwidth() >= 64 || partitioner.currArea().lheight() >= 64))
{
partitioner.splitCurrArea(CU_QUAD_SPLIT, cs);
pPartitionerChroma->splitCurrArea(CU_QUAD_SPLIT, cs); //亮度和色度同时QT划分
bool beContinue = true;
bool lumaContinue = true;
bool chromaContinue = true;
bool lastSegmentC = false;
while (beContinue)
{
if (partitioner.currArea().lwidth() > 64 || partitioner.currArea().lheight() > 64)
{ //对大于64的DualITree块进行coding_tree时,亮度色度同时
if (!lastSegmentC && cs.area.blocks[partitioner.chType].contains(partitioner.currArea().blocks[partitioner.chType].pos()))
{
lastSegmentC = coding_tree(cs, partitioner, cuCtx, pPartitionerChroma, pCuCtxChroma); //既有亮度partitioner,又有色度chromaPartitioner
}
lumaContinue = partitioner.nextPart(cs);
chromaContinue = pPartitionerChroma->nextPart(cs);
CHECK(lumaContinue != chromaContinue, "luma chroma partition should be matched");
beContinue = lumaContinue;
}
else
{
//dual tree coding under 64x64 block
if (!lastSegment && cs.area.blocks[partitioner.chType].contains(partitioner.currArea().blocks[partitioner.chType].pos()))
{ //对小于64的DualITree块进行coding_tree时,亮度和色度分开进行解码
lastSegment = coding_tree(cs, partitioner, cuCtx);
}
lumaContinue = partitioner.nextPart(cs);
if (!lastSegmentC && cs.area.blocks[pPartitionerChroma->chType].contains(pPartitionerChroma->currArea().blocks[pPartitionerChroma->chType].pos()))
{
lastSegmentC = coding_tree(cs, *pPartitionerChroma, *pCuCtxChroma);
}
chromaContinue = pPartitionerChroma->nextPart(cs);
CHECK(lumaContinue != chromaContinue, "luma chroma partition should be matched");
CHECK(lastSegment == true, "luma should not be the last segment");
beContinue = lumaContinue;
}
}
partitioner.exitCurrSplit();
pPartitionerChroma->exitCurrSplit(); //至此已经将当前128x128块中的cu解码完全
//cat the chroma CUs together
CodingUnit* currentCu = cs.getCU(partitioner.currArea().lumaPos(), CHANNEL_TYPE_LUMA); //得到当前块(一般为128x128,即CTU)第一个luma CU
CodingUnit* nextCu = nullptr;
CodingUnit* tempLastLumaCu = nullptr;
CodingUnit* tempLastChromaCu = nullptr;
ChannelType currentChType = currentCu->chType; //算法写的挺好的
while (currentCu->next != nullptr)
{ //CS中是将luma和chroma的CU存储在一个vector中的,这里将luma和chroma的CU分开
nextCu = currentCu->next;
if (currentChType != nextCu->chType && currentChType == CHANNEL_TYPE_LUMA)
{
tempLastLumaCu = currentCu;
if (tempLastChromaCu != nullptr) //swap
{
tempLastChromaCu->next = nextCu; //将CS::cus[]的CU分成两条链表luma和chroma
}
}
else if (currentChType != nextCu->chType && currentChType == CHANNEL_TYPE_CHROMA)
{
tempLastChromaCu = currentCu;
if (tempLastLumaCu != nullptr) //swap
{
tempLastLumaCu->next = nextCu; //将CS::cus[]的CU分成两条链表luma和chroma
}
}
currentCu = nextCu;
currentChType = currentCu->chType;
}
CodingUnit* chromaFirstCu = cs.getCU(pPartitionerChroma->currArea().chromaPos(), CHANNEL_TYPE_CHROMA);
tempLastLumaCu->next = chromaFirstCu; //lumaCU链表的尾接chromaCU的头
lastSegment = lastSegmentC;
}
else //P/B帧、I帧非DualITree、I帧块小于等于64
{
partitioner.splitCurrArea( CU_QUAD_SPLIT, cs ); //当前区域qt划分为4分子区域
do
{
if( !lastSegment && cs.area.blocks[partitioner.chType].contains( partitioner.currArea().blocks[partitioner.chType].pos() ) )
{
lastSegment = coding_tree( cs, partitioner, cuCtx ); //子区域递归调用coding_tree,寻址解码其中的cu
}
} while( partitioner.nextPart( cs ) );
partitioner.exitCurrSplit();
}
return lastSegment; //当前区域解码完毕,return
}
}
{
// MT
bool mtSplit = partitioner.canSplit( CU_MT_SPLIT, cs ); //是否可以mt划分
if( mtSplit )
{
const PartSplit splitMode = split_cu_mode_mt( cs, partitioner ); //解码mt划分模式
if( splitMode != CU_DONT_SPLIT )
{
partitioner.splitCurrArea( splitMode, cs ); //根据解码的mt划分模式,划分当前区域
do
{
if( !lastSegment && cs.area.blocks[partitioner.chType].contains( partitioner.currArea().blocks[partitioner.chType].pos() ) )
{
lastSegment = coding_tree(cs, partitioner, cuCtx); //子区域递归调用coding_tree,寻址解码其中的cu
}
} while( partitioner.nextPart( cs ) );
partitioner.exitCurrSplit();
return lastSegment; //当前区域解码完毕,return
}
}
}
//如果当前区域解码上面的qt和mt的划分flag,均不再划分,则表示递归到了划分树的叶子节点,当前区域即为cu区域,开始解码cu
CodingUnit& cu = cs.addCU( CS::getArea( cs, currArea, partitioner.chType ), partitioner.chType );
partitioner.setCUData( cu ); //cs中添加cu,给它赋值depth等信息,接下来给它解码预测信息
cu.slice = cs.slice;
#if HEVC_TILES_WPP
cu.tileIdx = cs.picture->tileMap->getTileIdxMap( currArea.lumaPos() );
#endif
// Predict QP on start of quantization group
if( pps.getUseDQP() && !cuCtx.isDQPCoded && CU::isQGStart( cu ) )
{
cuCtx.qp = CU::predictQP( cu, cuCtx.qp );
}
cu.qp = cuCtx.qp; //NOTE: CU QP can be changed by deltaQP signaling at TU level
cu.chromaQpAdj = cs.chromaQpAdj; //NOTE: CU chroma QP adjustment can be changed by adjustment signaling at TU level
// coding unit
bool isLastCtu = coding_unit( cu, partitioner, cuCtx ); //cu解码,返回isLastCtu表示是否为最后一个ctu的最后一个cu
DTRACE( g_trace_ctx, D_QP, "x=%d, y=%d, w=%d, h=%d, qp=%d\n", cu.Y().x, cu.Y().y, cu.Y().width, cu.Y().height, cu.qp );
return isLastCtu; //返回isLastCtu
}