[x265] [PATCH 2 of 3] Implementation for Intra refresh

Deepthi Nandakumar deepthi at multicorewareinc.com
Mon Sep 28 11:33:57 CEST 2015


So, why should maxNumReferences be 1 always?

I'm nicely confused by the requirement here. When intra-refresh is enabled
(even if the user did not call  the API function x265_intra_refresh), all I
frames are converted to P frames. The responsibility of the API call is
only to start a mid-GOP refresh. Does this require a no-scenecut, if not,
what happens when a scenecut is inserted mid-GOP? Trigger a refresh?

On Thu, Sep 24, 2015 at 9:00 AM, <santhoshini at multicorewareinc.com> wrote:

> # HG changeset patch
> # User Santhoshini Sekar<santhoshini at multicorewareinc.com>
> # Date 1442393819 -19800
> #      Wed Sep 16 14:26:59 2015 +0530
> # Node ID 97b62cb57bfa171e50d3fa736527634bb507cbe5
> # Parent  98c0dcd5a10b8806aa1ceb775ff9342f7a7ae6c6
> Implementation for Intra refresh
>
> diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/analysis.cpp
> --- a/source/encoder/analysis.cpp       Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/analysis.cpp       Wed Sep 16 14:26:59 2015 +0530
> @@ -171,10 +171,14 @@
>      }
>      else
>      {
> -        if (!m_param->rdLevel)
> +        if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE &&
> +            ctu.m_cuPelX / g_maxCUSize >=
> frame.m_encData->m_pir.pirStartCol
> +            && ctu.m_cuPelX / g_maxCUSize <
> frame.m_encData->m_pir.pirEndCol)
> +            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 */
> +             * they are available for intra predictions */
>              m_modeDepth[0].fencYuv.copyToPicYuv(*m_frame->m_reconPic,
> ctu.m_cuAddr, 0);
>
>              compressInterCU_rd0_4(ctu, cuGeom, qp);
> @@ -1458,13 +1462,23 @@
>      bestPred->sa8dCost = MAX_INT64;
>      int bestSadCand = -1;
>      int sizeIdx = cuGeom.log2CUSize - 2;
> -
> +    int safeX, maxSafeMv;
> +    if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE)
> +    {
> +        safeX = m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndCol
> * g_maxCUSize - 3;
> +        maxSafeMv = (safeX - tempPred->cu.m_cuPelX) * 4;
> +    }
>      for (uint32_t i = 0; i < numMergeCand; ++i)
>      {
>          if (m_bFrameParallel &&
>              (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 &&
> +            tempPred->cu.m_cuPelX / g_maxCUSize <
> m_frame->m_encData->m_pir.pirEndCol &&
> +            candMvField[i][0].mv.x > maxSafeMv)
> +            // skip merge candidates which reference beyond safe
> reference area
> +            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");
> @@ -1570,7 +1584,12 @@
>          first = *m_reuseBestMergeCand;
>          last = first + 1;
>      }
> -
> +    int safeX, maxSafeMv;
> +    if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE)
> +    {
> +        safeX = m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndCol
> * g_maxCUSize - 3;
> +        maxSafeMv = (safeX - tempPred->cu.m_cuPelX) * 4;
> +    }
>      for (uint32_t i = first; i < last; i++)
>      {
>          if (m_bFrameParallel &&
> @@ -1593,7 +1612,11 @@
>                  continue;
>              triedBZero = true;
>          }
> -
> +        if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE &&
> +            tempPred->cu.m_cuPelX / g_maxCUSize <
> m_frame->m_encData->m_pir.pirEndCol &&
> +            candMvField[i][0].mv.x > maxSafeMv)
> +            // skip merge candidates which reference beyond safe
> reference area
> +            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 98c0dcd5a10b -r 97b62cb57bfa source/encoder/api.cpp
> --- a/source/encoder/api.cpp    Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/api.cpp    Wed Sep 16 14:26:59 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)
> @@ -317,6 +327,7 @@
>      &x265_cleanup,
>
>      sizeof(x265_frame_stats),
> +    &x265_encoder_intra_refresh,
>  };
>
>  typedef const x265_api* (*api_get_func)(int bitDepth);
> diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/encoder.cpp
> --- a/source/encoder/encoder.cpp        Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/encoder.cpp        Wed Sep 16 14:26:59 2015 +0530
> @@ -437,6 +437,46 @@
>      }
>  }
>
> +void Encoder::calcRefreshInterval(Frame* frameEnc)
> +{
> +    Slice* slice = frameEnc->m_encData->m_slice;
> +    uint32_t numBlocksInRow = slice->m_sps->numCuInWidth;
> +    FrameData::PeriodicIR* pir = &frameEnc->m_encData->m_pir;
> +    if (slice->m_sliceType == I_SLICE)
> +    {
> +        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. */
> +        pir->position = numBlocksInRow;
> +    }
> +    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;
> +        float increment = X265_MAX(((float)numBlocksInRow - 1) /
> m_param->keyframeMax, 1);
> +        pir->position = ref->m_encData->m_pir.position;
> +        pir->framesSinceLastPir =
> ref->m_encData->m_pir.framesSinceLastPir + pocdiff;
> +        if (pir->framesSinceLastPir >= m_param->keyframeMax ||
> +            (m_param->bQueuedIntraRefresh && pir->position + 0.5 >=
> numBlocksInRow))
> +        {
> +            pir->position = 0;
> +            pir->framesSinceLastPir = 0;
> +            m_param->bQueuedIntraRefresh = 0;
> +            frameEnc->m_lowres.bKeyframe = 1;
> +        }
> +        pir->pirStartCol = (uint32_t)(pir->position + 0.5);
> +        pir->position += increment * pocdiff;
> +        pir->pirEndCol = (uint32_t)(pir->position + 0.5);
> +        /* If our intra refresh has reached the right side of the frame,
> we're done. */
> +        if (pir->pirEndCol >= numBlocksInRow)
> +        {
> +            pir->position = numBlocksInRow;
> +            pir->pirEndCol = numBlocksInRow;
> +        }
> +    }
> +}
> +
>  /**
>   * Feed one new input frame into the encoder, get one frame out. If
> pic_in is
>   * NULL, a flush condition is implied and pic_in must be NULL for all
> subsequent
> @@ -768,6 +808,8 @@
>
>              if (m_param->rc.rateControlMode != X265_RC_CQP)
>                  m_lookahead->getEstimatedPictureCost(frameEnc);
> +             if (m_param->bIntraRefresh)
> +                 calcRefreshInterval(frameEnc);
>
>              /* Allow FrameEncoder::compressFrame() to start in the frame
> encoder thread */
>              if (!curEncoder->startCompressFrame(frameEnc))
> diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/encoder.h
> --- a/source/encoder/encoder.h  Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/encoder.h  Wed Sep 16 14:26:59 2015 +0530
> @@ -168,6 +168,8 @@
>
>      void finishFrameStats(Frame* pic, FrameEncoder *curEncoder, uint64_t
> bits, x265_frame_stats* frameStats);
>
> +    void calcRefreshInterval(Frame* frameEnc);
> +
>  protected:
>
>      void initVPS(VPS *vps);
> diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/search.cpp
> --- a/source/encoder/search.cpp Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/search.cpp Wed Sep 16 14:26:59 2015 +0530
> @@ -2460,6 +2460,17 @@
>      cu.clipMv(mvmin);
>      cu.clipMv(mvmax);
>
> +    if (cu.m_encData->m_param->bIntraRefresh && m_slice->m_sliceType ==
> P_SLICE &&
> +          cu.m_cuPelX / g_maxCUSize <
> m_frame->m_encData->m_pir.pirStartCol &&
> +          m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndCol <
> m_slice->m_sps->numCuInWidth)
>

