VVC has added a number of new interframe prediction tools on the basis of HEVC, VTM5 added the following interframe prediction tools:

  • Extended merge prediction
  • Merge mode with MVD (MMVD)
  • AMVP mode with symmetric MVD signalling
  • Affine motion compensated prediction
  • Subblock-based temporal motion vector prediction (SbTMVP)
  • Adaptive motion vector resolution (AMVR)
  • Motion field storage: 1/16th luma sample MV storage and 8×8 motion field compression
  • Bi-prediction with CU-level weight (BCW)
  • Bi-directional optical flow (BDOF)
  • Decoder side motion vector refinement (DMVR)
  • Triangle partition prediction
  • Combined inter and intra prediction (CIIP)

Each of these tools will be introduced in the next series of articles. This article introduces the first Extended Merge Prediction tool for inter-frame prediction

1. Extended Merge Prediction

Merge is a new MV prediction technology proposed in HEVC. It uses MV of adjacent blocks in time domain or space domain to predict the current block MV. The merge mode in HEVC creates a MV candidate list for the current PU. There are five MV candidates in the list. The MV with the lowest calculation rate distortion cost is taken as the MV of the current PU (no MVD exists) by traversal of these five MV candidates. In the code stream, only the index of the optimal MV in the list can be transmitted.

In HEVC, merge uses adjacent blocks in time domain or spatial domain to construct a MV list, and there are only 5 candidate MVS in the list. VVC extends this to include a maximum of six candidate MVS in the merge list. There are five types of candidate MVS in the following order.

  1. Spatial MVP from Spatial Neighbour CUs Spatial MVP of adjacent blocks
  2. Temporal MVP from Collocated CUs co-location block time MVP
  3. History-based MVP from an FIFO Table MVP of a FIFO table constructed based on historical information
  4. Pairwise Average MVP
  5. Zero mvs. 0 vector

1.1 construction of airspace candidate list

Airspace candidate list is established in the same way as HEVC. A0,A1,B0,B1,B2 are adjacent blocks in the current CU airspace. The airspace in VVC provides a maximum of four candidate MV. In other words, the motion information of 4 candidate blocks in the 5 candidate blocks is used at most. The candidate list is established in the order of A1->B1->B0->A0->B2, where B2 is the substitute and only when one or more of the first 4 blocks do not exist (such as no longer the same slice or tile or using intra-frame coding) will B2 be added to the candidate list. After the candidate list is added to A1, redundancy check should be carried out when new candidates are added to avoid the same movement information of the new candidate as the existing one. To reduce computational complexity, the redundancy check is not compared to every existing item, only the items connected by arrows in the figure below.

 

 

1.2. Construction of time domain candidate list

The time domain candidate list is established in the same way as HEVC. Different from the spatial case, the time domain candidate list cannot directly use the motion information of candidate blocks, and needs to be scaled according to the position relationship. As shown in the figure below, curr_CU is the current CU, col_CU is its co-location CU, TB is the distance between the current image and the reference image (measured in POC), td is the distance between the co-location image and its reference image. Then the candidate MV is calculated as follows:

CurMV = (td/ TB) *colMV

 

In VVC, the time domain provides at most 1 candidate MV. In the figure below, the candidate MV of C0 is obtained by scaling the MV of its co-location CU. If the co-location CU of C0 is not available, it is replaced by the co-location CU of C1.

 

1.3, history-based merge requirements

Merge candidates for history-based MVP (HMVP) are added to the Merge list after the spatial and temporal candidates. The HMVP candidate is derived from a FIFO table that is constructed from the movement information of encoded blocks. The table is reset (emptied) every time a new ROW of CTU is reached. Every time an interframe encoded CU (non-subblock) is encountered, its associated motion information is added to the last item of the table as a new HMVP candidate.

In VTM5, the HMVP table size is 6. Whenever a new candidate item is inserted, redundancy check is carried out first, that is, check whether the movement information of the item to be inserted is the same as that of the existing item in the table. If not, insert according to FIFO rules; if they are the same, remove the same HMVP from the table, and then move all items forward one bit.

After the HMVP table is constructed, the HMVP candidates in the table can be inserted into the Merge list. The redundancy check of the candidates in the HMVP table from back to front is carried out (that is, check whether the movement information of the candidates in the airspace and time domain is the same as that in the Merge List), and the merge list is inserted once the check passes.

