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 {