encodeResAndCalcRdInterCU

原理和细节

经过运动估计、运动补偿,我们得到了MV以及参考块,那么接下来是计算残差、计算MVD,然后对系数进行变换、量化。

encodeResAndCalcRdInterCU函数就是帧间预测之后,专门用来计算残差、进行变换量化的函数。但是,除了变换量化之外,encodeResAndCalcRdInterCU函数中还有熵编码方面的处理,我们都知道熵编码是在encodeSlice(xEncodeCU)中进行的,为什么会这样呢,这样做不就导致熵编码进行了多次吗?为了搞清楚这个问题,我们需要了解下面的一些概念:
    1、compressSlice/compressCU,这些函数的作用是模式选取,目的是选取最优的编码模式,使得率失真代价/比特代价最优。具体的操作就是利用率失真优化的原理,选取LCU到CU的最优划分模式,CU到PU的最优划分模式,PU的最优预测模式,CU到TU的最优划分模式,最优的QP参数,使得率失真代价/比特代价最优,但是为了算比特代价(bitCost),必须要经过熵编码这一步骤才能得到,而熵编码之前又有变化量化等步骤,因此compressSlice实际上是把各种各样的模式进行组合,然后每一个组合进行一个完整的编码操作(预测、变换、量化、熵编码),根据率失真代价/比特代价来选取最优的模式组合!
    2、encodeSlice/encodeCU,经过了compressSlice之后,我们已经得到了最优的划分模式、预测模式等参数,同时也得到了变换量化之后的系数既然已经得到了最优模式和变换系数,那么最后的一步就是对这些模式和系数进行熵编码,得到最终的比特率,因此encodeCU的功能就是熵编码。

encodeResAndCalcRdInterCU的流程

encodeResAndCalcRdInterCU实际就是计算PU的残差,然后进行变换量化,同时通过熵编码来计算率失真代价/比特代价来得到最优QP参数,这就是为什么encodeResAndCalcRdInterCU中还会有熵编码处理的原因。该函数执行完毕之后,变换量化、反变换反量化就已经处理好了,同时还选取了最优的QP参数。所以,这个函数的核心实际是变换量化!