In order to reduce the operation of the redundancy check, the following simplification is performed:

  1. The number of HMVP candidates used to build the Merge List is set to (N<=4)? M:(8-n), where N represents the number of existing items in the merge list and M represents the number of candidate items in the HMVP table.
  2. Once the number of candidates in the Merge List reaches the maximum number of allowable candidates minus 1 (that is, 6-1=5), the process of generating merge candidates from HMVP is stopped.

1.4, Pair-wise average Merge Requirements

The pin-by-pair average is calculated for the average value of the existing candidates in the Merge list according to the predefined group relationship, which is {(0, 1), (0, 2), (1, 2), (0, 3), (1, 3), (2, 3)}, The number represents the index of the candidate in the Merge List. The average vectors of L0 and L1 are calculated separately. If both vectors are valid in a list, they are averaged even if they point to different reference images. If only one vector is valid in a list, it is used directly; if a list has no valid motion vectors, the list is invalid.

If the Merge list is not filled after calculating the average candidate, it is filled with the 0 vector.

2, summarize

This is how the Merge list is constructed. The length of the merge list is 6. The first step is to find spatial candidates (up to 4), then temporal candidates (up to 1), then candidates based on historical information, then average candidates, and if the Merge list is not yet filled, the 0 vector is filled. If the merge List is filled in the above procedure, the next steps are not performed.

The biggest difference between the merge list construction of VVC and HEVC is that the merge list in HEVC only contains spatial candidates and temporal candidates.

After the merge list of the current CU is constructed, the six candidates are traversed to calculate the rATE-distortion cost. The candidate with the lowest rate-distortion cost is directly selected as the MV of the current CU.

Merge List builds the code below, with comments in place

