From 6b55742564936b5ff916ec9cdf56ed6daecdfe7d Mon Sep 17 00:00:00 2001 From: Raja Subramanian Date: Sun, 19 Feb 2023 11:41:40 +0530 Subject: [PATCH] Use available layers in optimal allocation. (#1445) Addressing edge case where a layer stopped before bitrate could be measured. Purely bit rate based change deduction missed this as the before and after did not have bit rates. Use available layers to look for changes, especially currently forwarding layer going away. Also, simplifying bits. Only in the optimal allocation path, these things are required. When congested, bitrate is always needed. So, for optimal path, just look at available layer changes and adjust. Don't need to look for bitrate based layer changes. Clean up that code. --- pkg/rtc/wrappedreceiver.go | 4 +- pkg/sfu/downtrack.go | 18 ++- pkg/sfu/forwarder.go | 155 ++++++--------------- pkg/sfu/forwarder_test.go | 232 ++++++++++++++++++-------------- pkg/sfu/receiver.go | 22 +-- pkg/sfu/streamtrackermanager.go | 7 +- 6 files changed, 192 insertions(+), 246 deletions(-) diff --git a/pkg/rtc/wrappedreceiver.go b/pkg/rtc/wrappedreceiver.go index fb5718080..839275485 100644 --- a/pkg/rtc/wrappedreceiver.go +++ b/pkg/rtc/wrappedreceiver.go @@ -187,11 +187,11 @@ func (d *DummyReceiver) ReadRTP(buf []byte, layer uint8, sn uint16) (int, error) return 0, errors.New("no receiver") } -func (d *DummyReceiver) GetLayeredBitrate() sfu.Bitrates { +func (d *DummyReceiver) GetLayeredBitrate() ([]int32, sfu.Bitrates) { if r, ok := d.receiver.Load().(sfu.TrackReceiver); ok { return r.GetLayeredBitrate() } - return sfu.Bitrates{} + return nil, sfu.Bitrates{} } func (d *DummyReceiver) GetAudioLevel() (float64, bool) { diff --git a/pkg/sfu/downtrack.go b/pkg/sfu/downtrack.go index 455f6e1fd..24429f382 100644 --- a/pkg/sfu/downtrack.go +++ b/pkg/sfu/downtrack.go @@ -943,7 +943,8 @@ func (d *DownTrack) IsDeficient() bool { } func (d *DownTrack) BandwidthRequested() int64 { - return d.forwarder.BandwidthRequested(d.receiver.GetLayeredBitrate()) + _, brs := d.receiver.GetLayeredBitrate() + return d.forwarder.BandwidthRequested(brs) } func (d *DownTrack) DistanceToDesired() int32 { @@ -951,13 +952,15 @@ func (d *DownTrack) DistanceToDesired() int32 { } func (d *DownTrack) AllocateOptimal(allowOvershoot bool) VideoAllocation { - allocation := d.forwarder.AllocateOptimal(d.receiver.GetLayeredBitrate(), allowOvershoot) + al, brs := d.receiver.GetLayeredBitrate() + allocation := d.forwarder.AllocateOptimal(al, brs, allowOvershoot) d.maybeStartKeyFrameRequester() return allocation } func (d *DownTrack) ProvisionalAllocatePrepare() { - d.forwarder.ProvisionalAllocatePrepare(d.receiver.GetLayeredBitrate()) + _, brs := d.receiver.GetLayeredBitrate() + d.forwarder.ProvisionalAllocatePrepare(brs) } func (d *DownTrack) ProvisionalAllocate(availableChannelCapacity int64, layers VideoLayers, allowPause bool, allowOvershoot bool) int64 { @@ -983,19 +986,22 @@ func (d *DownTrack) ProvisionalAllocateCommit() VideoAllocation { } func (d *DownTrack) AllocateNextHigher(availableChannelCapacity int64, allowOvershoot bool) (VideoAllocation, bool) { - allocation, available := d.forwarder.AllocateNextHigher(availableChannelCapacity, d.receiver.GetLayeredBitrate(), allowOvershoot) + _, brs := d.receiver.GetLayeredBitrate() + allocation, available := d.forwarder.AllocateNextHigher(availableChannelCapacity, brs, allowOvershoot) d.maybeStartKeyFrameRequester() return allocation, available } func (d *DownTrack) GetNextHigherTransition(allowOvershoot bool) (VideoTransition, bool) { - transition, available := d.forwarder.GetNextHigherTransition(d.receiver.GetLayeredBitrate(), allowOvershoot) + _, brs := d.receiver.GetLayeredBitrate() + transition, available := d.forwarder.GetNextHigherTransition(brs, allowOvershoot) d.logger.Debugw("stream: get next higher layer", "transition", transition, "available", available) return transition, available } func (d *DownTrack) Pause() VideoAllocation { - allocation := d.forwarder.Pause(d.receiver.GetLayeredBitrate()) + _, brs := d.receiver.GetLayeredBitrate() + allocation := d.forwarder.Pause(brs) d.maybeStartKeyFrameRequester() return allocation } diff --git a/pkg/sfu/forwarder.go b/pkg/sfu/forwarder.go index 6ec5a33cf..6f196b38a 100644 --- a/pkg/sfu/forwarder.go +++ b/pkg/sfu/forwarder.go @@ -466,38 +466,6 @@ func (f *Forwarder) getOptimalBandwidthNeeded(brs Bitrates, maxLayers VideoLayer return 0 } -func (f *Forwarder) getLayerChanges(brs Bitrates) (addedLayers []int32, removedLayers []int32) { - lastAllocationLayers := f.lastAllocation.bitrates.GetLayers() - nowLayers := brs.GetLayers() - - for _, ln := range nowLayers { - found := false - for _, lla := range lastAllocationLayers { - if lla == ln { - found = true - break - } - } - if !found { - addedLayers = append(addedLayers, ln) - } - } - - for _, lla := range lastAllocationLayers { - found := false - for _, ln := range nowLayers { - if ln == lla { - found = true - break - } - } - if !found { - removedLayers = append(removedLayers, lla) - } - } - return -} - func (f *Forwarder) getDistanceToDesired(brs Bitrates, targetLayers VideoLayers, maxLayers VideoLayers) int32 { if f.muted || f.pubMuted { return 0 @@ -578,7 +546,7 @@ func (f *Forwarder) DistanceToDesired() int32 { return f.lastAllocation.distanceToDesired } -func (f *Forwarder) AllocateOptimal(brs Bitrates, allowOvershoot bool) VideoAllocation { +func (f *Forwarder) AllocateOptimal(availableLayers []int32, brs Bitrates, allowOvershoot bool) VideoAllocation { f.lock.Lock() defer f.lock.Unlock() @@ -597,6 +565,9 @@ func (f *Forwarder) AllocateOptimal(brs Bitrates, allowOvershoot bool) VideoAllo } switch { + case !f.maxLayers.IsValid(): + // nothing to do when max layers are not valid + case f.muted: alloc.pauseReason = VideoPauseReasonMuted @@ -609,8 +580,19 @@ func (f *Forwarder) AllocateOptimal(brs Bitrates, allowOvershoot bool) VideoAllo // if parked on a layer, let it continue alloc.targetLayers = f.parkedLayers - case !f.targetLayers.IsValid(): - if f.maxLayers.IsValid() { + case f.maxLayers != f.lastAllocation.maxLayers: + alloc.targetLayers = f.maxLayers + + case len(availableLayers) == 0: + // feed may be dry + if f.currentLayers.IsValid() { + // let it continue at current layer if valid. + // Covers the cases of + // 1. mis-detection of layer stop - can continue streaming + // 2. current layer resuming - can latch on when it starts + alloc.targetLayers = f.currentLayers + } else { + // opportunistically latch on to anything if allowOvershoot { alloc.targetLayers = VideoLayers{ Spatial: int32(math.Max(0, float64(f.numAdvertisedLayers-1))), @@ -621,97 +603,37 @@ func (f *Forwarder) AllocateOptimal(brs Bitrates, allowOvershoot bool) VideoAllo } } - case f.lastAllocation.maxLayers != f.maxLayers: - alloc.targetLayers = f.maxLayers - default: - doAlloc := false - added, removed := f.getLayerChanges(brs) - - // check for an added higher than current target - for _, l := range added { - if l > f.targetLayers.Spatial { - doAlloc = true - break - } - } - - // check for current target being removed - if !doAlloc { - for _, l := range removed { - if l == f.targetLayers.Spatial { - doAlloc = true + isCurrentLayerAvailable := false + if f.currentLayers.IsValid() { + for _, l := range availableLayers { + if l == f.currentLayers.Spatial { + isCurrentLayerAvailable = true break } } } - if !doAlloc { - // no layer changes, leave target as is - alloc.targetLayers = f.targetLayers - alloc.bandwidthRequested = brs[alloc.targetLayers.Spatial][alloc.targetLayers.Temporal] + if !isCurrentLayerAvailable && f.currentLayers.IsValid() { + // current layer maybe stopped, move to highest available + for _, l := range availableLayers { + if l > alloc.targetLayers.Spatial { + alloc.targetLayers.Spatial = l + } + } + alloc.targetLayers.Temporal = DefaultMaxLayerTemporal } else { - // allocate best layer available - for s := f.maxLayers.Spatial; s >= 0; s-- { - for t := f.maxLayers.Temporal; t >= 0; t-- { - if brs[s][t] == 0 { - continue - } - - alloc.targetLayers = VideoLayers{ - Spatial: s, - Temporal: t, - } - - alloc.bandwidthRequested = brs[s][t] - break - } - - if alloc.bandwidthRequested != 0 { - break - } - } - - if alloc.bandwidthRequested == 0 && f.maxLayers.IsValid() && allowOvershoot { - // 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 restrictions). - // 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 := f.maxLayers.Spatial + 1; s <= DefaultMaxLayerSpatial; s++ { - for t := int32(0); t <= DefaultMaxLayerTemporal; t++ { - if brs[s][t] == 0 { - continue - } - - alloc.targetLayers = VideoLayers{ - Spatial: s, - Temporal: t, - } - - alloc.bandwidthRequested = brs[s][t] - alloc.pauseReason = VideoPauseReasonNone - f.logger.Infow("allowing overshoot", "maxLayer", f.maxLayers, "targetLayers", alloc.targetLayers) - break - } - - if alloc.bandwidthRequested != 0 { - break - } - } - } - - // feed may be dry, leave target at current if already started for opportunistic resume - if alloc.bandwidthRequested == 0 && f.maxLayers.IsValid() { - if f.started && f.currentLayers.IsValid() { - alloc.targetLayers = f.currentLayers - } else { - // opportunisitically latch on to anything + if f.targetLayers.IsValid() { + alloc.targetLayers = f.targetLayers + } else { + // opportunistically latch on to anything + if allowOvershoot { alloc.targetLayers = VideoLayers{ Spatial: int32(math.Max(0, float64(f.numAdvertisedLayers-1))), Temporal: DefaultMaxLayerTemporal, } + } else { + alloc.targetLayers = f.maxLayers } } } @@ -720,6 +642,9 @@ func (f *Forwarder) AllocateOptimal(brs Bitrates, allowOvershoot bool) VideoAllo if !alloc.targetLayers.IsValid() { alloc.targetLayers = InvalidLayers } + if alloc.targetLayers.IsValid() { + alloc.bandwidthRequested = brs[alloc.targetLayers.Spatial][alloc.targetLayers.Temporal] + } alloc.bandwidthDelta = alloc.bandwidthRequested - f.lastAllocation.bandwidthRequested alloc.distanceToDesired = f.getDistanceToDesired(brs, alloc.targetLayers, f.maxLayers) diff --git a/pkg/sfu/forwarder_test.go b/pkg/sfu/forwarder_test.go index 1f43fc114..f7fa0bfc8 100644 --- a/pkg/sfu/forwarder_test.go +++ b/pkg/sfu/forwarder_test.go @@ -112,8 +112,6 @@ func TestForwarderLayersVideo(t *testing.T) { func TestForwarderAllocateOptimal(t *testing.T) { f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo) - f.SetMaxSpatialLayer(DefaultMaxLayerSpatial) - f.SetMaxTemporalLayer(DefaultMaxLayerTemporal) emptyBitrates := Bitrates{} bitrates := Bitrates{ @@ -122,10 +120,28 @@ func TestForwarderAllocateOptimal(t *testing.T) { {0, 7, 0, 0}, } + // invalid max layers + f.maxLayers = InvalidLayers + expectedResult := VideoAllocation{ + pauseReason: VideoPauseReasonFeedDry, + bandwidthRequested: 0, + bandwidthDelta: 0, + bitrates: bitrates, + targetLayers: InvalidLayers, + maxLayers: InvalidLayers, + distanceToDesired: 0, + } + result := f.AllocateOptimal(nil, bitrates, true) + require.Equal(t, expectedResult, result) + require.Equal(t, expectedResult, f.lastAllocation) + + f.SetMaxSpatialLayer(DefaultMaxLayerSpatial) + f.SetMaxTemporalLayer(DefaultMaxLayerTemporal) + // muted should not consume any bandwidth f.Mute(true) disable(f) - expectedResult := VideoAllocation{ + expectedResult = VideoAllocation{ pauseReason: VideoPauseReasonMuted, bandwidthRequested: 0, bandwidthDelta: 0, @@ -134,12 +150,30 @@ func TestForwarderAllocateOptimal(t *testing.T) { maxLayers: DefaultMaxLayers, distanceToDesired: 0, } - result := f.AllocateOptimal(bitrates, true) + result = f.AllocateOptimal(nil, bitrates, true) require.Equal(t, expectedResult, result) require.Equal(t, expectedResult, f.lastAllocation) f.Mute(false) + // pub muted should not consume any bandwidth + f.PubMute(true) + disable(f) + expectedResult = VideoAllocation{ + pauseReason: VideoPauseReasonPubMuted, + bandwidthRequested: 0, + bandwidthDelta: 0, + bitrates: bitrates, + targetLayers: InvalidLayers, + maxLayers: DefaultMaxLayers, + distanceToDesired: 0, + } + result = f.AllocateOptimal(nil, bitrates, true) + require.Equal(t, expectedResult, result) + require.Equal(t, expectedResult, f.lastAllocation) + + f.PubMute(false) + // when parked layers valid, should stay there f.parkedLayers = VideoLayers{ Spatial: 0, @@ -154,13 +188,33 @@ func TestForwarderAllocateOptimal(t *testing.T) { maxLayers: DefaultMaxLayers, distanceToDesired: 0, } - result = f.AllocateOptimal(emptyBitrates, true) + result = f.AllocateOptimal(nil, emptyBitrates, true) require.Equal(t, expectedResult, result) require.Equal(t, expectedResult, f.lastAllocation) require.Equal(t, f.parkedLayers, f.TargetLayers()) f.parkedLayers = InvalidLayers - // when target is invalid, should set up for opportunistic forwarding + // when max layers changes, should switch to that + f.maxLayers = VideoLayers{Spatial: 1, Temporal: 3} + expectedResult = VideoAllocation{ + pauseReason: VideoPauseReasonFeedDry, + bandwidthRequested: 0, + bandwidthDelta: 0, + bitrates: emptyBitrates, + targetLayers: f.maxLayers, + maxLayers: f.maxLayers, + distanceToDesired: 0, + } + result = f.AllocateOptimal(nil, emptyBitrates, true) + require.Equal(t, expectedResult, result) + require.Equal(t, expectedResult, f.lastAllocation) + require.Equal(t, f.maxLayers, f.TargetLayers()) + + // reset max layers for rest of the tests below + f.maxLayers = DefaultMaxLayers + f.lastAllocation.maxLayers = DefaultMaxLayers + + // when feed is dry and current is not valid, should set up for opportunistic forwarding f.SetNumAdvertisedLayers(3) disable(f) expectedTargetLayers := VideoLayers{ @@ -176,16 +230,37 @@ func TestForwarderAllocateOptimal(t *testing.T) { maxLayers: DefaultMaxLayers, distanceToDesired: 0, } - result = f.AllocateOptimal(emptyBitrates, true) + result = f.AllocateOptimal(nil, emptyBitrates, true) require.Equal(t, expectedResult, result) require.Equal(t, expectedResult, f.lastAllocation) require.Equal(t, expectedTargetLayers, f.TargetLayers()) - f.targetLayers = VideoLayers{Spatial: 0, Temporal: 0} // set to valid to trigger paths in tests below + f.targetLayers = VideoLayers{Spatial: 0, Temporal: 0} // set to valid to trigger paths in tests below + f.currentLayers = VideoLayers{Spatial: 0, Temporal: 3} // set to valid to trigger paths in tests below - // max layers changing should adopt that + // when feed is dry and current is valid, should stay at current + expectedTargetLayers = VideoLayers{ + Spatial: 0, + Temporal: 3, + } + expectedResult = VideoAllocation{ + pauseReason: VideoPauseReasonFeedDry, + bandwidthRequested: 0, + bandwidthDelta: 0, + bitrates: emptyBitrates, + targetLayers: expectedTargetLayers, + maxLayers: DefaultMaxLayers, + distanceToDesired: 0, + } + result = f.AllocateOptimal(nil, emptyBitrates, true) + require.Equal(t, expectedResult, result) + require.Equal(t, expectedResult, f.lastAllocation) + require.Equal(t, expectedTargetLayers, f.TargetLayers()) + + // max layers changing, feed dry, current invalid, no overshoot, should set target to max layers f.SetMaxSpatialLayer(0) f.SetMaxTemporalLayer(3) + f.currentLayers = InvalidLayers expectedTargetLayers = VideoLayers{ Spatial: 0, Temporal: DefaultMaxLayerTemporal, @@ -203,15 +278,45 @@ func TestForwarderAllocateOptimal(t *testing.T) { maxLayers: expectedMaxLayers, distanceToDesired: 0, } - result = f.AllocateOptimal(emptyBitrates, true) + result = f.AllocateOptimal(nil, emptyBitrates, false) require.Equal(t, expectedResult, result) require.Equal(t, expectedResult, f.lastAllocation) require.Equal(t, expectedTargetLayers, f.TargetLayers()) - // when no layer changes, should not change target + // stays at target if feed is not dry and current is not valid, i. e. not forwarding + expectedResult = VideoAllocation{ + pauseReason: VideoPauseReasonFeedDry, + bandwidthRequested: 0, + bandwidthDelta: 0, + bitrates: emptyBitrates, + targetLayers: expectedTargetLayers, + maxLayers: expectedMaxLayers, + distanceToDesired: 0, + } + result = f.AllocateOptimal([]int32{0, 1}, emptyBitrates, true) + require.Equal(t, expectedResult, result) + require.Equal(t, expectedResult, f.lastAllocation) + require.Equal(t, expectedTargetLayers, f.TargetLayers()) + + // if feed is not dry and target is not valid, should be opportunistic (with and without overshoot) + f.targetLayers = InvalidLayers + expectedResult = VideoAllocation{ + pauseReason: VideoPauseReasonFeedDry, + bandwidthRequested: 0, + bandwidthDelta: 0, + bitrates: emptyBitrates, + targetLayers: expectedTargetLayers, + maxLayers: expectedMaxLayers, + distanceToDesired: 0, + } + result = f.AllocateOptimal([]int32{0, 1}, emptyBitrates, false) + require.Equal(t, expectedResult, result) + require.Equal(t, expectedResult, f.lastAllocation) + + f.targetLayers = InvalidLayers expectedTargetLayers = VideoLayers{ - Spatial: 0, - Temporal: 3, + Spatial: 2, + Temporal: DefaultMaxLayerTemporal, } expectedResult = VideoAllocation{ pauseReason: VideoPauseReasonFeedDry, @@ -222,121 +327,46 @@ func TestForwarderAllocateOptimal(t *testing.T) { maxLayers: expectedMaxLayers, distanceToDesired: 0, } - result = f.AllocateOptimal(emptyBitrates, true) + result = f.AllocateOptimal([]int32{0, 1}, emptyBitrates, true) require.Equal(t, expectedResult, result) require.Equal(t, expectedResult, f.lastAllocation) - // allocate using bitrates, allocation should choose optimal - f.currentLayers = InvalidLayers - expectedTargetLayers = VideoLayers{ - Spatial: 0, - Temporal: 1, - } - expectedResult = VideoAllocation{ - bandwidthRequested: bitrates[0][1], - bandwidthDelta: bitrates[0][1], - bitrates: bitrates, - targetLayers: expectedTargetLayers, - maxLayers: expectedMaxLayers, - distanceToDesired: 0, - } - result = f.AllocateOptimal(bitrates, true) - 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, allowing overshoot - sparseBitrates := Bitrates{ - {0, 0, 0, 0}, - {0, 0, 0, 0}, - {0, 7, 0, 0}, - } - expectedTargetLayers = VideoLayers{ - Spatial: 2, - Temporal: 1, - } - expectedResult = VideoAllocation{ - bandwidthRequested: sparseBitrates[2][1], - bandwidthDelta: sparseBitrates[2][1] - bitrates[0][1], - bitrates: sparseBitrates, - targetLayers: expectedTargetLayers, - maxLayers: expectedMaxLayers, - distanceToDesired: -1, - } - result = f.AllocateOptimal(sparseBitrates, true) - require.Equal(t, expectedResult, result) - require.Equal(t, expectedResult, f.lastAllocation) - require.Equal(t, InvalidLayers, f.CurrentLayers()) - require.Equal(t, expectedTargetLayers, f.TargetLayers()) - - // when not allowing overshoot, should leave it at current when started and current is valid - sparseBitrates[1][2] = 10 - sparseBitrates[2][1] = 0 - f.started = true + // stays at target if feed is not dry and current is valid and current is available f.currentLayers = VideoLayers{Spatial: 0, Temporal: 1} expectedTargetLayers = VideoLayers{ - Spatial: 0, - Temporal: 1, + Spatial: 2, + Temporal: DefaultMaxLayerTemporal, } expectedResult = VideoAllocation{ pauseReason: VideoPauseReasonFeedDry, bandwidthRequested: 0, - bandwidthDelta: -7, - bitrates: sparseBitrates, + bandwidthDelta: 0, + bitrates: emptyBitrates, targetLayers: expectedTargetLayers, maxLayers: expectedMaxLayers, distanceToDesired: 0, } - result = f.AllocateOptimal(sparseBitrates, false) + result = f.AllocateOptimal([]int32{0, 1}, emptyBitrates, true) require.Equal(t, expectedResult, result) require.Equal(t, expectedResult, f.lastAllocation) - require.Equal(t, expectedTargetLayers, f.CurrentLayers()) - require.Equal(t, expectedTargetLayers, f.TargetLayers()) - // when not allowing overshoot, should leave it at max when started and current is not valid - sparseBitrates[1][2] = 0 - sparseBitrates[2][1] = 7 - f.started = true - f.currentLayers = InvalidLayers + // switches to highest available if feed is not dry and current is valid and current is not available expectedTargetLayers = VideoLayers{ - Spatial: 2, - Temporal: 3, + Spatial: 1, + Temporal: DefaultMaxLayerTemporal, } expectedResult = VideoAllocation{ pauseReason: VideoPauseReasonFeedDry, bandwidthRequested: 0, bandwidthDelta: 0, - bitrates: sparseBitrates, + bitrates: emptyBitrates, targetLayers: expectedTargetLayers, maxLayers: expectedMaxLayers, - distanceToDesired: -1, + distanceToDesired: 0, } - result = f.AllocateOptimal(sparseBitrates, false) + result = f.AllocateOptimal([]int32{1}, emptyBitrates, true) require.Equal(t, expectedResult, result) require.Equal(t, expectedResult, f.lastAllocation) - require.Equal(t, InvalidLayers, f.CurrentLayers()) - require.Equal(t, expectedTargetLayers, f.TargetLayers()) - - // when not allowing overshoot, should leave it at max for opportunistic forwarding when not started - f.started = false - f.targetLayers = VideoLayers{Spatial: 2, Temporal: 3} - sparseBitrates[1][2] = 10 - sparseBitrates[2][1] = 0 - expectedResult = VideoAllocation{ - pauseReason: VideoPauseReasonFeedDry, - bandwidthRequested: 0, - bandwidthDelta: 0, - bitrates: sparseBitrates, - targetLayers: expectedTargetLayers, - maxLayers: expectedMaxLayers, - distanceToDesired: -1, - } - result = f.AllocateOptimal(sparseBitrates, false) - require.Equal(t, expectedResult, result) - require.Equal(t, expectedResult, f.lastAllocation) - require.Equal(t, InvalidLayers, f.CurrentLayers()) - require.Equal(t, DefaultMaxLayers, f.TargetLayers()) } func TestForwarderProvisionalAllocate(t *testing.T) { diff --git a/pkg/sfu/receiver.go b/pkg/sfu/receiver.go index a3e03fbb4..ea7f8b5b0 100644 --- a/pkg/sfu/receiver.go +++ b/pkg/sfu/receiver.go @@ -31,26 +31,8 @@ var ( type AudioLevelHandle func(level uint8, duration uint32) -// --------------------------------------------------- - type Bitrates [DefaultMaxLayerSpatial + 1][DefaultMaxLayerTemporal + 1]int64 -func (b *Bitrates) GetLayers() []int32 { - layers := []int32{} - for i := 0; i < len(b); i++ { - for j := 0; j < len(b[0]); j++ { - if b[i][j] != 0 { - layers = append(layers, int32(i)) - break - } - } - } - - return layers -} - -// --------------------------------------------------- - // TrackReceiver defines an interface receive media from remote peer type TrackReceiver interface { TrackID() livekit.TrackID @@ -59,7 +41,7 @@ type TrackReceiver interface { HeaderExtensions() []webrtc.RTPHeaderExtensionParameter ReadRTP(buf []byte, layer uint8, sn uint16) (int, error) - GetLayeredBitrate() Bitrates + GetLayeredBitrate() ([]int32, Bitrates) GetAudioLevel() (float64, bool) @@ -421,7 +403,7 @@ func (w *WebRTCReceiver) downTrackBitrateAvailabilityChange() { } } -func (w *WebRTCReceiver) GetLayeredBitrate() Bitrates { +func (w *WebRTCReceiver) GetLayeredBitrate() ([]int32, Bitrates) { return w.streamTrackerManager.GetLayeredBitrate() } diff --git a/pkg/sfu/streamtrackermanager.go b/pkg/sfu/streamtrackermanager.go index 88d0fc08a..ca381e601 100644 --- a/pkg/sfu/streamtrackermanager.go +++ b/pkg/sfu/streamtrackermanager.go @@ -303,7 +303,7 @@ func (s *StreamTrackerManager) GetMaxExpectedLayer() int32 { return maxExpectedLayer } -func (s *StreamTrackerManager) GetLayeredBitrate() Bitrates { +func (s *StreamTrackerManager) GetLayeredBitrate() ([]int32, Bitrates) { s.lock.RLock() defer s.lock.RUnlock() @@ -334,7 +334,10 @@ func (s *StreamTrackerManager) GetLayeredBitrate() Bitrates { } } - return br + availableLayers := make([]int32, len(s.availableLayers)) + copy(availableLayers, s.availableLayers) + + return availableLayers, br } func (s *StreamTrackerManager) hasSpatialLayerLocked(layer int32) bool {