Why check for m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndCol <
m_slice->m_sps->numCuInWidth here? This should be taken care of in
calcRefreshInterval.


> +    {
> +        int safeX, maxSafeMv;
> +        safeX = m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndCol
> * g_maxCUSize - 3;
> +        maxSafeMv = (safeX - cu.m_cuPelX) * 4;
> +        mvmax.x = X265_MIN(mvmax.x, maxSafeMv);
> +        mvmin.x = X265_MIN(mvmin.x, maxSafeMv);
> +    }
> +
>
     /* Clip search range to signaled maximum MV length.
>       * We do not support this VUI field being changed from the default */
>      const int maxMvLen = (1 << 15) - 1;
> diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/slicetype.cpp
> --- a/source/encoder/slicetype.cpp      Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/encoder/slicetype.cpp      Wed Sep 16 14:26:59 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.pirStartCol; x <=
> curFrame->m_encData->m_pir.pirEndCol; 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 98c0dcd5a10b -r 97b62cb57bfa source/x265.def.in
> --- a/source/x265.def.in        Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/x265.def.in        Wed Sep 16 14:26:59 2015 +0530
> @@ -22,3 +22,4 @@
>  x265_cleanup
>  x265_api_get_${X265_BUILD}
>  x265_api_query
> +x265_encoder_intra_refresh
> diff -r 98c0dcd5a10b -r 97b62cb57bfa source/x265.h
> --- a/source/x265.h     Wed Sep 09 14:52:35 2015 +0530
> +++ b/source/x265.h     Wed Sep 16 14:26:59 2015 +0530
> @@ -708,6 +708,8 @@
>       * big keyframe, the keyframe is "spread" over many frames. */
>

I dont see why we need two variables in x265_param. To track whether the
external refresh API was called, we can set an internal Encoder variable.
The basic bIntraRefresh will decide whether this feature is available for
this encode.

     int       bIntraRefresh;
>
> +    int       bQueuedIntraRefresh;
> +
>      /*== Coding Unit (CU) definitions ==*/
>
>      /* Maximum CU width and height in pixels.  The size must be 64, 32,
> or 16.
> @@ -1379,6 +1381,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.
>

It should be noted that if bIntraRefresh is not set, then this API call
will do nothing.

+ *
> + *      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);
> @@ -1426,6 +1444,7 @@
>      void          (*cleanup)(void);
>
>      int           sizeof_frame_stats;   /* sizeof(x265_frame_stats) */
> +    int           (*encoder_intra_refresh)(x265_encoder*);
>      /* add new pointers to the end, or increment X265_MAJOR_VERSION */
>  } x265_api;
>
> _______________________________________________
> x265-devel mailing list
> x265-devel at videolan.org
> https://mailman.videolan.org/listinfo/x265-devel
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/x265-devel/attachments/20150928/43d9ba70/attachment-0001.html>


More information about the x265-devel mailing list