[x265] [PATCH] Implementation for Intra-refresh
Santhoshini Sekar
santhoshini at multicorewareinc.com
Tue Sep 15 10:19:39 CEST 2015
On Mon, Sep 14, 2015 at 9:16 PM, Steve Borho <steve at borho.org> wrote:
> 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?
>
Yes setSearchRange() is called in many places wherein clipMvPIR() is not
needed. Initially I had the functionality of clipMvPIR() in clipMV(). But,
later
added this new function as clipMV is also called in multiple 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
> _______________________________________________
> 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/20150915/aba99e04/attachment-0001.html>
More information about the x265-devel
mailing list