当前位置: 移动技术网 > IT编程>网页制作>Html5 > HEVC代码学习:帧间预测——MVP过程中MV的获取、传递及存储

HEVC代码学习:帧间预测——MVP过程中MV的获取、传递及存储

2020年07月13日  | 移动技术网IT编程  | 我要评论

作为一个视频编码小白,最近开始着手啃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的总结中已知,getMvFieldTComDataCU中定义的一个用于获取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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网