mirror of
https://github.com/livekit/livekit.git
synced 2026-08-28 11:44:43 +00:00
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:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user