[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