[x265] [PATCH] Implementation for Intra-refresh

Steve Borho steve at borho.org
Mon Sep 14 17:46:02 CEST 2015


On 09/14, santhoshini at multicorewareinc.com wrote:
> # HG changeset patch
> # User Santhoshini Sekar<santhoshini at multicorewareinc.com>
> # Date 1442213046 -19800
> #      Mon Sep 14 12:14:06 2015 +0530
> # Node ID 0eb755da6cab80020bf26410438d83337f9aed9a
> # Parent  f6892dcc7f4e74af58d7f39e085aed44afba919c
> Implementation for Intra-refresh
> 
> diff -r f6892dcc7f4e -r 0eb755da6cab source/encoder/analysis.cpp
> --- a/source/encoder/analysis.cpp	Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/analysis.cpp	Mon Sep 14 12:14:06 2015 +0530
> @@ -170,7 +170,12 @@
>      }
>      else
>      {
> -        if (!m_param->rdLevel)
> +        int bForceIntra = m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE &&
> +                          ctu.m_cuPelX / g_maxCUSize >= frame.m_encData->m_pir.pirStartPelX && ctu.m_cuPelX / g_maxCUSize < frame.m_encData->m_pir.pirEndPelX;
> +
> +        if (bForceIntra)
> +            compressIntraCU(ctu, cuGeom, zOrder, qp);
> +        else if (!m_param->rdLevel)
>          {
>              /* In RD Level 0/1, copy source pixels into the reconstructed block so
>              * they are available for intra predictions */
> @@ -1464,7 +1469,13 @@
>              (candMvField[i][0].mv.y >= (m_param->searchRange + 1) * 4 ||
>              candMvField[i][1].mv.y >= (m_param->searchRange + 1) * 4))
>              continue;
> -
> +        if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE)
> +        {
> +            int maxX = (m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndPelX * g_maxCUSize - 3) * 4;

I'm not following this math, is m_pir.pirEndPelX not in units of PELs
(pixels)?  The multiplication by g_maxCUSize would indicate it's in
units of CTUs. And what is 3? is that the half-filter width? if so it
needs to be adjusted for HEVC's filter-width (and probably use the
existing macro)