void PU::getInterMergeCandidates( const PredictionUnit &pu, MergeCtx& mrgCtx,
                                 int mmvdList,
                                 const int& mrgCandIdx )
{
  const CodingStructure &cs  = *pu.cs;
  const Slice &slice         = *pu.cs->slice;
  const uint32_t maxNumMergeCand = slice.getMaxNumMergeCand(a);const bool canFastExit     = pu.cs->pps->getLog2ParallelMergeLevelMinus2() = =0;

#if! JVET_L0090_PAIR_AVG
  // this variable is unused if remove HEVC combined candidates
  bool isCandInter[MRG_MAX_NUM_CANDS];
#endif

  for (uint32_t ui = 0; ui < maxNumMergeCand; ++ui)
  {
#if! JVET_L0090_PAIR_AVG
    isCandInter[ui] = false;
#endif
    mrgCtx.GBiIdx[ui] = GBI_DEFAULT;
    mrgCtx.interDirNeighbours[ui] = 0;
    mrgCtx.mrgTypeNeighbours [ui] = MRG_TYPE_DEFAULT_N;
    mrgCtx.mvFieldNeighbours[(ui << 1)    ].refIdx = NOT_VALID;
    mrgCtx.mvFieldNeighbours[(ui << 1) + 1].refIdx = NOT_VALID;
  }

  mrgCtx.numValidMergeCand = maxNumMergeCand;
  // compute the location of the current PU

  int cnt = 0;

#if JVET_N0266_SMALL_BLOCKS
  const Position posLT = pu.Y().topLeft(a);const Position posRT = pu.Y().topRight(a);const Position posLB = pu.Y().bottomLeft(a);#else
  const Position posLT = pu.shareParentPos;
  const Position posRT = pu.shareParentPos.offset(pu.shareParentSize.width - 1.0);
  const Position posLB = pu.shareParentPos.offset(0, pu.shareParentSize.height - 1);
#endif
  MotionInfo miAbove, miLeft, miAboveLeft, miAboveRight, miBelowLeft;

  //left
  const PredictionUnit* puLeft = cs.getPURestricted( posLB.offset( - 1.0 ), pu, pu.chType );

  const bool isAvailableA1 = puLeft && isDiffMER( pu, *puLeft ) && pu.cu ! = puLeft->cu && CU::isInter( *puLeft->cu );
  / /! < block A1, block to the left
  if( isAvailableA1 )
  {
    miLeft = puLeft->getMotionInfo( posLB.offset(- 1.0));#if! JVET_L0090_PAIR_AVG
    isCandInter[cnt] = true;
#endif

    // get Inter Dir
    mrgCtx.interDirNeighbours[cnt] = miLeft.interDir;
    mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3)? puLeft->cu->GBiIdx : GBI_DEFAULT;// get Mv from Left
    mrgCtx.mvFieldNeighbours[cnt << 1].setMvField(miLeft.mv[0], miLeft.refIdx[0]);

    if (slice.isInterB())
    {
      mrgCtx.mvFieldNeighbours[(cnt << 1) + 1].setMvField(miLeft.mv[1], miLeft.refIdx[1]);
    }
    if (mrgCandIdx == cnt && canFastExit)
    {
      return;
    }

    cnt++;
  }

  // early termination
  if (cnt == maxNumMergeCand)
  {
    return;
  }


  // above
  const PredictionUnit *puAbove = cs.getPURestricted( posRT.offset( 0.- 1 ), pu, pu.chType );

  bool isAvailableB1 = puAbove && isDiffMER( pu, *puAbove ) && pu.cu ! = puAbove->cu && CU::isInter( *puAbove->cu );
  / /! < block B1, block on the right
  if( isAvailableB1 )
  {
    miAbove = puAbove->getMotionInfo( posRT.offset( 0.- 1));/ /! < Redundancy check, B1 is only compared with A1
    if(! isAvailableA1 || ( miAbove ! = miLeft ) ) {#if! JVET_L0090_PAIR_AVG
      isCandInter[cnt] = true;
#endif

      // get Inter Dir
      mrgCtx.interDirNeighbours[cnt] = miAbove.interDir;
      // get Mv from Above
      mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3)? puAbove->cu->GBiIdx : GBI_DEFAULT; mrgCtx.mvFieldNeighbours[cnt <<1].setMvField( miAbove.mv[0], miAbove.refIdx[0]);if( slice.isInterB() )
      {
        mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miAbove.mv[1], miAbove.refIdx[1]); }if (mrgCandIdx == cnt && canFastExit)
      {
        return; } cnt++; }}// early termination
  if( cnt == maxNumMergeCand )
  {
    return;
  }

  int spatialCandPos = cnt;

  // above right
  const PredictionUnit *puAboveRight = cs.getPURestricted( posRT.offset( 1.- 1 ), pu, pu.chType );

  bool isAvailableB0 = puAboveRight && isDiffMER( pu, *puAboveRight ) && CU::isInter( *puAboveRight->cu );
  / /! 
  if( isAvailableB0 )
  {
    miAboveRight = puAboveRight->getMotionInfo( posRT.offset( 1.- 1));#if HM_JEM_MERGE_CANDS
    if((! isAvailableB1 || ( miAbove ! = miAboveRight ) ) && ( ! isAvailableA1 || ( miLeft ! = miAboveRight ) ) )#else/ /! < Redundancy check, B0 is only compared with B1
    if(! isAvailableB1 || ( miAbove ! = miAboveRight ) )#endif
    {
#if! JVET_L0090_PAIR_AVG
      isCandInter[cnt] = true;
#endif

      // get Inter Dir
      mrgCtx.interDirNeighbours[cnt] = miAboveRight.interDir;
      // get Mv from Above-right
      mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3)? puAboveRight->cu->GBiIdx : GBI_DEFAULT; mrgCtx.mvFieldNeighbours[cnt <<1].setMvField( miAboveRight.mv[0], miAboveRight.refIdx[0]);if( slice.isInterB() )
      {
        mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miAboveRight.mv[1], miAboveRight.refIdx[1]); }if (mrgCandIdx == cnt && canFastExit)
      {
        return; } cnt++; }}// early termination
  if( cnt == maxNumMergeCand )
  {
    return;
  }

  //left bottom
  const PredictionUnit *puLeftBottom = cs.getPURestricted( posLB.offset( - 1.1 ), pu, pu.chType );

  bool isAvailableA0 = puLeftBottom && isDiffMER( pu, *puLeftBottom ) && CU::isInter( *puLeftBottom->cu );
  / /! 
  if( isAvailableA0 )
  {
    miBelowLeft = puLeftBottom->getMotionInfo( posLB.offset( - 1.1));#if HM_JEM_MERGE_CANDS
    if((! isAvailableA1 || ( miBelowLeft ! = miLeft ) ) && ( ! isAvailableB1 || ( miBelowLeft ! = miAbove ) ) && ( ! isAvailableB0 || ( miBelowLeft ! = miAboveRight ) ) )#else/ /! < Redundancy check, A0 is compared with A1 only
    if(! isAvailableA1 || ( miBelowLeft ! = miLeft ) )#endif
    {
#if! JVET_L0090_PAIR_AVG
      isCandInter[cnt] = true;
#endif

      // get Inter Dir
      mrgCtx.interDirNeighbours[cnt] = miBelowLeft.interDir;
      mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3)? puLeftBottom->cu->GBiIdx : GBI_DEFAULT;// get Mv from Bottom-Left
      mrgCtx.mvFieldNeighbours[cnt << 1].setMvField( miBelowLeft.mv[0], miBelowLeft.refIdx[0]);if( slice.isInterB() )
      {
        mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miBelowLeft.mv[1], miBelowLeft.refIdx[1]); }if (mrgCandIdx == cnt && canFastExit)
      {
        return; } cnt++; }}// early termination
  if( cnt == maxNumMergeCand )
  {
    return;
  }

  / /! 
  // above left
  if ( cnt < 4 )
  {
    const PredictionUnit *puAboveLeft = cs.getPURestricted( posLT.offset( - 1.- 1 ), pu, pu.chType );

    bool isAvailableB2 = puAboveLeft && isDiffMER( pu, *puAboveLeft ) && CU::isInter( *puAboveLeft->cu );

    if( isAvailableB2 )
    {
      miAboveLeft = puAboveLeft->getMotionInfo( posLT.offset( - 1.- 1));#if HM_JEM_MERGE_CANDS
      if((! isAvailableA1 || ( miLeft ! = miAboveLeft ) ) && ( ! isAvailableB1 || ( miAbove ! = miAboveLeft ) ) && ( ! isAvailableA0 || ( miBelowLeft ! = miAboveLeft ) ) && ( ! isAvailableB0 || ( miAboveRight ! = miAboveLeft ) ) )#else/ /! < Redundancy check, compare B2 with A1 and B1
      if((! isAvailableA1 || ( miLeft ! = miAboveLeft ) ) && ( ! isAvailableB1 || ( miAbove ! = miAboveLeft ) ) )#endif
      {
#if! JVET_L0090_PAIR_AVG
        isCandInter[cnt] = true;
#endif

        // get Inter Dir
        mrgCtx.interDirNeighbours[cnt] = miAboveLeft.interDir;
        mrgCtx.GBiIdx[cnt] = (mrgCtx.interDirNeighbours[cnt] == 3)? puAboveLeft->cu->GBiIdx : GBI_DEFAULT;// get Mv from Above-Left
        mrgCtx.mvFieldNeighbours[cnt << 1].setMvField( miAboveLeft.mv[0], miAboveLeft.refIdx[0]);if( slice.isInterB() )
        {
          mrgCtx.mvFieldNeighbours[( cnt << 1 ) + 1].setMvField( miAboveLeft.mv[1], miAboveLeft.refIdx[1]); }if (mrgCandIdx == cnt && canFastExit)
        {
          return; } cnt++; }}}// early termination
  if (cnt == maxNumMergeCand)
  {
    return;
  }

