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.
This commit is contained in:
Raja Subramanian
2023-02-19 11:41:40 +05:30
committed by GitHub
parent b35d64ae86
commit 6b55742564
6 changed files with 192 additions and 246 deletions
+2 -2
View File
@@ -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) {
+12 -6
View File
@@ -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
}
+40 -115
View File
@@ -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)
+131 -101
View File
@@ -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) {
+2 -20
View File
@@ -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()
}
+5 -2
View File
@@ -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 {