欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-22 08:05:11
作为一个视频编码小白,最近开始着手啃HEVC帧间预测的代码,想用博客记录一下自己的学习过程,也想与大家分享、交流一下。HEVC代码的学习主要是参考两位大神岳麓吹雪、NB_vol_1的博客以及HM参考软件。两位大神的关于HEVC帧间部分的博客如下:HEVC代码学习HM编码器代码阅读(33)——帧间预测的总结而HM软件的安装配置可参考HEVC代码:HM使用+码流分析教程应接下来研究的需要(想将自己用其他方式获取的MV直接喂入HEVC),先对HEVC帧间预测部分MV的获取、传递和存储方式进行总结,摸清...

作为一个视频编码小白,最近开始着手啃HEVC帧间预测的代码,想用博客记录一下自己的学习过程,也想与大家分享、交流一下。

HEVC代码的学习主要是参考两位大神岳麓吹雪NB_vol_1的博客以及HM参考软件。
两位大神的关于HEVC帧间部分的博客如下:
HEVC代码学习
HM编码器代码阅读(33)——帧间预测的总结
而HM软件的安装配置可参考HEVC代码:HM使用+码流分析教程

应接下来研究的需要(想将自己用其他方式获取的MV直接喂入HEVC),先对HEVC帧间预测部分MV的获取、传递和存储方式进行总结,摸清楚在HEVC中MV的传输路径。

帧间预测基本架构

下面是我暂时总结的帧间部分跟MV有关的基本结构,忽略了很多细节的部分,主要是帧间预测部分中关键函数的调用关系。
HEVC代码学习:帧间预测——MVP过程中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候选列表
协作图:
HEVC代码学习:帧间预测——MVP过程中MV的获取、传递及存储
该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?)

协作图:
HEVC代码学习:帧间预测——MVP过程中MV的获取、传递及存储
该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候选列表。
HEVC代码学习:帧间预测——MVP过程中MV的获取、传递及存储
其处理流程如下:
(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