@@ -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 (¶m);
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 (¶m);
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