<div dir="ltr"><div>So, why should maxNumReferences be 1 always? <br><br></div>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?<br><br><div><div><div class="gmail_extra"><div class="gmail_quote">On Thu, Sep 24, 2015 at 9:00 AM, <span dir="ltr"><<a href="mailto:santhoshini@multicorewareinc.com" target="_blank">santhoshini@multicorewareinc.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"># HG changeset patch<br>
# User Santhoshini Sekar<<a href="mailto:santhoshini@multicorewareinc.com" target="_blank">santhoshini@multicorewareinc.com</a>><br>
# Date 1442393819 -19800<br>
# Wed Sep 16 14:26:59 2015 +0530<br>
# Node ID 97b62cb57bfa171e50d3fa736527634bb507cbe5<br>
# Parent 98c0dcd5a10b8806aa1ceb775ff9342f7a7ae6c6<br>
Implementation for Intra refresh<br>
<br>
diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/analysis.cpp<br>
--- a/source/encoder/analysis.cpp Wed Sep 09 14:52:35 2015 +0530<br>
+++ b/source/encoder/analysis.cpp Wed Sep 16 14:26:59 2015 +0530<br>
@@ -171,10 +171,14 @@<br>
}<br>
else<br>
{<br>
- if (!m_param->rdLevel)<br>
+ if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE &&<br>
+ ctu.m_cuPelX / g_maxCUSize >= frame.m_encData->m_pir.pirStartCol<br>
+ && ctu.m_cuPelX / g_maxCUSize < frame.m_encData->m_pir.pirEndCol)<br>
+ compressIntraCU(ctu, cuGeom, zOrder, qp);<br>
+ else if (!m_param->rdLevel)<br>
{<br>
/* In RD Level 0/1, copy source pixels into the reconstructed block so<br>
- * they are available for intra predictions */<br>
+ * they are available for intra predictions */<br>
m_modeDepth[0].fencYuv.copyToPicYuv(*m_frame->m_reconPic, ctu.m_cuAddr, 0);<br>
<br>
compressInterCU_rd0_4(ctu, cuGeom, qp);<br>
@@ -1458,13 +1462,23 @@<br>
bestPred->sa8dCost = MAX_INT64;<br>
int bestSadCand = -1;<br>
int sizeIdx = cuGeom.log2CUSize - 2;<br>
-<br>
+ int safeX, maxSafeMv;<br>
+ if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE)<br>
+ {<br>
+ safeX = m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndCol * g_maxCUSize - 3;<br>
+ maxSafeMv = (safeX - tempPred->cu.m_cuPelX) * 4;<br>
+ }<br>
for (uint32_t i = 0; i < numMergeCand; ++i)<br>
{<br>
if (m_bFrameParallel &&<br>
(candMvField[i][0].mv.y >= (m_param->searchRange + 1) * 4 ||<br>
candMvField[i][1].mv.y >= (m_param->searchRange + 1) * 4))<br>
continue;<br>
+ if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE &&<br>
+ tempPred->cu.m_cuPelX / g_maxCUSize < m_frame->m_encData->m_pir.pirEndCol &&<br>
+ candMvField[i][0].mv.x > maxSafeMv)<br>
+ // skip merge candidates which reference beyond safe reference area<br>
+ continue;<br>
<br>
tempPred->cu.m_mvpIdx[0][0] = (uint8_t)i; // merge candidate ID is stored in L0 MVP idx<br>
X265_CHECK(m_slice->m_sliceType == B_SLICE || !(candDir[i] & 0x10), " invalid merge for P slice\n");<br>
@@ -1570,7 +1584,12 @@<br>
first = *m_reuseBestMergeCand;<br>
last = first + 1;<br>
}<br>
-<br>
+ int safeX, maxSafeMv;<br>
+ if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE)<br>
+ {<br>
+ safeX = m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndCol * g_maxCUSize - 3;<br>
+ maxSafeMv = (safeX - tempPred->cu.m_cuPelX) * 4;<br>
+ }<br>
for (uint32_t i = first; i < last; i++)<br>
{<br>
if (m_bFrameParallel &&<br>
@@ -1593,7 +1612,11 @@<br>
continue;<br>
triedBZero = true;<br>
}<br>
-<br>
+ if (m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE &&<br>
+ tempPred->cu.m_cuPelX / g_maxCUSize < m_frame->m_encData->m_pir.pirEndCol &&<br>
+ candMvField[i][0].mv.x > maxSafeMv)<br>
+ // skip merge candidates which reference beyond safe reference area<br>
+ continue;<br>
tempPred->cu.m_mvpIdx[0][0] = (uint8_t)i; /* merge candidate ID is stored in L0 MVP idx */<br>
tempPred->cu.m_interDir[0] = candDir[i];<br>
tempPred->cu.m_mv[0][0] = candMvField[i][0].mv;<br>
diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/api.cpp<br>
--- a/source/encoder/api.cpp Wed Sep 09 14:52:35 2015 +0530<br>
+++ b/source/encoder/api.cpp Wed Sep 16 14:26:59 2015 +0530<br>
@@ -245,6 +245,16 @@<br>
}<br>
}<br>
<br>
+int x265_encoder_intra_refresh(x265_encoder *enc)<br>
+{<br>
+ if (!enc)<br>
+ return -1;<br>
+<br>
+ Encoder *encoder = static_cast<Encoder*>(enc);<br>
+ encoder->m_param->bQueuedIntraRefresh = 1;<br>
+ return 0;<br>
+}<br>
+<br>
void x265_cleanup(void)<br>
{<br>
if (!g_ctuSizeConfigured)<br>
@@ -317,6 +327,7 @@<br>
&x265_cleanup,<br>
<br>
sizeof(x265_frame_stats),<br>
+ &x265_encoder_intra_refresh,<br>
};<br>
<br>
typedef const x265_api* (*api_get_func)(int bitDepth);<br>
diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/encoder.cpp<br>
--- a/source/encoder/encoder.cpp Wed Sep 09 14:52:35 2015 +0530<br>
+++ b/source/encoder/encoder.cpp Wed Sep 16 14:26:59 2015 +0530<br>
@@ -437,6 +437,46 @@<br>
}<br>
}<br>
<br>
+void Encoder::calcRefreshInterval(Frame* frameEnc)<br>
+{<br>
+ Slice* slice = frameEnc->m_encData->m_slice;<br>
+ uint32_t numBlocksInRow = slice->m_sps->numCuInWidth;<br>
+ FrameData::PeriodicIR* pir = &frameEnc->m_encData->m_pir;<br>
+ if (slice->m_sliceType == I_SLICE)<br>
+ {<br>
+ pir->framesSinceLastPir = 0;<br>
+ m_param->bQueuedIntraRefresh = 0;<br>
+ /* PIR is currently only supported with ref == 1, so any intra frame effectively refreshes<br>
+ * the whole frame and counts as an intra refresh. */<br>
+ pir->position = numBlocksInRow;<br>
+ }<br>
+ else if (slice->m_sliceType == P_SLICE)<br>
+ {<br>
+ Frame* ref = frameEnc->m_encData->m_slice->m_refFrameList[0][0];<br>
+ int pocdiff = frameEnc->m_poc - ref->m_poc;<br>
+ float increment = X265_MAX(((float)numBlocksInRow - 1) / m_param->keyframeMax, 1);<br>
+ pir->position = ref->m_encData->m_pir.position;<br>
+ pir->framesSinceLastPir = ref->m_encData->m_pir.framesSinceLastPir + pocdiff;<br>
+ if (pir->framesSinceLastPir >= m_param->keyframeMax ||<br>
+ (m_param->bQueuedIntraRefresh && pir->position + 0.5 >= numBlocksInRow))<br>
+ {<br>
+ pir->position = 0;<br>
+ pir->framesSinceLastPir = 0;<br>
+ m_param->bQueuedIntraRefresh = 0;<br>
+ frameEnc->m_lowres.bKeyframe = 1;<br>
+ }<br>
+ pir->pirStartCol = (uint32_t)(pir->position + 0.5);<br>
+ pir->position += increment * pocdiff;<br>
+ pir->pirEndCol = (uint32_t)(pir->position + 0.5);<br>
+ /* If our intra refresh has reached the right side of the frame, we're done. */<br>
+ if (pir->pirEndCol >= numBlocksInRow)<br>
+ {<br>
+ pir->position = numBlocksInRow;<br>
+ pir->pirEndCol = numBlocksInRow;<br>
+ }<br>
+ }<br>
+}<br>
+<br>
/**<br>
* Feed one new input frame into the encoder, get one frame out. If pic_in is<br>
* NULL, a flush condition is implied and pic_in must be NULL for all subsequent<br>
@@ -768,6 +808,8 @@<br>
<br>
if (m_param->rc.rateControlMode != X265_RC_CQP)<br>
m_lookahead->getEstimatedPictureCost(frameEnc);<br>
+ if (m_param->bIntraRefresh)<br>
+ calcRefreshInterval(frameEnc);<br>
<br>
/* Allow FrameEncoder::compressFrame() to start in the frame encoder thread */<br>
if (!curEncoder->startCompressFrame(frameEnc))<br>
diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/encoder.h<br>
--- a/source/encoder/encoder.h Wed Sep 09 14:52:35 2015 +0530<br>
+++ b/source/encoder/encoder.h Wed Sep 16 14:26:59 2015 +0530<br>
@@ -168,6 +168,8 @@<br>
<br>
void finishFrameStats(Frame* pic, FrameEncoder *curEncoder, uint64_t bits, x265_frame_stats* frameStats);<br>
<br>
+ void calcRefreshInterval(Frame* frameEnc);<br>
+<br>
protected:<br>
<br>
void initVPS(VPS *vps);<br>
diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/search.cpp<br>
--- a/source/encoder/search.cpp Wed Sep 09 14:52:35 2015 +0530<br>
+++ b/source/encoder/search.cpp Wed Sep 16 14:26:59 2015 +0530<br>
@@ -2460,6 +2460,17 @@<br>
cu.clipMv(mvmin);<br>
cu.clipMv(mvmax);<br>
<br>
+ if (cu.m_encData->m_param->bIntraRefresh && m_slice->m_sliceType == P_SLICE &&<br>
+ cu.m_cuPelX / g_maxCUSize < m_frame->m_encData->m_pir.pirStartCol &&<br>
+ m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndCol < m_slice->m_sps->numCuInWidth)<br></blockquote><div><br></div><div>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.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ {<br>
+ int safeX, maxSafeMv;<br>
+ safeX = m_slice->m_refFrameList[0][0]->m_encData->m_pir.pirEndCol * g_maxCUSize - 3;<br>
+ maxSafeMv = (safeX - cu.m_cuPelX) * 4;<br>
+ mvmax.x = X265_MIN(mvmax.x, maxSafeMv);<br>
+ mvmin.x = X265_MIN(mvmin.x, maxSafeMv);<br>
+ }<br>
+<br></blockquote><div></div><div></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
/* Clip search range to signaled maximum MV length.<br>
* We do not support this VUI field being changed from the default */<br>
const int maxMvLen = (1 << 15) - 1;<br>
diff -r 98c0dcd5a10b -r 97b62cb57bfa source/encoder/slicetype.cpp<br>
--- a/source/encoder/slicetype.cpp Wed Sep 09 14:52:35 2015 +0530<br>
+++ b/source/encoder/slicetype.cpp Wed Sep 16 14:26:59 2015 +0530<br>
@@ -774,6 +774,7 @@<br>
for (uint32_t cnt = 0; cnt < scale && lowresRow < heightInLowresCu; lowresRow++, cnt++)<br>
{<br>
sum = 0; intraSum = 0;<br>
+ int diff = 0;<br>
lowresCuIdx = lowresRow * widthInLowresCu;<br>
for (lowresCol = 0; lowresCol < widthInLowresCu; lowresCol++, lowresCuIdx++)<br>
{<br>
@@ -781,14 +782,18 @@<br>
if (qp_offset)<br>
{<br>
lowresCuCost = (uint16_t)((lowresCuCost * x265_exp2fix8(qp_offset[lowresCuIdx]) + 128) >> 8);<br>
- int32_t intraCuCost = curFrame->m_lowres.intraCost[lowresCuIdx];<br>
+ int32_t intraCuCost = curFrame->m_lowres.intraCost[lowresCuIdx];<br>
curFrame->m_lowres.intraCost[lowresCuIdx] = (intraCuCost * x265_exp2fix8(qp_offset[lowresCuIdx]) + 128) >> 8;<br>
}<br>
+ if (m_param->bIntraRefresh && slice->m_sliceType == X265_TYPE_P)<br>
+ for (uint32_t x = curFrame->m_encData->m_pir.pirStartCol; x <= curFrame->m_encData->m_pir.pirEndCol; x++)<br>
+ diff += curFrame->m_lowres.intraCost[lowresCuIdx] - lowresCuCost;<br>
curFrame->m_lowres.lowresCostForRc[lowresCuIdx] = lowresCuCost;<br>
sum += lowresCuCost;<br>
intraSum += curFrame->m_lowres.intraCost[lowresCuIdx];<br>
}<br>
curFrame->m_encData->m_rowStat[row].satdForVbv += sum;<br>
+ curFrame->m_encData->m_rowStat[row].satdForVbv += diff;<br>
curFrame->m_encData->m_rowStat[row].intraSatdForVbv += intraSum;<br>
}<br>
}<br>
@@ -900,8 +905,7 @@<br>
x265_log(m_param, X265_LOG_WARNING, "B-ref at frame %d incompatible with B-pyramid and %d reference frames\n",<br>
frm.sliceType, m_param->maxNumReferences);<br>
}<br>
-<br>
- if (/* (!param->intraRefresh || frm.frameNum == 0) && */ frm.frameNum - m_lastKeyframe >= m_param->keyframeMax)<br>
+ if ((!m_param->bIntraRefresh || frm.frameNum == 0) && frm.frameNum - m_lastKeyframe >= m_param->keyframeMax)<br>
{<br>
if (frm.sliceType == X265_TYPE_AUTO || frm.sliceType == X265_TYPE_I)<br>
frm.sliceType = m_param->bOpenGOP && m_lastKeyframe >= 0 ? X265_TYPE_I : X265_TYPE_IDR;<br>
@@ -1184,7 +1188,7 @@<br>
frames[framecnt + 1] = NULL;<br>
<br>
keyintLimit = m_param->keyframeMax - frames[0]->frameNum + m_lastKeyframe - 1;<br>
- origNumFrames = numFrames = X265_MIN(framecnt, keyintLimit);<br>
+ origNumFrames = numFrames = m_param->bIntraRefresh ? framecnt : X265_MIN(framecnt, keyintLimit);<br>
<br>
if (bIsVbvLookahead)<br>
numFrames = framecnt;<br>
@@ -1384,12 +1388,12 @@<br>
if (m_param->rc.cuTree)<br>
cuTree(frames, X265_MIN(numFrames, m_param->keyframeMax), bKeyframe);<br>
<br>
- // if (!param->bIntraRefresh)<br>
- for (int j = keyintLimit + 1; j <= numFrames; j += m_param->keyframeMax)<br>
- {<br>
- frames[j]->sliceType = X265_TYPE_I;<br>
- resetStart = X265_MIN(resetStart, j + 1);<br>
- }<br>
+ if (!m_param->bIntraRefresh)<br>
+ for (int j = keyintLimit + 1; j <= numFrames; j += m_param->keyframeMax)<br>
+ {<br>
+ frames[j]->sliceType = X265_TYPE_I;<br>
+ resetStart = X265_MIN(resetStart, j + 1);<br>
+ }<br>
<br>
if (bIsVbvLookahead)<br>
vbvLookahead(frames, numFrames, bKeyframe);<br>
@@ -1504,7 +1508,7 @@<br>
{<br>
if (m_param->keyframeMin == m_param->keyframeMax)<br>
threshMin = threshMax;<br>
- if (gopSize <= m_param->keyframeMin / 4)<br>
+ if (gopSize <= m_param->keyframeMin / 4 || m_param->bIntraRefresh)<br>
bias = threshMin / 4;<br>
else if (gopSize <= m_param->keyframeMin)<br>
bias = threshMin * gopSize / m_param->keyframeMin;<br>
diff -r 98c0dcd5a10b -r 97b62cb57bfa source/<a href="http://x265.def.in" rel="noreferrer" target="_blank">x265.def.in</a><br>
--- a/source/<a href="http://x265.def.in" rel="noreferrer" target="_blank">x265.def.in</a> Wed Sep 09 14:52:35 2015 +0530<br>
+++ b/source/<a href="http://x265.def.in" rel="noreferrer" target="_blank">x265.def.in</a> Wed Sep 16 14:26:59 2015 +0530<br>
@@ -22,3 +22,4 @@<br>
x265_cleanup<br>
x265_api_get_${X265_BUILD}<br>
x265_api_query<br>
+x265_encoder_intra_refresh<br>
diff -r 98c0dcd5a10b -r 97b62cb57bfa source/x265.h<br>
--- a/source/x265.h Wed Sep 09 14:52:35 2015 +0530<br>
+++ b/source/x265.h Wed Sep 16 14:26:59 2015 +0530<br>
@@ -708,6 +708,8 @@<br>
* big keyframe, the keyframe is "spread" over many frames. */<br></blockquote><div><br></div><div>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. <br><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
int bIntraRefresh;<br>
<br>
+ int bQueuedIntraRefresh;<br>
+<br>
/*== Coding Unit (CU) definitions ==*/<br>
<br>
/* Maximum CU width and height in pixels. The size must be 64, 32, or 16.<br>
@@ -1379,6 +1381,22 @@<br>
* close an encoder handler */<br>
void x265_encoder_close(x265_encoder *);<br>
<br>
+/* x265_encoder_intra_refresh:<br>
+ * If an intra refresh is not in progress, begin one with the next P-frame.<br>
+ * If an intra refresh is in progress, begin one as soon as the current one finishes.<br>
+ * Requires bIntraRefresh to be set.<br></blockquote><div><br></div><div>It should be noted that if bIntraRefresh is not set, then this API call will do nothing. <br><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
+ *<br>
+ * Useful for interactive streaming where the client can tell the server that packet loss has<br>
+ * occurred. In this case, keyint can be set to an extremely high value so that intra refreshes<br>
+ * occur only when calling x265_encoder_intra_refresh.<br>
+ *<br>
+ * In multi-pass encoding, if x265_encoder_intra_refresh is called differently in each pass,<br>
+ * behavior is undefined.<br>
+ *<br>
+ * Should not be called during an x265_encoder_encode. */<br>
+<br>
+int x265_encoder_intra_refresh(x265_encoder *);<br>
+<br>
/* x265_cleanup:<br>
* release library static allocations, reset configured CTU size */<br>
void x265_cleanup(void);<br>
@@ -1426,6 +1444,7 @@<br>
void (*cleanup)(void);<br>
<br>
int sizeof_frame_stats; /* sizeof(x265_frame_stats) */<br>
+ int (*encoder_intra_refresh)(x265_encoder*);<br>
/* add new pointers to the end, or increment X265_MAJOR_VERSION */<br>
} x265_api;<br>
<br>
_______________________________________________<br>
x265-devel mailing list<br>
<a href="mailto:x265-devel@videolan.org" target="_blank">x265-devel@videolan.org</a><br>
<a href="https://mailman.videolan.org/listinfo/x265-devel" rel="noreferrer" target="_blank">https://mailman.videolan.org/listinfo/x265-devel</a><br>
</blockquote></div><br></div></div></div></div>