HEVC函式入門(2)——幀內編碼一個CU
這裡依然整理自http://blog.csdn.net/shaqoneal/article/details/37500715
且閱讀CU這部分主要對我而言是為了QP。另外一個方向是Tile不要迷失啦!!!
提醒我自己看http://blog.csdn.net/leixiaohua1020/article/details/46483721?locationNum=15&fps=1
在一個compressSlice()中,在compressCU函式中實現對一個CU的編碼,其中主要進行了CU的初始化,以及實際的編碼操作。
其中完成實際編碼一個CU操作的是xCompressCU方法。前面的綜述中已經描述過,每一個CTU按照四叉樹結構進行劃分,CompressCU中呼叫的xCompressCU則相當於四叉樹的根節點。另外,在每一個xCompressCU方法中間,會對每一個CU進行分析判斷是否進行下一級劃分。(不是64*64那個節點,而是每一個深度(depth)的節點)
xCompressCU函式由於包含了Intra和InterFrame編碼的程式碼,因此同樣非常長,共有600餘行。下面著重對幀內編碼的部分做一下梳理。
實現幀內編碼的部分程式碼如下(剛剛找到哈哈):
// do normal intra modes
// speedup for inter frames
Double intraCost = 0.0;
if((rpcBestCU->getSlice()->getSliceType() == I_SLICE) ||
(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, intraCost, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) ) ;
rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
if( uiDepth == g_uiMaxCUDepth - g_uiAddCUDepth )
{
if( rpcTempCU->getWidth(0) > ( 1 << rpcTempCU->getSlice()->getSPS()->getQuadtreeTULog2MinSize() ) )
{
Double tmpIntraCost;
xCheckRDCostIntra( rpcBestCU, rpcTempCU, tmpIntraCost, SIZE_NxN DEBUG_STRING_PASS_INTO(sDebug) );
intraCost = std::min(intraCost, tmpIntraCost);
rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );
}
}
}
在這部分程式碼中xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_2Nx2N )查看了各種intra預測模式下的代價:
Void TEncCu::xCheckRDCostIntra( TComDataCU *&rpcBestCU,
TComDataCU *&rpcTempCU,
Double &cost,
PartSize eSize
DEBUG_STRING_FN_DECLARE(sDebug) )
{
DEBUG_STRING_NEW(sTest)
UInt uiDepth = rpcTempCU->getDepth( 0 );
rpcTempCU->setSkipFlagSubParts( false, 0, uiDepth );
rpcTempCU->setPartSizeSubParts( eSize, 0, uiDepth );
rpcTempCU->setPredModeSubParts( MODE_INTRA, 0, uiDepth );
rpcTempCU->setChromaQpAdjSubParts( rpcTempCU->getCUTransquantBypass(0) ? 0 : m_ChromaQpAdjIdc, 0, uiDepth );
Pel resiLuma[NUMBER_OF_STORED_RESIDUAL_TYPES][MAX_CU_SIZE * MAX_CU_SIZE];
m_pcPredSearch->estIntraPredLumaQT( rpcTempCU, m_ppcOrigYuv[uiDepth], m_ppcPredYuvTemp[uiDepth], m_ppcResiYuvTemp[uiDepth], m_ppcRecoYuvTemp[uiDepth], resiLuma DEBUG_STRING_PASS_INTO(sTest) );
m_ppcRecoYuvTemp[uiDepth]->copyToPicComponent(COMPONENT_Y, rpcTempCU->getPic()->getPicYuvRec(), rpcTempCU->getCtuRsAddr(), rpcTempCU->getZorderIdxInCtu() );
if (rpcBestCU->getPic()->getChromaFormat()!=CHROMA_400)
{
m_pcPredSearch->estIntraPredChromaQT( rpcTempCU, m_ppcOrigYuv[uiDepth], m_ppcPredYuvTemp[uiDepth], m_ppcResiYuvTemp[uiDepth], m_ppcRecoYuvTemp[uiDepth], resiLuma DEBUG_STRING_PASS_INTO(sTest) );
}
m_pcEntropyCoder->resetBits();
if ( rpcTempCU->getSlice()->getPPS()->getTransquantBypassEnableFlag())
{
m_pcEntropyCoder->encodeCUTransquantBypassFlag( rpcTempCU, 0, true );
}
m_pcEntropyCoder->encodeSkipFlag ( rpcTempCU, 0, true );
m_pcEntropyCoder->encodePredMode( rpcTempCU, 0, true );
m_pcEntropyCoder->encodePartSize( rpcTempCU, 0, uiDepth, true );
m_pcEntropyCoder->encodePredInfo( rpcTempCU, 0 );
m_pcEntropyCoder->encodeIPCMInfo(rpcTempCU, 0, true );
// Encode Coefficients
Bool bCodeDQP = getdQPFlag();
Bool codeChromaQpAdjFlag = getCodeChromaQpAdjFlag();
m_pcEntropyCoder->encodeCoeff( rpcTempCU, 0, uiDepth, bCodeDQP, codeChromaQpAdjFlag );
setCodeChromaQpAdjFlag( codeChromaQpAdjFlag );
setdQPFlag( bCodeDQP );
m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[uiDepth][CI_TEMP_BEST]);
rpcTempCU->getTotalBits() = m_pcEntropyCoder->getNumberOfWrittenBits();
rpcTempCU->getTotalBins() = ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded();
rpcTempCU->getTotalCost() = m_pcRdCost->calcRdCost( rpcTempCU->getTotalBits(), rpcTempCU->getTotalDistortion() );
xCheckDQP( rpcTempCU );
cost = rpcTempCU->getTotalCost();
xCheckBestMode(rpcBestCU, rpcTempCU, uiDepth DEBUG_STRING_PASS_INTO(sDebug) DEBUG_STRING_PASS_INTO(sTest));
}
在這個函式中,呼叫了estIntraPredQT和estIntraPredChromaQT方法,這兩個函式的作用是類似的,區別只在於前者針對亮度分量後者針對色度分量。我們重點關注對亮度分量的操作,即estIntraPredQT函式。
下面是estIntraPredQT的一段程式碼(對我自己是重點):
Void
TEncSearch::estIntraPredLumaQT(TComDataCU* pcCU,
TComYuv* pcOrgYuv,
TComYuv* pcPredYuv,
TComYuv* pcResiYuv,
TComYuv* pcRecoYuv,
Pel resiLuma[NUMBER_OF_STORED_RESIDUAL_TYPES][MAX_CU_SIZE * MAX_CU_SIZE]
DEBUG_STRING_FN_DECLARE(sDebug))
{
//......
for( Int modeIdx = 0; modeIdx < numModesAvailable; modeIdx++ )
{
UInt uiMode = modeIdx;
Distortion uiSad = 0;
const Bool bUseFilter=TComPrediction::filteringIntraReferenceSamples(COMPONENT_Y, uiMode, puRect.width, puRect.height, chFmt, pcCU->getSlice()->getSPS()->getDisableIntraReferenceSmoothing());
predIntraAng( COMPONENT_Y, uiMode, piOrg, uiStride, piPred, uiStride, tuRecurseWithPU, bAboveAvail, bLeftAvail, bUseFilter, TComPrediction::UseDPCMForFirstPassIntraEstimation(tuRecurseWithPU, uiMode) );
// use hadamard transform here
uiSad+=distParam.DistFunc(&distParam);
UInt iModeBits = 0;
// NB xModeBitsIntra will not affect the mode for chroma that may have already been pre-estimated.
iModeBits+=xModeBitsIntra( pcCU, uiMode, uiPartOffset, uiDepth, uiInitTrDepth, CHANNEL_TYPE_LUMA );
Double cost = (Double)uiSad + (Double)iModeBits * sqrtLambdaForFirstPass;
#ifdef DEBUG_INTRA_SEARCH_COSTS
std::cout << "1st pass mode " << uiMode << " SAD = " << uiSad << ", mode bits = " << iModeBits << ", cost = " << cost << "\n";
#endif
CandNum += xUpdateCandList( uiMode, cost, numModesForFullRD, uiRdModeList, CandCostList );
}
}
//......
}
這個for迴圈的意義就是遍歷多種幀內預測模式,其中numModesAvailable==35,對應整個intra的35個模式。
在predIntraLumaAng函式中(在16.3我的版本中是predIntraAng),編碼器完成計算出當前PU的預測值:
Void TComPrediction::predIntraAng( const ComponentID compID, UInt uiDirMode, Pel* piOrg /* Will be null for decoding */, UInt uiOrgStride, Pel* piPred, UInt uiStride, TComTU &rTu, Bool bAbove, Bool bLeft, const Bool bUseFilteredPredSamples, const Bool bUseLosslessDPCM )
{
const ChromaFormat format = rTu.GetChromaFormat();
const ChannelType channelType = toChannelType(compID);
const TComRectangle &rect = rTu.getRect(isLuma(compID) ? COMPONENT_Y : COMPONENT_Cb);
const Int iWidth = rect.width;
const Int iHeight = rect.height;
assert( g_aucConvertToBit[ iWidth ] >= 0 ); // 4x 4
assert( g_aucConvertToBit[ iWidth ] <= 5 ); // 128x128
//assert( iWidth == iHeight );
Pel *pDst = piPred;
// get starting pixel in block
const Int sw = (2 * iWidth + 1);
if ( bUseLosslessDPCM )
{
const Pel *ptrSrc = getPredictorPtr( compID, false );
// Sample Adaptive intra-Prediction (SAP)
if (uiDirMode==HOR_IDX)
{
// left column filled with reference samples
// remaining columns filled with piOrg data (if available).
for(Int y=0; y<iHeight; y++)
{
piPred[y*uiStride+0] = ptrSrc[(y+1)*sw];
}
if (piOrg!=0)
{
piPred+=1; // miss off first column
for(Int y=0; y<iHeight; y++, piPred+=uiStride, piOrg+=uiOrgStride)
{
memcpy(piPred, piOrg, (iWidth-1)*sizeof(Pel));
}
}
}
else // VER_IDX
{
// top row filled with reference samples
// remaining rows filled with piOrd data (if available)
for(Int x=0; x<iWidth; x++)
{
piPred[x] = ptrSrc[x+1];
}
if (piOrg!=0)
{
piPred+=uiStride; // miss off the first row
for(Int y=1; y<iHeight; y++, piPred+=uiStride, piOrg+=uiOrgStride)
{
memcpy(piPred, piOrg, iWidth*sizeof(Pel));
}
}
}
}
else
{
const Pel *ptrSrc = getPredictorPtr( compID, bUseFilteredPredSamples );
if ( uiDirMode == PLANAR_IDX )
{
xPredIntraPlanar( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType, format );
}
else
{
// Create the prediction
TComDataCU *const pcCU = rTu.getCU();
const UInt uiAbsPartIdx = rTu.GetAbsPartIdxTU();
const Bool enableEdgeFilters = !(pcCU->isRDPCMEnabled(uiAbsPartIdx) && pcCU->getCUTransquantBypass(uiAbsPartIdx));
#if O0043_BEST_EFFORT_DECODING
xPredIntraAng( g_bitDepthInStream[channelType], ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType, format, uiDirMode, bAbove, bLeft, enableEdgeFilters );
#else
xPredIntraAng( g_bitDepth[channelType], ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType, format, uiDirMode, bAbove, bLeft, enableEdgeFilters );
#endif
if(( uiDirMode == DC_IDX ) && bAbove && bLeft )
{
xDCPredFiltering( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType );
}
}
}
}
在這個函式中主要起作用的是xPredIntraPlanar和xPredIntraAng兩個函式,另外在PU大小小於16×16,且模式為DC模式時還會呼叫xDCPredFiltering函式。在這裡我們主要關心前面兩個。
xPredIntraPlanar的作用是以平面模式構建當前PU的幀內預測塊,而xPredIntraAng函式則承擔了其他模式的預測塊構建,也即,不同的模式索引值代表N多中不同的預測角度,從這些角度上以參考資料構建預測塊。