/*
** 估计残差的编码代价
** 主要是进行变换量化以及熵编码,然后选出最优的模式
*/
Void TEncSearch::xEstimateResidualQT( TComDataCU* pcCU, UInt uiQuadrant, UInt uiAbsPartIdx, UInt absTUPartIdx, TComYuv* pcResi, const UInt uiDepth, Double &rdCost, UInt &ruiBits, UInt &ruiDist, UInt *puiZeroDist )
{// 删除无关紧要代码***************// 完整检查的标志Bool bCheckFull;if ( SplitFlag && uiDepth == pcCU->getDepth(uiAbsPartIdx) && ( uiLog2TrSize >  pcCU->getQuadtreeTULog2MinSizeInCU(uiAbsPartIdx) ) ){bCheckFull = false;}else{bCheckFull =  ( uiLog2TrSize <= pcCU->getSlice()->getSPS()->getQuadtreeTULog2MaxSize() );}// 是否检查split标志const Bool bCheckSplit  = ( uiLog2TrSize >  pcCU->getQuadtreeTULog2MinSizeInCU(uiAbsPartIdx) );// 完整检查模式if( bCheckFull ){// 删除无关紧要代码***************// 是否使用了率失真优化的量化if (m_pcEncCfg->getUseRDOQ()){// 估算比特数m_pcEntropyCoder->estimateBit(m_pcTrQuant->m_pcEstBitsSbac, trWidth, trHeight, TEXT_LUMA );        }// 删除无关紧要代码***************/*【==============变换量化开始===================】*/// Y分量变换和量化m_pcTrQuant->transformNxN( pcCU, pcResi->getLumaAddr( absTUPartIdx ), pcResi->getStride (), pcCoeffCurrY,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrY,
#endif      trWidth,   trHeight,    uiAbsSumY, TEXT_LUMA,     uiAbsPartIdx );// 删除无关紧要代码***************if( bCodeChroma ){if (m_pcEncCfg->getUseRDOQ()){m_pcEntropyCoder->estimateBit(m_pcTrQuant->m_pcEstBitsSbac, trWidthC, trHeightC, TEXT_CHROMA );          }// 删除无关紧要代码***************// 变换和量化(U分量)m_pcTrQuant->transformNxN( pcCU, pcResi->getCbAddr(absTUPartIdxC), pcResi->getCStride(), pcCoeffCurrU,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrU,
#endif        trWidthC, trHeightC, uiAbsSumU, TEXT_CHROMA_U, uiAbsPartIdx );// 删除无关紧要代码***************// 变换和量化(V分量)m_pcTrQuant->transformNxN( pcCU, pcResi->getCrAddr(absTUPartIdxC), pcResi->getCStride(), pcCoeffCurrV,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrV,
#endif        trWidthC, trHeightC, uiAbsSumV, TEXT_CHROMA_V, uiAbsPartIdx );// 删除无关紧要代码***************}/*【==============变换量化结束===================】*//*【==============变换系数以及其他信息熵编码开始===================】*/m_pcEntropyCoder->resetBits();// 对CBF进行编码(Y分量)m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_LUMA,     uiTrMode );// 对残差系数进行编码(Y分量)m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrY, uiAbsPartIdx,  trWidth,  trHeight,    uiDepth, TEXT_LUMA );// 删除无关紧要代码***************// 对色度部分进行编码if( bCodeChroma ){// 删除无关紧要代码***************// 对CBF进行编码(U分量)m_pcEntropyCoder->encodeQtCbf   ( pcCU, uiAbsPartIdx, TEXT_CHROMA_U, uiTrMode );// 对系数进行编码(U分量)m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrU, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_U );// 删除无关紧要代码***************// 对CBF进行编码(V分量)m_pcEntropyCoder->encodeQtCbf   ( pcCU, uiAbsPartIdx, TEXT_CHROMA_V, uiTrMode );// 对系数进行编码(V分量)m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrV, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_V );// 删除无关紧要代码***************}/*【==============变换系数以及其他信息熵编码结束===================】*/// 删除无关紧要代码***************/*【==============反量化、反变换开始===================】*/// 如果Y分量绝对系数和大于0// 开始执行反量化、反变换if( uiAbsSumY ){// 删除无关紧要代码***************// 反变换、反量化(Y分量)m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_LUMA,REG_DCT, pcResiCurrY, m_pcQTTempTComYuv[uiQTTempAccessLayer].getStride(),  pcCoeffCurrY, trWidth, trHeight, scalingListType );//this is for inter mode onlyconst UInt uiNonzeroDistY = m_pcRdCost->getDistPart(g_bitDepthY, m_pcQTTempTComYuv[uiQTTempAccessLayer].getLumaAddr( absTUPartIdx ), m_pcQTTempTComYuv[uiQTTempAccessLayer].getStride(),pcResi->getLumaAddr( absTUPartIdx ), pcResi->getStride(), trWidth,trHeight );if (pcCU->isLosslessCoded(0)) {uiDistY = uiNonzeroDistY;}else{// 删除无关紧要代码***************m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_LUMA,     uiTrMode );// 删除无关紧要代码***************}}// 否则,如果是跳过Y分量else if( checkTransformSkipY ){// 删除无关紧要代码***************// 编码Y分量的CBF的0系数m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_LUMA, uiTrMode );// 删除无关紧要代码***************}// 删除无关紧要代码***************// 如果对色度部分进行了变换量化,那么需要对色度部分进行反变换反量化if( bCodeChroma ){// 删除无关紧要代码***************// 如果对U分量进行了变换量化if( uiAbsSumU ){// 删除无关紧要代码***************// 反变换、反量化(U分量)m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_CHROMA,REG_DCT, pcResiCurrU, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(), pcCoeffCurrU, trWidthC, trHeightC, scalingListType  );const UInt uiNonzeroDistU = m_pcRdCost->getDistPart(g_bitDepthC, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCbAddr( absTUPartIdxC), m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(),pcResi->getCbAddr( absTUPartIdxC), pcResi->getCStride(), trWidthC, trHeightC, TEXT_CHROMA_U);if(pcCU->isLosslessCoded(0))  {uiDistU = uiNonzeroDistU;}else{// 删除无关紧要代码***************m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_CHROMA_U,     uiTrMode );// 删除无关紧要代码***************}}// 否则(即变换量化没有效果或者不够优?)else if( checkTransformSkipUV ){// 删除无关紧要代码***************// 编码CBF的0系数m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_CHROMA_U, uiTrMode );// 删除无关紧要代码***************}// 删除无关紧要代码***************// 对V分量进行处理(过程同U分量)if( uiAbsSumV ){// 删除无关紧要代码***************// 反变换反量化(V分量)m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_CHROMA,REG_DCT, pcResiCurrV, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(), pcCoeffCurrV, trWidthC, trHeightC, scalingListType );const UInt uiNonzeroDistV = m_pcRdCost->getDistPart(g_bitDepthC, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCrAddr( absTUPartIdxC ), m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(),pcResi->getCrAddr( absTUPartIdxC ), pcResi->getCStride(), trWidthC, trHeightC, TEXT_CHROMA_V);if (pcCU->isLosslessCoded(0)) {uiDistV = uiNonzeroDistV;}else{// 删除无关紧要代码***************// 对CBF的0系数进行编码m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_CHROMA_V,     uiTrMode );// 删除无关紧要代码***************}}// 如果跳过了UV分量else if( checkTransformSkipUV ){// 删除无关紧要代码***************// 对CBF的0系数进行编码m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_CHROMA_V, uiTrMode );// 删除无关紧要代码***************}if( !uiAbsSumV ){// 删除无关紧要代码***************}}/*【==============反量化、反变换结束===================】*/// 删除无关紧要代码***************/*【==============TransformSkip模式的变换量化、反变换反量化、熵编码 开始===================】*/if( checkTransformSkipY ){// 删除无关紧要代码***************if (m_pcEncCfg->getUseRDOQTS()){// 估算比特数m_pcEntropyCoder->estimateBit( m_pcTrQuant->m_pcEstBitsSbac, trWidth, trHeight, TEXT_LUMA );        }// 删除无关紧要代码***************// 变换和量化m_pcTrQuant->transformNxN( pcCU, pcResi->getLumaAddr( absTUPartIdx ), pcResi->getStride (), pcCoeffCurrY,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrY,
#endif      trWidth,   trHeight,    uiAbsSumTransformSkipY, TEXT_LUMA, uiAbsPartIdx, true );pcCU->setCbfSubParts( uiAbsSumTransformSkipY ? uiSetCbf : 0, TEXT_LUMA, uiAbsPartIdx, uiDepth );if( uiAbsSumTransformSkipY != 0 ){m_pcEntropyCoder->resetBits();// 对CBF的0系数进行编码m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_LUMA, uiTrMode );// 对残差系数进行编码m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrY, uiAbsPartIdx, trWidth, trHeight, uiDepth, TEXT_LUMA );// 删除无关紧要代码***************// 反变换、反量化m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_LUMA,REG_DCT, pcResiCurrY, m_pcQTTempTComYuv[uiQTTempAccessLayer].getStride(),  pcCoeffCurrY, trWidth, trHeight, scalingListType, true );// 删除无关紧要代码***************}// 删除无关紧要代码***************}// 如果对色度部分进行编码,但是跳过了UV分量(这是什么情况?)if( bCodeChroma && checkTransformSkipUV  ){// 删除无关紧要代码***************if (m_pcEncCfg->getUseRDOQTS()){// 比特数估算m_pcEntropyCoder->estimateBit(m_pcTrQuant->m_pcEstBitsSbac, trWidthC, trHeightC, TEXT_CHROMA );          }// 删除无关紧要代码***************// 变换量化(U分量)m_pcTrQuant->transformNxN( pcCU, pcResi->getCbAddr(absTUPartIdxC), pcResi->getCStride(), pcCoeffCurrU,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrU,
#endif        trWidthC, trHeightC, uiAbsSumTransformSkipU, TEXT_CHROMA_U, uiAbsPartIdx, true );// 删除无关紧要代码***************// 变换量化(V分量)m_pcTrQuant->transformNxN( pcCU, pcResi->getCrAddr(absTUPartIdxC), pcResi->getCStride(), pcCoeffCurrV,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrV,
#endif        trWidthC, trHeightC, uiAbsSumTransformSkipV, TEXT_CHROMA_V, uiAbsPartIdx, true );// 删除无关紧要代码***************// 如果跳过了U分量if( uiAbsSumTransformSkipU ){// 删除无关紧要代码***************// 对系数进行编码m_pcEntropyCoder->encodeQtCbf   ( pcCU, uiAbsPartIdx, TEXT_CHROMA_U, uiTrMode );m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrU, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_U );// 删除无关紧要代码***************// 反变换反量化m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_CHROMA,REG_DCT, pcResiCurrU, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(), pcCoeffCurrU, trWidthC, trHeightC, scalingListType, true  );// 删除无关紧要代码***************}// 删除无关紧要代码***************// 如果跳过了V分量if( uiAbsSumTransformSkipV ){// 删除无关紧要代码***************// 对系数进行编码m_pcEntropyCoder->encodeQtCbf   ( pcCU, uiAbsPartIdx, TEXT_CHROMA_V, uiTrMode );m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrV, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_V );// 删除无关紧要代码***************// 反变换反量化m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_CHROMA,REG_DCT, pcResiCurrV, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(), pcCoeffCurrV, trWidthC, trHeightC, scalingListType, true );// 删除无关紧要代码***************}// 删除无关紧要代码***************}/*【==============TransformSkip模式的变换量化、反变换反量化、熵编码 结束===================】*/// 删除无关紧要代码***************if( uiLog2TrSize > pcCU->getQuadtreeTULog2MinSizeInCU(uiAbsPartIdx) ){// 编码分割标志?m_pcEntropyCoder->encodeTransformSubdivFlag( 0, 5 - uiLog2TrSize );}if( bCodeChroma ){m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_CHROMA_U, uiTrMode );m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_CHROMA_V, uiTrMode );}m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_LUMA,     uiTrMode );m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrY, uiAbsPartIdx, trWidth, trHeight,    uiDepth, TEXT_LUMA );if( bCodeChroma ){m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrU, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_U );m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrV, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_V );}// 删除无关紧要代码***************}  // code sub-blocks// 对TU的四个子TU进行计算(使用递归的方式)if( bCheckSplit ){// 删除无关紧要代码***************for( UInt ui = 0; ui < 4; ++ui ){UInt nsAddr = uiAbsPartIdx + ui * uiQPartNumSubdiv;// 递归调用xEstimateResidualQT( pcCU, ui, uiAbsPartIdx + ui * uiQPartNumSubdiv, nsAddr, pcResi, uiDepth + 1, dSubdivCost, uiSubdivBits, uiSubdivDist, bCheckFull ? NULL : puiZeroDist );}// 删除无关紧要代码***************// 对残差四叉树进行编码xEncodeResidualQT( pcCU, uiAbsPartIdx, uiDepth, true,  TEXT_LUMA );xEncodeResidualQT( pcCU, uiAbsPartIdx, uiDepth, false, TEXT_LUMA );xEncodeResidualQT( pcCU, uiAbsPartIdx, uiDepth, false, TEXT_CHROMA_U );xEncodeResidualQT( pcCU, uiAbsPartIdx, uiDepth, false, TEXT_CHROMA_V );// 删除无关紧要代码***************}// 删除无关紧要代码***************
}

