HEVC代码学习34:compressSlice函数
转载自https://blog.csdn.net/lin453701006/article/details/78673978侵删
compressSlice,是Slice层编码的入口函数,主要完成的功能就是Slice层编码参数的初始化,其中会调用compressCtu(其中会调用xCompressCU,对CU进行划分)和encodeCtu(其中对调用xEncodeCU,对CU进行编码),这两个函数只是入口函数,十分简单,在本文第二部分进行分析。
compressSlice
主要工作流程:
1.计算Slice的起始CTU和边界CTU。
2.初始化率失真参数:bit、RD cost、失真。
3.初始化Sbac熵编码器,根据当前Slice设置参数。
4.遍历当前Slice中的CTU,对CU进行划分和编码,计算bit、RD cost、失真。其中调用了重要函数compressCtu和encodeCtu。
推荐阅读http://blog.csdn.net/nb_vol_1/article/details/51151803,不过大神使用的HM版本较早,没有引入CTU,使用的是LCU。
/** \param pcPic picture class
*/
//Slice层编码
Void TEncSlice::compressSlice( TComPic* pcPic, const Bool bCompressEntireSlice, const Bool bFastDeltaQP )
{
// if bCompressEntireSlice is true, then the entire slice (not slice segment) is compressed,
// effectively disabling the slice-segment-mode.
//起始CTU序号
UInt startCtuTsAddr;
//CTU边界序号
UInt boundingCtuTsAddr;
//当前Slice
TComSlice* const pcSlice = pcPic->getSlice(getSliceIdx());
//将Slice中当前bit置零
pcSlice->setSliceSegmentBits(0);
//计算当前Slice中的起始CTU和边界CTU
xDetermineStartAndBoundingCtuTsAddr ( startCtuTsAddr, boundingCtuTsAddr, pcPic );
//bCompressEntireSlice默认为false
if (bCompressEntireSlice)
{
boundingCtuTsAddr = pcSlice->getSliceCurEndCtuTsAddr();
pcSlice->setSliceSegmentCurEndCtuTsAddr(boundingCtuTsAddr);
}
// initialize cost values - these are used by precompressSlice (they should be parameters).
//初始化总bit、RD cost和失真
m_uiPicTotalBits = 0;
m_dPicRdCost = 0; // NOTE: This is a write-only variable!
m_uiPicDist = 0;
//初始化熵编码器
m_pcEntropyCoder->setEntropyCoder ( m_pppcRDSbacCoder[0][CI_CURR_BEST] );
//根据当前Slice设置熵编码参数
m_pcEntropyCoder->resetEntropy ( pcSlice );
//加载熵编码器SBAC
TEncBinCABAC* pRDSbacCoder = (TEncBinCABAC *) m_pppcRDSbacCoder[0][CI_CURR_BEST]->getEncBinIf();
pRDSbacCoder->setBinCountingEnableFlag( false );
pRDSbacCoder->setBinsCoded( 0 );
//bit计数
TComBitCounter tempBitCounter;
//帧每行的CTU个数
const UInt frameWidthInCtus = pcPic->getPicSym()->getFrameWidthInCtus();
//快速DeltaQp默认关闭
m_pcCuEncoder->setFastDeltaQp(bFastDeltaQP);
//------------------------------------------------------------------------------
// Weighted Prediction parameters estimation.
//------------------------------------------------------------------------------
// calculate AC/DC values for current picture
//默认关闭
if( pcSlice->getPPS()->getUseWP() || pcSlice->getPPS()->getWPBiPred() )
{
xCalcACDCParamSlice(pcSlice);
}
const Bool bWp_explicit = (pcSlice->getSliceType()==P_SLICE && pcSlice->getPPS()->getUseWP()) || (pcSlice->getSliceType()==B_SLICE && pcSlice->getPPS()->getWPBiPred());
//bWp_explicit默认为false
if ( bWp_explicit )
{
//------------------------------------------------------------------------------
// Weighted Prediction implemented at Slice level. SliceMode=2 is not supported yet.
//------------------------------------------------------------------------------
if ( pcSlice->getSliceMode()==FIXED_NUMBER_OF_BYTES || pcSlice->getSliceSegmentMode()==FIXED_NUMBER_OF_BYTES )
{
printf("Weighted Prediction is not supported with slice mode determined by max number of bins.\n"); exit(0);
}
xEstimateWPParamSlice( pcSlice, m_pcCfg->getWeightedPredictionMethod() );
pcSlice->initWpScaling(pcSlice->getSPS());
// check WP on/off
xCheckWPEnable( pcSlice );
}
#if ADAPTIVE_QP_SELECTION
//自适应QP,默认关闭
if( m_pcCfg->getUseAdaptQpSelect() && !(pcSlice->getDependentSliceSegmentFlag()))
{
// TODO: this won't work with dependent slices: they do not have their own QP. Check fix to mask clause execution with && !(pcSlice->getDependentSliceSegmentFlag())
m_pcTrQuant->clearSliceARLCnt(); // TODO: this looks wrong for multiple slices - the results of all but the last slice will be cleared before they are used (all slices compressed, and then all slices encoded)
if(pcSlice->getSliceType()!=I_SLICE)
{
Int qpBase = pcSlice->getSliceQpBase();
pcSlice->setSliceQp(qpBase + m_pcTrQuant->getQpDelta(qpBase));
}
}
#endif
// Adjust initial state if this is the start of a dependent slice.
//调整初始状态
{
//CTU地址
const UInt ctuRsAddr = pcPic->getPicSym()->getCtuTsToRsAddrMap( startCtuTsAddr);
//当前Tile序号
const UInt currentTileIdx = pcPic->getPicSym()->getTileIdxMap(ctuRsAddr);
//当前Tile
const TComTile *pCurrentTile = pcPic->getPicSym()->getTComTile(currentTileIdx);
//Tile中第一个CTU的地址
const UInt firstCtuRsAddrOfTile = pCurrentTile->getFirstCtuRsAddr();
//独立Slice且非第一个Tile时启用
if( pcSlice->getDependentSliceSegmentFlag() && ctuRsAddr != firstCtuRsAddrOfTile )
{
// This will only occur if dependent slice-segments (m_entropyCodingSyncContextState=true) are being used.
if( pCurrentTile->getTileWidthInCtus() >= 2 || !m_pcCfg->getEntropyCodingSyncEnabledFlag() )
{
m_pppcRDSbacCoder[0][CI_CURR_BEST]->loadContexts( &m_lastSliceSegmentEndContextState );
}
}
}
// for every CTU in the slice segment (may terminate sooner if there is a byte limit on the slice-segment)
//遍历Slice中的每一个CTU,对CTU进行编码
for( UInt ctuTsAddr = startCtuTsAddr; ctuTsAddr < boundingCtuTsAddr; ++ctuTsAddr )
{
//CTU地址
const UInt ctuRsAddr = pcPic->getPicSym()->getCtuTsToRsAddrMap(ctuTsAddr);
// initialize CTU encoder
//当前CTU
TComDataCU* pCtu = pcPic->getCtu( ctuRsAddr );
//初始化CTU
pCtu->initCtu( pcPic, ctuRsAddr );
// update CABAC state
//第一个CTU的地址
const UInt firstCtuRsAddrOfTile = pcPic->getPicSym()->getTComTile(pcPic->getPicSym()->getTileIdxMap(ctuRsAddr))->getFirstCtuRsAddr();
//计算Tile的x坐标
const UInt tileXPosInCtus = firstCtuRsAddrOfTile % frameWidthInCtus;
//计算CTU的x坐标
const UInt ctuXPosInCtus = ctuRsAddr % frameWidthInCtus;
//如果是Tile的第一个CTU
if (ctuRsAddr == firstCtuRsAddrOfTile)
{
//设置熵编码参数
m_pppcRDSbacCoder[0][CI_CURR_BEST]->resetEntropy(pcSlice);
}
else if ( ctuXPosInCtus == tileXPosInCtus && m_pcCfg->getEntropyCodingSyncEnabledFlag())
{
// reset and then update contexts to the state at the end of the top-right CTU (if within current slice and tile).
m_pppcRDSbacCoder[0][CI_CURR_BEST]->resetEntropy(pcSlice);
// Sync if the Top-Right is available.
TComDataCU *pCtuUp = pCtu->getCtuAbove();
if ( pCtuUp && ((ctuRsAddr%frameWidthInCtus+1) < frameWidthInCtus) )
{
TComDataCU *pCtuTR = pcPic->getCtu( ctuRsAddr - frameWidthInCtus + 1 );
if ( pCtu->CUIsFromSameSliceAndTile(pCtuTR) )
{
// Top-Right is available, we use it.
m_pppcRDSbacCoder[0][CI_CURR_BEST]->loadContexts( &m_entropyCodingSyncContextState );
}
}
}
// set go-on entropy coder (used for all trial encodings - the cu encoder and encoder search also have a copy of the same pointer)
//设置熵编码器
m_pcEntropyCoder->setEntropyCoder ( m_pcRDGoOnSbacCoder );
m_pcEntropyCoder->setBitstream( &tempBitCounter );
tempBitCounter.resetBits();
m_pcRDGoOnSbacCoder->load( m_pppcRDSbacCoder[0][CI_CURR_BEST] ); // this copy is not strictly necessary here, but indicates that the GoOnSbacCoder
// is reset to a known state before every decision process.
((TEncBinCABAC*)m_pcRDGoOnSbacCoder->getEncBinIf())->setBinCountingEnableFlag(true);
//当前lambda
Double oldLambda = m_pcRdCost->getLambda();
//码率控制时启用
if ( m_pcCfg->getUseRateCtrl() )
{
Int estQP = pcSlice->getSliceQp();
Double estLambda = -1.0;
Double bpp = -1.0;
if ( ( pcPic->getSlice( 0 )->getSliceType() == I_SLICE && m_pcCfg->getForceIntraQP() ) || !m_pcCfg->getLCULevelRC() )
{
estQP = pcSlice->getSliceQp();
}
else
{
bpp = m_pcRateCtrl->getRCPic()->getLCUTargetBpp(pcSlice->getSliceType());
if ( pcPic->getSlice( 0 )->getSliceType() == I_SLICE)
{
estLambda = m_pcRateCtrl->getRCPic()->getLCUEstLambdaAndQP(bpp, pcSlice->getSliceQp(), &estQP);
}
else
{
estLambda = m_pcRateCtrl->getRCPic()->getLCUEstLambda( bpp );
estQP = m_pcRateCtrl->getRCPic()->getLCUEstQP ( estLambda, pcSlice->getSliceQp() );
}
estQP = Clip3( -pcSlice->getSPS()->getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, estQP );
m_pcRdCost->setLambda(estLambda, pcSlice->getSPS()->getBitDepths());
#if RDOQ_CHROMA_LAMBDA
// set lambda for RDOQ
const Double chromaLambda = estLambda / m_pcRdCost->getChromaWeight();
const Double lambdaArray[MAX_NUM_COMPONENT] = { estLambda, chromaLambda, chromaLambda };
m_pcTrQuant->setLambdas( lambdaArray );
#else
m_pcTrQuant->setLambda( estLambda );
#endif
}
m_pcRateCtrl->setRCQP( estQP );
#if ADAPTIVE_QP_SELECTION
pCtu->getSlice()->setSliceQpBase( estQP );
#endif
}
// run CTU trial encoder
//CU划分
m_pcCuEncoder->compressCtu( pCtu );
// All CTU decisions have now been made. Restore entropy coder to an initial stage, ready to make a true encode,
// which will result in the state of the contexts being correct. It will also count up the number of bits coded,
// which is used if there is a limit of the number of bytes per slice-segment.
//设置熵编码器参数
m_pcEntropyCoder->setEntropyCoder ( m_pppcRDSbacCoder[0][CI_CURR_BEST] );
m_pcEntropyCoder->setBitstream( &tempBitCounter );
pRDSbacCoder->setBinCountingEnableFlag( true );
m_pppcRDSbacCoder[0][CI_CURR_BEST]->resetBits();
pRDSbacCoder->setBinsCoded( 0 );
// encode CTU and calculate the true bit counters.
//CTU编码
m_pcCuEncoder->encodeCtu( pCtu );
pRDSbacCoder->setBinCountingEnableFlag( false );
const Int numberOfWrittenBits = m_pcEntropyCoder->getNumberOfWrittenBits();
// Calculate if this CTU puts us over slice bit size.
// cannot terminate if current slice/slice-segment would be 0 Ctu in size,
const UInt validEndOfSliceCtuTsAddr = ctuTsAddr + (ctuTsAddr == startCtuTsAddr ? 1 : 0);
// Set slice end parameter
//设置Slice结束参数
if(pcSlice->getSliceMode()==FIXED_NUMBER_OF_BYTES && pcSlice->getSliceBits()+numberOfWrittenBits > (pcSlice->getSliceArgument()<<3))
{
pcSlice->setSliceSegmentCurEndCtuTsAddr(validEndOfSliceCtuTsAddr);
pcSlice->setSliceCurEndCtuTsAddr(validEndOfSliceCtuTsAddr);
boundingCtuTsAddr=validEndOfSliceCtuTsAddr;
}
else if((!bCompressEntireSlice) && pcSlice->getSliceSegmentMode()==FIXED_NUMBER_OF_BYTES && pcSlice->getSliceSegmentBits()+numberOfWrittenBits > (pcSlice->getSliceSegmentArgument()<<3))
{
pcSlice->setSliceSegmentCurEndCtuTsAddr(validEndOfSliceCtuTsAddr);
boundingCtuTsAddr=validEndOfSliceCtuTsAddr;
}
//如果当前CTU超过了边界CTU,跳出
if (boundingCtuTsAddr <= ctuTsAddr)
{
break;
}
//设置bit数
pcSlice->setSliceBits( (UInt)(pcSlice->getSliceBits() + numberOfWrittenBits) );
pcSlice->setSliceSegmentBits(pcSlice->getSliceSegmentBits()+numberOfWrittenBits);
// Store probabilities of second CTU in line into buffer - used only if wavefront-parallel-processing is enabled.
//默认关闭
if ( ctuXPosInCtus == tileXPosInCtus+1 && m_pcCfg->getEntropyCodingSyncEnabledFlag())
{
m_entropyCodingSyncContextState.loadContexts(m_pppcRDSbacCoder[0][CI_CURR_BEST]);
}
//码率控制时启用
if ( m_pcCfg->getUseRateCtrl() )
{
Int actualQP = g_RCInvalidQPValue;
Double actualLambda = m_pcRdCost->getLambda();
Int actualBits = pCtu->getTotalBits();
Int numberOfEffectivePixels = 0;
for ( Int idx = 0; idx < pcPic->getNumPartitionsInCtu(); idx++ )
{
if ( pCtu->getPredictionMode( idx ) != NUMBER_OF_PREDICTION_MODES && ( !pCtu->isSkipped( idx ) ) )
{
numberOfEffectivePixels = numberOfEffectivePixels + 16;
break;
}
}
if ( numberOfEffectivePixels == 0 )
{
actualQP = g_RCInvalidQPValue;
}
else
{
actualQP = pCtu->getQP( 0 );
}
m_pcRdCost->setLambda(oldLambda, pcSlice->getSPS()->getBitDepths());
m_pcRateCtrl->getRCPic()->updateAfterCTU( m_pcRateCtrl->getRCPic()->getLCUCoded(), actualBits, actualQP, actualLambda,
pCtu->getSlice()->getSliceType() == I_SLICE ? 0 : m_pcCfg->getLCULevelRC() );
}
//计算总的bit、RD cost、失真
m_uiPicTotalBits += pCtu->getTotalBits();
m_dPicRdCost += pCtu->getTotalCost();
m_uiPicDist += pCtu->getTotalDistortion();
}
// store context state at the end of this slice-segment, in case the next slice is a dependent slice and continues using the CABAC contexts.
if( pcSlice->getPPS()->getDependentSliceSegmentsEnabledFlag() )
{
m_lastSliceSegmentEndContextState.loadContexts( m_pppcRDSbacCoder[0][CI_CURR_BEST] );//ctx end of dep.slice
}
// stop use of temporary bit counter object.
m_pppcRDSbacCoder[0][CI_CURR_BEST]->setBitstream(NULL);
m_pcRDGoOnSbacCoder->setBitstream(NULL); // stop use of tempBitCounter.
// TODO: optimise cabac_init during compress slice to improve multi-slice operation
//if (pcSlice->getPPS()->getCabacInitPresentFlag() && !pcSlice->getPPS()->getDependentSliceSegmentsEnabledFlag())
//{
// m_encCABACTableIdx = m_pcEntropyCoder->determineCabacInitIdx();
//}
//else
//{
// m_encCABACTableIdx = pcSlice->getSliceType();
//}
}
compressCtu和encodeCtu
compressCtu是块划分的入口函数,主要包含两部分:初始化CTU和调用xCompressCU进行块划分。xCompressCU是非常重要的函数,详细介绍见:http://blog.csdn.net/lin453701006/article/details/72401400
代码分析如下:
/**
\param pCtu pointer of CU data class
*/
Void TEncCu::compressCtu( TComDataCU* pCtu )
{
// initialize CU data
//将最优CU置为当前CTU
m_ppcBestCU[0]->initCtu( pCtu->getPic(), pCtu->getCtuRsAddr() );
//将当前CU置为当前CTU
m_ppcTempCU[0]->initCtu( pCtu->getPic(), pCtu->getCtuRsAddr() );
// analysis of CU
DEBUG_STRING_NEW(sDebug)
//递归进行块划分
xCompressCU( m_ppcBestCU[0], m_ppcTempCU[0], 0 DEBUG_STRING_PASS_INTO(sDebug) );
DEBUG_STRING_OUTPUT(std::cout, sDebug)
#if ADAPTIVE_QP_SELECTION
if( m_pcEncCfg->getUseAdaptQpSelect() )
{
if(pCtu->getSlice()->getSliceType()!=I_SLICE) //IIII
{
xCtuCollectARLStats( pCtu );
}
}
#endif
}
encodeCtu是CTU编码的入口函数,主要包含两部分:根据情况设置QP相关Flag和调用xEncodeCU进行CU编码。xEncodeCU详细分析见http://blog.csdn.net/lin453701006/article/details/78688889。
/** \param pCtu pointer of CU data class
*/
Void TEncCu::encodeCtu ( TComDataCU* pCtu )
{
//当开启DQP时,置flag
if ( pCtu->getSlice()->getPPS()->getUseDQP() )
{
setdQPFlag(true);
}
//当开启色度QP自适应时,置flag
if ( pCtu->getSlice()->getUseChromaQpAdj() )
{
setCodeChromaQpAdjFlag(true);
}
// Encode CU data
//CU编码
xEncodeCU( pCtu, 0, 0 );
}