先推荐几篇帧内预测的文章:
https://blog.csdn.net/shaqoneal/article/details/44856469
https://blog.csdn.net/nb_vol_1/article/details/51144828
https://blog.csdn.net/cpp12341234/article/details/46043615
https://blog.csdn.net/m0_37579288/article/details/79153952
和HEVC_CJL大神的帧内预测系列https://blog.csdn.net/HEVC_CJL/article/details/8175721
下面写一下我对HM16.9帧内预测部分代码的理解。
编码的入口函数是encmain.cpp文件中的main函数,调用encode函数进行编码
cTAppEncTop.encode();
TAppEncTop::encode函数的处理流程是读入m_iGOPSize大小的帧统一处理,调用下面函数
m_cTEncTop.encode( bEos, flush ? 0 : pcPicYuvOrg, flush ? 0 : &cPicYuvTrueOrg, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded );//帧编码
TEncTop::encode函数调用下面函数处理GOP:
// compress GOP
m_cGOPEncoder.compressGOP(m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, accessUnitsOut, false, false, snrCSC, m_printFrameMSE);
TEncGOP::compressGOP函数的处理流程是遍历GOP中的每一帧,处理每一帧的Slice:
m_pcSliceEncoder->compressSlice ( pcPic, false, false );
TEncSlice::compressSlice函数是对Slice中的每一个CTU(64x64)进行处理:
for( UInt ctuTsAddr = startCtuTsAddr; ctuTsAddr < boundingCtuTsAddr; ++ctuTsAddr )
{
...
// initialize CTU encoder
TComDataCU* pCtu = pcPic->getCtu( ctuRsAddr );
pCtu->initCtu( pcPic, ctuRsAddr );
...
// run CTU trial encoder
m_pcCuEncoder->compressCtu( pCtu );
...
m_uiPicTotalBits += pCtu->getTotalBits();
m_dPicRdCost += pCtu->getTotalCost();
m_uiPicDist += pCtu->getTotalDistortion();
}
TEncCu::compressCtu函数就是调用xCompressCU函数处理CU,其中最优的CU划分存储在m_ppcBestCU[0]变量中:
xCompressCU( m_ppcBestCU[0], m_ppcTempCU[0], 0 DEBUG_STRING_PASS_INTO(sDebug) );
对于帧内预测,TEncCu::xCompressCU函数的处理流程是先判断当前CU是否到边界,如果不到,则进行帧内预测的处理;然后判断当前CU是否可以继续划分CU,若可以,则划分成4个CU,递归调用xCompressCU函数进行处理:
Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, const UInt uiDepth DEBUG_STRING_FN_DECLARE(sDebug_), PartSize eParentPartSize )
{
//变量定义和初始化省略
...
//获取当前CU的左、右下像素位置,用于判断是否到达边界
const UInt uiLPelX = rpcBestCU->getCUPelX();
const UInt uiRPelX = uiLPelX + rpcBestCU->getWidth(0) - 1;//m_puhWidth[uiIdx]
const UInt uiTPelY = rpcBestCU->getCUPelY();
const UInt uiBPelY = uiTPelY + rpcBestCU->getHeight(0) - 1;
const UInt uiWidth = rpcBestCU->getWidth(0);
//各种控制判断处理,省略
...
//判断是否到边界
const Bool bBoundary = !( uiRPelX < sps.getPicWidthInLumaSamples() && uiBPelY < sps.getPicHeightInLumaSamples() );
if ( !bBoundary )
{
...
// do inter modes, SKIP and 2Nx2N 帧间模式省略
if( rpcBestCU->getSlice()->getSliceType() != I_SLICE )
{
...
}
...
// do normal intra modes帧内模式
// speedup for inter frames
if((rpcBestCU->getSlice()->getSliceType() == I_SLICE) ||
((!m_pcEncCfg->getDisableIntraPUsInInterSlices()) && (
(rpcBestCU->getCbf( 0, COMPONENT_Y ) != 0) ||
((rpcBestCU->getCbf( 0, COMPONENT_Cb ) != 0) && (numberValidComponents > COMPONENT_Cb)) ||
((rpcBestCU->getCbf( 0, COMPONENT_Cr ) != 0) && (numberValidComponents > COMPONENT_Cr)) // avoid very complex intra if it is unlikely
)))//判断是否进行帧内模式
{
xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) );//帧内模式处理入口,2Nx2N的PU划分
rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
if( uiDepth == sps.getLog2DiffMaxMinCodingBlockSize())//如果当前CU的深度等于CB的最大块和最小块的差距,正常是3
{
if( rpcTempCU->getWidth(0) > ( 1 << sps.getQuadtreeTULog2MinSize() ) )//若当前CU的长大于最小TU块长度,正常是4
{
xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_NxN DEBUG_STRING_PASS_INTO(sDebug) );//进行NxN的PU划分进行处理
rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
}
}
}
...
}
...
//判断子块是否到边界
const Bool bSubBranch = bBoundary || !( m_pcEncCfg->getUseEarlyCU() && rpcBestCU->getTotalCost()!=MAX_DOUBLE && rpcBestCU->isSkipped(0) );
//若当前CU可以继续划分
if( bSubBranch && uiDepth < sps.getLog2DiffMaxMinCodingBlockSize() && (!getFastDeltaQp() || uiWidth > fastDeltaQPCuMaxSize || bBoundary))
{
...
//划分成4个CU继续调用 xCompressCU函数递归
for ( UInt uiPartUnitIdx = 0; uiPartUnitIdx < 4; uiPartUnitIdx++ )
{
if ( !(rpcBestCU->getTotalCost()!=MAX_DOUBLE && rpcBestCU->isInter(0)) )
{
xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth DEBUG_STRING_PASS_INTO(sChild), NUMBER_OF_PART_SIZES );
}
else
{
xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth DEBUG_STRING_PASS_INTO(sChild), rpcBestCU->getPartitionSize(0) );
}
DEBUG_STRING_APPEND(sTempDebug, sChild)
#else
xCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth );
...
}
...
}
...
}
TEncCu::xCheckRDCostIntra函数是帧内预测的入口函数,它调用estIntraPredLumaQT函数进行亮度分量的预测、变换、量化和编码;调用estIntraPredChromaQT函数进行色度分量的处理;最后调用xCheckBestMode函数check最优的模式:
Void TEncCu::xCheckRDCostIntra( TComDataCU *&rpcBestCU,
TComDataCU *&rpcTempCU,
PartSize eSize
DEBUG_STRING_FN_DECLARE(sDebug) )
{
DEBUG_STRING_NEW(sTest)
if(getFastDeltaQp())//默认false
{
const TComSPS &sps=*(rpcTempCU->getSlice()-&