估算残差的率失真代价和比特代价

入口函数是:xEstimateResidualQT

流程如下:

一、首先看到CheckFull标志,如果为真,就对各种模式进行测试,估计他们的率失真代价和比特代价等,主要步骤如下:
        1、分别对Y、U、V三个分量进行变换和量化,transformNxN最后的参数是false,表示不是TransformSkip模式
        2、对Y、U、V三个分量的变换系数以及其他的信息进行熵编码,计算出率失真代价以及比特代价
        3、对Y、U、V三个分量进行反变换反量化
        4、如果是TransformSkip模式,那么在TransformSkip模式下,重新执行步骤1、2、3,但是transformNxN最后的参数是true,表示是TransformSkip模式
        5、判断比较得出最优的模式
    二、检测CheckSplit标识,如果为真,那么进行子TU的递归处理,并对当前TU进行熵编码
        1、对四个子TU递归调用xEstimateResidualQT
        2、对Y分量调用xEncodeResidualQT,进行熵编码,bSubdivAndCbf参数是true,表示计算当前TU划分成4个子TU时的代价,另外如果bSubdivAndCbf参数是true,那么会在xEncodeResidualQT里面计算U、V分量的率失真代价/比特代价
        3、对Y、U、V三个分量调用xEncodeResidualQT进行熵编码,bSubdivAndCbf参数是false,表示计算当前TU不划分成子TU时的代价