> +            int maxMv = maxX - 4 * g_maxCUSize * candMvField[i][0].mv.x;
> +            if (maxMv > 0 && tempPred->cu.m_cuPelX / g_maxCUSize < m_frame->m_encData->m_pir.pirEndPelX)
> +                continue;
> +        }
>          tempPred->cu.m_mvpIdx[0][0] = (uint8_t)i; // merge candidate ID is stored in L0 MVP idx
>          X265_CHECK(m_slice->m_sliceType == B_SLICE || !(candDir[i] & 0x10), " invalid merge for P slice\n");
>          tempPred->cu.m_interDir[0] = candDir[i];
> @@ -1592,7 +1603,13 @@
>                  continue;
>              triedBZero = true;
>          }
> -
> +        if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE)
> +        {
> +            int maxX = (m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndPelX * g_maxCUSize - 3) * 4;
> +            int maxMv = maxX - 4 * g_maxCUSize * candMvField[i][0].mv.x;
> +            if (maxMv > 0 && tempPred->cu.m_cuPelX / g_maxCUSize < m_frame->m_encData->m_pir.pirEndPelX)
> +                continue;
> +        }
>          tempPred->cu.m_mvpIdx[0][0] = (uint8_t)i;    /* merge candidate ID is stored in L0 MVP idx */
>          tempPred->cu.m_interDir[0] = candDir[i];
>          tempPred->cu.m_mv[0][0] = candMvField[i][0].mv;
> diff -r f6892dcc7f4e -r 0eb755da6cab source/encoder/api.cpp
> --- a/source/encoder/api.cpp	Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/api.cpp	Mon Sep 14 12:14:06 2015 +0530
> @@ -245,6 +245,16 @@
>      }
>  }
>  
> +int x265_encoder_intra_refresh(x265_encoder *enc)
> +{
> +    if (!enc)
> +        return -1;
> +
> +    Encoder *encoder = static_cast<Encoder*>(enc);
> +    encoder->m_param->bQueuedIntraRefresh = 1;
> +    return 0;
> +}
> +
>  void x265_cleanup(void)
>  {
>      if (!g_ctuSizeConfigured)
> @@ -315,6 +325,7 @@
>      &x265_encoder_log,
>      &x265_encoder_close,
>      &x265_cleanup,
> +    &x265_encoder_intra_refresh,
>  
>      sizeof(x265_frame_stats),
>  };
> diff -r f6892dcc7f4e -r 0eb755da6cab source/encoder/encoder.cpp
> --- a/source/encoder/encoder.cpp	Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/encoder.cpp	Mon Sep 14 12:14:06 2015 +0530
> @@ -768,6 +768,43 @@
>              if (m_param->rc.rateControlMode != X265_RC_CQP)
>                  m_lookahead->getEstimatedPictureCost(frameEnc);
>  
> +            if (m_param->bIntraRefresh)
> +            {
> +                Slice* slice = frameEnc->m_encData->m_slice;

this code would be more readable if you made a reference to
frameEnc->m_encData->m_pir

> +                if (slice->m_sliceType == I_SLICE)
> +                {
> +                    frameEnc->m_encData->m_pir.framesSinceLastPir = 0;
> +                    m_param->bQueuedIntraRefresh = 0;
> +                    /* PIR is currently only supported with ref == 1, so any intra frame effectively refreshes
> +                     * the whole frame and counts as an intra refresh. */
> +                    frameEnc->m_encData->m_pir.position = frameEnc->m_lowres.maxBlocksInRow;
> +                }
> +                else if (slice->m_sliceType == P_SLICE)
> +                {
> +                    Frame* ref = frameEnc->m_encData->m_slice->m_refFrameList[0][0];
> +                    int pocdiff = (frameEnc->m_poc - ref->m_poc);

parens are unnecessary

> +                    float increment = X265_MAX(((float)frameEnc->m_lowres.maxBlocksInRow - 1) / m_param->keyframeMax, 1);
> +                    frameEnc->m_encData->m_pir.position = ref->m_encData->m_pir.position;
> +                    frameEnc->m_encData->m_pir.framesSinceLastPir = ref->m_encData->m_pir.framesSinceLastPir + pocdiff;
> +                    if (frameEnc->m_encData->m_pir.framesSinceLastPir >= m_param->keyframeMax ||
> +                        (m_param->bQueuedIntraRefresh && frameEnc->m_encData->m_pir.position + 0.5 >= frameEnc->m_lowres.maxBlocksInRow))
> +                    {
> +                        frameEnc->m_encData->m_pir.position = 0;
> +                        frameEnc->m_encData->m_pir.framesSinceLastPir = 0;
> +                        m_param->bQueuedIntraRefresh = 0;
> +                        frameEnc->m_lowres.bKeyframe = 1;
> +                    }
> +                    frameEnc->m_encData->m_pir.pirStartPelX = (uint32_t)(frameEnc->m_encData->m_pir.position + 0.5);
> +                    frameEnc->m_encData->m_pir.position += increment * pocdiff;
> +                    frameEnc->m_encData->m_pir.pirEndPelX = (uint32_t)(frameEnc->m_encData->m_pir.position + 0.5);
> +                    /* If our intra refresh has reached the right side of the frame, we're done. */
> +                    if (frameEnc->m_encData->m_pir.pirEndPelX >= frameEnc->m_lowres.maxBlocksInRow - 1)
> +                    {
> +                        frameEnc->m_encData->m_pir.position = frameEnc->m_lowres.maxBlocksInRow;
> +                        frameEnc->m_encData->m_pir.pirEndPelX = frameEnc->m_lowres.maxBlocksInRow - 1;
> +                    }
> +                }
> +            }
>              /* Allow FrameEncoder::compressFrame() to start in the frame encoder thread */
>              if (!curEncoder->startCompressFrame(frameEnc))
>                  m_aborted = true;
> diff -r f6892dcc7f4e -r 0eb755da6cab source/encoder/search.cpp
> --- a/source/encoder/search.cpp	Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/search.cpp	Mon Sep 14 12:14:06 2015 +0530
> @@ -1923,6 +1923,26 @@
>      return costs[0] <= costs[1] ? 0 : 1;
>  }
>  
> +void Search::clipMvPIR(const CUData& cu, MV& outMV) const
> +{
> +    const uint32_t mvshift = 2;
> +    uint32_t offset = 8;
> +
> +    int16_t xmax = (int16_t)((m_slice->m_sps->picWidthInLumaSamples + offset - cu.m_cuPelX - 1) << mvshift);
> +    int16_t xmin = -(int16_t)((g_maxCUSize + offset + cu.m_cuPelX - 1) << mvshift);
> +
> +    int16_t ymax = (int16_t)((m_slice->m_sps->picHeightInLumaSamples + offset - cu.m_cuPelY - 1) << mvshift);
> +    int16_t ymin = -(int16_t)((g_maxCUSize + offset + cu.m_cuPelY - 1) << mvshift);
> +
> +    int maxX = (m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndPelX * g_maxCUSize - 3) << mvshift;
> +    int maxMv = maxX - 4* g_maxCUSize * outMV.x;
> +    if (maxMv > 0 && cu.m_cuPelX / g_maxCUSize < m_frame->m_encData->m_pir.pirStartPelX)
> +        xmax = X265_MIN(xmax, (int16_t)maxX);
> +
> +    outMV.x = X265_MIN(xmax, X265_MAX(xmin, outMV.x));
> +    outMV.y = X265_MIN(ymax, X265_MAX(ymin, outMV.y));
> +}
> +
>  void Search::PME::processTasks(int workerThreadId)
>  {
>  #if DETAILED_CU_STATS
> @@ -2088,6 +2108,11 @@
>                      mvc[numMvc++] = lmv;
>  
>                  setSearchRange(cu, mvp, m_param->searchRange, mvmin, mvmax);
> +                if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE)
> +                {
> +                    clipMvPIR(cu, mvmin);
> +                    clipMvPIR(cu, mvmax);
> +                }