#if JVET_N0213_TMVP_REMOVAL
  if (slice.getEnableTMVPFlag() && (pu.lumaSize().width + pu.lumaSize().height > 12))
#else
  if (slice.getEnableTMVPFlag())
#endif
  {
    //>> MTK colocated-RightBottom
    // offset the pos to be sure to "point" to the same position the uiAbsPartIdx would've pointed to
#if JVET_N0266_SMALL_BLOCKS
    Position posRB = pu.Y().bottomRight().offset( - 3.- 3 );
#else
    Position posRB = pu.shareParentPos.offset(pu.shareParentSize.width- 3, pu.shareParentSize.height - 3);
#endif
    const PreCalcValues& pcv = *cs.pcv;

    Position posC0;
#if JVET_N0266_SMALL_BLOCKS
    Position posC1 = pu.Y().center(a);#else
    Position posC1 = pu.shareParentPos.offset((pu.shareParentSize.width/2), (pu.shareParentSize.height/2));
#endif
    bool C0Avail = false;
#if! JVET_N0266_SMALL_BLOCKS
    bool C1Avail = (posC1.x < pcv.lumaWidth) && (posC1.y  < pcv.lumaHeight);
#endif
    if (((posRB.x + pcv.minCUWidth) < pcv.lumaWidth) && ((posRB.y + pcv.minCUHeight) < pcv.lumaHeight))
    {
      {
        Position posInCtu( posRB.x & pcv.maxCUWidthMask, posRB.y & pcv.maxCUHeightMask );

        if( ( posInCtu.x + 4 < pcv.maxCUWidth ) &&           // is not at the last column of CTU
            ( posInCtu.y + 4 < pcv.maxCUHeight ) )           // is not at the last row of CTU
        {
          posC0 = posRB.offset( 4.4 );
          C0Avail = true;
        }
        else if( posInCtu.x + 4 < pcv.maxCUWidth )           // is not at the last column of CTU But is last row of CTU
        {
          posC0 = posRB.offset( 4.4 );
          // in the reference the CTU address is not set - thus probably resulting in no using this C0 possibility
        }
        else if( posInCtu.y + 4 < pcv.maxCUHeight )          // is not at the last row of CTU But is last column of CTU
        {
          posC0 = posRB.offset( 4.4 );
          C0Avail = true;
        }
        else //is the right bottom corner of CTU
        {
          posC0 = posRB.offset( 4.4 );
          // same as for last column but not last row}}}/ /! < Time domain MV candidate
    Mv        cColMv;
    int       iRefIdx     = 0;
    int       dir         = 0;
    unsigned  uiArrayAddr = cnt;
    bool      bExistMV    = ( C0Avail && getColocatedMVP(pu, REF_PIC_LIST_0, posC0, cColMv, iRefIdx ) )  / /! < get the same MV after scaling
#if JVET_N0266_SMALL_BLOCKS
                              || getColocatedMVP( pu, REF_PIC_LIST_0, posC1, cColMv, iRefIdx );
#else
                                      || ( C1Avail && getColocatedMVP(pu, REF_PIC_LIST_0, posC1, cColMv, iRefIdx ));
#endif
    if (bExistMV)
    {
      dir     |= 1;
      mrgCtx.mvFieldNeighbours[2 * uiArrayAddr].setMvField(cColMv, iRefIdx);
    }

    if (slice.isInterB())
    {
      bExistMV = ( C0Avail && getColocatedMVP(pu, REF_PIC_LIST_1, posC0, cColMv, iRefIdx ) )
#if JVET_N0266_SMALL_BLOCKS
                   || getColocatedMVP( pu, REF_PIC_LIST_1, posC1, cColMv, iRefIdx );
#else
                           || (C1Avail &&  getColocatedMVP(pu, REF_PIC_LIST_1, posC1, cColMv, iRefIdx ) );
#endif
      if (bExistMV)
      {
        dir     |= 2;
        mrgCtx.mvFieldNeighbours[2 * uiArrayAddr + 1].setMvField(cColMv, iRefIdx); }}if( dir ! =0 )
    {
      bool addTMvp = true;
#if HM_JEM_MERGE_CANDS
      int iSpanCand = cnt;
      for( int i = 0; i < iSpanCand; i++ )
      {
        if( mrgCtx.interDirNeighbours[  i           ] == dir &&
            mrgCtx.mvFieldNeighbours [  i << 1      ] == mrgCtx.mvFieldNeighbours[  uiArrayAddr << 1      ] &&
            mrgCtx.mvFieldNeighbours [( i << 1 ) + 1] == mrgCtx.mvFieldNeighbours[( uiArrayAddr << 1 ) + 1] )
        {
          addTMvp = false; }}#endif
      if( addTMvp )
      {
        mrgCtx.interDirNeighbours[uiArrayAddr] = dir;
#if! JVET_L0090_PAIR_AVG
        isCandInter              [uiArrayAddr] = true;
#endif
        mrgCtx.GBiIdx[uiArrayAddr] = GBI_DEFAULT;
        if (mrgCandIdx == cnt && canFastExit)
        {
          return; } cnt++; }}}// early termination
  if (cnt == maxNumMergeCand)
  {
    return;
  }
  / /! < Maximum number of candidates minus 1, HMVP
  int maxNumMergeCandMin1 = maxNumMergeCand - 1;
  if(cnt ! = maxNumMergeCandMin1) {bool isAvailableSubPu = false;
    unsigned subPuMvpPos = 0;
#if JVET_L0090_PAIR_AVG
#if JVET_N0266_SMALL_BLOCKS
    bool isShared = false;
#else
    bool  isShared = ((pu.Y().lumaSize().width ! = pu.shareParentSize.width) || (pu.Y().lumaSize().height ! = pu.shareParentSize.height));#endif   / /! The < add HMVP
    bool bFound = addMergeHMVPCand(cs, mrgCtx, canFastExit
      , mrgCandIdx
      , maxNumMergeCandMin1, cnt
      , spatialCandPos
      , isAvailableSubPu, subPuMvpPos
      , CU::isIBC(*pu.cu)
      , isShared
    );
#else
    bool bFound = addMergeHMVPCand(slice, mrgCtx, isCandInter, canFastExit , (mmvdList ! =0&& mrgCandIdx ! =- 1)? (const int)mrgCandIdxIBC : mrgCandIdx
      , maxNumMergeCandMin1, cnt, cnt, isAvailableSubPu, subPuMvpPos
      , mmvdList
    );
#endif
    if (bFound)
    {
      return; }}#if JVET_L0090_PAIR_AVG
  // pairwise-average candidates
  {
	  / /! The average MV
    if (cnt > 1 && cnt < maxNumMergeCand)
    {

      mrgCtx.mvFieldNeighbours[cnt * 2].setMvField( Mv( 0.0 ), NOT_VALID );
      mrgCtx.mvFieldNeighbours[cnt * 2 + 1].setMvField( Mv( 0.0 ), NOT_VALID );
      // calculate average MV for L0 and L1 seperately
      unsigned char interDir = 0;

	  / /! < The two lists compute the average vector separately
      for( int refListId = 0; refListId < (slice.isInterB()?2 : 1); refListId++ )
      {
        const short refIdxI = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].refIdx;
        const short refIdxJ = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].refIdx;
		/ /! < The case where both vectors are invalid
        // both MVs are invalid, skip
        if( (refIdxI == NOT_VALID) && (refIdxJ == NOT_VALID) )
        {
          continue;
        }
		/ /! < Case where both vectors are valid
        interDir += 1 << refListId;
        // both MVs are valid, average these two MVs
        if( (refIdxI ! = NOT_VALID) && (refIdxJ ! = NOT_VALID) ) {const Mv& MvI = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].mv;
          const Mv& MvJ = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].mv;
		  / /! < average, by shifting
          // average two MVs
          Mv avgMv = MvI;
          avgMv += MvJ;
          roundAffineMv(avgMv.hor, avgMv.ver, 1);

          mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( avgMv, refIdxI );
        }/ /! < The case where only one vector is valid
        // only one MV is valid, take the only one MV
        else if( refIdxI ! = NOT_VALID ) { Mv singleMv = mrgCtx.mvFieldNeighbours[0 * 2 + refListId].mv;
          mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( singleMv, refIdxI );
        }
        else if( refIdxJ ! = NOT_VALID ) { Mv singleMv = mrgCtx.mvFieldNeighbours[1 * 2 + refListId].mv;
          mrgCtx.mvFieldNeighbours[cnt * 2 + refListId].setMvField( singleMv, refIdxJ );
        }
      }

      mrgCtx.interDirNeighbours[cnt] = interDir;
      if( interDir > 0) { cnt++; }}// early termination
    if( cnt == maxNumMergeCand )
    {
      return; }}#endif

  uint32_t uiArrayAddr = cnt;