/*
** 估计残差的编码代价
** 主要是进行变换量化以及熵编码,然后选出最优的模式
*/
Void TEncSearch::xEstimateResidualQT( TComDataCU* pcCU, UInt uiQuadrant, UInt uiAbsPartIdx, UInt absTUPartIdx, TComYuv* pcResi, const UInt uiDepth, Double &rdCost, UInt &ruiBits, UInt &ruiDist, UInt *puiZeroDist )
{// 删除无关紧要代码***************// 完整检查的标志Bool bCheckFull;if ( SplitFlag && uiDepth == pcCU->getDepth(uiAbsPartIdx) && ( uiLog2TrSize >  pcCU->getQuadtreeTULog2MinSizeInCU(uiAbsPartIdx) ) ){bCheckFull = false;}else{bCheckFull =  ( uiLog2TrSize <= pcCU->getSlice()->getSPS()->getQuadtreeTULog2MaxSize() );}// 是否检查split标志const Bool bCheckSplit  = ( uiLog2TrSize >  pcCU->getQuadtreeTULog2MinSizeInCU(uiAbsPartIdx) );// 完整检查模式if( bCheckFull ){// 删除无关紧要代码***************// 是否使用了率失真优化的量化if (m_pcEncCfg->getUseRDOQ()){// 估算比特数m_pcEntropyCoder->estimateBit(m_pcTrQuant->m_pcEstBitsSbac, trWidth, trHeight, TEXT_LUMA );        }// 删除无关紧要代码***************/*【==============变换量化开始===================】*/// Y分量变换和量化m_pcTrQuant->transformNxN( pcCU, pcResi->getLumaAddr( absTUPartIdx ), pcResi->getStride (), pcCoeffCurrY,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrY,
#endif      trWidth,   trHeight,    uiAbsSumY, TEXT_LUMA,     uiAbsPartIdx );// 删除无关紧要代码***************if( bCodeChroma ){if (m_pcEncCfg->getUseRDOQ()){m_pcEntropyCoder->estimateBit(m_pcTrQuant->m_pcEstBitsSbac, trWidthC, trHeightC, TEXT_CHROMA );          }// 删除无关紧要代码***************// 变换和量化(U分量)m_pcTrQuant->transformNxN( pcCU, pcResi->getCbAddr(absTUPartIdxC), pcResi->getCStride(), pcCoeffCurrU,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrU,
#endif        trWidthC, trHeightC, uiAbsSumU, TEXT_CHROMA_U, uiAbsPartIdx );// 删除无关紧要代码***************// 变换和量化(V分量)m_pcTrQuant->transformNxN( pcCU, pcResi->getCrAddr(absTUPartIdxC), pcResi->getCStride(), pcCoeffCurrV,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrV,
#endif        trWidthC, trHeightC, uiAbsSumV, TEXT_CHROMA_V, uiAbsPartIdx );// 删除无关紧要代码***************}/*【==============变换量化结束===================】*//*【==============变换系数以及其他信息熵编码开始===================】*/m_pcEntropyCoder->resetBits();// 对CBF进行编码(Y分量)m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_LUMA,     uiTrMode );// 对残差系数进行编码(Y分量)m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrY, uiAbsPartIdx,  trWidth,  trHeight,    uiDepth, TEXT_LUMA );// 删除无关紧要代码***************// 对色度部分进行编码if( bCodeChroma ){// 删除无关紧要代码***************// 对CBF进行编码(U分量)m_pcEntropyCoder->encodeQtCbf   ( pcCU, uiAbsPartIdx, TEXT_CHROMA_U, uiTrMode );// 对系数进行编码(U分量)m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrU, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_U );// 删除无关紧要代码***************// 对CBF进行编码(V分量)m_pcEntropyCoder->encodeQtCbf   ( pcCU, uiAbsPartIdx, TEXT_CHROMA_V, uiTrMode );// 对系数进行编码(V分量)m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrV, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_V );// 删除无关紧要代码***************}/*【==============变换系数以及其他信息熵编码结束===================】*/// 删除无关紧要代码***************/*【==============反量化、反变换开始===================】*/// 如果Y分量绝对系数和大于0// 开始执行反量化、反变换if( uiAbsSumY ){// 删除无关紧要代码***************// 反变换、反量化(Y分量)m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_LUMA,REG_DCT, pcResiCurrY, m_pcQTTempTComYuv[uiQTTempAccessLayer].getStride(),  pcCoeffCurrY, trWidth, trHeight, scalingListType );//this is for inter mode onlyconst UInt uiNonzeroDistY = m_pcRdCost->getDistPart(g_bitDepthY, m_pcQTTempTComYuv[uiQTTempAccessLayer].getLumaAddr( absTUPartIdx ), m_pcQTTempTComYuv[uiQTTempAccessLayer].getStride(),pcResi->getLumaAddr( absTUPartIdx ), pcResi->getStride(), trWidth,trHeight );if (pcCU->isLosslessCoded(0)) {uiDistY = uiNonzeroDistY;}else{// 删除无关紧要代码***************m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_LUMA,     uiTrMode );// 删除无关紧要代码***************}}// 否则,如果是跳过Y分量else if( checkTransformSkipY ){// 删除无关紧要代码***************// 编码Y分量的CBF的0系数m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_LUMA, uiTrMode );// 删除无关紧要代码***************}// 删除无关紧要代码***************// 如果对色度部分进行了变换量化,那么需要对色度部分进行反变换反量化if( bCodeChroma ){// 删除无关紧要代码***************// 如果对U分量进行了变换量化if( uiAbsSumU ){// 删除无关紧要代码***************// 反变换、反量化(U分量)m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_CHROMA,REG_DCT, pcResiCurrU, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(), pcCoeffCurrU, trWidthC, trHeightC, scalingListType  );const UInt uiNonzeroDistU = m_pcRdCost->getDistPart(g_bitDepthC, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCbAddr( absTUPartIdxC), m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(),pcResi->getCbAddr( absTUPartIdxC), pcResi->getCStride(), trWidthC, trHeightC, TEXT_CHROMA_U);if(pcCU->isLosslessCoded(0))  {uiDistU = uiNonzeroDistU;}else{// 删除无关紧要代码***************m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_CHROMA_U,     uiTrMode );// 删除无关紧要代码***************}}// 否则(即变换量化没有效果或者不够优?)else if( checkTransformSkipUV ){// 删除无关紧要代码***************// 编码CBF的0系数m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_CHROMA_U, uiTrMode );// 删除无关紧要代码***************}// 删除无关紧要代码***************// 对V分量进行处理(过程同U分量)if( uiAbsSumV ){// 删除无关紧要代码***************// 反变换反量化(V分量)m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_CHROMA,REG_DCT, pcResiCurrV, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(), pcCoeffCurrV, trWidthC, trHeightC, scalingListType );const UInt uiNonzeroDistV = m_pcRdCost->getDistPart(g_bitDepthC, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCrAddr( absTUPartIdxC ), m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(),pcResi->getCrAddr( absTUPartIdxC ), pcResi->getCStride(), trWidthC, trHeightC, TEXT_CHROMA_V);if (pcCU->isLosslessCoded(0)) {uiDistV = uiNonzeroDistV;}else{// 删除无关紧要代码***************// 对CBF的0系数进行编码m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_CHROMA_V,     uiTrMode );// 删除无关紧要代码***************}}// 如果跳过了UV分量else if( checkTransformSkipUV ){// 删除无关紧要代码***************// 对CBF的0系数进行编码m_pcEntropyCoder->encodeQtCbfZero( pcCU, TEXT_CHROMA_V, uiTrMode );// 删除无关紧要代码***************}if( !uiAbsSumV ){// 删除无关紧要代码***************}}/*【==============反量化、反变换结束===================】*/// 删除无关紧要代码***************/*【==============TransformSkip模式的变换、量化、熵编码 开始===================】*/if( checkTransformSkipY ){// 删除无关紧要代码***************if (m_pcEncCfg->getUseRDOQTS()){// 估算比特数m_pcEntropyCoder->estimateBit( m_pcTrQuant->m_pcEstBitsSbac, trWidth, trHeight, TEXT_LUMA );        }// 删除无关紧要代码***************// 变换和量化m_pcTrQuant->transformNxN( pcCU, pcResi->getLumaAddr( absTUPartIdx ), pcResi->getStride (), pcCoeffCurrY,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrY,
#endif      trWidth,   trHeight,    uiAbsSumTransformSkipY, TEXT_LUMA, uiAbsPartIdx, true );pcCU->setCbfSubParts( uiAbsSumTransformSkipY ? uiSetCbf : 0, TEXT_LUMA, uiAbsPartIdx, uiDepth );if( uiAbsSumTransformSkipY != 0 ){m_pcEntropyCoder->resetBits();// 对CBF的0系数进行编码m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_LUMA, uiTrMode );// 对残差系数进行编码m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrY, uiAbsPartIdx, trWidth, trHeight, uiDepth, TEXT_LUMA );// 删除无关紧要代码***************// 反变换、反量化m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_LUMA,REG_DCT, pcResiCurrY, m_pcQTTempTComYuv[uiQTTempAccessLayer].getStride(),  pcCoeffCurrY, trWidth, trHeight, scalingListType, true );// 删除无关紧要代码***************}// 删除无关紧要代码***************}// 如果对色度部分进行编码,但是跳过了UV分量(这是什么情况?)if( bCodeChroma && checkTransformSkipUV  ){// 删除无关紧要代码***************if (m_pcEncCfg->getUseRDOQTS()){// 比特数估算m_pcEntropyCoder->estimateBit(m_pcTrQuant->m_pcEstBitsSbac, trWidthC, trHeightC, TEXT_CHROMA );          }// 删除无关紧要代码***************// 变换量化(U分量)m_pcTrQuant->transformNxN( pcCU, pcResi->getCbAddr(absTUPartIdxC), pcResi->getCStride(), pcCoeffCurrU,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrU,
#endif        trWidthC, trHeightC, uiAbsSumTransformSkipU, TEXT_CHROMA_U, uiAbsPartIdx, true );// 删除无关紧要代码***************// 变换量化(V分量)m_pcTrQuant->transformNxN( pcCU, pcResi->getCrAddr(absTUPartIdxC), pcResi->getCStride(), pcCoeffCurrV,
#if ADAPTIVE_QP_SELECTIONpcArlCoeffCurrV,
#endif        trWidthC, trHeightC, uiAbsSumTransformSkipV, TEXT_CHROMA_V, uiAbsPartIdx, true );// 删除无关紧要代码***************// 如果跳过了U分量if( uiAbsSumTransformSkipU ){// 删除无关紧要代码***************// 对系数进行编码m_pcEntropyCoder->encodeQtCbf   ( pcCU, uiAbsPartIdx, TEXT_CHROMA_U, uiTrMode );m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrU, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_U );// 删除无关紧要代码***************// 反变换反量化m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_CHROMA,REG_DCT, pcResiCurrU, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(), pcCoeffCurrU, trWidthC, trHeightC, scalingListType, true  );// 删除无关紧要代码***************}// 删除无关紧要代码***************// 如果跳过了V分量if( uiAbsSumTransformSkipV ){// 删除无关紧要代码***************// 对系数进行编码m_pcEntropyCoder->encodeQtCbf   ( pcCU, uiAbsPartIdx, TEXT_CHROMA_V, uiTrMode );m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrV, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_V );// 删除无关紧要代码***************// 反变换反量化m_pcTrQuant->invtransformNxN( pcCU->getCUTransquantBypass(uiAbsPartIdx), TEXT_CHROMA,REG_DCT, pcResiCurrV, m_pcQTTempTComYuv[uiQTTempAccessLayer].getCStride(), pcCoeffCurrV, trWidthC, trHeightC, scalingListType, true );// 删除无关紧要代码***************}// 删除无关紧要代码***************}/*【==============TransformSkip模式的变换、量化、熵编码 结束===================】*/// 删除无关紧要代码***************if( uiLog2TrSize > pcCU->getQuadtreeTULog2MinSizeInCU(uiAbsPartIdx) ){// 编码分割标志?m_pcEntropyCoder->encodeTransformSubdivFlag( 0, 5 - uiLog2TrSize );}// 前面的是为了选出最优模式,下面对系数进行编码/*【==============前面的好几次变换量化熵编码,只是为了选取最优的编码模式,现在根据最优的编码模式进行编码 开始===================】*/// 对色度部分CBF进行编码if( bCodeChroma ){m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_CHROMA_U, uiTrMode );m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_CHROMA_V, uiTrMode );}// 对亮度的CBF进行编码m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_LUMA,     uiTrMode );// 编码亮度部分的系数m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrY, uiAbsPartIdx, trWidth, trHeight,    uiDepth, TEXT_LUMA );// 对色度部分的系数进行编码if( bCodeChroma ){m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrU, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_U );m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrV, uiAbsPartIdx, trWidthC, trHeightC, uiDepth, TEXT_CHROMA_V );}/*【==============前面的好几次变换量化熵编码,只是为了选取最优的编码模式,现在根据最优的编码模式进行编码 结束===================】*/// 删除无关紧要代码***************}  // code sub-blocks// 对TU的四个子TU进行计算(使用递归的方式)if( bCheckSplit ){// 删除无关紧要代码***************for( UInt ui = 0; ui < 4; ++ui ){UInt nsAddr = uiAbsPartIdx + ui * uiQPartNumSubdiv;// 递归调用xEstimateResidualQT( pcCU, ui, uiAbsPartIdx + ui * uiQPartNumSubdiv, nsAddr, pcResi, uiDepth + 1, dSubdivCost, uiSubdivBits, uiSubdivDist, bCheckFull ? NULL : puiZeroDist );}// 删除无关紧要代码***************// 对残差四叉树进行编码xEncodeResidualQT( pcCU, uiAbsPartIdx, uiDepth, true,  TEXT_LUMA );xEncodeResidualQT( pcCU, uiAbsPartIdx, uiDepth, false, TEXT_LUMA );xEncodeResidualQT( pcCU, uiAbsPartIdx, uiDepth, false, TEXT_CHROMA_U );xEncodeResidualQT( pcCU, uiAbsPartIdx, uiDepth, false, TEXT_CHROMA_V );// 删除无关紧要代码***************}// 删除无关紧要代码***************
}

