HEVC代码学习:帧间预测——MVP过程中MV的获取、传递及存储
作为一个视频编码小白,最近开始着手啃HEVC帧间预测的代码,想用博客记录一下自己的学习过程,也想与大家分享、交流一下。
HEVC代码的学习主要是参考两位大神岳麓吹雪、NB_vol_1的博客以及HM参考软件。
两位大神的关于HEVC帧间部分的博客如下:
HEVC代码学习
HM编码器代码阅读(33)——帧间预测的总结
而HM软件的安装配置可参考HEVC代码:HM使用+码流分析教程
应接下来研究的需要(想将自己用其他方式获取的MV直接喂入HEVC),先对HEVC帧间预测部分MV的获取、传递和存储方式进行总结,摸清楚在HEVC中MV的传输路径。
目录
帧间预测基本架构
下面是我暂时总结的帧间部分跟MV有关的基本结构,忽略了很多细节的部分,主要是帧间预测部分中关键函数的调用关系。
涉及的主要Class/Struct
在学习代码的过程中,我发现如果想更好的理清楚HM的代码结构,那么对其中各种Class的定义及调用关系的学习至关重要。
关于Class的学习可以参考HM参考网站HEVC Test Model (HM) HM-16.18——Class List
下面我总结出了帧间预测中与MV相关的几个重要Class,有助于更好的理解MV信息在帧间预测过程中如何获取、传递以及存储。
TComDataCU
官方定义:CU data structure class.
是HM中CU的数据类型,其中定义了非常多CU内涉及到的变量及函数。
该Class内与MV有关的变量及函数总结如下(根据自己的进度持续更新):
// 将pcCU的MV及其参考信息赋给rcMvField
static Void getMvField( const TComDataCU* pcCU, UInt uiAbsPartIdx, RefPicList eRefPicList, TComMvField& rcMvField );
// 获取AMVP候选列表
Void fillMvpCand( const UInt uiPartIdx, const UInt uiPartAddr, const RefPicList eRefPicList, const Int iRefIdx, AMVPInfo* pInfo ) const;
// 获取Merge候选列表
Void getInterMergeCandidates( UInt uiAbsPartIdx, UInt uiPUIdx, TComMvField* pcMFieldNeighbours, UChar* puhInterDirNeighbours, Int& numValidMergeCand, UInt& numSpatialMergeCandidates , Int mrgCandIdx = -1) const;
// MV信息
TComCUMvField m_acCUMvField[NUM_REF_PIC_LIST_01]; ///< array of motion vectors.
// 获取MV信息
TComCUMvField* getCUMvField( RefPicList e ) { return &m_acCUMvField[e]; }
// 帧间预测方向
UChar* m_puhInterDir; ///< array of inter directions
// 设置帧间预测方向
Void setInterDirSubParts( UInt uiDir, UInt uiAbsPartIdx, UInt uiPartIdx, UInt uiDepth );
// 最优MVP索引
SChar* m_apiMVPIdx[NUM_REF_PIC_LIST_01]; ///< array of motion vector predictor candidates
// 获取最优MVP索引
SChar* getMVPIdx( RefPicList eRefPicList ) { return m_apiMVPIdx[eRefPicList]; }
// 设置/存储最优MVP索引
Void setMVPIdxSubParts( Int iMVPIdx, RefPicList eRefPicList, UInt uiAbsPartIdx, UInt uiPartIdx, UInt uiDepth );
// 有效MVP候选数量
SChar* m_apiMVPNum[NUM_REF_PIC_LIST_01]; ///< array of number of possible motion vectors predictors
// 获取有效MVP候选数量
SChar* getMVPNum( RefPicList eRefPicList ) { return m_apiMVPNum[eRefPicList]; }
// 设置/存储有效MVP候选数量
Void setMVPNumSubParts( Int iMVPNum, RefPicList eRefPicList, UInt uiAbsPartIdx, UInt uiPartIdx, UInt uiDepth );
TComCUMvField
官方定义:class for motion information in one CU
该Class用来表示一个CU内的MV信息。主要包括MV、MVD、参考帧索引、AMVP候选列表
协作图:
该Class定义如下:
class TComCUMvField
{
private:
TComMv* m_pcMv; // MV
TComMv* m_pcMvd; // MVD
SChar* m_piRefIdx; // 参考帧索引
UInt m_uiNumPartition;
AMVPInfo m_cAMVPInfo; // AMVP候选列表
template <typename T>
Void setAll( T *p, T const & val, PartSize eCUMode, Int iPartAddr, UInt uiDepth, Int iPartIdx );
public:
TComCUMvField() : m_pcMv(NULL), m_pcMvd(NULL), m_piRefIdx(NULL), m_uiNumPartition(0) {}
~TComCUMvField() {}
// ------------------------------------------------------------------------------------------------------------------
// create / destroy
// ------------------------------------------------------------------------------------------------------------------
Void create( UInt uiNumPartition );
Void destroy();
// ------------------------------------------------------------------------------------------------------------------
// clear / copy
// ------------------------------------------------------------------------------------------------------------------
Void clearMvField();
Void copyFrom( TComCUMvField const * pcCUMvFieldSrc, Int iNumPartSrc, Int iPartAddrDst );
Void copyTo ( TComCUMvField* pcCUMvFieldDst, Int iPartAddrDst ) const;
Void copyTo ( TComCUMvField* pcCUMvFieldDst, Int iPartAddrDst, UInt uiOffset, UInt uiNumPart ) const;
// ------------------------------------------------------------------------------------------------------------------
// get
// ------------------------------------------------------------------------------------------------------------------
TComMv const & getMv ( Int iIdx ) const { return m_pcMv [iIdx]; } // 获取MV
TComMv const & getMvd ( Int iIdx ) const { return m_pcMvd [iIdx]; } // 获取MVD
Int getRefIdx( Int iIdx ) const { return m_piRefIdx[iIdx]; } // 获取参考帧索引
AMVPInfo* getAMVPInfo () { return &m_cAMVPInfo; } // 和获取AMVP候选列表
// ------------------------------------------------------------------------------------------------------------------
// set
// ------------------------------------------------------------------------------------------------------------------
// 以下四个函数的设置方式都是将第一个值(如rcMv)赋给当前CU对应值(如m_pcMv)
Void setAllMv ( TComMv const & rcMv, PartSize eCUMode, Int iPartAddr, UInt uiDepth, Int iPartIdx=0 ); // 设置/存储MV
Void setAllMvd ( TComMv const & rcMvd, PartSize eCUMode, Int iPartAddr, UInt uiDepth, Int iPartIdx=0 ); // 设置/存储MVD
Void setAllRefIdx ( Int iRefIdx, PartSize eMbMode, Int iPartAddr, UInt uiDepth, Int iPartIdx=0 ); // 设置/存储参考帧索引
Void setAllMvField( TComMvField const & mvField, PartSize eMbMode, Int iPartAddr, UInt uiDepth, Int iPartIdx=0 ); // 同时设置/存储MV及参考帧索引
Void setNumPartition( Int iNumPart )
{
m_uiNumPartition = iNumPart;
}
Void linkToWithOffset( TComCUMvField const * src, Int offset )
{
m_pcMv = src->m_pcMv + offset;
m_pcMvd = src->m_pcMvd + offset;
m_piRefIdx = src->m_piRefIdx + offset;
}
#if REDUCED_ENCODER_MEMORY
Void compress(SChar *pePredMode, const SChar* pePredModeSource, const Int scale, const TComCUMvField &source);
#else
Void compress(SChar* pePredMode, Int scale);
#endif
};
TComMvField
官方定义:class for motion vector with reference index
该Class表示带有参考帧索引信息的MV
(疑问:为什么不直接用TComCUMvField的MV和参考帧索引呢?而是额外定义了这个Class?)
协作图:
该Class定义如下:
/// class for motion vector with reference index
class TComMvField
{
private:
TComMv m_acMv; // MV
Int m_iRefIdx; // 参考帧索引
public:
TComMvField() : m_iRefIdx( NOT_VALID ) {}
// 设置MV及其参考帧索引信息
Void setMvField( TComMv const & cMv, Int iRefIdx )
{
m_acMv = cMv;
m_iRefIdx = iRefIdx;
}
// 单独设置参考帧索引
Void setRefIdx( Int refIdx ) { m_iRefIdx = refIdx; }
// 获取MV
TComMv const & getMv() const { return m_acMv; }
TComMv & getMv() { return m_acMv; }
Int getRefIdx() const { return m_iRefIdx; }
Int getHor () const { return m_acMv.getHor(); }
Int getVer () const { return m_acMv.getVer(); }
};
TComMv
官方定义:basic motion vector class
即MV的Class,内部主要包括其水平和垂直分量
主要内容:
private:
Short m_iHor; ///< horizontal component of motion vector
Short m_iVer; ///< vertical component of motion vector
public:
// 设置水平/垂直变量
Void set ( Short iHor, Short iVer) { m_iHor = iHor; m_iVer = iVer; }
Void setHor ( Short i ) { m_iHor = i; }
Void setVer ( Short i ) { m_iVer = i; }
// 获取水平/垂直变量
Int getHor () const { return m_iHor; }
Int getVer () const { return m_iVer; }
Int getAbsHor () const { return abs( m_iHor ); }
Int getAbsVer () const { return abs( m_iVer ); }
AMVPInfo
官方定义:parameters for AMVP
该结构体表示AMVP候选列表,其内容包括三个:AMVP候选列表m_acMvCand
、列表内有效候选数量iN
、列表内空域候选数量numSpatialMVPCandidates
typedef struct _AMVPInfo
{
TComMv m_acMvCand[ AMVP_MAX_NUM_CANDS ]; ///< array of motion vector predictor candidates
Int iN; ///< number of motion vector predictor candidates
#if MCTS_ENC_CHECK
UInt numSpatialMVPCandidates;
#endif
} AMVPInfo;
帧间预测入口函数
帧间预测部分的入口函数是xCompressCU
,其主要作用是完成块划分,确定最优预测模式。主要可以分为:
1.帧间预测xCheckRDCostInter
——Inter模式、xCheckRDCostMerge2Nx2N
——Merge模式
2.帧内预测xCheckRDCostIntra
3.PCM模式xCheckIntraPCM
xCompressCU
的学习可参考博客:
HEVC代码学习11:xCompressCU函数
HM编码器代码阅读(12)——CU编码
Inter模式(AMVP)
入口函数——xCheckRDCostInter
Inter模式(AMVP模式)的入口函数为xCheckRDCostInter
,被xCompressCU
调用。
该函数主要流程如下:
(1)调用predInterSearch
,进行ME(运动估计)和MC(运动补偿)。
(2)调用encodeResAndCalcRdInterCU
,根据预测值计算残差,然后进行TU的划分、变换、量化等操作,并计算RD cost。
(3)调用xCheckBestMode
选择最好的模式。
xCheckRDCostInter
的学习可参考博客:
HEVC代码学习12:xCheckRDCostInter函数
HM编码器代码阅读(13)——帧间预测之AMVP模式(一)总体流程
predInterSearch
predInterSearch
的作用是进行ME(运动估计)和MC(运动补偿)。predInterSearch
的学习可参考博客:
HEVC代码学习13:predInterSearch函数
HM编码器代码阅读(14)——帧间预测之AMVP模式(二)predInterSearch函数
该函数的主要流程如下:
对当前CU下所有PU逐一进行以下操作(PU地址索引为ipartIdx
):
(1)遍历参考列表(iRefList
)以及参考列表中所有参考帧(参考帧索引iRefIdxTemp
),进行以下操作:
- 调用
xEstimateMvPredAMVP
获取最优MVP,以及最优MVP在候选列表中的索引、MVP候选列表中候选数量。调用代码如下:
// 获取最优MVP
xEstimateMvPredAMVP( pcCU, pcOrgYuv, iPartIdx, eRefPicList, iRefIdxTemp, cMvPred[iRefList][iRefIdxTemp], false, &biPDistTemp);
// 获取最优MVP索引
aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->getMVPIdx(eRefPicList, uiPartAddr);
// 获取MVP候选列表中候选数量
aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->getMVPNum(eRefPicList, uiPartAddr);
xEstimateMvPredAMVP
函数的详细情况将单独写一篇文章进行总结,文章链接如下:
(待更)
总之对于predInterSearch
来说,上述三个语句执行后的返回结果为:
//最优MV存入cMvPred[iRefList][iRefIdxTemp]
cMvPred[iRefList][iRefIdxTemp] = cBestMv
// 最优MVP索引存入CU中(执行xEstimateMvPredAMVP的结果),然后赋值给aaiMvpIdx[iRefList][iRefIdxTemp]
aaiMvpIdx[iRefList][iRefIdxTemp] = pcCU->m_apiMVPIdx[eRefPicList][uiPartAddr]
// MVP候选数量存入CU中(执行xEstimateMvPredAMVP的结果),然后赋值给aaiMvpNum[iRefList][iRefIdxTemp]
aaiMvpNum[iRefList][iRefIdxTemp] = pcCU->m_apiMVPNum[eRefPicList][uiPartAddr]
- 调用
xMotionEstimation
进行运动估计,以上面得到的最优MVP为起点进行搜索,最终得到最优MV以及其bits、cost。
调用语句如下:
xMotionEstimation ( pcCU, pcOrgYuv, iPartIdx, eRefPicList, &cMvPred[iRefList] [iRefIdxTemp], iRefIdxTemp, cMvTemp[iRefList][iRefIdxTemp], uiBitsTemp, uiCostTemp );
xMotionEstimation
的详细情况将单独写一篇文章进行总结。文章链接如下:
(待更)
总之调用后的返回结果为:
cMvTemp[iRefList][iRefIdxTemp] = 最优MV
uiBitsTemp = 最优MV的bits
uiCostTemp = 最优MV的cost
-
调用
xCheckBestMVP
更新最优MVP。在得到最优MV后,重新寻找最优MVP,并于之前的最优MVP比较bits,从而更新最优MVP。
(这里有个疑问,更新的时候为什么比较bit而不是cost?是为接下来编码做准备吗?而且更新MVP的目的是什么?更新之后的MVP还有什么用?) -
更新每一参考列表下最优(cost最小)MV,以及其对应的参考帧索引。
代码如下:
if ( iRefList == 0 )
{
uiCostTempL0[iRefIdxTemp] = uiCostTemp;
uiBitsTempL0[iRefIdxTemp] = uiBitsTemp;
}
if ( uiCostTemp < uiCost[iRefList] )
{
uiCost[iRefList] = uiCostTemp;
uiBits[iRefList] = uiBitsTemp; // storing for bi-prediction
// set motion
cMv[iRefList] = cMvTemp[iRefList][iRefIdxTemp];
iRefIdx[iRefList] = iRefIdxTemp;
}
if ( iRefList == 1 && uiCostTemp < costValidList1 && pcCU->getSlice()->getList1IdxToList0Idx( iRefIdxTemp ) < 0 )
{
costValidList1 = uiCostTemp;
bitsValidList1 = uiBitsTemp;
// set motion
mvValidList1 = cMvTemp[iRefList][iRefIdxTemp];
refIdxValidList1 = iRefIdxTemp;
}
因此在遍历完所有参考列表下的所有参考帧后,将得到该PU在所有参考列表[iRefList]
下的最优MVcMv[iRefList]
以及其对应的参考帧索引iRefIdx[iRefList]
、bits、cost。
(2)B帧处理
(3)存储MV、MVD及其信息。
// B帧
if ( uiCostBi <= uiCost[0] && uiCostBi <= uiCost[1])
{
uiLastMode = 2;
pcCU->getCUMvField(REF_PIC_LIST_0)->setAllMv( cMvBi[0], ePartSize, uiPartAddr, 0, iPartIdx );
pcCU->getCUMvField(REF_PIC_LIST_0)->setAllRefIdx( iRefIdxBi[0], ePartSize, uiPartAddr, 0, iPartIdx );
pcCU->getCUMvField(REF_PIC_LIST_1)->setAllMv( cMvBi[1], ePartSize, uiPartAddr, 0, iPartIdx );
pcCU->getCUMvField(REF_PIC_LIST_1)->setAllRefIdx( iRefIdxBi[1], ePartSize, uiPartAddr, 0, iPartIdx );
TempMv = cMvBi[0] - cMvPredBi[0][iRefIdxBi[0]];
pcCU->getCUMvField(REF_PIC_LIST_0)->setAllMvd ( TempMv, ePartSize, uiPartAddr, 0, iPartIdx );
TempMv = cMvBi[1] - cMvPredBi[1][iRefIdxBi[1]];
pcCU->getCUMvField(REF_PIC_LIST_1)->setAllMvd ( TempMv, ePartSize, uiPartAddr, 0, iPartIdx );
pcCU->setInterDirSubParts( 3, uiPartAddr, iPartIdx, pcCU->getDepth(0) );
pcCU->setMVPIdxSubParts( aaiMvpIdxBi[0][iRefIdxBi[0]], REF_PIC_LIST_0, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
pcCU->setMVPNumSubParts( aaiMvpNum[0][iRefIdxBi[0]], REF_PIC_LIST_0, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
pcCU->setMVPIdxSubParts( aaiMvpIdxBi[1][iRefIdxBi[1]], REF_PIC_LIST_1, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
pcCU->setMVPNumSubParts( aaiMvpNum[1][iRefIdxBi[1]], REF_PIC_LIST_1, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
uiMEBits = uiBits[2];
}
//P帧(List0)
else if ( uiCost[0] <= uiCost[1] )
{
uiLastMode = 0;
pcCU->getCUMvField(REF_PIC_LIST_0)->setAllMv( cMv[0], ePartSize, uiPartAddr, 0, iPartIdx );
pcCU->getCUMvField(REF_PIC_LIST_0)->setAllRefIdx( iRefIdx[0], ePartSize, uiPartAddr, 0, iPartIdx );
TempMv = cMv[0] - cMvPred[0][iRefIdx[0]];
pcCU->getCUMvField(REF_PIC_LIST_0)->setAllMvd ( TempMv, ePartSize, uiPartAddr, 0, iPartIdx );
pcCU->setInterDirSubParts( 1, uiPartAddr, iPartIdx, pcCU->getDepth(0) );
pcCU->setMVPIdxSubParts( aaiMvpIdx[0][iRefIdx[0]], REF_PIC_LIST_0, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
pcCU->setMVPNumSubParts( aaiMvpNum[0][iRefIdx[0]], REF_PIC_LIST_0, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
uiMEBits = uiBits[0];
}
// P帧(List1)
else
{
uiLastMode = 1;
pcCU->getCUMvField(REF_PIC_LIST_1)->setAllMv( cMv[1], ePartSize, uiPartAddr, 0, iPartIdx );
pcCU->getCUMvField(REF_PIC_LIST_1)->setAllRefIdx( iRefIdx[1], ePartSize, uiPartAddr, 0, iPartIdx );
TempMv = cMv[1] - cMvPred[1][iRefIdx[1]];
pcCU->getCUMvField(REF_PIC_LIST_1)->setAllMvd ( TempMv, ePartSize, uiPartAddr, 0, iPartIdx );
pcCU->setInterDirSubParts( 2, uiPartAddr, iPartIdx, pcCU->getDepth(0) );
pcCU->setMVPIdxSubParts( aaiMvpIdx[1][iRefIdx[1]], REF_PIC_LIST_1, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
pcCU->setMVPNumSubParts( aaiMvpNum[1][iRefIdx[1]], REF_PIC_LIST_1, uiPartAddr, iPartIdx, pcCU->getDepth(uiPartAddr));
uiMEBits = uiBits[1];
}
以上代码简单来说即:
MV存入: pcCU->m_acCUMVField[eRefPicList]->m_pcMv
MV参考帧索引存入: pcCU->m_acCUMVField[eRefPicList]->m_piRefIdx
MVD信息存入: pcCU->m_acCUMVField[eRefPicList]->m_pcMvd
帧间预测方向信息存入: pcCU->m_puhInterDir
最优MVP索引信息存入: pcCU->m_apiMVPIdx[eRefPicList]
MVP候选数量信息存入: pcCU->m_apiMVPNum[eRefPicList]
(4)对于非2Nx2N的分块,需要计算并合并他们的运动估计代价。
(5)调用motionCompensation
进行运动补偿
Merge模式
入口函数—xCheckRDCostMerge2Nx2N
Merge模式与AMVP模式不同,其得到的MVP将直接作为MV(没有MVD),因此不需要再进行运动估计,直接进行运动补偿。
执行流程如下:
(1)调用getInterMergeCandidates
获取MVP候选列表;
(2)调用motionCompensation
进行运动补偿;
(3)调用encodeResAndCalcRdInterCU
计算残差,变换量化,选出最优的QP参数;
(4)调用xCheckBestMode
,比较选出最优MV和最优模式信息
xCheckRDCostMerge2Nx2N
的学习可以参考博客:
HM编码器代码阅读(17)——帧间预测之merge模式(一)Merge模式的介绍以及相关函数
HEVC代码学习31:xCheckRDCostMerge2Nx2N函数
getInterMergeCandidates
该函数的作用是建立Merge模式下的MVP候选列表。
其处理流程如下:
(1)先建立空域候选列表。空域最多只能提供4个候选MV,候选的遍历顺序是A1->B1->B0->A0->B2,优先处理前面四个,如果前面四个有不满足条件的时,才处理B2。在遍历每一个相邻PU时,都进行以下操作:
- 检测是否有效
- 若有效则写入候选列表记录其MV
- 检测列表是否已满
代码如下(以A1为例):
if ( isAvailableA1 ) // 检测是否有效,后面的PU在检测时会考虑之前PU的情况,所以检测flag会更复杂
{
abCandIsInter[iCount] = true;
// get Inter Dir 获取帧间预测方向
puhInterDirNeighbours[iCount] = pcCULeft->getInterDir( uiLeftPartIdx );
// get Mv from Left 获取List0的MV信息,该函数的原理详见下面
TComDataCU::getMvField( pcCULeft, uiLeftPartIdx, REF_PIC_LIST_0, pcMvFieldNeighbours[iCount<<1] );
if ( getSlice()->isInterB() ) // 如果是B帧,再获取List1的MV信息
{
TComDataCU::getMvField( pcCULeft, uiLeftPartIdx, REF_PIC_LIST_1, pcMvFieldNeighbours[(iCount<<1)+1] );
}
// 空间候选数量标志 numSpatialMergeCandidates + 1
if ( mrgCandIdx == iCount )
{
#if MCTS_ENC_CHECK
numSpatialMergeCandidates = iCount + 1;
#endif
return;
}
// 候选数量+1
iCount ++;
}
// early termination 检测列表是否已满,如果已满则直接结束列表的建立
if (iCount == getSlice()->getMaxNumMergeCand())
{
#if MCTS_ENC_CHECK
numSpatialMergeCandidates = iCount;
#endif
return;
}
其中获取相邻PU的MV的函数是TComDataCU::getMvField
,在上面关于TComDataCU
的总结中已知,getMvField
是TComDataCU
中定义的一个用于获取MV信息的函数,下面具体学习一下这个函数:
Void TComDataCU::getMvField ( const TComDataCU* pcCU, UInt uiAbsPartIdx, RefPicList eRefPicList, TComMvField& rcMvField )
{
if ( pcCU == NULL ) // OUT OF BOUNDARY
{
TComMv cZeroMv;
rcMvField.setMvField( cZeroMv, NOT_VALID );
return;
}
const TComCUMvField* pcCUMvField = pcCU->getCUMvField( eRefPicList );
rcMvField.setMvField( pcCUMvField->getMv( uiAbsPartIdx ), pcCUMvField->getRefIdx( uiAbsPartIdx ) );
}
而setMvField
的定义为:
Void setMvField( TComMv const & cMv, Int iRefIdx )
{
m_acMv = cMv;
m_iRefIdx = iRefIdx;
}
所以getMvField
的返回结果为:
rcMvField.m_acMv = pcCU->m_acCUMvField[eRefPicList]->m_pcMv[uiAbsPartIdx];
rcMvField.m_iRefIdx = pcCU->m_acCUMvField[eRefPicList]->m_piRefIdx[uiAbsPartIdx];
即,将pcCU
的MV和参考帧索引信息赋给rcMvField
。具体到调用getMvField
语句(以A1为例),就是将pcCULeft
的List0的MV和参考帧索引信息赋值给pcMvFieldNeighbours[iCount<<1]
(偶数位),将pcCULeft
的List1的MV和参考帧索引信息赋值给pcMvFieldNeighbours[(iCount<<1)+1]
(奇数位)。iCount
表示当前选中的MVP在MVP候选列表中的位置。
TComDataCU::getMvField( pcCULeft, uiLeftPartIdx, REF_PIC_LIST_0, pcMvFieldNeighbours[iCount<<1] );
if ( getSlice()->isInterB() )
{
TComDataCU::getMvField( pcCULeft, uiLeftPartIdx, REF_PIC_LIST_1, pcMvFieldNeighbours[(iCount<<1)+1] );
}
(2)建立时域候选列表
(3)为B Slice建立组合列表
(4)候选列表未满时用0填充
在xCheckRDCostMerge2Nx2N
调用getInterMergeCandidates
的代码吗如下:
#if MCTS_ENC_CHECK
UInt numSpatialMergeCandidates = 0;
rpcTempCU->getInterMergeCandidates( 0, 0, cMvFieldNeighbours, uhInterDirNeighbours, numValidMergeCand, numSpatialMergeCandidates );
#else
rpcTempCU->getInterMergeCandidates( 0, 0, cMvFieldNeighbours,uhInterDirNeighbours, numValidMergeCand );
#endif
因此从getInterMergeCandidates
返回的结果有:
cMvFieldNeighbours[] = MVP候选列表
uhInterDirNeighbours[] = 列表中对应的帧间预测方向
numValidMergeCand = 候选列表中有效候选数量
numSpatialMergeCandidates = 候选列表中空域候选数量
本文地址:https://blog.csdn.net/zzz_zzz12138/article/details/107174760
上一篇: bat 批处理 for 命令快速入门
下一篇: 舞钢SA516Gr70容器板化学成分