#if! JVET_L0090_PAIR_AVG
  uint32_t uiCutoff    = std::min( uiArrayAddr, 3u );
  if (slice.isInterB())
  {
    static const uint32_t NUM_PRIORITY_LIST = 12;
    static const uint32_t uiPriorityList0[NUM_PRIORITY_LIST] = { 0 , 1.0.2.1.2.0.3.1.3.2.3 };
    static const uint32_t uiPriorityList1[NUM_PRIORITY_LIST] = { 1 , 0.2.0.2.1.3.0.3.1.3.2 };

    for (int idx = 0; idx < uiCutoff * (uiCutoff - 1) && uiArrayAddr ! = maxNumMergeCand; idx++) {CHECK( idx >= NUM_PRIORITY_LIST, "Invalid priority list number" );
      int i = uiPriorityList0[idx];
      int j = uiPriorityList1[idx];
      if (isCandInter[i] && isCandInter[j] && (mrgCtx.interDirNeighbours[i] & 0x1) && (mrgCtx.interDirNeighbours[j] & 0x2))
      {
        isCandInter[uiArrayAddr] = true;
        mrgCtx.interDirNeighbours[uiArrayAddr] = 3;
        mrgCtx.GBiIdx[uiArrayAddr] = ((mrgCtx.interDirNeighbours[uiArrayAddr] == 3))? CU::deriveGbiIdx(mrgCtx.GBiIdx[i], mrgCtx.GBiIdx[j]) : GBI_DEFAULT;

        // get Mv from cand[i] and cand[j]
        mrgCtx.mvFieldNeighbours[ uiArrayAddr << 1     ].setMvField(mrgCtx.mvFieldNeighbours[ i << 1     ].mv, mrgCtx.mvFieldNeighbours[ i << 1     ].refIdx);
        mrgCtx.mvFieldNeighbours[(uiArrayAddr << 1) + 1].setMvField(mrgCtx.mvFieldNeighbours[(j << 1) + 1].mv, mrgCtx.mvFieldNeighbours[(j << 1) + 1].refIdx);

        int iRefPOCL0 = slice.getRefPOC(REF_PIC_LIST_0, mrgCtx.mvFieldNeighbours[(uiArrayAddr << 1)    ].refIdx);
        int iRefPOCL1 = slice.getRefPOC(REF_PIC_LIST_1, mrgCtx.mvFieldNeighbours[(uiArrayAddr << 1) + 1].refIdx);

        if( iRefPOCL0 == iRefPOCL1 && mrgCtx.mvFieldNeighbours[( uiArrayAddr << 1 )].mv == mrgCtx.mvFieldNeighbours[( uiArrayAddr << 1 ) + 1].mv )
        {
          isCandInter[uiArrayAddr] = false;
        }
        else{ uiArrayAddr++; }}}}// early termination
  if (uiArrayAddr == maxNumMergeCand)
  {
    return;
  }