对Cbf、残差系数进行熵编码

/*
** xEncodeResidualQT的主要功能是对Cbf、残差系数进行熵编码
*/
Void TEncSearch::xEncodeResidualQT( TComDataCU* pcCU, UInt uiAbsPartIdx, const UInt uiDepth, Bool bSubdivAndCbf, TextType eType )
{assert( pcCU->getDepth( 0 ) == pcCU->getDepth( uiAbsPartIdx ) );// 模式const UInt uiCurrTrMode = uiDepth - pcCU->getDepth( 0 );const UInt uiTrMode = pcCU->getTransformIdx( uiAbsPartIdx );// 对当前TU是否会继续向下分割const Bool bSubdiv = uiCurrTrMode != uiTrMode;const UInt uiLog2TrSize = g_aucConvertToBit[pcCU->getSlice()->getSPS()->getMaxCUWidth() >> uiDepth]+2;if( bSubdivAndCbf && uiLog2TrSize <= pcCU->getSlice()->getSPS()->getQuadtreeTULog2MaxSize() && uiLog2TrSize > pcCU->getQuadtreeTULog2MinSizeInCU(uiAbsPartIdx) ){// 编码向下分割的标志m_pcEntropyCoder->encodeTransformSubdivFlag( bSubdiv, 5 - uiLog2TrSize );}assert( pcCU->getPredictionMode(uiAbsPartIdx) != MODE_INTRA );// 如果bSubdivAndCbf是true,那么对U、V分量进行处理,Y分量在前面已经处理了if( bSubdivAndCbf ){const Bool bFirstCbfOfCU = uiCurrTrMode == 0;// 编码UV分量的CBF(Y分量已经在调用这个函数之前处理完了)if( bFirstCbfOfCU || uiLog2TrSize > 2 ){if( bFirstCbfOfCU || pcCU->getCbf( uiAbsPartIdx, TEXT_CHROMA_U, uiCurrTrMode - 1 ) ){m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_CHROMA_U, uiCurrTrMode );}if( bFirstCbfOfCU || pcCU->getCbf( uiAbsPartIdx, TEXT_CHROMA_V, uiCurrTrMode - 1 ) ){m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_CHROMA_V, uiCurrTrMode );}}else if( uiLog2TrSize == 2 ){assert( pcCU->getCbf( uiAbsPartIdx, TEXT_CHROMA_U, uiCurrTrMode ) == pcCU->getCbf( uiAbsPartIdx, TEXT_CHROMA_U, uiCurrTrMode - 1 ) );assert( pcCU->getCbf( uiAbsPartIdx, TEXT_CHROMA_V, uiCurrTrMode ) == pcCU->getCbf( uiAbsPartIdx, TEXT_CHROMA_V, uiCurrTrMode - 1 ) );}}// 如果不在继续向下分割,那么直接编码YUV三个分量的系数,否则需要对子TU递归调用if( !bSubdiv ){const UInt uiNumCoeffPerAbsPartIdxIncrement = pcCU->getSlice()->getSPS()->getMaxCUWidth() * pcCU->getSlice()->getSPS()->getMaxCUHeight() >> ( pcCU->getSlice()->getSPS()->getMaxCUDepth() << 1 );//assert( 16 == uiNumCoeffPerAbsPartIdxIncrement ); // checkconst UInt uiQTTempAccessLayer = pcCU->getSlice()->getSPS()->getQuadtreeTULog2MaxSize() - uiLog2TrSize;TCoeff *pcCoeffCurrY = m_ppcQTTempCoeffY [uiQTTempAccessLayer] +  uiNumCoeffPerAbsPartIdxIncrement * uiAbsPartIdx;TCoeff *pcCoeffCurrU = m_ppcQTTempCoeffCb[uiQTTempAccessLayer] + (uiNumCoeffPerAbsPartIdxIncrement * uiAbsPartIdx>>2);TCoeff *pcCoeffCurrV = m_ppcQTTempCoeffCr[uiQTTempAccessLayer] + (uiNumCoeffPerAbsPartIdxIncrement * uiAbsPartIdx>>2);Bool  bCodeChroma   = true;UInt  uiTrModeC     = uiTrMode;UInt  uiLog2TrSizeC = uiLog2TrSize-1;if( uiLog2TrSize == 2 ){uiLog2TrSizeC++;uiTrModeC    --;UInt  uiQPDiv = pcCU->getPic()->getNumPartInCU() >> ( ( pcCU->getDepth( 0 ) + uiTrModeC ) << 1 );bCodeChroma   = ( ( uiAbsPartIdx % uiQPDiv ) == 0 );}if( bSubdivAndCbf ){m_pcEntropyCoder->encodeQtCbf( pcCU, uiAbsPartIdx, TEXT_LUMA,     uiTrMode );}else{if( eType == TEXT_LUMA     && pcCU->getCbf( uiAbsPartIdx, TEXT_LUMA,     uiTrMode ) ){Int trWidth  = 1 << uiLog2TrSize;Int trHeight = 1 << uiLog2TrSize;// 对Y分量的系数进行编码m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrY, uiAbsPartIdx, trWidth, trHeight,    uiDepth, TEXT_LUMA );}if( bCodeChroma ){Int trWidth  = 1 << uiLog2TrSizeC;Int trHeight = 1 << uiLog2TrSizeC;// 对U分量的系数进行编码if( eType == TEXT_CHROMA_U && pcCU->getCbf( uiAbsPartIdx, TEXT_CHROMA_U, uiTrMode ) ){m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrU, uiAbsPartIdx, trWidth, trHeight, uiDepth, TEXT_CHROMA_U );}// 对V分量的系数进行编码if( eType == TEXT_CHROMA_V && pcCU->getCbf( uiAbsPartIdx, TEXT_CHROMA_V, uiTrMode ) ){m_pcEntropyCoder->encodeCoeffNxN( pcCU, pcCoeffCurrV, uiAbsPartIdx, trWidth, trHeight, uiDepth, TEXT_CHROMA_V );}}}}else{if( bSubdivAndCbf || pcCU->getCbf( uiAbsPartIdx, eType, uiCurrTrMode ) ){const UInt uiQPartNumSubdiv = pcCU->getPic()->getNumPartInCU() >> ((uiDepth + 1 ) << 1);// 对子TU递归调用该函数for( UInt ui = 0; ui < 4; ++ui ){xEncodeResidualQT( pcCU, uiAbsPartIdx + ui * uiQPartNumSubdiv, uiDepth + 1, bSubdivAndCbf, eType );}}}
}

