Skip to content

Commit 5d057e6

Browse files
author
Erik Språng
committed
Clamp screen content scroll motion vectors to configured range
Ensure that scroll motion vectors detected during screen content preprocessing and applied during mode decision are clamped to the encoder's motion vector range limit (iMvRange). This ensures that motion vector difference table indexing remains within allocated bounds during motion estimation.
1 parent 3bc90ba commit 5d057e6

3 files changed

Lines changed: 110 additions & 2 deletions

File tree

codec/encoder/core/src/svc_mode_decision.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,14 @@ bool MdInterSCDPskipProcess (sWelsEncCtx* pEncCtx, SWelsMD* pWelsMd, SSlice* pSl
482482
PredSkipMv (pMbCache, &sVaaPredSkipMv);
483483

484484
if (eSkipMode == SCROLLED) {
485-
sCurMbMv[1].iMvX = static_cast<int16_t> (pVaaExt->sScrollDetectInfo.iScrollMvX << 2);
486-
sCurMbMv[1].iMvY = static_cast<int16_t> (pVaaExt->sScrollDetectInfo.iScrollMvY << 2);
485+
sCurMbMv[1].iMvX =
486+
static_cast<int16_t>(WELS_CLIP3(pVaaExt->sScrollDetectInfo.iScrollMvX,
487+
-pEncCtx->iMvRange, pEncCtx->iMvRange)
488+
<< 2);
489+
sCurMbMv[1].iMvY =
490+
static_cast<int16_t>(WELS_CLIP3(pVaaExt->sScrollDetectInfo.iScrollMvY,
491+
-pEncCtx->iMvRange, pEncCtx->iMvRange)
492+
<< 2);
487493
}
488494

489495
bool bMbSkipFlag = (LD32 (&sVaaPredSkipMv) == LD32 (&sCurMbMv[eSkipMode])) ;

codec/encoder/core/src/wels_preprocess.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,16 @@ ESceneChangeIdc CWelsPreProcessScreen::DetectSceneChange (SPicture* pCurPicture,
11771177

11781178
if (ret == 0) {
11791179
m_pInterfaceVp->Get (iMethodIdx, (void*) (pScrollDetectInfo));
1180+
// Ensure detected scroll motion vectors stay within the configured
1181+
// motion vector range.
1182+
if (pScrollDetectInfo->bScrollDetectFlag) {
1183+
pScrollDetectInfo->iScrollMvX =
1184+
WELS_CLIP3(pScrollDetectInfo->iScrollMvX, -m_pEncCtx->iMvRange,
1185+
m_pEncCtx->iMvRange);
1186+
pScrollDetectInfo->iScrollMvY =
1187+
WELS_CLIP3(pScrollDetectInfo->iScrollMvY, -m_pEncCtx->iMvRange,
1188+
m_pEncCtx->iMvRange);
1189+
}
11801190
}
11811191
sSceneChangeResult.sScrollResult = pVaaExt->sScrollDetectInfo;
11821192
}

test/api/encoder_test.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,95 @@ TEST_F(EncoderInitTest, VeryLargeSlices) {
259259
ASSERT_EQ(0, rv);
260260
}
261261
}
262+
263+
// This test verifies that the encoder correctly handles screen content
264+
// sequences with large vertical scrolling motion vectors. It ensures that
265+
// motion vector difference table indexing remains within allocated bounds when
266+
// scroll detection predicts large vertical displacements at high quantization
267+
// parameters.
268+
TEST_F(EncoderInitTest, ScreenContentScrollMotionVectorBounds) {
269+
SEncParamExt param;
270+
encoder_->GetDefaultParams(&param);
271+
272+
param.iUsageType = SCREEN_CONTENT_REAL_TIME;
273+
param.iPicWidth = 640;
274+
param.iPicHeight = 1800;
275+
param.fMaxFrameRate = 30.0f;
276+
param.iSpatialLayerNum = 1;
277+
param.iRCMode = RC_OFF_MODE;
278+
279+
param.sSpatialLayers[0].iVideoWidth = param.iPicWidth;
280+
param.sSpatialLayers[0].iVideoHeight = param.iPicHeight;
281+
param.sSpatialLayers[0].fFrameRate = param.fMaxFrameRate;
282+
param.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_SINGLE_SLICE;
283+
param.sSpatialLayers[0].iDLayerQp = 51;
284+
param.iMinQp = 51;
285+
param.iMaxQp = 51;
286+
287+
int rv = encoder_->InitializeExt(&param);
288+
ASSERT_EQ(0, rv);
289+
290+
SFrameBSInfo info;
291+
memset(&info, 0, sizeof(SFrameBSInfo));
292+
293+
int width = param.iPicWidth;
294+
int height = param.iPicHeight;
295+
int frameSize = width * height * 3 / 2;
296+
std::vector<uint8_t> frame0(frameSize, 128);
297+
std::vector<uint8_t> frame1(frameSize, 128);
298+
299+
// Fill frame0 luma with pseudo-random patterns to ensure CheckLine qualifies
300+
for (int y = 0; y < height; ++y) {
301+
for (int x = 0; x < width; ++x) {
302+
frame0[y * width + x] =
303+
static_cast<uint8_t>((x * 29 + y * 43 + 17) % 251);
304+
}
305+
}
306+
307+
// Frame 1: shift vertically by -512 pixels (content moving downward by 512)
308+
int scroll_mv = -512;
309+
for (int y = 0; y < height; ++y) {
310+
if (y + scroll_mv >= 0 && y + scroll_mv < height) {
311+
memcpy(&frame1[y * width], &frame0[(y + scroll_mv) * width], width);
312+
} else {
313+
for (int x = 0; x < width; ++x) {
314+
frame1[y * width + x] =
315+
static_cast<uint8_t>((x * 53 + y * 71 + 101) & 0xFF);
316+
}
317+
}
318+
}
319+
320+
// Modify macroblock (1, 32) at pixel rows 512..527 and cols 16..31 in frame1.
321+
// MB(0, 32) will be skipped by scroll detection with MV -2048.
322+
// MB(1, 32) cannot be skipped due to this modification, forcing it into
323+
// motion estimation where it uses MB(0, 32)'s scrolled MV as a predictor
324+
// during vertical full search.
325+
for (int y = 512; y < 528; ++y) {
326+
for (int x = 16; x < 32; ++x) {
327+
frame1[y * width + x] ^= 0xFF;
328+
}
329+
}
330+
331+
SSourcePicture pic;
332+
memset(&pic, 0, sizeof(SSourcePicture));
333+
pic.iPicWidth = width;
334+
pic.iPicHeight = height;
335+
pic.iColorFormat = videoFormatI420;
336+
pic.iStride[0] = width;
337+
pic.iStride[1] = pic.iStride[2] = width >> 1;
338+
pic.pData[0] = frame0.data();
339+
pic.pData[1] = frame0.data() + width * height;
340+
pic.pData[2] = pic.pData[1] + (width * height >> 2);
341+
342+
// Encode Frame 0 (Base pattern)
343+
rv = encoder_->EncodeFrame(&pic, &info);
344+
ASSERT_EQ(0, rv);
345+
pic.uiTimeStamp += 33;
346+
347+
// Encode Frame 1 (Scrolled pattern with modified MB)
348+
pic.pData[0] = frame1.data();
349+
pic.pData[1] = frame1.data() + width * height;
350+
pic.pData[2] = pic.pData[1] + (width * height >> 2);
351+
rv = encoder_->EncodeFrame(&pic, &info);
352+
ASSERT_EQ(0, rv);
353+
}

0 commit comments

Comments
 (0)