#endif

  int iNumRefIdx = slice.isInterB()? std::min(slice.getNumRefIdx(REF_PIC_LIST_0), slice.getNumRefIdx(REF_PIC_LIST_1)) : slice.getNumRefIdx(REF_PIC_LIST_0);

  int r = 0;
  int refcnt = 0;
  while (uiArrayAddr < maxNumMergeCand)
  {
#if! JVET_L0090_PAIR_AVG
    isCandInter               [uiArrayAddr     ] = true;
#endif
    mrgCtx.interDirNeighbours [uiArrayAddr     ] = 1;
    mrgCtx.GBiIdx             [uiArrayAddr     ] = GBI_DEFAULT;
    mrgCtx.mvFieldNeighbours  [uiArrayAddr << 1].setMvField(Mv(0.0), r);  / /! < fill the 0 vector

    if (slice.isInterB())
    {
      mrgCtx.interDirNeighbours [ uiArrayAddr          ] = 3;
      mrgCtx.mvFieldNeighbours  [(uiArrayAddr << 1) + 1].setMvField(Mv(0.0), r);
    }

    if ( mrgCtx.interDirNeighbours[uiArrayAddr] == 1 && pu.cs->slice->getRefPic(REF_PIC_LIST_0, mrgCtx.mvFieldNeighbours[uiArrayAddr << 1].refIdx)->getPOC() == pu.cs->slice->getPOC())
    {
      mrgCtx.mrgTypeNeighbours[uiArrayAddr] = MRG_TYPE_IBC;
    }

    uiArrayAddr++;

    if (refcnt == iNumRefIdx - 1)
    {
      r = 0;
    }
    else
    {
      ++r;
      ++refcnt;
    }
  }
  mrgCtx.numValidMergeCand = uiArrayAddr;
}
Copy the code

reference

JVET-N1002

JVET-L0266

JVET-L0090

If you are interested, please pay attention to wechat public account Video Coding