至此,帧间预测的AMVP模式全部讲解完毕!

HM编码器代码阅读(32)——帧间预测之AMVP/Merge模式(七)encodeResAndCalcRdInterCU函数:残差计算、变换量化相关推荐

  1. HM编码器代码阅读(31)——帧间预测之AMVP/Merge模式(六)运动补偿

    运动补偿 原理 说实话一直很难理解运动补偿中"补偿"二字的意思,在参考了 http://blog.csdn.net/hevc_cjl/article/details/8457642 ...

  2. HM编码器代码阅读(16)——帧间预测之AMVP模式(四)预测MV的获取

    帧间预测的原理 AMVP的原理 帧间预测的实质就是为当前的PU在参考帧中寻找一块最相似块(相似度的判断准则有SAD等方法).但是参考图像通常都比较大,我们直接去搜索的话就太费时了,应该使用某种方法在参 ...

  3. HM编码器代码阅读(13)——帧间预测之AMVP模式(一)总体流程

    帧间预测的原理 AMVP的原理 帧间预测的实质就是为当前的PU在参考帧中寻找一块最相似块(相似度的判断准则有SAD等方法).但是参考图像通常都比较大,我们直接去搜索的话就太费时了,应该使用某种方法在参 ...

  4. HM编码器代码阅读(14)——帧间预测之AMVP模式(二)predInterSearch函数

    简介     predInterSearch主要的工作是ME(运动估计)和MC(运动补偿).     函数中有一个bTestNormalMC变量,它表示是否进行正常的MC过程,正常的MC过程就是进行M ...

  5. HM编码器代码阅读(30)——帧间预测之AMVP模式(五)运动估计

    运动估计 通过 点击打开链接 介绍的方法得到MVP之后,可以根据该MVP确定运动估计的搜索起点,然后进行运动估计 xMotionEstimation就是进行运动估计的入口函数     1.先进行一些初 ...

  6. HM编码器代码阅读(15)——帧间预测之AMVP模式(三)xGetBlkBits函数

    GetBlkBits函数的主要功能是计算使用某种PU划分模式的时候,该种模式占用的比特数 Void TEncSearch::xGetBlkBits( PartSize eCUMode, Bool bP ...

  7. HM编码器代码阅读(38)——帧内预测(五)帧内预测之正式的预测操作

    正式的预测操作 在前面的操作中,我们已经得到了模式候选列表,但是我们的目的是要得到一个最优的模式,因此我们还需要对这个列表中的模式进行遍历,对于每一个模式,进行预测操作,为了计算率失真代价还必须进行变 ...

  8. HM编码器代码阅读(14)——帧间预測之AMVP模式(二)predInterSearch函数

    简单介绍     predInterSearch基本的工作是ME(运动预计)和MC(运动补偿).     函数中有一个bTestNormalMC变量.它表示是否进行正常的MC过程,正常的MC过程就是进 ...

  9. HM代码阅读1: 帧间预测函数Void TEncSearch::predInterSearch()

    AMVP理论知识简单回顾(含GPB) MVP主要是为了给当前PU提供一个运动矢量的预测,可加快ME的计算速度以及提升准确性.并且在后续编码中也只用编码MVD,减少了传输bit数. HM中获取每个参考图 ...

最新文章

  1. 1070 Mooncake
  2. Scalable IO in Java
  3. vs中没有fstream_vs++2010 编译说找不到 fstream.h 解决方法
  4. 文献学习(part28)--Biclustering of gene expression data based on related genes and conditions extraction
  5. apache jmx_用于JMX访问的Apache Ant任务
  6. 转学到斯坦福大学计算机专业,斯坦福大学转学申请条件有哪些?
  7. android创建构建方法,Android 应用程序构建实战+原理精讲
  8. 国外一些知名ASP.Net开源CMS系统
  9. 从Excel文件中找出在TXT文件中没有出现的 行之_代码片段
  10. 如何在 Laravel 中 “规范” 的开发验证码发送功能
  11. 通过Powershell重新挂接父VHD磁盘的方法
  12. CF 914 D. Bash and a Tough Math Puzzle
  13. csf格式手机播放器(安卓csf格式播放器)
  14. 智力题(猜凶手,确定比赛名次)
  15. 神经元如何将视觉世界映射到人脑?
  16. 阿里国际站各数据更新时间汇总
  17. 3月20 Bundle Adjustment光束平差法概述
  18. android手机什么架构图,从架构图看Android分为几层呢?
  19. Podium Vue客户端组件库
  20. Omi官方插件系列 - omi-transform介绍

热门文章

  1. locust工具学习笔记(三)-Tasks属性、tag修饰符、TaskSet类
  2. php如何删除文件夹里的图片,php如何删除文件夹
  3. 利用WordPress源代码轻松搭建个人博客站点
  4. android布局空格以及首行缩进表示符
  5. AFM测试常见问题及解答(一)
  6. java的可执行文件_java生成可执行文件的方法总结
  7. win10右键文件夹无反应
  8. 校招(含实习生春招)指南
  9. 百姓基因:新一代基因测序技术及其在肿瘤研究中的应用
  10. 小布助手在面向中文短文本的实体链指比赛中的实践应用