[x265] [PATCH] Implementation for Intra-refresh
santhoshini at multicorewareinc.com
santhoshini at multicorewareinc.com
Wed Sep 9 12:15:18 CEST 2015
# HG changeset patch
# User Santhoshini Sekar<santhoshini at multicorewareinc.com>
# Date 1441783208 -19800
# Wed Sep 09 12:50:08 2015 +0530
# Node ID b83e67c7cc436142f0e4c370681af1ff9aa38402
# Parent f6892dcc7f4e74af58d7f39e085aed44afba919c
Implementation for Intra-refresh
diff -r f6892dcc7f4e -r b83e67c7cc43 source/encoder/analysis.cpp
--- a/source/encoder/analysis.cpp Wed Sep 09 14:52:35 2015 +0530
+++ b/source/encoder/analysis.cpp Wed Sep 09 12:50:08 2015 +0530
@@ -170,6 +170,9 @@
}
else
{
+ 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 (!m_param->rdLevel)
{
/* In RD Level 0/1, copy source pixels into the reconstructed block so
@@ -828,7 +831,7 @@
bool splitIntra = true;
uint32_t splitRefs[4] = { 0, 0, 0, 0 };
/* Step 1. Evaluate Merge/Skip candidates for likely early-outs */
- if (mightNotSplit && depth >= minDepth)
+ if (mightNotSplit && depth >= minDepth && !bForceIntra)
{
/* Compute Merge Cost */
md.pred[PRED_MERGE].cu.initSubCU(parentCTU, cuGeom, qp);
@@ -909,7 +912,7 @@
if (m_slice->m_pps->bUseDQP && depth <= m_slice->m_pps->maxCuDQPDepth && m_slice->m_pps->maxCuDQPDepth != 0)
setLambdaFromQP(parentCTU, qp);
- if (!earlyskip)
+ if (!earlyskip && !bForceIntra)
{
uint32_t refMasks[2];
refMasks[0] = allSplitRefs;
@@ -1119,7 +1122,21 @@
}
}
}
- } // !earlyskip
+ }
+ if (!earlyskip && bForceIntra)
+ {
+ ProfileCounter(parentCTU, totalIntraCU[cuGeom.depth]);
+ md.pred[PRED_INTRA].cu.initSubCU(parentCTU, cuGeom, qp);
+ checkIntra(md.pred[PRED_INTRA], cuGeom, SIZE_2Nx2N, NULL, NULL);
+ checkBestMode(md.pred[PRED_INTRA], depth);
+
+ if (cuGeom.log2CUSize == 3 && m_slice->m_sps->quadtreeTULog2MinSize < 3)
+ {
+ md.pred[PRED_INTRA_NxN].cu.initSubCU(parentCTU, cuGeom, qp);
+ checkIntra(md.pred[PRED_INTRA_NxN], cuGeom, SIZE_NxN, NULL, NULL);
+ checkBestMode(md.pred[PRED_INTRA_NxN], depth);
+ }
+ }// !earlyskip
if (m_bTryLossless)
tryLossless(cuGeom);
@@ -1189,23 +1206,36 @@
{
uint8_t* reuseDepth = &m_reuseInterDataCTU->depth[parentCTU.m_cuAddr * parentCTU.m_numPartitions];
uint8_t* reuseModes = &m_reuseInterDataCTU->modes[parentCTU.m_cuAddr * parentCTU.m_numPartitions];
- if (mightNotSplit && depth == reuseDepth[zOrder] && zOrder == cuGeom.absPartIdx && reuseModes[zOrder] == MODE_SKIP)
+ if (!bForceIntra)
{
- md.pred[PRED_SKIP].cu.initSubCU(parentCTU, cuGeom, qp);
- md.pred[PRED_MERGE].cu.initSubCU(parentCTU, cuGeom, qp);
- checkMerge2Nx2N_rd5_6(md.pred[PRED_SKIP], md.pred[PRED_MERGE], cuGeom, true);
+ if (mightNotSplit && depth == reuseDepth[zOrder] && zOrder == cuGeom.absPartIdx && reuseModes[zOrder] == MODE_SKIP)
+ {
+ md.pred[PRED_SKIP].cu.initSubCU(parentCTU, cuGeom, qp);
+ md.pred[PRED_MERGE].cu.initSubCU(parentCTU, cuGeom, qp);
+ checkMerge2Nx2N_rd5_6(md.pred[PRED_SKIP], md.pred[PRED_MERGE], cuGeom, true);
- if (m_bTryLossless)
- tryLossless(cuGeom);
+ if (m_bTryLossless)
+ tryLossless(cuGeom);
- if (mightSplit)
- addSplitFlagCost(*md.bestMode, cuGeom.depth);
+ if (mightSplit)
+ addSplitFlagCost(*md.bestMode, cuGeom.depth);
- // increment zOrder offset to point to next best depth in sharedDepth buffer
- zOrder += g_depthInc[g_maxCUDepth - 1][reuseDepth[zOrder]];
+ // increment zOrder offset to point to next best depth in sharedDepth buffer
+ zOrder += g_depthInc[g_maxCUDepth - 1][reuseDepth[zOrder]];
- mightSplit = false;
- mightNotSplit = false;
+ mightSplit = false;
+ mightNotSplit = false;
+ }
+ }
+ else
+ {
+ char* reusePartSizes = &m_reuseIntraDataCTU->partSizes[parentCTU.m_cuAddr * parentCTU.m_numPartitions];
+ uint8_t* reuseChromaModes = &m_reuseIntraDataCTU->chromaModes[parentCTU.m_cuAddr * parentCTU.m_numPartitions];
+ PartSize size = (PartSize)reusePartSizes[zOrder];
+ Mode& mode = size == SIZE_2Nx2N ? md.pred[PRED_INTRA] : md.pred[PRED_INTRA_NxN];
+ int ipOffset = (int)(6.0 * X265_LOG2(m_param->rc.ipFactor) + 0.5);
+ mode.cu.initSubCU(parentCTU, cuGeom, X265_MAX(mode.cu.m_qp[0] - ipOffset, QP_MIN));
+ checkIntra(mode, cuGeom, size, &reuseModes[zOrder], &reuseChromaModes[zOrder]);
}
}
@@ -1213,7 +1243,7 @@
bool splitIntra = true;
uint32_t splitRefs[4] = { 0, 0, 0, 0 };
/* Step 1. Evaluate Merge/Skip candidates for likely early-outs */
- if (mightNotSplit)
+ if (mightNotSplit && !bForceIntra)
{
md.pred[PRED_SKIP].cu.initSubCU(parentCTU, cuGeom, qp);
md.pred[PRED_MERGE].cu.initSubCU(parentCTU, cuGeom, qp);
@@ -1282,7 +1312,7 @@
if (m_slice->m_pps->bUseDQP && depth <= m_slice->m_pps->maxCuDQPDepth && m_slice->m_pps->maxCuDQPDepth != 0)
setLambdaFromQP(parentCTU, qp);
- if (!(foundSkip && m_param->bEnableEarlySkip))
+ if (!(foundSkip && m_param->bEnableEarlySkip) && !bForceIntra)
{
uint32_t refMasks[2];
refMasks[0] = allSplitRefs;
@@ -1389,6 +1419,20 @@
}
}
}
+ if (!(foundSkip && m_param->bEnableEarlySkip) && bForceIntra)
+ {
+ ProfileCounter(parentCTU, totalIntraCU[cuGeom.depth]);
+ md.pred[PRED_INTRA].cu.initSubCU(parentCTU, cuGeom, qp);
+ checkIntra(md.pred[PRED_INTRA], cuGeom, SIZE_2Nx2N, NULL, NULL);
+ checkBestMode(md.pred[PRED_INTRA], depth);
+
+ if (cuGeom.log2CUSize == 3 && m_slice->m_sps->quadtreeTULog2MinSize < 3)
+ {
+ md.pred[PRED_INTRA_NxN].cu.initSubCU(parentCTU, cuGeom, qp);
+ checkIntra(md.pred[PRED_INTRA_NxN], cuGeom, SIZE_NxN, NULL, NULL);
+ checkBestMode(md.pred[PRED_INTRA_NxN], depth);
+ }
+ }
if (m_bTryLossless)
tryLossless(cuGeom);
@@ -1464,7 +1508,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 * 16 - 3) * 4;
+ int maxMv = maxX - 4 * 16 * 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 +1642,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 * 16 - 3) * 4;
+ int maxMv = maxX - 4 * 16 * 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 b83e67c7cc43 source/encoder/analysis.h
--- a/source/encoder/analysis.h Wed Sep 09 14:52:35 2015 +0530
+++ b/source/encoder/analysis.h Wed Sep 09 12:50:08 2015 +0530
@@ -110,6 +110,8 @@
uint32_t m_splitRefIdx[4];
+ int bForceIntra; // For Periodic Intra Refresh.Supported only in P-frames
+
/* full analysis for an I-slice CU */
void compressIntraCU(const CUData& parentCTU, const CUGeom& cuGeom, uint32_t &zOrder, int32_t qp);
diff -r f6892dcc7f4e -r b83e67c7cc43 source/encoder/api.cpp
--- a/source/encoder/api.cpp Wed Sep 09 14:52:35 2015 +0530
+++ b/source/encoder/api.cpp Wed Sep 09 12:50:08 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 b83e67c7cc43 source/encoder/encoder.cpp
--- a/source/encoder/encoder.cpp Wed Sep 09 14:52:35 2015 +0530
+++ b/source/encoder/encoder.cpp Wed Sep 09 12:50:08 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;
+ 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);
+ 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 b83e67c7cc43 source/encoder/search.cpp
--- a/source/encoder/search.cpp Wed Sep 09 14:52:35 2015 +0530
+++ b/source/encoder/search.cpp Wed Sep 09 12:50:08 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 * 16 - 3) << mvshift;
+ int maxMv = maxX - 4* 16 * 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);
+ }
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 b83e67c7cc43 source/encoder/search.h
--- a/source/encoder/search.h Wed Sep 09 14:52:35 2015 +0530
+++ b/source/encoder/search.h Wed Sep 09 12:50:08 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 b83e67c7cc43 source/encoder/slicetype.cpp
--- a/source/encoder/slicetype.cpp Wed Sep 09 14:52:35 2015 +0530
+++ b/source/encoder/slicetype.cpp Wed Sep 09 12:50:08 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 b83e67c7cc43 source/x265.h
--- a/source/x265.h Wed Sep 09 14:52:35 2015 +0530
+++ b/source/x265.h Wed Sep 09 12:50:08 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 */
More information about the x265-devel
mailing list