there is a lot of code duplication between setSearchRange() and
clipMvPIR(). Should the two be combined?

is setSearchRange() called in more places?

>                  int satdCost = m_me.motionEstimate(&slice->m_mref[list][ref], mvmin, mvmax, mvp, numMvc, mvc, m_param->searchRange, outmv);
>  
>                  /* Get total cost of partition, but only include MV bit cost once */
> diff -r f6892dcc7f4e -r 0eb755da6cab source/encoder/search.h
> --- a/source/encoder/search.h	Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/search.h	Mon Sep 14 12:14:06 2015 +0530
> @@ -449,6 +449,7 @@
>  
>      /* inter/ME helper functions */
>      int       selectMVP(const CUData& cu, const PredictionUnit& pu, const MV amvp[AMVP_NUM_CANDS], int list, int ref);
> +    void     clipMvPIR(const CUData& cu, MV& outMV) const; // used only for Periodic Intra refresh
>      const MV& checkBestMVP(const MV amvpCand[2], const MV& mv, int& mvpIdx, uint32_t& outBits, uint32_t& outCost) const;
>      void     setSearchRange(const CUData& cu, const MV& mvp, int merange, MV& mvmin, MV& mvmax) const;
>      uint32_t mergeEstimation(CUData& cu, const CUGeom& cuGeom, const PredictionUnit& pu, int puIdx, MergeData& m);
> diff -r f6892dcc7f4e -r 0eb755da6cab source/encoder/slicetype.cpp
> --- a/source/encoder/slicetype.cpp	Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/slicetype.cpp	Mon Sep 14 12:14:06 2015 +0530
> @@ -774,6 +774,7 @@
>              for (uint32_t cnt = 0; cnt < scale && lowresRow < heightInLowresCu; lowresRow++, cnt++)
>              {
>                  sum = 0; intraSum = 0;
> +                int diff = 0;
>                  lowresCuIdx = lowresRow * widthInLowresCu;
>                  for (lowresCol = 0; lowresCol < widthInLowresCu; lowresCol++, lowresCuIdx++)
>                  {
> @@ -781,14 +782,18 @@
>                      if (qp_offset)
>                      {
>                          lowresCuCost = (uint16_t)((lowresCuCost * x265_exp2fix8(qp_offset[lowresCuIdx]) + 128) >> 8);
> -                        int32_t intraCuCost = curFrame->m_lowres.intraCost[lowresCuIdx]; 
> +                        int32_t intraCuCost = curFrame->m_lowres.intraCost[lowresCuIdx];
>                          curFrame->m_lowres.intraCost[lowresCuIdx] = (intraCuCost * x265_exp2fix8(qp_offset[lowresCuIdx]) + 128) >> 8;
>                      }
> +                    if (m_param->bIntraRefresh && slice->m_sliceType == X265_TYPE_P)
> +                        for (uint32_t x = curFrame->m_encData->m_pir.pirStartPelX; x <= curFrame->m_encData->m_pir.pirEndPelX; x++)
> +                            diff += curFrame->m_lowres.intraCost[lowresCuIdx] - lowresCuCost;
>                      curFrame->m_lowres.lowresCostForRc[lowresCuIdx] = lowresCuCost;
>                      sum += lowresCuCost;
>                      intraSum += curFrame->m_lowres.intraCost[lowresCuIdx];
>                  }
>                  curFrame->m_encData->m_rowStat[row].satdForVbv += sum;
> +                curFrame->m_encData->m_rowStat[row].satdForVbv += diff;
>                  curFrame->m_encData->m_rowStat[row].intraSatdForVbv += intraSum;
>              }
>          }
> @@ -900,8 +905,7 @@
>              x265_log(m_param, X265_LOG_WARNING, "B-ref at frame %d incompatible with B-pyramid and %d reference frames\n",
>                       frm.sliceType, m_param->maxNumReferences);
>          }
> -
> -        if (/* (!param->intraRefresh || frm.frameNum == 0) && */ frm.frameNum - m_lastKeyframe >= m_param->keyframeMax)
> +        if ((!m_param->bIntraRefresh || frm.frameNum == 0) && frm.frameNum - m_lastKeyframe >= m_param->keyframeMax)
>          {
>              if (frm.sliceType == X265_TYPE_AUTO || frm.sliceType == X265_TYPE_I)
>                  frm.sliceType = m_param->bOpenGOP && m_lastKeyframe >= 0 ? X265_TYPE_I : X265_TYPE_IDR;
> @@ -1184,7 +1188,7 @@
>      frames[framecnt + 1] = NULL;
>  
>      keyintLimit = m_param->keyframeMax - frames[0]->frameNum + m_lastKeyframe - 1;
> -    origNumFrames = numFrames = X265_MIN(framecnt, keyintLimit);
> +    origNumFrames = numFrames = m_param->bIntraRefresh ? framecnt : X265_MIN(framecnt, keyintLimit);
>  
>      if (bIsVbvLookahead)
>          numFrames = framecnt;
> @@ -1384,12 +1388,12 @@
>      if (m_param->rc.cuTree)
>          cuTree(frames, X265_MIN(numFrames, m_param->keyframeMax), bKeyframe);
>  
> -    // if (!param->bIntraRefresh)
> -    for (int j = keyintLimit + 1; j <= numFrames; j += m_param->keyframeMax)
> -    {
> -        frames[j]->sliceType = X265_TYPE_I;
> -        resetStart = X265_MIN(resetStart, j + 1);
> -    }
> +    if (!m_param->bIntraRefresh)
> +        for (int j = keyintLimit + 1; j <= numFrames; j += m_param->keyframeMax)
> +        {
> +            frames[j]->sliceType = X265_TYPE_I;
> +            resetStart = X265_MIN(resetStart, j + 1);
> +        }
>  
>      if (bIsVbvLookahead)
>          vbvLookahead(frames, numFrames, bKeyframe);
> @@ -1504,7 +1508,7 @@
>      {
>          if (m_param->keyframeMin == m_param->keyframeMax)
>              threshMin = threshMax;
> -        if (gopSize <= m_param->keyframeMin / 4)
> +        if (gopSize <= m_param->keyframeMin / 4 || m_param->bIntraRefresh)
>              bias = threshMin / 4;
>          else if (gopSize <= m_param->keyframeMin)
>              bias = threshMin * gopSize / m_param->keyframeMin;
> diff -r f6892dcc7f4e -r 0eb755da6cab source/x265.h
> --- a/source/x265.h	Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/x265.h	Mon Sep 14 12:14:06 2015 +0530
> @@ -707,6 +707,8 @@
>       * big keyframe, the keyframe is "spread" over many frames. */
>      int       bIntraRefresh;
>  
> +    int       bQueuedIntraRefresh;
> +
>      /*== Coding Unit (CU) definitions ==*/
>  
>      /* Maximum CU width and height in pixels.  The size must be 64, 32, or 16.
> @@ -1378,6 +1380,22 @@
>   *      close an encoder handler */
>  void x265_encoder_close(x265_encoder *);
>  
> +/* x265_encoder_intra_refresh:
> + *      If an intra refresh is not in progress, begin one with the next P-frame.
> + *      If an intra refresh is in progress, begin one as soon as the current one finishes.
> + *      Requires bIntraRefresh to be set.
> + *
> + *      Useful for interactive streaming where the client can tell the server that packet loss has
> + *      occurred.  In this case, keyint can be set to an extremely high value so that intra refreshes
> + *      occur only when calling x265_encoder_intra_refresh.
> + *
> + *      In multi-pass encoding, if x265_encoder_intra_refresh is called differently in each pass,
> + *      behavior is undefined.
> + *
> + *      Should not be called during an x265_encoder_encode. */
> +
> +int x265_encoder_intra_refresh(x265_encoder *);
> +
>  /* x265_cleanup:
>   *       release library static allocations, reset configured CTU size */
>  void x265_cleanup(void);
> @@ -1423,6 +1441,7 @@
>      void          (*encoder_log)(x265_encoder*, int, char**);
>      void          (*encoder_close)(x265_encoder*);
>      void          (*cleanup)(void);
> +    int           (*encoder_intra_refresh)(x265_encoder*);
>  
>      int           sizeof_frame_stats;   /* sizeof(x265_frame_stats) */
>      /* add new pointers to the end, or increment X265_MAJOR_VERSION */

x265_encoder_intra_refresh needs to be added to x265.def.in

-- 
Steve Borho


More information about the x265-devel mailing list