【HEVC代码阅读】帧内预测
HEVC的帧内预测的架构分为三个步骤:
①构建参考像素数组;②生成预测像素;③后处理操作。
HEVC标准将这三个步骤进行了精密设计,以求达到较高的编码效率,同时降低编码和解码端的运算要求。HEVC标准的多种预定义的预测模式构成一个模式集合,可以对包括视频和静态图像中的多种内容进行预测建模的方法。HEVC的角度预测提供了对包含方向性纹理的物体更高的预测精确度,此外平面和DC模式可以高效地表示图像的平滑区域。
接下来记录一下我对HEVC中帧内预测部分代码的理解,不正确的地方望指教
编码的入口函数是encmain.cpp文件中的main函数,调用encode函数进行编码
Void TAppEncTop::encode()
{fstream bitstreamFile(m_bitstreamFileName.c_str(), fstream::binary | fstream::out); //以二进制输出方式打开比特流文件if (!bitstreamFile) //判断比特流文件是否存在,若bitstreamFile为空则输出错误提示并退出程序{fprintf(stderr, "\nfailed to open bitstream file `%s' for writing\n", m_bitstreamFileName.c_str());exit(EXIT_FAILURE);}TComPicYuv* pcPicYuvOrg = new TComPicYuv; //定义YUV类TComPicYuv* pcPicYuvRec = NULL;// initialize internal class & member variables 初始化内部类和成员变量xInitLibCfg(); //初始化编码器的参数xCreateLib(); //创建视频源文件以及编码重建后的二进制视频文件和程序的连接,初始化GOP、Slice、CU的部分对象函数xInitLib(m_isField); //初始化SPS、PPS,GOP、Slice、CU的部分对象函数,变换和量化类,编码器搜索类函数printChromaFormat(); //打印输入和输出的YUV格式// main encoder loop 初始化编码器中的部分变量Int iNumEncoded = 0; //记录已编码帧数Bool bEos = false; //控制编码是否结束const InputColourSpaceConversion ipCSC = m_inputColourSpaceConvert;const InputColourSpaceConversion snrCSC = (!m_snrInternalColourSpace) ? m_inputColourSpaceConvert : IPCOLOURSPACE_UNCHANGED;list<AccessUnit> outputAccessUnits; ///< list of access units to write out. is populated by the encoding processTComPicYuv cPicYuvTrueOrg;// allocate original YUV buffer 为原始YUV缓冲区分配内存空间if( m_isField ){pcPicYuvOrg->create ( m_iSourceWidth, m_iSourceHeightOrg, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true );cPicYuvTrueOrg.create(m_iSourceWidth, m_iSourceHeightOrg, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true);}else{pcPicYuvOrg->create ( m_iSourceWidth, m_iSourceHeight, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true );cPicYuvTrueOrg.create(m_iSourceWidth, m_iSourceHeight, m_chromaFormatIDC, m_uiMaxCUWidth, m_uiMaxCUHeight, m_uiMaxTotalCUDepth, true );}while ( !bEos ) //由bEos控制,对视频帧进行编码{// get buffers 设置缓冲区xGetBuffer(pcPicYuvRec);// read input YUV file 读入输入的YUV文件m_cTVideoIOYuvInputFile.read( pcPicYuvOrg, &cPicYuvTrueOrg, ipCSC, m_aiPad, m_InputChromaFormatIDC, m_bClipInputVideoToRec709Range );// increase number of received frames 接收到帧数自增m_iFrameRcvd++;bEos = (m_isField && (m_iFrameRcvd == (m_framesToBeEncoded >> 1) )) || ( !m_isField && (m_iFrameRcvd == m_framesToBeEncoded) );Bool flush = 0;// if end of file (which is only detected on a read failure) flush the encoder of any queued pictures 文件读取完成刷新编码器中的图像队列if (m_cTVideoIOYuvInputFile.isEof()){flush = true;bEos = true;m_iFrameRcvd--;m_cTEncTop.setFramesToBeEncoded(m_iFrameRcvd);}// call encoding function for one frame 调用编码器函数对单帧进行编码if ( m_isField ){m_cTEncTop.encode( bEos, flush ? 0 : pcPicYuvOrg, flush ? 0 : &cPicYuvTrueOrg, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded, m_isTopFieldFirst );}else{m_cTEncTop.encode( bEos, flush ? 0 : pcPicYuvOrg, flush ? 0 : &cPicYuvTrueOrg, snrCSC, m_cListPicYuvRec, outputAccessUnits, iNumEncoded );}// write bistream to file if necessary 当iNumEncoded>0时写入比特流文件if ( iNumEncoded > 0 ){xWriteOutput(bitstreamFile, iNumEncoded, outputAccessUnits);outputAccessUnits.clear();}// temporally skip framesif( m_temporalSubsampleRatio > 1 ){m_cTVideoIOYuvInputFile.skipFrames(m_temporalSubsampleRatio-1, m_iSourceWidth - m_aiPad[0], m_iSourceHeight - m_aiPad[1], m_InputChromaFormatIDC);}}m_cTEncTop.printSummary(m_isField); //打印编码结果统计信息// delete original YUV buffer 删除原始YUV缓冲区pcPicYuvOrg->destroy();delete pcPicYuvOrg;pcPicYuvOrg = NULL;// delete used buffers in encoder class 删除编码器类使用的缓冲区m_cTEncTop.deletePicBuffer();cPicYuvTrueOrg.destroy();// delete buffers & classes 删除缓冲区和类xDeleteBuffer();xDestroyLib();printRateSummary(); //打印总比特率信息return;
}
TAppEncTop::encode函数的处理流程是读入m_iGOPSize大小的帧统一处理,调用下面函数
Void TEncTop::encode( Bool flush, TComPicYuv* pcPicYuvOrg, TComPicYuv* pcPicYuvTrueOrg, const InputColourSpaceConversion snrCSC, TComList<TComPicYuv*>& rcListPicYuvRecOut, std::list<AccessUnit>& accessUnitsOut, Int& iNumEncoded )
{if (pcPicYuvOrg != NULL){// get original YUV 获取原始YUVTComPic* pcPicCurr = NULL;xGetNewPicBuffer( pcPicCurr ); //给当前图像分配新的缓冲区pcPicYuvOrg->copyToPic( pcPicCurr->getPicYuvOrg() ); //将pcPicYuvOrg的信息赋给当前图像pcPicYuvTrueOrg->copyToPic( pcPicCurr->getPicYuvTrueOrg() ); //将pcPicYuvTrueOrg的信息赋给当前图像// compute image characteristics //计算图像的特征if ( getUseAdaptiveQP() ) //如果使用自适应QP,则调用TEncPreanalyzer::xPreanalyze来分析图像并计算用于QP自适应的局部图像特征{m_cPreanalyzer.xPreanalyze( dynamic_cast<TEncPic*>( pcPicCurr ) );}}if ((m_iNumPicRcvd == 0) || (!flush && (m_iPOCLast != 0) && (m_iNumPicRcvd != m_iGOPSize) && (m_iGOPSize != 0))){iNumEncoded = 0;return;}if ( m_RCEnableRateControl ) //若使用m_RCEnableRateControl,则对GOP进行初始化{m_cRateCtrl.initRCGOP( m_iNumPicRcvd );}// compress GOP 调用TEncGop::compressGOP压缩GOPm_cGOPEncoder.compressGOP(m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, accessUnitsOut, false, false, snrCSC, m_printFrameMSE);if ( m_RCEnableRateControl ) //若使用了m_RCEnableRateControl,则需要消灭之前初始化的GOP{m_cRateCtrl.destroyRCGOP();}iNumEncoded = m_iNumPicRcvd;m_iNumPicRcvd = 0;m_uiNumAllPicCoded += iNumEncoded;
}/**------------------------------------------------Separate interlaced frame into two fields-------------------------------------------------**/
Void separateFields(Pel* org, Pel* dstField, UInt stride, UInt width, UInt height, Bool isTop)
{if (!isTop){org += stride;}for (Int y = 0; y < height>>1; y++){for (Int x = 0; x < width; x++){dstField[x] = org[x];}dstField += stride;org += stride*2;}}
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 encoderTComDataCU* pCtu = pcPic->getCtu( ctuRsAddr );pCtu->initCtu( pcPic, ctuRsAddr );...// run CTU trial encoderm_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函数进行处理
#if AMP_ENC_SPEEDUP
//对当前CU计算最好代价
//对当前CU的子块继续递归调用xCompressCU
Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, const UInt uiDepth DEBUG_STRING_FN_DECLARE(sDebug_), PartSize eParentPartSize )
#else
Void TEncCu::xCompressCU( TComDataCU*& rpcBestCU, TComDataCU*& rpcTempCU, const UInt uiDepth )
#endif
{TComPic* pcPic = rpcBestCU->getPic(); //获取当前CU的图像DEBUG_STRING_NEW(sDebug)const TComPPS &pps=*(rpcTempCU->getSlice()->getPPS()); //获取图像参数集const TComSPS &sps=*(rpcTempCU->getSlice()->getSPS()); //获取序列参数集// These are only used if getFastDeltaQp() is trueconst UInt fastDeltaQPCuMaxSize = Clip3(sps.getMaxCUHeight()>>sps.getLog2DiffMaxMinCodingBlockSize(), sps.getMaxCUHeight(), 32u);// get Original YUV data from picture 从图像中获取原始YUV数据m_ppcOrigYuv[uiDepth]->copyFromPicYuv( pcPic->getPicYuvOrg(), rpcBestCU->getCtuRsAddr(), rpcBestCU->getZorderIdxInCtu() );// variable for Cbf fast mode PU decisionBool doNotBlockPu = true; //快速cbf标识Bool earlyDetectionSkipMode = false; //early skip早期跳出标识const UInt uiLPelX = rpcBestCU->getCUPelX(); //最左端点x坐标const UInt uiRPelX = uiLPelX + rpcBestCU->getWidth(0) - 1; //最右端点x坐标const UInt uiTPelY = rpcBestCU->getCUPelY(); //最上端点y坐标const UInt uiBPelY = uiTPelY + rpcBestCU->getHeight(0) - 1; //最下端点y坐标const UInt uiWidth = rpcBestCU->getWidth(0); //当前CU块宽度//传入当前CU和深度,计算对当前CU的QP;如果不是对每个CU自适应的改变QP,则直接用之前slice算出的QPInt iBaseQP = xComputeQP( rpcBestCU, uiDepth );Int iMinQP;Int iMaxQP;Bool isAddLowestQP = false;//获取成分数量,如果色度格式是CHROMA_400,数量为1,反之为3(最大)const UInt numberValidComponents = rpcBestCU->getPic()->getNumberValidComponents();if( uiDepth <= pps.getMaxCuDQPDepth() ){Int idQP = m_pcEncCfg->getMaxDeltaQP();iMinQP = Clip3( -sps.getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, iBaseQP-idQP );iMaxQP = Clip3( -sps.getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, iBaseQP+idQP );}else{iMinQP = rpcTempCU->getQP(0);iMaxQP = rpcTempCU->getQP(0);}if ( m_pcEncCfg->getUseRateCtrl() ){iMinQP = m_pcRateCtrl->getRCQP();iMaxQP = m_pcRateCtrl->getRCQP();}// transquant-bypass (TQB) processing loop variable initialisation ---//根据当前的深度,是否使用码率控制,是否使用TQB(TransquantBypass模式),调整QP最大和最小的范围(iMinQP-iMaxQP)const Int lowestQP = iMinQP; // For TQB, use this QP which is the lowest non TQB QP tested (rather than QP'=0) - that way delta QPs are smaller, and TQB can be tested at all CU levels.if ( (pps.getTransquantBypassEnableFlag()) ){isAddLowestQP = true; // mark that the first iteration is to cost TQB mode.iMinQP = iMinQP - 1; // increase loop variable range by 1, to allow testing of TQB mode along with other QPsif ( m_pcEncCfg->getCUTransquantBypassFlagForceValue() ){iMaxQP = iMinQP;}}TComSlice * pcSlice = rpcTempCU->getPic()->getSlice(rpcTempCU->getPic()->getCurrSliceIdx()); //获取当前所在slice// 当前CU块的右边界在整个图像的最右边 或者 下边界在整个图像最下边 则为TRUE(即在边界)const Bool bBoundary = !( uiRPelX < sps.getPicWidthInLumaSamples() && uiBPelY < sps.getPicHeightInLumaSamples() );if ( !bBoundary ) //如果不在边界{for (Int iQP=iMinQP; iQP<=iMaxQP; iQP++) //在之前确定的QP范围中枚举QP{const Bool bIsLosslessMode = isAddLowestQP && (iQP == iMinQP);if (bIsLosslessMode){iQP = lowestQP;}m_cuChromaQpOffsetIdxPlus1 = 0;if (pcSlice->getUseChromaQpAdj()){/* Pre-estimation of chroma QP based on input block activity may be performed* here, using for example m_ppcOrigYuv[uiDepth] *//* To exercise the current code, the index used for adjustment is based on* block position*//*/* 如果是TransquantBypass模式(这里用bIsLosslessMode布尔型标识)且如果当前枚举到最小QP,将其改为lowestQP* 如果是自适应改变QP,设置相关的对最小编码块大小取Log的值、色度QP偏移量索引*/Int lgMinCuSize = sps.getLog2MinCodingBlockSize() +std::max<Int>(0, sps.getLog2DiffMaxMinCodingBlockSize()-Int(pps.getPpsRangeExtension().getDiffCuChromaQpOffsetDepth()));m_cuChromaQpOffsetIdxPlus1 = ((uiLPelX >> lgMinCuSize) + (uiTPelY >> lgMinCuSize)) % (pps.getPpsRangeExtension().getChromaQpOffsetListLen() + 1);}//使用CTU四叉树子层的deltaQP初始化预测数据,根据深度设置CU的宽度和高度,对QP赋值rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );// do inter modes, SKIP and 2Nx2N 做帧间预测,SKIP和2N*2Nif( rpcBestCU->getSlice()->getSliceType() != I_SLICE ){// 2Nx2Nif(m_pcEncCfg->getUseEarlySkipDetection()) //使用early skip早期跳出模式{xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) ); //尝试用普通模式进行预测rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode ); //rpcBestCU保存性能最优的预测方式下的参数,rpcTempCU是每次用于尝试划分预测的CU,每次做完后重新恢复初始化}// SKIPxCheckRDCostMerge2Nx2N( rpcBestCU, rpcTempCU DEBUG_STRING_PASS_INTO(sDebug), &earlyDetectionSkipMode ); //尝试用Merge模式进行预测,传入早期跳出标识,如果模式为skip则修改该布尔值rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(!m_pcEncCfg->getUseEarlySkipDetection()){// 2Nx2N, NxNxCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(m_pcEncCfg->getUseCbfFastMode()) //使用快速cbf模式{doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0; //判断四叉树根节点的CBFlag如果为true,则不需要后续继续划分}}}if (bIsLosslessMode) // Restore loop variable if lossless mode was searched.{iQP = iMinQP;}}if(!earlyDetectionSkipMode) //如果之前没有设置提前跳出,继续尝试所有的划分方式{for (Int iQP=iMinQP; iQP<=iMaxQP; iQP++) //枚举QP{const Bool bIsLosslessMode = isAddLowestQP && (iQP == iMinQP); // If lossless, then iQP is irrelevant for subsequent modules.if (bIsLosslessMode){iQP = lowestQP;}rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode ); //CU恢复初始化// do inter modes, NxN, 2NxN, and Nx2N 帧间预测if( rpcBestCU->getSlice()->getSliceType() != I_SLICE ){// 2Nx2N, NxNif(!( (rpcBestCU->getWidth(0)==8) && (rpcBestCU->getHeight(0)==8) )) //当前CU划分为最小(8*8){if( uiDepth == sps.getLog2DiffMaxMinCodingBlockSize() && doNotBlockPu) //如果当前块的深度为当前的四叉树底层且不满足跳出快速cbf条件{xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_NxN DEBUG_STRING_PASS_INTO(sDebug) ); //做N*N的普通预测rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );}}if(doNotBlockPu) //不满足跳出快速cbf条件{xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_Nx2N DEBUG_STRING_PASS_INTO(sDebug) );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_Nx2N ) //如果使用快速cbf策略(刚刚尝试的)N*2N是最佳的划分{doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0; //判断四叉树根节点的CBFlag如果为true,则不需要后续继续划分}}if(doNotBlockPu) //与上面一样的{xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxN DEBUG_STRING_PASS_INTO(sDebug) );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxN){doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;}}//! Try AMP (SIZE_2NxnU, SIZE_2NxnD, SIZE_nLx2N, SIZE_nRx2N) //尝试非对称分割if(sps.getUseAMP() && uiDepth < sps.getLog2DiffMaxMinCodingBlockSize() ) //如果允许非对称分割 且 当前块的深度不是当前的四叉树底层{/* 如果AMP_ENC_SPEEDUP(AMP编码加速)则根据之前尝试划分的最好情况省去尝试一些AMP的划分情况,以此达到加快编码的目的否则,则朴素的尝试所有的AMP划分方式(这种情况的代码省去了,下面只解释加速情况下的代码)*/
#if AMP_ENC_SPEEDUPBool bTestAMP_Hor = false, bTestAMP_Ver = false; //是否使用AMP横向划分,纵向划分的标识/*根据之前划分的最佳模式是横切或竖切或四分等判断使用横向或者竖向的非对称划分 *//* 如果AMP_MRG(AMP Merge) 则增加一对bTestMergeAMP_Hor,bTestMergeAMP_Ver标识横向和纵向划分;在AMG_MRG下,调用xCheckRDCostInter()时在最后增加一个布尔类型为真的参数,会在predInterSearch()函数中对传入的残差清零;其他代码结构与非AMG_MRG相同,因此以下将AMG_MRG预编译判断内的部分都省去了 */
#if AMP_MRGBool bTestMergeAMP_Hor = false, bTestMergeAMP_Ver = false;deriveTestModeAMP (rpcBestCU, eParentPartSize, bTestAMP_Hor, bTestAMP_Ver, bTestMergeAMP_Hor, bTestMergeAMP_Ver);
#elsederiveTestModeAMP (rpcBestCU, eParentPartSize, bTestAMP_Hor, bTestAMP_Ver);
#endif//! Do horizontal AMP 做横向的非对称运动分割if ( bTestAMP_Hor ) //如果可以进行横向AMP划分{//{2N*nU}if(doNotBlockPu) //和之前的对称划分的普通模式一样,只是传入参数PartSize改为相应的非对称划分{xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnU DEBUG_STRING_PASS_INTO(sDebug) );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxnU ){doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;}}//{2N*nD}if(doNotBlockPu){xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnD DEBUG_STRING_PASS_INTO(sDebug) );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxnD ){doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;}}}
#if AMP_MRGelse if ( bTestMergeAMP_Hor ){if(doNotBlockPu){xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnU DEBUG_STRING_PASS_INTO(sDebug), true );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxnU ){doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;}}if(doNotBlockPu){xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnD DEBUG_STRING_PASS_INTO(sDebug), true );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_2NxnD ){doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;}}}
#endif//! Do horizontal AMP 做纵向的非对称运动分割if ( bTestAMP_Ver ) //如果可以进行横向AMP划分{//{nL*2N}if(doNotBlockPu){xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nLx2N DEBUG_STRING_PASS_INTO(sDebug) );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_nLx2N ){doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;}}//{nR*2N}if(doNotBlockPu){xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nRx2N DEBUG_STRING_PASS_INTO(sDebug) );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );}}
#if AMP_MRGelse if ( bTestMergeAMP_Ver ){if(doNotBlockPu){xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nLx2N DEBUG_STRING_PASS_INTO(sDebug), true );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if(m_pcEncCfg->getUseCbfFastMode() && rpcBestCU->getPartitionSize(0) == SIZE_nLx2N ){doNotBlockPu = rpcBestCU->getQtRootCbf( 0 ) != 0;}}if(doNotBlockPu){xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nRx2N DEBUG_STRING_PASS_INTO(sDebug), true );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );}}
#endif#elsexCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnU );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_2NxnD );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nLx2N );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );xCheckRDCostInter( rpcBestCU, rpcTempCU, SIZE_nRx2N );rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );#endif} //AMP结束} //帧间预测结束// 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))) //如果是I帧或者允许做帧间预测且CU已被标记CBF(预测残差为0){xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_2Nx2N DEBUG_STRING_PASS_INTO(sDebug) ); //尝试2N*2N帧内预测rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );if( uiDepth == sps.getLog2DiffMaxMinCodingBlockSize() ) //如果当前深度为四叉树最底层{if( rpcTempCU->getWidth(0) > ( 1 << sps.getQuadtreeTULog2MinSize() ) ) //如果当前CU宽度大于最小的TU宽度{xCheckRDCostIntra( rpcBestCU, rpcTempCU, SIZE_NxN DEBUG_STRING_PASS_INTO(sDebug) ); //尝试N*N帧内预测rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );}}} //帧内预测结束// test PCM 尝试PCM模式(该模式下编码器直接传输一个CU的像素值,而不经过预测变换等操作)if(sps.getUsePCM()&& rpcTempCU->getWidth(0) <= (1<<sps.getPCMLog2MaxSize())&& rpcTempCU->getWidth(0) >= (1<<sps.getPCMLog2MinSize()) ) //如果允许PCM且当前CU的宽度在PCM最小到最大范围内{UInt uiRawBits = getTotalBits(rpcBestCU->getWidth(0), rpcBestCU->getHeight(0), rpcBestCU->getPic()->getChromaFormat(), sps.getBitDepths().recon);UInt uiBestBits = rpcBestCU->getTotalBits(); //对CU进行最佳预测编码的码率if((uiBestBits > uiRawBits) || (rpcBestCU->getTotalCost() > m_pcRdCost->calcRdCost(uiRawBits, 0))){//如果进行预测编码的码率大于传递整个CU像素的码率,或者前者的RDO大于后者的RDOxCheckIntraPCM (rpcBestCU, rpcTempCU); //尝试使用PCM模式rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );}}if (bIsLosslessMode) // Restore loop variable if lossless mode was searched.{iQP = iMinQP;}} //枚举iQP结束} //尝试所有划分方式结束if( rpcBestCU->getTotalCost()!=MAX_DOUBLE ) //正在测试的配置没有超过最大字节数,进行熵编码{m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[uiDepth][CI_NEXT_BEST]);m_pcEntropyCoder->resetBits(); //重置码率m_pcEntropyCoder->encodeSplitFlag( rpcBestCU, 0, uiDepth, true ); //对分割标志进行编码rpcBestCU->getTotalBits() += m_pcEntropyCoder->getNumberOfWrittenBits(); // split bitsrpcBestCU->getTotalBins() += ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded(); //计算熵编码码率rpcBestCU->getTotalCost() = m_pcRdCost->calcRdCost( rpcBestCU->getTotalBits(), rpcBestCU->getTotalDistortion() );m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[uiDepth][CI_NEXT_BEST]); //计算总的RD Cost}} //如果不在边界的判断结束// copy original YUV samples to PCM bufferif( rpcBestCU->getTotalCost()!=MAX_DOUBLE && rpcBestCU->isLosslessCoded(0) && (rpcBestCU->getIPCMFlag(0) == false)){xFillPCMBuffer(rpcBestCU, m_ppcOrigYuv[uiDepth]); //将原始YUV样本复制到PCM缓冲区}/*根据最大CUDeltaQP深度,是否使用码率控制调整QP最大和最小的范围(iMinQP-iMaxQP)*/if( uiDepth == pps.getMaxCuDQPDepth() ){Int idQP = m_pcEncCfg->getMaxDeltaQP();iMinQP = Clip3( -sps.getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, iBaseQP-idQP );iMaxQP = Clip3( -sps.getQpBDOffset(CHANNEL_TYPE_LUMA), MAX_QP, iBaseQP+idQP );}else if( uiDepth < pps.getMaxCuDQPDepth() ){iMinQP = iBaseQP;iMaxQP = iBaseQP;}else{const Int iStartQP = rpcTempCU->getQP(0);iMinQP = iStartQP;iMaxQP = iStartQP;}if ( m_pcEncCfg->getUseRateCtrl() ){iMinQP = m_pcRateCtrl->getRCQP();iMaxQP = m_pcRateCtrl->getRCQP();}if ( m_pcEncCfg->getCUTransquantBypassFlagForceValue() ){iMaxQP = iMinQP; // If all TUs are forced into using transquant bypass, do not loop here.}const Bool bSubBranch = bBoundary || !( m_pcEncCfg->getUseEarlyCU() && rpcBestCU->getTotalCost()!=MAX_DOUBLE && rpcBestCU->isSkipped(0) ); //是否继续划分四叉树标识(Early CU)if( bSubBranch && uiDepth < sps.getLog2DiffMaxMinCodingBlockSize() && (!getFastDeltaQp() || uiWidth > fastDeltaQPCuMaxSize || bBoundary)) //如果可以继续划分并且当前深度不在四叉树最底层{// further splitfor (Int iQP=iMinQP; iQP<=iMaxQP; iQP++) //枚举QP{const Bool bIsLosslessMode = false; // False at this level. Next level down may set it to true.rpcTempCU->initEstData( uiDepth, iQP, bIsLosslessMode );UChar uhNextDepth = uiDepth+1; //下一层的深度TComDataCU* pcSubBestPartCU = m_ppcBestCU[uhNextDepth]; //下一层的最好CU数组TComDataCU* pcSubTempPartCU = m_ppcTempCU[uhNextDepth]; //下一层的临时CU数组DEBUG_STRING_NEW(sTempDebug)for ( UInt uiPartUnitIdx = 0; uiPartUnitIdx < 4; uiPartUnitIdx++ ) //枚举划分四叉树的四个子块的下标{pcSubBestPartCU->initSubCU( rpcTempCU, uiPartUnitIdx, uhNextDepth, iQP ); //清空或初始化BestCU子块的数据pcSubTempPartCU->initSubCU( rpcTempCU, uiPartUnitIdx, uhNextDepth, iQP ); //清空或初始化TempCU子块的数据if( ( pcSubBestPartCU->getCUPelX() < sps.getPicWidthInLumaSamples() ) && ( pcSubBestPartCU->getCUPelY() < sps.getPicHeightInLumaSamples() ) ) //子块CU的横纵坐标位置在亮度样本图像之内(可以继续往下迭代){if ( 0 == uiPartUnitIdx) //initialize RD with previous depth buffer 如果迭代到第一块子块(左上角){m_pppcRDSbacCoder[uhNextDepth][CI_CURR_BEST]->load(m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST]); //使用之前(当前深度)的缓存初始化RDO}else //迭代其他子块{m_pppcRDSbacCoder[uhNextDepth][CI_CURR_BEST]->load(m_pppcRDSbacCoder[uhNextDepth][CI_NEXT_BEST]); //使用现在(下一深度)的缓存初始化RDO}/*如果使用AMP_ENC_SPEEDUP(与在AMP加速是同一个),在递归调用xCompressXU()时在最后增加一个PartSize参数仅用于在加速的AMP决策时判断较优的划分方式用;总之不管使不使用AMP加速,这里都要递归调用子块的compressCU*/
#if AMP_ENC_SPEEDUPDEBUG_STRING_NEW(sChild)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)
#elsexCompressCU( pcSubBestPartCU, pcSubTempPartCU, uhNextDepth ); //递归下一层的子块
#endifrpcTempCU->copyPartFrom( pcSubBestPartCU, uiPartUnitIdx, uhNextDepth ); //将最好的子块的数据存在当前的临时数据中xCopyYuv2Tmp( pcSubBestPartCU->getTotalNumPart()*uiPartUnitIdx, uhNextDepth ); //复制预测图像和重建图像的YUV数据} //结束:可以继续往下迭代else{pcSubBestPartCU->copyToPic( uhNextDepth ); //将当前预测的部分复制到图片中的CU,用于预测下一个子块rpcTempCU->copyPartFrom( pcSubBestPartCU, uiPartUnitIdx, uhNextDepth ); //将最好的子块的数据存在当前的临时数据中}} //枚举四叉树的子块结束m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[uhNextDepth][CI_NEXT_BEST]); //使用现在(下一深度)的缓存初始化RDOif( !bBoundary ) //如果当前块不在边界,进行熵编码{m_pcEntropyCoder->resetBits(); //重置码率m_pcEntropyCoder->encodeSplitFlag( rpcTempCU, 0, uiDepth, true ); //对分割标志进行编码rpcTempCU->getTotalBits() += m_pcEntropyCoder->getNumberOfWrittenBits(); // split bitsrpcTempCU->getTotalBins() += ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded(); //计算熵编码码率}rpcTempCU->getTotalCost() = m_pcRdCost->calcRdCost( rpcTempCU->getTotalBits(), rpcTempCU->getTotalDistortion() ); //计算总的RD Costif( uiDepth == pps.getMaxCuDQPDepth() && pps.getUseDQP()) //如果使用DeltaQP且当前深度到达DeltaQP最大深度{Bool hasResidual = false; //是否有残差的标识for( UInt uiBlkIdx = 0; uiBlkIdx < rpcTempCU->getTotalNumPart(); uiBlkIdx ++) //枚举所有划分到最小的CU块 //枚举所有划分到最小的CU块{if( ( rpcTempCU->getCbf(uiBlkIdx, COMPONENT_Y)|| (rpcTempCU->getCbf(uiBlkIdx, COMPONENT_Cb) && (numberValidComponents > COMPONENT_Cb))|| (rpcTempCU->getCbf(uiBlkIdx, COMPONENT_Cr) && (numberValidComponents > COMPONENT_Cr)) ) ){ //Cbf!=0代表有残差hasResidual = true; //标识有残差为truebreak;}}if ( hasResidual ) //如果有残差,进行熵编码{m_pcEntropyCoder->resetBits();m_pcEntropyCoder->encodeQP( rpcTempCU, 0, false );rpcTempCU->getTotalBits() += m_pcEntropyCoder->getNumberOfWrittenBits(); // dQP bitsrpcTempCU->getTotalBins() += ((TEncBinCABAC *)((TEncSbac*)m_pcEntropyCoder->m_pcEntropyCoderIf)->getEncBinIf())->getBinsCoded();rpcTempCU->getTotalCost() = m_pcRdCost->calcRdCost( rpcTempCU->getTotalBits(), rpcTempCU->getTotalDistortion() );Bool foundNonZeroCbf = false; //找到非零cbf标识rpcTempCU->setQPSubCUs( rpcTempCU->getRefQP( 0 ), 0, uiDepth, foundNonZeroCbf ); //设置子块QPassert( foundNonZeroCbf );}else //所有最小CU都没有残差{rpcTempCU->setQPSubParts( rpcTempCU->getRefQP( 0 ), 0, uiDepth ); // set QP to default QP}} //处理DeltaQP情况结束m_pcRDGoOnSbacCoder->store(m_pppcRDSbacCoder[uiDepth][CI_TEMP_BEST]); //存储当前深度缓存的临时最优RD Cost// If the configuration being tested exceeds the maximum number of bytes for a slice / slice-segment, then// a proper RD evaluation cannot be performed. Therefore, termination of the// slice/slice-segment must be made prior to this CTU.// This can be achieved by forcing the decision to be that of the rpcTempCU.// The exception is each slice / slice-segment must have at least one CTU.if (rpcBestCU->getTotalCost()!=MAX_DOUBLE) //正在测试的配置没有超过最大字节数{const Bool isEndOfSlice = pcSlice->getSliceMode()==FIXED_NUMBER_OF_BYTES&& ((pcSlice->getSliceBits()+rpcBestCU->getTotalBits())>pcSlice->getSliceArgument()<<3)&& rpcBestCU->getCtuRsAddr() != pcPic->getPicSym()->getCtuTsToRsAddrMap(pcSlice->getSliceCurStartCtuTsAddr())&& rpcBestCU->getCtuRsAddr() != pcPic->getPicSym()->getCtuTsToRsAddrMap(pcSlice->getSliceSegmentCurStartCtuTsAddr()); //是否是Slice最末的标识const Bool isEndOfSliceSegment = pcSlice->getSliceSegmentMode()==FIXED_NUMBER_OF_BYTES&& ((pcSlice->getSliceSegmentBits()+rpcBestCU->getTotalBits()) > pcSlice->getSliceSegmentArgument()<<3)&& rpcBestCU->getCtuRsAddr() != pcPic->getPicSym()->getCtuTsToRsAddrMap(pcSlice->getSliceSegmentCurStartCtuTsAddr()); //是否是SS最末的标识// Do not need to check slice condition for slice-segment since a slice-segment is a subset of a slice.if(isEndOfSlice||isEndOfSliceSegment) //由于切片段是切片的子集,因此不需要检查切片段的切片条件{rpcBestCU->getTotalCost()=MAX_DOUBLE; //如果是最末端,将RD Cost设置为最大字节数}}xCheckBestMode( rpcBestCU, rpcTempCU, uiDepth DEBUG_STRING_PASS_INTO(sDebug) DEBUG_STRING_PASS_INTO(sTempDebug) DEBUG_STRING_PASS_INTO(false) ); // RD compare current larger prediction// with sub partitioned prediction.} //枚举iQP结束} //可以继续划分结束DEBUG_STRING_APPEND(sDebug_, sDebug);/*子层和递归结束返回父层的每个块都要进行以下的部分*/rpcBestCU->copyToPic(uiDepth); //复制最好的方式的数据用于下一个块的预测 // Copy Best data to Picture for next partition prediction.xCopyYuv2Pic( rpcBestCU->getPic(), rpcBestCU->getCtuRsAddr(), rpcBestCU->getZorderIdxInCtu(), uiDepth, uiDepth ); // 复制预测图像和重建图像的YUV数据if (bBoundary){return;}// Assert if Best prediction mode is NONE// Selected mode's RD-cost must be not MAX_DOUBLE.assert( rpcBestCU->getPartitionSize ( 0 ) != NUMBER_OF_PART_SIZES );assert( rpcBestCU->getPredictionMode( 0 ) != NUMBER_OF_PREDICTION_MODES );assert( rpcBestCU->getTotalCost ( ) != MAX_DOUBLE );}
TEncCu::xCheckRDCostIntra函数是帧内预测的入口函数,它调用 estIntraPredLumaQT函数进行亮度分量的预测、变换、量化和编码;调用estIntraPredChromaQT函数进行色度分量的处理;最后调用xCheckBestMode函数check最优的模式
/*
帧内预测的入口函数,调用estIntraPredLumaQT函数进行亮度分量的预测,变换,量化和编码;
调用estIntraPredChromaQT函数进行色度分量的处理;
最后用xCheckBestMode函数check最优的模式
*/
Void TEncCu::xCheckRDCostIntra( TComDataCU *&rpcBestCU,TComDataCU *&rpcTempCU,PartSize eSizeDEBUG_STRING_FN_DECLARE(sDebug) )
{DEBUG_STRING_NEW(sTest)if(getFastDeltaQp()) //默认false{const TComSPS &sps=*(rpcTempCU->getSlice()->getSPS());const UInt fastDeltaQPCuMaxSize = Clip3(sps.getMaxCUHeight()>>(sps.getLog2DiffMaxMinCodingBlockSize()), sps.getMaxCUHeight(), 32u);if(rpcTempCU->getWidth( 0 ) > fastDeltaQPCuMaxSize){return; // only check necessary 2Nx2N Intra in fast deltaqp mode}}//设置参数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_cuChromaQpOffsetIdxPlus1, 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 CoefficientsBool 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 );//Check最优模式xCheckBestMode(rpcBestCU, rpcTempCU, uiDepth DEBUG_STRING_PASS_INTO(sDebug) DEBUG_STRING_PASS_INTO(sTest));
}
estIntraPredLumaQT函数是进行预测、变换、量化和编码的函数,处理流程是:循环处理每一个PU块,对于每一个PU块:遍历35个模式选择出numModesForFullRD个最优模式uiRdModeList,然后将MPM和numModesForFullRD个模式不同的模式也加入到uiRdModeList中;然后循环uiRdModeList,对每一个模式调用xRecurIntraCodingLumaQT函数进行TU(这时的PU除非64x64的块否则不划分)的预测、变换、量化和编码;然后选出最小的Cost对应的模式;最后对于最优的模式再次调用xRecurIntraCodingLumaQT函数进行TU(这时对PU进行划分)的预测、变换量化和编码,选择最小的Cost对应的模式
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] //Pel像素类型,[2][64*64]DEBUG_STRING_FN_DECLARE(sDebug))
{const UInt uiDepth = pcCU->getDepth(0); //当前CU的深度const UInt uiInitTrDepth = pcCU->getPartitionSize(0) == SIZE_2Nx2N ? 0 : 1; //用于计算变化的深度,实际深度为该值+uiDepthconst UInt uiNumPU = 1<<(2*uiInitTrDepth); //当前CU的分割模式,PU分块数,(SIZE_2N*2N:1,SIZE_2N*N:2,SIZE_N*2N:2,SIZE_N*N:4)const UInt uiQNumParts = pcCU->getTotalNumPart() >> 2; //最小的分区是4x4大小的块,这里计算出以该4x4块为单位的分割数,这么做便于计算当前CU的Zorder坐标const UInt uiWidthBit = pcCU->getIntraSizeIdx(0);const ChromaFormat chFmt = pcCU->getPic()->getChromaFormat(); //颜色格式const UInt numberValidComponents = getNumberValidComponents(chFmt);const TComSPS &sps = *(pcCU->getSlice()->getSPS()); //SPSconst TComPPS &pps = *(pcCU->getSlice()->getPPS()); //PPSDistortion uiOverallDistY = 0;UInt CandNum; //候选数Double CandCostList[ FAST_UDI_MAX_RDMODE_NUM ]; //候选代价列表Pel resiLumaPU[NUMBER_OF_STORED_RESIDUAL_TYPES][MAX_CU_SIZE * MAX_CU_SIZE];Bool bMaintainResidual[NUMBER_OF_STORED_RESIDUAL_TYPES];for (UInt residualTypeIndex = 0; residualTypeIndex < NUMBER_OF_STORED_RESIDUAL_TYPES; residualTypeIndex++){bMaintainResidual[residualTypeIndex] = true; //assume true unless specified otherwise 除非另有说明,否则假定为真}bMaintainResidual[RESIDUAL_ENCODER_SIDE] = !(m_pcEncCfg->getUseReconBasedCrossCPredictionEstimate());// Lambda calculation at equivalent Qp of 4 is recommended because at that Qp, the quantisation divisor is 1.
#if FULL_NBITconst Double sqrtLambdaForFirstPass= (m_pcEncCfg->getCostMode()==COST_MIXED_LOSSLESS_LOSSY_CODING && pcCU->getCUTransquantBypass(0)) ?sqrt(0.57 * pow(2.0, ((LOSSLESS_AND_MIXED_LOSSLESS_RD_COST_TEST_QP_PRIME - 12) / 3.0))): m_pcRdCost->getSqrtLambda();
#else//计算Lambdaconst Double sqrtLambdaForFirstPass= (m_pcEncCfg->getCostMode()==COST_MIXED_LOSSLESS_LOSSY_CODING && pcCU->getCUTransquantBypass(0)) ?sqrt(0.57 * pow(2.0, ((LOSSLESS_AND_MIXED_LOSSLESS_RD_COST_TEST_QP_PRIME - 12 - 6 * (sps.getBitDepth(CHANNEL_TYPE_LUMA) - 8)) / 3.0))): m_pcRdCost->getSqrtLambda();
#endif//===== set QP and clear Cbf =====if ( pps.getUseDQP() == true){pcCU->setQPSubParts( pcCU->getQP(0), 0, uiDepth );}else{pcCU->setQPSubParts( pcCU->getSlice()->getSliceQp(), 0, uiDepth );}//===== loop over partitions 在分区上循环=====TComTURecurse tuRecurseCU(pcCU, 0); //用于记录当前PU的Zorder坐标TComTURecurse tuRecurseWithPU(tuRecurseCU, false, (uiInitTrDepth==0)?TComTU::DONT_SPLIT : TComTU::QUAD_SPLIT);do //对当前CU中的每个PU进行遍历{const UInt uiPartOffset=tuRecurseWithPU.GetAbsPartIdxTU(); //当前PU的偏移
// for( UInt uiPU = 0, uiPartOffset=0; uiPU < uiNumPU; uiPU++, uiPartOffset += uiQNumParts )//{//===== init pattern for luma prediction =====DEBUG_STRING_NEW(sTemp2)//===== determine set of modes to be tested (using prediction signal only) =====Int numModesAvailable = 35; //total number of Intra modes 可用模式总数UInt uiRdModeList[FAST_UDI_MAX_RDMODE_NUM];//根据块宽度设定全率失真优化的模式数Int numModesForFullRD = m_pcEncCfg->getFastUDIUseMPMEnabled()?g_aucIntraModeNumFast_UseMPM[ uiWidthBit ] : g_aucIntraModeNumFast_NotUseMPM[ uiWidthBit ];// this should always be trueassert (tuRecurseWithPU.ProcessComponentSection(COMPONENT_Y));initIntraPatternChType( tuRecurseWithPU, COMPONENT_Y, true DEBUG_STRING_PASS_INTO(sTemp2) );/***********************快速搜索粗选阶段***********************/Bool doFastSearch = (numModesForFullRD != numModesAvailable); //默认开启快速搜索if (doFastSearch) //快速搜索{assert(numModesForFullRD < numModesAvailable);for( Int i=0; i < numModesForFullRD; i++ ) //对numModesForFullRD个候选设置代价为最大{CandCostList[ i ] = MAX_DOUBLE;}CandNum = 0;const TComRectangle &puRect=tuRecurseWithPU.getRect(COMPONENT_Y); //获取Y分量PUconst UInt uiAbsPartIdx=tuRecurseWithPU.GetAbsPartIdxTU(); //PU地址Pel* piOrg = pcOrgYuv ->getAddr( COMPONENT_Y, uiAbsPartIdx ); //原始图像Pel* piPred = pcPredYuv->getAddr( COMPONENT_Y, uiAbsPartIdx ); //预测图象UInt uiStride = pcPredYuv->getStride( COMPONENT_Y ); //跨度DistParam distParam; //失真参数const Bool bUseHadamard=pcCU->getCUTransquantBypass(0) == 0; //是否使用哈达玛变换m_pcRdCost->setDistParam(distParam, sps.getBitDepth(CHANNEL_TYPE_LUMA), piOrg, uiStride, piPred, uiStride, puRect.width, puRect.height, bUseHadamard); //使用哈达玛变换初始化失真distParam.bApplyWeight = false;for( Int modeIdx = 0; modeIdx < numModesAvailable; modeIdx++ ) //遍历35种帧内预测模式{UInt uiMode = modeIdx; //当前模式Distortion uiSad = 0; //失真//参考采样滤波const Bool bUseFilter=TComPrediction::filteringIntraReferenceSamples(COMPONENT_Y, uiMode, puRect.width, puRect.height, chFmt, sps.getSpsRangeExtension().getIntraSmoothingDisabledFlag());//计算相应模式的预测值predIntraAng( COMPONENT_Y, uiMode, piOrg, uiStride, piPred, uiStride, tuRecurseWithPU, bUseFilter, TComPrediction::UseDPCMForFirstPassIntraEstimation(tuRecurseWithPU, uiMode) );// use hadamard transform hereuiSad+=distParam.DistFunc(&distParam); //计算哈达玛失真UInt iModeBits = 0; //bit数// NB xModeBitsIntra will not affect the mode for chroma that may have already been pre-estimated.iModeBits+=xModeBitsIntra( pcCU, uiMode, uiPartOffset, uiDepth, CHANNEL_TYPE_LUMA ); //计算bit数Double cost = (Double)uiSad + (Double)iModeBits * sqrtLambdaForFirstPass; //使用哈达玛失真计算率失真代价#if DEBUG_INTRA_SEARCH_COSTSstd::cout << "1st pass mode " << uiMode << " SAD = " << uiSad << ", mode bits = " << iModeBits << ", cost = " << cost << "\n";
#endif//比较代价更新候选列表CandNum += xUpdateCandList( uiMode, cost, numModesForFullRD, uiRdModeList, CandCostList );}/*****************************快速搜索细选阶段********************************/if (m_pcEncCfg->getFastUDIUseMPMEnabled()){Int uiPreds[NUM_MOST_PROBABLE_MODES] = {-1, -1, -1}; //初始化MPM列表,长度为3Int iMode = -1; //如果三个MPMs的前两个相同,则iMode=1,否则iMode=2//利用临近PU构建MPMpcCU->getIntraDirPredictor( uiPartOffset, uiPreds, COMPONENT_Y, &iMode );//当有可用MPM时,numCand就等于MPM对应的模式,否则为NUM_MOST_PROBABLE_MODESconst Int numCand = ( iMode >= 0 ) ? iMode : Int(NUM_MOST_PROBABLE_MODES);for( Int j=0; j < numCand; j++) //把MPM加入全率失真优化列表中{Bool mostProbableModeIncluded = false;Int mostProbableMode = uiPreds[j]; //取出MPMfor( Int i=0; i < numModesForFullRD; i++){mostProbableModeIncluded |= (mostProbableMode == uiRdModeList[i]); //检查MPMs是否被uiRdModeList所包含}if (!mostProbableModeIncluded) //如果没被包含,则将该MPM包含到uiRdModeList里{uiRdModeList[numModesForFullRD++] = mostProbableMode;}}}}else //不启用快速搜索,将所有模式都加入全率失真优化列表中{for( Int i=0; i < numModesForFullRD; i++){uiRdModeList[i] = i;}}//===== check modes (using r-d costs) =====//! 帧内预测模式最佳值的确定主要有以下几个步骤:1. 对numModesForFullRD种预测模式进行遍历,即对每种模式计算出//! 对应的RD costs,但该步骤中,并不会把一个CU的所有分割都算一遍,而仅仅对于至多深度为1的分割进行遍历,这么做//! 大大减少了运算量,提高速度;2. 在第1个步骤中,会粗略得到最佳预测模式(在HM9.0中会得到包括次优解在内的两个//! 预测模式),存储下来,以供第3步使用;3. 在第2步的基础上,对最佳(及次优)预测模式的所有分割模式遍历一遍,//! 得到最终的最佳结果
#if HHI_RQT_INTRA_SPEEDUP_MODUInt uiSecondBestMode = MAX_UINT;Double dSecondBestPUCost = MAX_DOUBLE;
#endifDEBUG_STRING_NEW(sPU)UInt uiBestPUMode = 0; //存放最优预测模式Distortion uiBestPUDistY = 0; //存放最优预测模式对应的亮度失真值Double dBestPUCost = MAX_DOUBLE; //存放最优预测模式对应的总代价#if ENVIRONMENT_VARIABLE_DEBUG_AND_TESTUInt max=numModesForFullRD;if (DebugOptionList::ForceLumaMode.isSet()){max=0; // we are forcing a direction, so don't bother with mode check}for ( UInt uiMode = 0; uiMode < max; uiMode++)
#else//遍历全率失真优化列表for( UInt uiMode = 0; uiMode < numModesForFullRD; uiMode++ )
#endif{// set luma prediction modeUInt uiOrgMode = uiRdModeList[uiMode]; //原始模式//设置子块的帧内预测模式pcCU->setIntraDirSubParts ( CHANNEL_TYPE_LUMA, uiOrgMode, uiPartOffset, uiDepth + uiInitTrDepth );DEBUG_STRING_NEW(sMode)// set context models 设置上下文模型m_pcRDGoOnSbacCoder->load( m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST] );// determine residual for partitionDistortion uiPUDistY = 0; //初始化当前失真Double dPUCost = 0.0; //初始化当前代价
#if HHI_RQT_INTRA_SPEEDUP//重构帧内亮度分量,计算率失真代价//注意倒数第二个参数bCheckFirst是true,表示会继续按照四叉树的方式向下划分xRecurIntraCodingLumaQT( pcOrgYuv, pcPredYuv, pcResiYuv, resiLumaPU, uiPUDistY, true, dPUCost, tuRecurseWithPU DEBUG_STRING_PASS_INTO(sMode) );
#elsexRecurIntraCodingLumaQT( pcOrgYuv, pcPredYuv, pcResiYuv, resiLumaPU, uiPUDistY, dPUCost, tuRecurseWithPU DEBUG_STRING_PASS_INTO(sMode) );
#endif#if DEBUG_INTRA_SEARCH_COSTSstd::cout << "2nd pass [luma,chroma] mode [" << Int(pcCU->getIntraDir(CHANNEL_TYPE_LUMA, uiPartOffset)) << "," << Int(pcCU->getIntraDir(CHANNEL_TYPE_CHROMA, uiPartOffset)) << "] cost = " << dPUCost << "\n";
#endif// check r-d cost 如果当前代价小于最优代价,将当前模式设置为最优模式if( dPUCost < dBestPUCost ) //更新最优预测模式相关参数{DEBUG_STRING_SWAP(sPU, sMode)
#if HHI_RQT_INTRA_SPEEDUP_MODuiSecondBestMode = uiBestPUMode;dSecondBestPUCost = dBestPUCost;
#endifuiBestPUMode = uiOrgMode;uiBestPUDistY = uiPUDistY;dBestPUCost = dPUCost;xSetIntraResultLumaQT( pcRecoYuv, tuRecurseWithPU );if (pps.getPpsRangeExtension().getCrossComponentPredictionEnabledFlag()){const Int xOffset = tuRecurseWithPU.getRect( COMPONENT_Y ).x0;const Int yOffset = tuRecurseWithPU.getRect( COMPONENT_Y ).y0;for (UInt storedResidualIndex = 0; storedResidualIndex < NUMBER_OF_STORED_RESIDUAL_TYPES; storedResidualIndex++){if (bMaintainResidual[storedResidualIndex]){xStoreCrossComponentPredictionResult(resiLuma[storedResidualIndex], resiLumaPU[storedResidualIndex], tuRecurseWithPU, xOffset, yOffset, MAX_CU_SIZE, MAX_CU_SIZE );}}}UInt uiQPartNum = tuRecurseWithPU.GetAbsPartIdxNumParts();::memcpy( m_puhQTTempTrIdx, pcCU->getTransformIdx() + uiPartOffset, uiQPartNum * sizeof( UChar ) );for (UInt component = 0; component < numberValidComponents; component++){const ComponentID compID = ComponentID(component);::memcpy( m_puhQTTempCbf[compID], pcCU->getCbf( compID ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );::memcpy( m_puhQTTempTransformSkipFlag[compID], pcCU->getTransformSkip(compID) + uiPartOffset, uiQPartNum * sizeof( UChar ) );}}
#if HHI_RQT_INTRA_SPEEDUP_MODelse if( dPUCost < dSecondBestPUCost ){uiSecondBestMode = uiOrgMode;dSecondBestPUCost = dPUCost;}
#endif} // Mode loop#if HHI_RQT_INTRA_SPEEDUP
#if HHI_RQT_INTRA_SPEEDUP_MODfor( UInt ui =0; ui < 2; ++ui )
#endif{
#if HHI_RQT_INTRA_SPEEDUP_MODUInt uiOrgMode = ui ? uiSecondBestMode : uiBestPUMode;if( uiOrgMode == MAX_UINT ){break;}
#else/*********************已获得最优模式,使用最优模式进行预测变换量化重构等,计算最终的率失真代价**********************///取最优模式UInt uiOrgMode = uiBestPUMode;
#endif#if ENVIRONMENT_VARIABLE_DEBUG_AND_TESTif (DebugOptionList::ForceLumaMode.isSet()){uiOrgMode = DebugOptionList::ForceLumaMode.getInt();}
#endif//设置子块的帧内预测模式pcCU->setIntraDirSubParts ( CHANNEL_TYPE_LUMA, uiOrgMode, uiPartOffset, uiDepth + uiInitTrDepth );DEBUG_STRING_NEW(sModeTree)// set context models 加载上下文模型m_pcRDGoOnSbacCoder->load( m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST] );// determine residual for partitionDistortion uiPUDistY = 0;Double dPUCost = 0.0;//重构亮度分量,注意倒数第二个参数bCheckFirst是false,表示当前PU不再进行划分,即只处理当前深度的PUxRecurIntraCodingLumaQT( pcOrgYuv, pcPredYuv, pcResiYuv, resiLumaPU, uiPUDistY, false, dPUCost, tuRecurseWithPU DEBUG_STRING_PASS_INTO(sModeTree));// check r-d costif( dPUCost < dBestPUCost ) //检查同一模式下,bCheckFirst为true和false的情况下,选最优{DEBUG_STRING_SWAP(sPU, sModeTree)uiBestPUMode = uiOrgMode;uiBestPUDistY = uiPUDistY;dBestPUCost = dPUCost;xSetIntraResultLumaQT( pcRecoYuv, tuRecurseWithPU );if (pps.getPpsRangeExtension().getCrossComponentPredictionEnabledFlag()){const Int xOffset = tuRecurseWithPU.getRect( COMPONENT_Y ).x0;const Int yOffset = tuRecurseWithPU.getRect( COMPONENT_Y ).y0;for (UInt storedResidualIndex = 0; storedResidualIndex < NUMBER_OF_STORED_RESIDUAL_TYPES; storedResidualIndex++){if (bMaintainResidual[storedResidualIndex]){xStoreCrossComponentPredictionResult(resiLuma[storedResidualIndex], resiLumaPU[storedResidualIndex], tuRecurseWithPU, xOffset, yOffset, MAX_CU_SIZE, MAX_CU_SIZE );}}}const UInt uiQPartNum = tuRecurseWithPU.GetAbsPartIdxNumParts();::memcpy( m_puhQTTempTrIdx, pcCU->getTransformIdx() + uiPartOffset, uiQPartNum * sizeof( UChar ) );for (UInt component = 0; component < numberValidComponents; component++){const ComponentID compID = ComponentID(component);::memcpy( m_puhQTTempCbf[compID], pcCU->getCbf( compID ) + uiPartOffset, uiQPartNum * sizeof( UChar ) );::memcpy( m_puhQTTempTransformSkipFlag[compID], pcCU->getTransformSkip(compID) + uiPartOffset, uiQPartNum * sizeof( UChar ) );}}} // Mode loop
#endifDEBUG_STRING_APPEND(sDebug, sPU)//--- update overall distortion ---uiOverallDistY += uiBestPUDistY;//--- update transform index and cbf ---const UInt uiQPartNum = tuRecurseWithPU.GetAbsPartIdxNumParts();::memcpy( pcCU->getTransformIdx() + uiPartOffset, m_puhQTTempTrIdx, uiQPartNum * sizeof( UChar ) );for (UInt component = 0; component < numberValidComponents; component++){const ComponentID compID = ComponentID(component);::memcpy( pcCU->getCbf( compID ) + uiPartOffset, m_puhQTTempCbf[compID], uiQPartNum * sizeof( UChar ) );::memcpy( pcCU->getTransformSkip( compID ) + uiPartOffset, m_puhQTTempTransformSkipFlag[compID ], uiQPartNum * sizeof( UChar ) );}//--- set reconstruction for next intra prediction blocks 设置重建块---if( !tuRecurseWithPU.IsLastSection() ){const TComRectangle &puRect=tuRecurseWithPU.getRect(COMPONENT_Y);const UInt uiCompWidth = puRect.width;const UInt uiCompHeight = puRect.height;const UInt uiZOrder = pcCU->getZorderIdxInCtu() + uiPartOffset;Pel* piDes = pcCU->getPic()->getPicYuvRec()->getAddr( COMPONENT_Y, pcCU->getCtuRsAddr(), uiZOrder );const UInt uiDesStride = pcCU->getPic()->getPicYuvRec()->getStride( COMPONENT_Y);const Pel* piSrc = pcRecoYuv->getAddr( COMPONENT_Y, uiPartOffset );const UInt uiSrcStride = pcRecoYuv->getStride( COMPONENT_Y);for( UInt uiY = 0; uiY < uiCompHeight; uiY++, piSrc += uiSrcStride, piDes += uiDesStride ){for( UInt uiX = 0; uiX < uiCompWidth; uiX++ ){piDes[ uiX ] = piSrc[ uiX ];}}}//=== update PU data ====pcCU->setIntraDirSubParts ( CHANNEL_TYPE_LUMA, uiBestPUMode, uiPartOffset, uiDepth + uiInitTrDepth );} while (tuRecurseWithPU.nextSection(tuRecurseCU));if( uiNumPU > 1 ){ // set Cbf for all blocks 设置CbfUInt uiCombCbfY = 0;UInt uiCombCbfU = 0;UInt uiCombCbfV = 0;UInt uiPartIdx = 0;for( UInt uiPart = 0; uiPart < 4; uiPart++, uiPartIdx += uiQNumParts ){uiCombCbfY |= pcCU->getCbf( uiPartIdx, COMPONENT_Y, 1 );uiCombCbfU |= pcCU->getCbf( uiPartIdx, COMPONENT_Cb, 1 );uiCombCbfV |= pcCU->getCbf( uiPartIdx, COMPONENT_Cr, 1 );}for( UInt uiOffs = 0; uiOffs < 4 * uiQNumParts; uiOffs++ ){pcCU->getCbf( COMPONENT_Y )[ uiOffs ] |= uiCombCbfY;pcCU->getCbf( COMPONENT_Cb )[ uiOffs ] |= uiCombCbfU;pcCU->getCbf( COMPONENT_Cr )[ uiOffs ] |= uiCombCbfV;}}//===== reset context models 重置上下文模型=====m_pcRDGoOnSbacCoder->load(m_pppcRDSbacCoder[uiDepth][CI_CURR_BEST]);//===== set distortion (rate and r-d costs are determined later) 设置总失真=====pcCU->getTotalDistortion() = uiOverallDistY;
}
TComPrediction::filteringIntraReferenceSamples函数是判断当前PU块的参考像素是否进行平滑,它的判断标准参考https://blog.csdn.net/shayashi/article/details/82877875
/*
判断当前PU块的参考像素是否进行平滑
*/
Bool TComPrediction::filteringIntraReferenceSamples(const ComponentID compID, UInt uiDirMode, UInt uiTuChWidth, UInt uiTuChHeight, const ChromaFormat chFmt, const Bool intraReferenceSmoothingDisabled)
{Bool bFilter;//若不满足局部变量 intraReferenceSmoothingDisabled==false且当前分量亮度或者当前取样模式是444,则不滤波if (!filterIntraReferenceSamples(toChannelType(compID), chFmt, intraReferenceSmoothingDisabled)){bFilter=false;}else{assert(uiTuChWidth>=4 && uiTuChHeight>=4 && uiTuChWidth<128 && uiTuChHeight<128);if (uiDirMode == DC_IDX) //当前模式是DC模式,不滤波{bFilter=false; //no smoothing for DC or LM chroma}else{//否则,则根据当前模式距离水平和垂直模式的绝对值最小值和阈值比较,判断是否进行滤波Int diff = min<Int>(abs((Int) uiDirMode - HOR_IDX), abs((Int)uiDirMode - VER_IDX)); //取最小值UInt sizeIndex=g_aucConvertToBit[uiTuChWidth]; //阈值assert(sizeIndex < MAX_INTRA_FILTER_DEPTHS); //MAX_INTRA_FILTER_DEPTHS==5bFilter = diff > m_aucIntraFilter[toChannelType(compID)][sizeIndex]; //判断}}return bFilter;
}
predIntraAng是预测函数,该函数的流程是获取参考像素,然后根据当前模式选择对应函数进行具体预测
/*
predIntraAng是预测函数,该函数的流程是获取参考像素,然后根据当前模式选择对应函数进行具体预测
*/
Void TComPrediction::predIntraAng( const ComponentID compID, UInt uiDirMode, Pel* piOrg /* Will be null for decoding */, UInt uiOrgStride, Pel* piPred, UInt uiStride, TComTU &rTu, const Bool bUseFilteredPredSamples, const Bool bUseLosslessDPCM )
{const ChannelType channelType = toChannelType(compID); //CHANNEL_TYPE_LUMA = 0const TComRectangle &rect = rTu.getRect(isLuma(compID) ? COMPONENT_Y : COMPONENT_Cb); //获得图片格式,一般为YUV420const Int iWidth = rect.width; //TU的宽const Int iHeight = rect.height; //TU的高assert( g_aucConvertToBit[ iWidth ] >= 0 ); // 4x 4assert( g_aucConvertToBit[ iWidth ] <= 5 ); // 128x128//assert( iWidth == iHeight );Pel *pDst = piPred; //预测值的首地址// get starting pixel in blockconst Int sw = (2 * iWidth + 1);if ( bUseLosslessDPCM ) //使用无损DPCM,如果预测方式为垂直或水平,则bUseLosslessDPCM=1{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 columnfor(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 rowfor(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 );}else{// Create the predictionTComDataCU *const pcCU = rTu.getCU();const UInt uiAbsPartIdx = rTu.GetAbsPartIdxTU();const Bool enableEdgeFilters = !(pcCU->isRDPCMEnabled(uiAbsPartIdx) && pcCU->getCUTransquantBypass(uiAbsPartIdx));
#if O0043_BEST_EFFORT_DECODINGconst Int channelsBitDepthForPrediction = rTu.getCU()->getSlice()->getSPS()->getStreamBitDepth(channelType);
#elseconst Int channelsBitDepthForPrediction = rTu.getCU()->getSlice()->getSPS()->getBitDepth(channelType);
#endifxPredIntraAng( channelsBitDepthForPrediction, ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType, uiDirMode, enableEdgeFilters );if( uiDirMode == DC_IDX ){xDCPredFiltering( ptrSrc+sw+1, sw, pDst, uiStride, iWidth, iHeight, channelType );}}}}
【HEVC代码阅读】帧内预测相关推荐
- HEVC亮度分量帧内预测模式代码详解
作者:66 (转载请注明出处) 从我自身的学习历程来看,建议大家先把理论扎实了再去理解代码,代码里也有注释,与理论的地方一对应加上一点C编程基础很容易就能理解. 我们先了解相关理论,此处可参考这一篇博 ...
- H.266/VVC-VTM代码学习-帧内预测05-Angular模式下计算预测像素值xPredIntraAng
H.266/VVC专栏传送 上一篇:H.266/VVC-VTM代码学习-帧内预测04-Planar模式下计算预测像素值xPredIntraPlanar 下一篇:H.266/VVC-VTM代码学习-帧内 ...
- Overview of HEVC之4 帧内预测
帧内预测是根据传输块的尺寸进行操作的,并且空间上相邻传输块的先前解码的边界像素被用来形成预测信号,对4*4到32*32的传输块定义了33种不同的方向预测.图6显示了可能的预测方向.另外也用到了平面预测 ...
- HEVC代码学习——帧间预测:预测MV获取(xEstimateMvPredAMVP、fillMVPCand)
HEVC帧间预测在AMVP模式下是依靠xEstimateMvPredAMVP函数获取预测MV(MVP)的. 这部分内容的学习还可以参考这两篇博客: HEVC代码学习15:AMVP相关函数 HM编码器代 ...
- VVC 帧内预测代码 xPredIntraAng()函数
帧内预测中的initPredIntraParams()函数 (负参考方向处在跑代码时再看一遍)_青椒鸡汤的博客-CSDN博客 H.266/VVC-VTM代码学习-帧内预测05-Angular模式下计算 ...
- HEVC算法和体系结构:预测编码之帧内预测
预测编码之帧内预测(Intra-Picture Prediction) 预测编码(Prediction Coding)是视频编码的核心技术之一,指利用已编码的一个或几个样本值,根据某种模型或方法,对当 ...
- 2.H.265/HEVC —— 帧内预测
在H.265/HEVC中,35种预测模式是在PU的基础上定义的,而具体帧内预测过程的实现则是以TU为单位的.编撰规定PU可以以四叉树的形式划分TU,且一个PU内所有TU共享同一种预测模式的形式划分TU ...
- 从HEVC到VVC:帧内预测技术的演进(2) – 多划分及多参考行帧内预测
当前主流的视频编码标准(如H.264/AVC,VP9,AVS1,HEVC等)均使用当前预测单元最邻近的已重构像素对当前预测单元进行帧内预测.因为当前预测单元与其临近的像素之间有很强的相关性,该帧内预测 ...
- H.266/VVC帧内预测总结
一.帧内预测基本原理 帧内预测技术是利用同一帧中相邻像素的相关性,利用当前块相邻区域的重建像素预测当前块中像素的技术,如下图所示,当前CU可以利用相邻A.B.C.D和E位置处的重建像素来预测当前CU中 ...
最新文章
- thymeleaf 中文文档
- 领域模型(domain model)贫血模型(anaemic domain model)充血模型(rich domain model)
- 牛客xiao白月赛32-- 拼三角(暴力却有坑)
- SQL数据库语言基础之SqlServer视图的创建、修改与视图数据的增删改查
- jquery的ajax查询数据库,jquery中使用ajax获取远程页面信息
- tvpvar模型的建模步骤_这种思路讲解数据仓库建模,你见过吗?数据人与架构师必看...
- 第 7 章 Neutron - 066 - Neutron 网络基本概念
- linux系统lvs技术,Linux 负载均衡二Lvs技术
- AccuMark 7.6.2 格柏服装软件
- 计算机网络自学指南,简直太全了!
- opencv批量修改图片分辨率
- 高考数学之快速解选择题
- RadioGroup 全部取消选中 和选中某个按钮
- 高通Spectra 2xx中GTM LTM的tuning重点
- VIPL Lab 9篇ACM MM 2019和IEEE TIP 论文精解
- HDMI-CEC功能之System Audio Control
- 如何在Outlook中检查电子邮件的可访问性
- c盘oracle文件夹,C盘的用户文件夹转移到其他分区
- Java 替换文件中的内容信息
- 批改网中的作文不能粘贴怎么办?