Skip to content

Commit dd49137

Browse files
author
Erik Språng
committed
Fix dynamic slice adjustment for frames with extreme aspect ratios
When rate control is disabled (RC_OFF_MODE), dynamic slice adjustment in DynamicAdjustSlicing() defaults the minimum macroblock requirement per slice (iMinimalMbNum) to the frame's macroblock width (1 row per slice). For frames with extreme aspect ratios (such as very wide resolutions), requiring 1 macroblock row per slice across multiple slices can exceed the total number of macroblocks in the frame. This change ensures robust macroblock distribution and load balancing by: - Falling back iMinimalMbNum to 1 macroblock per slice when row-based heuristics exceed frame capacity in RC_OFF_MODE. - Skipping adjustment and logging a warning when the requested number of slices equals or exceeds the total macroblocks in the frame. - Adding a capacity guard in GomValidCheckSliceMbNum() for rate-controlled encoding modes. - Adding a unit test (DynamicAdjustSlicingExtremeAspectRatio) to verify dynamic slice adjustment and load balancing on wide aspect ratio frames.
1 parent a8e04ad commit dd49137

3 files changed

Lines changed: 99 additions & 0 deletions

File tree

codec/encoder/core/src/slice_multi_threading.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,24 @@ void DynamicAdjustSlicing (sWelsEncCtx* pCtx,
197197
return;
198198
}
199199
iMinimalMbNum = iNumMbInEachGom;
200+
} else {
201+
// If the number of requested slices equals or exceeds the total macroblocks
202+
// in the frame, each slice cannot be assigned even 1 macroblock. In this
203+
// case, do not adjust slice sizes.
204+
if (kiCountSliceNum >= kiCountNumMb) {
205+
WelsLog(&(pCtx->sLogCtx), WELS_LOG_WARNING,
206+
"[MT] DynamicAdjustSlicing(), requested slice number (%d) equals "
207+
"or exceeds total macroblocks (%d), do not adjust",
208+
kiCountSliceNum, kiCountNumMb);
209+
return;
210+
} else if (iMinimalMbNum * kiCountSliceNum >= kiCountNumMb) {
211+
// When rate control is off, iMinimalMbNum defaults to 1 macroblock row
212+
// (iMbWidth). For extreme aspect ratios (such as very wide resolutions),
213+
// requiring 1 row per slice may exceed total frame capacity. Fall back to
214+
// 1 macroblock per slice to ensure valid upper bounds and allow proper
215+
// load balancing across slices.
216+
iMinimalMbNum = 1;
217+
}
200218
}
201219

202220
if (kiCountSliceNum < 2 || (kiCountSliceNum & 0x01)) // we need suppose uiSliceNum is even for multiple threading

