本文主要是介绍【HEVC学习与研究】32、编码一个CU(帧内部分)1,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
在一个compressSlice()中,在compressCU函数中实现对一个CU的编码,其中主要进行了CU的初始化,以及实际的编码操作。
Void TEncCu::compressCU( TComDataCU*& rpcCU )
{// initialize CU datam_ppcBestCU[0]->initCU( rpcCU->getPic(), rpcCU->getAddr() );m_ppcTempCU[0]->initCU( rpcCU->getPic(), rpcCU->getAddr() );#if RATE_CONTROL_LAMBDA_DOMAINm_addSADDepth = 0;m_LCUPredictionSAD = 0;m_temporalSAD = 0;
#endif// analysis of CUxCompressCU( m_ppcBestCU[0], m_ppcTempCU[0], 0 );#if ADAPTIVE_QP_SELECTIONif( m_pcEncCfg->getUseAdaptQpSelect() ){if(rpcCU->getSlice()->getSliceType()!=I_SLICE) //IIII{xLcuCollectARLStats( rpcCU);}}
#endif
}
其中完成实际编码一个CU操作的是xCompressCU方法。前面的综述中已经描述过,每一个CTU按照四叉树结构进行划分,CompressCU中调用的
xCompressCU则相当于四叉树的根节点。另外,在每一个xCompressCU方法中间,会对每一个CU进行分析判断是否进行下一级划分。
xCompressCU函数由于包含了Intra和InterFrame编码的代码,因此同样非常长,共有600余行。下面着重对帧内编码的部分做一下梳理。
实现帧内编码的部分代码如下:
Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, UInt uiDepth, PartSize eParentPartSize )
{
//......
// do normal intra modesif ( !bEarlySkip ){// speedup for inter framesif( rpcBestCU->getSlice()->getSliceType() == I_SLICE || rpcBestCU->getCbf( 0, TEXT_LUMA ) != 0 ||rpcBestCU->getCbf( 0, TEXT_CHROMA_U ) != 0 ||rpcBestCU->getCbf( 0, TEXT_CHROMA_V ) != 0 ) // avoid very complex intra if it is unlikely{xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_2Nx2N );rpcTempCU->initEstData( uiDepth, iQP );if( uiDepth == g_uiMaxCUDepth - g_uiAddCUDepth ){if( rpcTempCU->getWidth(0) > ( 1 << rpcTempCU->getSlice()->getSPS()->getQuadtreeTULog2MinSize() ) ){xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_NxN );rpcTempCU->initEstData( uiDepth, iQP );}}}}
//......
}
在这部分代码中xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_2Nx2N )查看了各种intra预测模式下的代价:
Void TEncCu::xCheckRDCostIntra( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, PartSize eSize )
{UInt uiDepth = rpcTempCU->getDepth( 0 );rpcTempCU->setSkipFlagSubParts( false, 0, uiDepth );rpcTempCU->setPartSizeSubParts( eSize, 0, uiDepth );rpcTempCU->setPredModeSubParts( MODE_INTRA, 0, uiDepth );rpcTempCU->setCUTransquantBypassSubParts( m_pcEncCfg->getCUTransquantBypassFlagValue(), 0, uiDepth );Bool bSeparateLumaChroma = true; // choose estimation modeUInt uiPreCalcDistC = 0;if( !bSeparateLumaChroma ){m_pcPredSearch->preestChromaPredMode( rpcTempCU, m_ppcOrigYuv[uiDepth], m_ppcPredYuvTemp[uiDepth] );}m_pcPredSearch ->estIntraPredQT ( rpcTempCU, m_ppcOrigYuv[uiDepth], m_ppcPredYuvTemp[uiDepth], m_ppcResiYuvTemp[uiDepth], m_ppcRecoYuvTemp[uiDepth], uiPreCalcDistC, bSeparateLumaChroma );m_ppcRecoYuvTemp[uiDepth]->copyToPicLuma(rpcTempCU->getPic()->getPicYuvRec(), rpcTempCU->getAddr(), rpcTempCU->getZorderIdxInCU() );m_pcPredSearch ->estIntraPredChromaQT( rpcTempCU, m_ppcOrigYuv[uiDepth], m_ppcPredYuvTemp[uiDepth], m_ppcResiYuvTemp[uiDepth], m_ppcRecoYuvTemp[uiDepth], uiPreCalcDistC );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, true );m_pcEntropyCoder->encodeIPCMInfo(rpcTempCU, 0, true );// Encode CoefficientsBool bCodeDQP = getdQPFlag();m_pcEntropyCoder->encodeCoeff( rpcTempCU, 0, uiDepth, rpcTempCU->getWidth (0), rpcTempCU->getHeight(0), bCodeDQP );setdQPFlag( bCodeDQP );if( m_bUseSBACRD ) m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[uiDepth][CI_TEMP_BEST]);rpcTempCU->getTotalBits() = m_pcEntropyCoder->getNumberOfWrittenBits();if(m_pcEncCfg->getUseSBACRD()){rpcTempCU->getTotalBins() = ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded();}rpcTempCU->getTotalCost() = m_pcRdCost->calcRdCost( rpcTempCU->getTotalBits(), rpcTempCU->getTotalDistortion() );xCheckDQP( rpcTempCU );xCheckBestMode(rpcBestCU, rpcTempCU, uiDepth);
}
在这个函数中,调用了estIntraPredQT和estIntraPredChromaQT方法,这两个函数的作用是类似的,区别只在于前者针对亮度分量后者针对色度分量。我们重点关注对亮度分量的操作,即estIntraPredQT函数。
下面是estIntraPredQT的一段代码:
Void
TEncSearch::estIntraPredQT( TComDataCU* pcCU, TComYuv* pcOrgYuv, TComYuv* pcPredYuv, TComYuv* pcResiYuv, TComYuv* pcRecoYuv,UInt& ruiDistC,Bool bLumaOnly )
{
//......for( Int modeIdx = 0; modeIdx < numModesAvailable; modeIdx++ ){UInt uiMode = modeIdx;predIntraLumaAng( pcCU->getPattern(), uiMode, piPred, uiStride, uiWidth, uiHeight, bAboveAvail, bLeftAvail );// use hadamard transform hereUInt uiSad = m_pcRdCost->calcHAD(g_bitDepthY, piOrg, uiStride, piPred, uiStride, uiWidth, uiHeight );UInt iModeBits = xModeBitsIntra( pcCU, uiMode, uiPU, uiPartOffset, uiDepth, uiInitTrDepth );Double cost = (Double)uiSad + (Double)iModeBits * m_pcRdCost->getSqrtLambda();CandNum += xUpdateCandList( uiMode, cost, numModesForFullRD, uiRdModeList, CandCostList );}
//......
}
这个for循环的意义就是遍历多种帧内预测模式,其中numModesAvailable==35,对应整个intra的35个模式。
在predIntraLumaAng函数中,编码器完成计算出当前PU的预测值:
Void TComPrediction::predIntraLumaAng(TComPattern* pcTComPattern, UInt uiDirMode, Pel* piPred, UInt uiStride, Int iWidth, Int iHeight, Bool bAbove, Bool bLeft )
{Pel *pDst = piPred;Int *ptrSrc;assert( g_aucConvertToBit[ iWidth ] >= 0 ); // 4x 4assert( g_aucConvertToBit[ iWidth ] <= 5 ); // 128x128assert( iWidth == iHeight );ptrSrc = pcTComPattern->getPredictorPtr( uiDirMode, g_aucConvertToBit[ iWidth ] + 2, m_piYuvExt );// get starting pixel in blockInt sw = 2 * iWidth + 1;// Create the predictionif ( uiDirMode == PLANAR_IDX ){xPredIntraPlanar( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight );}else{if ( (iWidth > 16) || (iHeight > 16) ){xPredIntraAng(g_bitDepthY, ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, uiDirMode, bAbove, bLeft, false );}else{xPredIntraAng(g_bitDepthY, ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, uiDirMode, bAbove, bLeft, true );if( (uiDirMode == DC_IDX ) && bAbove && bLeft ){xDCPredFiltering( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight);}}}
}
在这个函数中主要起作用的是xPredIntraPlanar和xPredIntraAng两个函数,另外在PU大小小于16×16,且模式为DC模式时还会调用xDCPredFiltering函数。在这里我们主要关心前面两个。
xPredIntraPlanar的作用是以平面模式构建当前PU的帧内预测块:
Void TComPrediction::xPredIntraPlanar( Int* pSrc, Int srcStride, Pel* rpDst, Int dstStride, UInt width, UInt height )
{assert(width == height);Int k, l, bottomLeft, topRight;Int horPred;Int leftColumn[MAX_CU_SIZE], topRow[MAX_CU_SIZE], bottomRow[MAX_CU_SIZE], rightColumn[MAX_CU_SIZE];UInt blkSize = width;UInt offset2D = width;UInt shift1D = g_aucConvertToBit[ width ] + 2;UInt shift2D = shift1D + 1;// Get left and above reference column and rowfor(k=0;k<blkSize+1;k++){topRow[k] = pSrc[k-srcStride];leftColumn[k] = pSrc[k*srcStride-1];}// Prepare intermediate variables used in interpolationbottomLeft = leftColumn[blkSize];topRight = topRow[blkSize];for (k=0;k<blkSize;k++){bottomRow[k] = bottomLeft - topRow[k];rightColumn[k] = topRight - leftColumn[k];topRow[k] <<= shift1D;leftColumn[k] <<= shift1D;}// Generate prediction signalfor (k=0;k<blkSize;k++){horPred = leftColumn[k] + offset2D;for (l=0;l<blkSize;l++){horPred += rightColumn[k];topRow[l] += bottomRow[l];rpDst[k*dstStride+l] = ( (horPred + topRow[l]) >> shift2D );}}
}
而
xPredIntraAng函数则承担了其他模式的预测块构建,也即,不同的模式索引值代表N多中不同的预测角度,从这些角度上以参考数据构建预测块。
Void TComPrediction::xPredIntraAng(Int bitDepth, Int* pSrc, Int srcStride, Pel*& rpDst, Int dstStride, UInt width, UInt height, UInt dirMode, Bool blkAboveAvailable, Bool blkLeftAvailable, Bool bFilter )
{Int k,l;Int blkSize = width;Pel* pDst = rpDst;// Map the mode index to main prediction direction and angleassert( dirMode > 0 ); //no planarBool modeDC = dirMode < 2;Bool modeHor = !modeDC && (dirMode < 18);Bool modeVer = !modeDC && !modeHor;Int intraPredAngle = modeVer ? (Int)dirMode - VER_IDX : modeHor ? -((Int)dirMode - HOR_IDX) : 0;Int absAng = abs(intraPredAngle);Int signAng = intraPredAngle < 0 ? -1 : 1;// Set bitshifts and scale the angle parameter to block sizeInt angTable[9] = {0, 2, 5, 9, 13, 17, 21, 26, 32};Int invAngTable[9] = {0, 4096, 1638, 910, 630, 482, 390, 315, 256}; // (256 * 32) / AngleInt invAngle = invAngTable[absAng];absAng = angTable[absAng];intraPredAngle = signAng * absAng;// Do the DC predictionif (modeDC){Pel dcval = predIntraGetPredValDC(pSrc, srcStride, width, height, blkAboveAvailable, blkLeftAvailable);for (k=0;k<blkSize;k++){for (l=0;l<blkSize;l++){pDst[k*dstStride+l] = dcval;}}}// Do angular predictionselse{Pel* refMain;Pel* refSide;Pel refAbove[2*MAX_CU_SIZE+1];Pel refLeft[2*MAX_CU_SIZE+1];// Initialise the Main and Left reference array.if (intraPredAngle < 0){for (k=0;k<blkSize+1;k++){refAbove[k+blkSize-1] = pSrc[k-srcStride-1];}for (k=0;k<blkSize+1;k++){refLeft[k+blkSize-1] = pSrc[(k-1)*srcStride-1];}refMain = (modeVer ? refAbove : refLeft) + (blkSize-1);refSide = (modeVer ? refLeft : refAbove) + (blkSize-1);// Extend the Main reference to the left.Int invAngleSum = 128; // rounding for (shift by 8)for (k=-1; k>blkSize*intraPredAngle>>5; k--){invAngleSum += invAngle;refMain[k] = refSide[invAngleSum>>8];}}else{for (k=0;k<2*blkSize+1;k++){refAbove[k] = pSrc[k-srcStride-1];}for (k=0;k<2*blkSize+1;k++){refLeft[k] = pSrc[(k-1)*srcStride-1];}refMain = modeVer ? refAbove : refLeft;refSide = modeVer ? refLeft : refAbove;}if (intraPredAngle == 0){for (k=0;k<blkSize;k++){for (l=0;l<blkSize;l++){pDst[k*dstStride+l] = refMain[l+1];}}if ( bFilter ){for (k=0;k<blkSize;k++){pDst[k*dstStride] = Clip3(0, (1<<bitDepth)-1, pDst[k*dstStride] + (( refSide[k+1] - refSide[0] ) >> 1) );}}}else{Int deltaPos=0;Int deltaInt;Int deltaFract;Int refMainIndex;for (k=0;k<blkSize;k++){deltaPos += intraPredAngle;deltaInt = deltaPos >> 5;deltaFract = deltaPos & (32 - 1);if (deltaFract){// Do linear filteringfor (l=0;l<blkSize;l++){refMainIndex = l+deltaInt+1;pDst[k*dstStride+l] = (Pel) ( ((32-deltaFract)*refMain[refMainIndex]+deltaFract*refMain[refMainIndex+1]+16) >> 5 );}}else{// Just copy the integer samplesfor (l=0;l<blkSize;l++){pDst[k*dstStride+l] = refMain[l+deltaInt+1];}}}}// Flip the block if this is the horizontal modeif (modeHor){Pel tmp;for (k=0;k<blkSize-1;k++){for (l=k+1;l<blkSize;l++){tmp = pDst[k*dstStride+l];pDst[k*dstStride+l] = pDst[l*dstStride+k];pDst[l*dstStride+k] = tmp;}}}}
}
具体的预测块构建的原理,将在下篇文章中详述。
这篇关于【HEVC学习与研究】32、编码一个CU(帧内部分)1的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!