diff --git a/codec/encoder/core/src/slice_multi_threading.cpp b/codec/encoder/core/src/slice_multi_threading.cpp index 09ea2c08c..7fd99929d 100644 --- a/codec/encoder/core/src/slice_multi_threading.cpp +++ b/codec/encoder/core/src/slice_multi_threading.cpp @@ -197,6 +197,24 @@ void DynamicAdjustSlicing (sWelsEncCtx* pCtx, return; } iMinimalMbNum = iNumMbInEachGom; + } else { + // If the number of requested slices equals or exceeds the total macroblocks + // in the frame, each slice cannot be assigned even 1 macroblock. In this + // case, do not adjust slice sizes. + if (kiCountSliceNum >= kiCountNumMb) { + WelsLog(&(pCtx->sLogCtx), WELS_LOG_WARNING, + "[MT] DynamicAdjustSlicing(), requested slice number (%d) equals " + "or exceeds total macroblocks (%d), do not adjust", + kiCountSliceNum, kiCountNumMb); + return; + } else if (iMinimalMbNum * kiCountSliceNum >= kiCountNumMb) { + // When rate control is off, iMinimalMbNum defaults to 1 macroblock row + // (iMbWidth). For extreme aspect ratios (such as very wide resolutions), + // requiring 1 row per slice may exceed total frame capacity. Fall back to + // 1 macroblock per slice to ensure valid upper bounds and allow proper + // load balancing across slices. + iMinimalMbNum = 1; + } } if (kiCountSliceNum < 2 || (kiCountSliceNum & 0x01)) // we need suppose uiSliceNum is even for multiple threading diff --git a/codec/encoder/core/src/svc_enc_slice_segment.cpp b/codec/encoder/core/src/svc_enc_slice_segment.cpp index 5a9bf739e..a920de7f3 100644 --- a/codec/encoder/core/src/svc_enc_slice_segment.cpp +++ b/codec/encoder/core/src/svc_enc_slice_segment.cpp @@ -281,6 +281,12 @@ bool GomValidCheckSliceMbNum (const int32_t kiMbWidth, const int32_t kiMbHeight, int32_t iCurNumMbAssigning = 0; iMinimalMbNum = iGomSize; + // Ensure that the minimum macroblock requirement across all slices does not + // exceed total frame capacity, preventing negative calculations for remaining + // macroblocks. + if (iMinimalMbNum * static_cast(kuiSliceNum) > kiMbNumInFrame) { + return false; + } while (uiSliceIdx + 1 < kuiSliceNum) { iMaximalMbNum = iNumMbLeft - (kuiSliceNum - uiSliceIdx - 1) * iMinimalMbNum; // get maximal num_mb in left parts // make sure one GOM at least in each slice for safe diff --git a/codec/encoder/core/src/svc_encode_slice.cpp b/codec/encoder/core/src/svc_encode_slice.cpp index 196f7d09b..db550a157 100644 --- a/codec/encoder/core/src/svc_encode_slice.cpp +++ b/codec/encoder/core/src/svc_encode_slice.cpp @@ -1026,6 +1026,7 @@ int32_t InitOneSliceInThread (sWelsEncCtx* pCtx, pSlice->sSliceBs.uiBsPos = 0; pSlice->sSliceBs.iNalIndex = 0; pSlice->sSliceBs.pBsBuffer = pCtx->pSliceThreading->pThreadBsBuffer[kiSlcBuffIdx]; + pSlice->sSliceBs.uiSize = pCtx->iFrameBsSize; return ENC_RETURN_SUCCESS; } diff --git a/test/api/encoder_test.cpp b/test/api/encoder_test.cpp index ebe4a0390..f1a81a787 100644 --- a/test/api/encoder_test.cpp +++ b/test/api/encoder_test.cpp @@ -259,3 +259,78 @@ TEST_F(EncoderInitTest, VeryLargeSlices) { ASSERT_EQ(0, rv); } } + +// This test verifies dynamic slice adjustment when encoding frames with extreme +// aspect ratios (very wide resolution) in RC_OFF_MODE using multiple +// slices/threads. It ensures that macroblocks are correctly distributed across +// slices without producing negative slice limits or invalid memory access when +// slice encoding speeds vary. +TEST_F(EncoderInitTest, DynamicAdjustSlicingExtremeAspectRatio) { + SEncParamExt param; + encoder_->GetDefaultParams(¶m); + + param.iUsageType = CAMERA_VIDEO_REAL_TIME; + // Extreme wide aspect ratio: frame height is only 2 macroblock rows (32 + // pixels), which tests slicing behavior when row-based heuristics exceed + // frame dimensions. + param.iPicWidth = 32752; + param.iPicHeight = 32; + param.fMaxFrameRate = 30.0f; + param.iSpatialLayerNum = 1; + param.iMultipleThreadIdc = 4; + param.iRCMode = RC_OFF_MODE; + // Enable load balancing and high complexity mode to trigger dynamic slice + // adjustments. + param.bUseLoadBalancing = true; + param.iComplexityMode = HIGH_COMPLEXITY; + param.iMinQp = 0; + param.iMaxQp = 51; + + param.sSpatialLayers[0].iVideoWidth = param.iPicWidth; + param.sSpatialLayers[0].iVideoHeight = param.iPicHeight; + param.sSpatialLayers[0].fFrameRate = param.fMaxFrameRate; + param.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_FIXEDSLCNUM_SLICE; + param.sSpatialLayers[0].sSliceArgument.uiSliceNum = 4; + param.sSpatialLayers[0].iDLayerQp = 24; + + int rv = encoder_->InitializeExt(¶m); + ASSERT_EQ(0, rv); + + int frameSize = param.iPicWidth * param.iPicHeight * 3 / 2; + BufferedData buf; + buf.SetLength(frameSize); + ASSERT_EQ(buf.Length(), (size_t)frameSize); + + SFrameBSInfo info; + memset(&info, 0, sizeof(SFrameBSInfo)); + + SSourcePicture pic; + memset(&pic, 0, sizeof(SSourcePicture)); + pic.iPicWidth = param.iPicWidth; + pic.iPicHeight = param.iPicHeight; + pic.iColorFormat = videoFormatI420; + pic.iStride[0] = pic.iPicWidth; + pic.iStride[1] = pic.iStride[2] = pic.iPicWidth >> 1; + pic.pData[0] = buf.data(); + pic.pData[1] = pic.pData[0] + param.iPicWidth * param.iPicHeight; + pic.pData[2] = pic.pData[1] + (param.iPicWidth * param.iPicHeight >> 2); + + for (int i = 0; i < 10; i++) { + // Generate varying random noise in Slices 1..3 while keeping Slice 0 + // completely flat (zeros). This creates a significant encoding speed + // differential, causing the encoder to assign a larger proportion of + // macroblocks to Slice 0 during dynamic load balancing. + for (int idx = 0; idx < frameSize; idx++) { + buf.data()[idx] = rand() % 256; + } + for (int y = 0; y < 16; y++) { + memset(pic.pData[0] + y * pic.iStride[0], 0, 16368); + } + for (int y = 0; y < 8; y++) { + memset(pic.pData[1] + y * pic.iStride[1], 0, 8184); + memset(pic.pData[2] + y * pic.iStride[2], 0, 8184); + } + rv = encoder_->EncodeFrame(&pic, &info); + ASSERT_EQ(0, rv); + } +}