diff --git a/pkg/sfu/forwarder.go b/pkg/sfu/forwarder.go index b3c2da4d8..e0e53ea50 100644 --- a/pkg/sfu/forwarder.go +++ b/pkg/sfu/forwarder.go @@ -482,6 +482,40 @@ func (f *Forwarder) AllocateOptimal(brs Bitrates) VideoAllocation { break } } + + if bandwidthRequested == 0 { + // if we cannot allocate anything below max layer, + // look for a layer above. It is okay to overshoot + // in optimal allocation (i. e. no bandwidth restricstions). + // It is possible that clients send only a higher layer. + // To accommodate cases like that, try finding a layer + // above the requested maximum to ensure streaming + for s := DefaultMaxLayerSpatial; s >= 0; s-- { + for t := DefaultMaxLayerTemporal; t >= 0; t-- { + if brs[s][t] == 0 { + continue + } + + targetLayers = VideoLayers{ + Spatial: s, + Temporal: t, + } + + bandwidthRequested = brs[s][t] + state = VideoAllocationStateOptimal + + if f.targetLayers == InvalidLayers { + change = VideoStreamingChangeResuming + } + f.logger.Infow("allowing overshoot", "maxLayer", f.maxLayers, "targetLayers", targetLayers) + break + } + + if bandwidthRequested != 0 { + break + } + } + } } if !targetLayers.IsValid() { diff --git a/pkg/sfu/forwarder_test.go b/pkg/sfu/forwarder_test.go index eea98e7e2..67976001c 100644 --- a/pkg/sfu/forwarder_test.go +++ b/pkg/sfu/forwarder_test.go @@ -233,6 +233,35 @@ func TestForwarderAllocate(t *testing.T) { require.Equal(t, InvalidLayers, f.CurrentLayers()) // allocate using bitrates, allocation should choose optimal + f.UpTrackLayersChange([]int32{0, 1, 2}) + f.maxLayers = VideoLayers{Spatial: 1, Temporal: 3} + expectedTargetLayers = VideoLayers{ + Spatial: 1, + Temporal: 3, + } + expectedResult = VideoAllocation{ + state: VideoAllocationStateOptimal, + change: VideoStreamingChangeNone, + bandwidthRequested: bitrates[1][3], + bandwidthDelta: bitrates[1][3], + availableLayers: []int32{0, 1, 2}, + bitrates: bitrates, + targetLayers: expectedTargetLayers, + distanceToDesired: 0, + } + result = f.AllocateOptimal(bitrates) + require.Equal(t, expectedResult, result) + require.Equal(t, expectedResult, f.lastAllocation) + require.Equal(t, InvalidLayers, f.CurrentLayers()) + require.Equal(t, expectedTargetLayers, f.TargetLayers()) + + // allocate using bitrates above maximum layer + f.UpTrackLayersChange([]int32{2}) + sparseBitrates := Bitrates{ + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 7, 0, 0}, + } expectedTargetLayers = VideoLayers{ Spatial: 2, Temporal: 1, @@ -240,14 +269,14 @@ func TestForwarderAllocate(t *testing.T) { expectedResult = VideoAllocation{ state: VideoAllocationStateOptimal, change: VideoStreamingChangeNone, - bandwidthRequested: bitrates[2][1], - bandwidthDelta: bitrates[2][1], - availableLayers: []int32{0}, - bitrates: bitrates, + bandwidthRequested: sparseBitrates[2][1], + bandwidthDelta: sparseBitrates[2][1] - bitrates[1][3], + availableLayers: []int32{2}, + bitrates: sparseBitrates, targetLayers: expectedTargetLayers, distanceToDesired: 0, } - result = f.AllocateOptimal(bitrates) + result = f.AllocateOptimal(sparseBitrates) require.Equal(t, expectedResult, result) require.Equal(t, expectedResult, f.lastAllocation) require.Equal(t, InvalidLayers, f.CurrentLayers())