codec/encoder/core/src/svc_enc_slice_segment.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,12 @@ bool GomValidCheckSliceMbNum (const int32_t kiMbWidth, const int32_t kiMbHeight,
281281
int32_t iCurNumMbAssigning = 0;
282282

283283
iMinimalMbNum = iGomSize;
284+
// Ensure that the minimum macroblock requirement across all slices does not
285+
// exceed total frame capacity, preventing negative calculations for remaining
286+
// macroblocks.
287+
if (iMinimalMbNum * kuiSliceNum > kiMbNumInFrame) {
288+
return false;
289+
}
284290
while (uiSliceIdx + 1 < kuiSliceNum) {
285291
iMaximalMbNum = iNumMbLeft - (kuiSliceNum - uiSliceIdx - 1) * iMinimalMbNum; // get maximal num_mb in left parts
286292
// make sure one GOM at least in each slice for safe

test/api/encoder_test.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,78 @@ TEST_F(EncoderInitTest, VeryLargeSlices) {
259259
ASSERT_EQ(0, rv);
260260
}
261261
}
262+
263+
// This test verifies dynamic slice adjustment when encoding frames with extreme
264+
// aspect ratios (very wide resolution) in RC_OFF_MODE using multiple
265+
// slices/threads. It ensures that macroblocks are correctly distributed across
266+
// slices without producing negative slice limits or invalid memory access when
267+
// slice encoding speeds vary.
268+
TEST_F(EncoderInitTest, DynamicAdjustSlicingExtremeAspectRatio) {
269+
SEncParamExt param;
270+
encoder_->GetDefaultParams(&param);
271+
272+
param.iUsageType = CAMERA_VIDEO_REAL_TIME;
273+
// Extreme wide aspect ratio: frame height is only 2 macroblock rows (32
274+
// pixels), which tests slicing behavior when row-based heuristics exceed
275+
// frame dimensions.
276+
param.iPicWidth = 32752;
277+
param.iPicHeight = 32;
278+
param.fMaxFrameRate = 30.0f;
279+
param.iSpatialLayerNum = 1;
280+
param.iMultipleThreadIdc = 4;
281+
param.iRCMode = RC_OFF_MODE;
282+
// Enable load balancing and high complexity mode to trigger dynamic slice
283+
// adjustments.
284+
param.bUseLoadBalancing = true;
285+
param.iComplexityMode = HIGH_COMPLEXITY;
286+
param.iMinQp = 0;
287+
param.iMaxQp = 51;
288+
289+
param.sSpatialLayers[0].iVideoWidth = param.iPicWidth;
290+
param.sSpatialLayers[0].iVideoHeight = param.iPicHeight;
291+
param.sSpatialLayers[0].fFrameRate = param.fMaxFrameRate;
292+
param.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_FIXEDSLCNUM_SLICE;
293+
param.sSpatialLayers[0].sSliceArgument.uiSliceNum = 4;
294+
param.sSpatialLayers[0].iDLayerQp = 24;
295+
296+
int rv = encoder_->InitializeExt(&param);
297+
ASSERT_EQ(0, rv);
298+
299+
int frameSize = param.iPicWidth * param.iPicHeight * 3 / 2;
300+
BufferedData buf;
301+
buf.SetLength(frameSize);
302+
ASSERT_EQ(buf.Length(), (size_t)frameSize);
303+
304+
SFrameBSInfo info;
305+
memset(&info, 0, sizeof(SFrameBSInfo));
306+
307+
SSourcePicture pic;
308+
memset(&pic, 0, sizeof(SSourcePicture));
309+
pic.iPicWidth = param.iPicWidth;
310+
pic.iPicHeight = param.iPicHeight;
311+
pic.iColorFormat = videoFormatI420;
312+
pic.iStride[0] = pic.iPicWidth;
313+
pic.iStride[1] = pic.iStride[2] = pic.iPicWidth >> 1;
314+
pic.pData[0] = buf.data();
315+
pic.pData[1] = pic.pData[0] + param.iPicWidth * param.iPicHeight;
316+
pic.pData[2] = pic.pData[1] + (param.iPicWidth * param.iPicHeight >> 2);
317+
318+
for (int i = 0; i < 10; i++) {
319+
// Generate varying random noise in Slices 1..3 while keeping Slice 0
320+
// completely flat (zeros). This creates a significant encoding speed
321+
// differential, causing the encoder to assign a larger proportion of
322+
// macroblocks to Slice 0 during dynamic load balancing.
323+
for (int idx = 0; idx < frameSize; idx++) {
324+
buf.data()[idx] = rand() % 256;
325+
}
326+
for (int y = 0; y < 16; y++) {
327+
memset(pic.pData[0] + y * pic.iStride[0], 0, 16368);
328+
}
329+
for (int y = 0; y < 8; y++) {
330+
memset(pic.pData[1] + y * pic.iStride[1], 0, 8184);
331+
memset(pic.pData[2] + y * pic.iStride[2], 0, 8184);
332+
}
333+
rv = encoder_->EncodeFrame(&pic, &info);
334+
ASSERT_EQ(0, rv);
335+
}
336+
}

0 commit comments

Comments
 (0)