mirror of
https://github.com/livekit/livekit.git
synced 2026-04-03 14:55:40 +00:00
1509 lines
45 KiB
Go
1509 lines
45 KiB
Go
package sfu
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/pion/webrtc/v3"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/livekit/protocol/logger"
|
|
|
|
"github.com/livekit/livekit-server/pkg/sfu/buffer"
|
|
"github.com/livekit/livekit-server/pkg/sfu/testutils"
|
|
)
|
|
|
|
func disable(f *Forwarder) {
|
|
f.currentLayers = InvalidLayers
|
|
f.targetLayers = InvalidLayers
|
|
}
|
|
|
|
func newForwarder(codec webrtc.RTPCodecCapability, kind webrtc.RTPCodecType) *Forwarder {
|
|
return NewForwarder(codec, kind, logger.Logger(logger.GetLogger()))
|
|
|
|
}
|
|
|
|
func TestForwarderMute(t *testing.T) {
|
|
f := newForwarder(testutils.TestOpusCodec, webrtc.RTPCodecTypeAudio)
|
|
require.False(t, f.IsMuted())
|
|
muted, _ := f.Mute(false)
|
|
require.False(t, muted) // no change in mute state
|
|
require.False(t, f.IsMuted())
|
|
muted, _ = f.Mute(true)
|
|
require.True(t, muted)
|
|
require.True(t, f.IsMuted())
|
|
muted, _ = f.Mute(false)
|
|
require.True(t, muted)
|
|
require.False(t, f.IsMuted())
|
|
}
|
|
|
|
func TestForwarderLayersAudio(t *testing.T) {
|
|
f := newForwarder(testutils.TestOpusCodec, webrtc.RTPCodecTypeAudio)
|
|
|
|
require.Equal(t, InvalidLayers, f.MaxLayers())
|
|
|
|
require.Equal(t, InvalidLayers, f.CurrentLayers())
|
|
require.Equal(t, InvalidLayers, f.TargetLayers())
|
|
|
|
changed, maxLayers, currentLayers := f.SetMaxSpatialLayer(1)
|
|
require.False(t, changed)
|
|
require.Equal(t, InvalidLayers, maxLayers)
|
|
require.Equal(t, InvalidLayers, currentLayers)
|
|
|
|
changed, maxLayers, currentLayers = f.SetMaxTemporalLayer(1)
|
|
require.False(t, changed)
|
|
require.Equal(t, InvalidLayers, maxLayers)
|
|
require.Equal(t, InvalidLayers, currentLayers)
|
|
|
|
require.Equal(t, InvalidLayers, f.MaxLayers())
|
|
}
|
|
|
|
func TestForwarderLayersVideo(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
|
|
maxLayers := f.MaxLayers()
|
|
expectedLayers := VideoLayers{spatial: InvalidLayerSpatial, temporal: DefaultMaxLayerTemporal}
|
|
require.Equal(t, expectedLayers, maxLayers)
|
|
|
|
require.Equal(t, InvalidLayers, f.CurrentLayers())
|
|
require.Equal(t, InvalidLayers, f.TargetLayers())
|
|
|
|
expectedLayers = VideoLayers{
|
|
spatial: DefaultMaxLayerSpatial,
|
|
temporal: DefaultMaxLayerTemporal,
|
|
}
|
|
changed, maxLayers, currentLayers := f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
require.True(t, changed)
|
|
require.Equal(t, expectedLayers, maxLayers)
|
|
require.Equal(t, InvalidLayers, currentLayers)
|
|
|
|
changed, maxLayers, currentLayers = f.SetMaxSpatialLayer(DefaultMaxLayerSpatial - 1)
|
|
require.True(t, changed)
|
|
expectedLayers = VideoLayers{
|
|
spatial: DefaultMaxLayerSpatial - 1,
|
|
temporal: DefaultMaxLayerTemporal,
|
|
}
|
|
require.Equal(t, expectedLayers, maxLayers)
|
|
require.Equal(t, expectedLayers, f.MaxLayers())
|
|
require.Equal(t, InvalidLayers, currentLayers)
|
|
|
|
f.currentLayers = VideoLayers{spatial: 0, temporal: 1}
|
|
changed, maxLayers, currentLayers = f.SetMaxSpatialLayer(DefaultMaxLayerSpatial - 1)
|
|
require.False(t, changed)
|
|
require.Equal(t, expectedLayers, maxLayers)
|
|
require.Equal(t, expectedLayers, f.MaxLayers())
|
|
require.Equal(t, VideoLayers{spatial: 0, temporal: 1}, currentLayers)
|
|
|
|
changed, maxLayers, currentLayers = f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
require.False(t, changed)
|
|
require.Equal(t, expectedLayers, maxLayers)
|
|
require.Equal(t, VideoLayers{spatial: 0, temporal: 1}, currentLayers)
|
|
|
|
changed, maxLayers, currentLayers = f.SetMaxTemporalLayer(DefaultMaxLayerTemporal - 1)
|
|
require.True(t, changed)
|
|
expectedLayers = VideoLayers{
|
|
spatial: DefaultMaxLayerSpatial - 1,
|
|
temporal: DefaultMaxLayerTemporal - 1,
|
|
}
|
|
require.Equal(t, expectedLayers, maxLayers)
|
|
require.Equal(t, expectedLayers, f.MaxLayers())
|
|
require.Equal(t, VideoLayers{spatial: 0, temporal: 1}, currentLayers)
|
|
}
|
|
|
|
func TestForwarderGetForwardingStatus(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
|
|
// no available layers, should be optimal
|
|
require.Equal(t, ForwardingStatusOptimal, f.GetForwardingStatus())
|
|
|
|
// with available layers, should be off
|
|
availableLayers := []int32{0, 1, 2}
|
|
f.UpTrackLayersChange(availableLayers)
|
|
require.Equal(t, ForwardingStatusOff, f.GetForwardingStatus())
|
|
|
|
// when muted, should be optimal
|
|
f.Mute(true)
|
|
require.Equal(t, ForwardingStatusOptimal, f.GetForwardingStatus())
|
|
|
|
// when target is the max, should be optimal
|
|
f.Mute(false)
|
|
f.targetLayers.spatial = DefaultMaxLayerSpatial
|
|
require.Equal(t, ForwardingStatusOptimal, f.GetForwardingStatus())
|
|
|
|
// when target is less than max subscribed and max available, should be partial
|
|
f.targetLayers.spatial = DefaultMaxLayerSpatial - 1
|
|
require.Equal(t, ForwardingStatusPartial, f.GetForwardingStatus())
|
|
|
|
// when available layers are lower than max subscribed, optimal as long as target is at max available
|
|
availableLayers = []int32{0, 1}
|
|
f.UpTrackLayersChange(availableLayers)
|
|
require.Equal(t, ForwardingStatusOptimal, f.GetForwardingStatus())
|
|
}
|
|
|
|
func TestForwarderUpTrackLayersChange(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
|
|
require.Nil(t, f.availableLayers)
|
|
|
|
availableLayers := []int32{0, 1, 2}
|
|
f.UpTrackLayersChange(availableLayers)
|
|
require.Equal(t, availableLayers, f.availableLayers)
|
|
|
|
availableLayers = []int32{0, 2}
|
|
f.UpTrackLayersChange(availableLayers)
|
|
require.Equal(t, availableLayers, f.availableLayers)
|
|
|
|
availableLayers = []int32{}
|
|
f.UpTrackLayersChange(availableLayers)
|
|
require.Equal(t, availableLayers, f.availableLayers)
|
|
}
|
|
|
|
func TestForwarderAllocate(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
|
|
emptyBitrates := Bitrates{}
|
|
bitrates := Bitrates{
|
|
{2, 3, 0, 0},
|
|
{4, 0, 0, 5},
|
|
{0, 7, 0, 0},
|
|
}
|
|
|
|
// muted should not consume any bandwidth
|
|
f.Mute(true)
|
|
disable(f)
|
|
expectedResult := VideoAllocation{
|
|
state: VideoAllocationStateMuted,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 0,
|
|
bandwidthDelta: 0,
|
|
availableLayers: nil,
|
|
bitrates: bitrates,
|
|
targetLayers: InvalidLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result := f.AllocateOptimal(bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
|
|
// feed dry state
|
|
f.Mute(false)
|
|
f.lastAllocation.state = VideoAllocationStateNone
|
|
disable(f)
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateFeedDry,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 0,
|
|
bandwidthDelta: 0,
|
|
availableLayers: nil,
|
|
bitrates: emptyBitrates,
|
|
targetLayers: InvalidLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result = f.AllocateOptimal(emptyBitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
|
|
// awaiting measurement, i.e. bitrates are not available, but layers available
|
|
f.lastAllocation.state = VideoAllocationStateNone
|
|
disable(f)
|
|
f.UpTrackLayersChange([]int32{0})
|
|
expectedTargetLayers := VideoLayers{
|
|
spatial: 0,
|
|
temporal: DefaultMaxLayerTemporal,
|
|
}
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateAwaitingMeasurement,
|
|
change: VideoStreamingChangeResuming,
|
|
bandwidthRequested: 0,
|
|
bandwidthDelta: 0,
|
|
availableLayers: []int32{0},
|
|
bitrates: emptyBitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result = f.AllocateOptimal(emptyBitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
require.Equal(t, InvalidLayers, f.CurrentLayers())
|
|
|
|
// allocate using bitrates, allocation should choose optimal
|
|
expectedTargetLayers = VideoLayers{
|
|
spatial: 2,
|
|
temporal: 1,
|
|
}
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateOptimal,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: bitrates[2][1],
|
|
bandwidthDelta: bitrates[2][1],
|
|
availableLayers: []int32{0},
|
|
bitrates: bitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result = f.AllocateOptimal(bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, InvalidLayers, f.CurrentLayers())
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
}
|
|
|
|
func TestForwarderProvisionalAllocate(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
|
|
availableLayers := []int32{0, 1, 2}
|
|
bitrates := Bitrates{
|
|
{1, 2, 3, 4},
|
|
{5, 6, 7, 8},
|
|
{9, 10, 11, 12},
|
|
}
|
|
|
|
f.availableLayers = availableLayers
|
|
f.ProvisionalAllocatePrepare(bitrates)
|
|
|
|
usedBitrate := f.ProvisionalAllocate(bitrates[2][3], VideoLayers{spatial: 0, temporal: 0}, true)
|
|
require.Equal(t, bitrates[0][0], usedBitrate)
|
|
|
|
usedBitrate = f.ProvisionalAllocate(bitrates[2][3], VideoLayers{spatial: 2, temporal: 3}, true)
|
|
require.Equal(t, bitrates[2][3]-bitrates[0][0], usedBitrate)
|
|
|
|
usedBitrate = f.ProvisionalAllocate(bitrates[2][3], VideoLayers{spatial: 0, temporal: 3}, true)
|
|
require.Equal(t, bitrates[0][3]-bitrates[2][3], usedBitrate)
|
|
|
|
usedBitrate = f.ProvisionalAllocate(bitrates[2][3], VideoLayers{spatial: 1, temporal: 2}, true)
|
|
require.Equal(t, bitrates[1][2]-bitrates[0][3], usedBitrate)
|
|
|
|
// available not enough to reach (2, 2), allocating at (2, 2) should not succeed
|
|
usedBitrate = f.ProvisionalAllocate(bitrates[2][2]-bitrates[1][2]-1, VideoLayers{spatial: 2, temporal: 2}, true)
|
|
require.Equal(t, int64(0), usedBitrate)
|
|
|
|
// committing should set target to (1, 2)
|
|
expectedTargetLayers := VideoLayers{
|
|
spatial: 1,
|
|
temporal: 2,
|
|
}
|
|
expectedResult := VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeResuming,
|
|
bandwidthRequested: bitrates[1][2],
|
|
bandwidthDelta: bitrates[1][2],
|
|
availableLayers: availableLayers,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 5,
|
|
}
|
|
result := f.ProvisionalAllocateCommit()
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
|
|
// when nothing fits and pausing disallowed, should allocate (0, 0)
|
|
f.targetLayers = InvalidLayers
|
|
f.ProvisionalAllocatePrepare(bitrates)
|
|
usedBitrate = f.ProvisionalAllocate(0, VideoLayers{spatial: 0, temporal: 0}, false)
|
|
require.Equal(t, int64(1), usedBitrate)
|
|
|
|
// committing should set target to (0, 0)
|
|
expectedTargetLayers = VideoLayers{
|
|
spatial: 0,
|
|
temporal: 0,
|
|
}
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeResuming,
|
|
bandwidthRequested: bitrates[0][0],
|
|
bandwidthDelta: bitrates[0][0] - bitrates[1][2],
|
|
availableLayers: availableLayers,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 11,
|
|
}
|
|
result = f.ProvisionalAllocateCommit()
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
}
|
|
|
|
func TestForwarderProvisionalAllocateMute(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
|
|
bitrates := Bitrates{
|
|
{1, 2, 3, 4},
|
|
{5, 6, 7, 8},
|
|
{9, 10, 11, 12},
|
|
}
|
|
|
|
f.Mute(true)
|
|
f.ProvisionalAllocatePrepare(bitrates)
|
|
|
|
usedBitrate := f.ProvisionalAllocate(bitrates[2][3], VideoLayers{spatial: 0, temporal: 0}, true)
|
|
require.Equal(t, int64(0), usedBitrate)
|
|
|
|
usedBitrate = f.ProvisionalAllocate(bitrates[2][3], VideoLayers{spatial: 1, temporal: 2}, true)
|
|
require.Equal(t, int64(0), usedBitrate)
|
|
|
|
// committing should set target to InvalidLayers as track is muted
|
|
expectedResult := VideoAllocation{
|
|
state: VideoAllocationStateMuted,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 0,
|
|
bandwidthDelta: 0,
|
|
availableLayers: nil,
|
|
bitrates: bitrates,
|
|
targetLayers: InvalidLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result := f.ProvisionalAllocateCommit()
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, InvalidLayers, f.TargetLayers())
|
|
}
|
|
|
|
func TestForwarderProvisionalAllocateGetCooperativeTransition(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
|
|
availableLayers := []int32{0, 1, 2}
|
|
bitrates := Bitrates{
|
|
{1, 2, 3, 4},
|
|
{5, 6, 7, 8},
|
|
{9, 10, 0, 0},
|
|
}
|
|
|
|
f.availableLayers = availableLayers
|
|
f.ProvisionalAllocatePrepare(bitrates)
|
|
|
|
// from scratch (InvalidLayers) should give back layer (0, 0)
|
|
expectedTransition := VideoTransition{
|
|
from: InvalidLayers,
|
|
to: VideoLayers{spatial: 0, temporal: 0},
|
|
bandwidthDelta: 1,
|
|
}
|
|
transition := f.ProvisionalAllocateGetCooperativeTransition()
|
|
require.Equal(t, expectedTransition, transition)
|
|
|
|
// committing should set target to (0, 0)
|
|
expectedLayers := VideoLayers{spatial: 0, temporal: 0}
|
|
expectedResult := VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeResuming,
|
|
bandwidthRequested: 1,
|
|
bandwidthDelta: 1,
|
|
availableLayers: availableLayers,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedLayers,
|
|
distanceToDesired: 9,
|
|
}
|
|
result := f.ProvisionalAllocateCommit()
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedLayers, f.TargetLayers())
|
|
|
|
// a higher target that is already streaming, just maintain it
|
|
targetLayers := VideoLayers{spatial: 2, temporal: 1}
|
|
f.targetLayers = targetLayers
|
|
f.lastAllocation.bandwidthRequested = 10
|
|
expectedTransition = VideoTransition{
|
|
from: targetLayers,
|
|
to: targetLayers,
|
|
bandwidthDelta: 0,
|
|
}
|
|
transition = f.ProvisionalAllocateGetCooperativeTransition()
|
|
require.Equal(t, expectedTransition, transition)
|
|
|
|
// committing should set target to (2, 1)
|
|
expectedLayers = VideoLayers{spatial: 2, temporal: 1}
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateOptimal,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 10,
|
|
bandwidthDelta: 0,
|
|
availableLayers: availableLayers,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result = f.ProvisionalAllocateCommit()
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedLayers, f.TargetLayers())
|
|
|
|
// from a target that has become unavailable, should switch to lower available layer
|
|
targetLayers = VideoLayers{spatial: 2, temporal: 2}
|
|
f.targetLayers = targetLayers
|
|
expectedTransition = VideoTransition{
|
|
from: targetLayers,
|
|
to: VideoLayers{spatial: 2, temporal: 1},
|
|
bandwidthDelta: 0,
|
|
}
|
|
transition = f.ProvisionalAllocateGetCooperativeTransition()
|
|
require.Equal(t, expectedTransition, transition)
|
|
|
|
f.ProvisionalAllocateCommit()
|
|
|
|
// mute
|
|
f.Mute(true)
|
|
f.ProvisionalAllocatePrepare(bitrates)
|
|
|
|
// mute should send target to InvalidLayers
|
|
expectedTransition = VideoTransition{
|
|
from: VideoLayers{spatial: 2, temporal: 1},
|
|
to: InvalidLayers,
|
|
bandwidthDelta: -10,
|
|
}
|
|
transition = f.ProvisionalAllocateGetCooperativeTransition()
|
|
require.Equal(t, expectedTransition, transition)
|
|
}
|
|
|
|
func TestForwarderProvisionalAllocateGetBestWeightedTransition(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
|
|
bitrates := Bitrates{
|
|
{1, 2, 3, 4},
|
|
{5, 6, 7, 8},
|
|
{9, 10, 11, 12},
|
|
}
|
|
|
|
f.ProvisionalAllocatePrepare(bitrates)
|
|
|
|
f.targetLayers = VideoLayers{spatial: 2, temporal: 2}
|
|
f.lastAllocation.bandwidthRequested = bitrates[2][2]
|
|
expectedTransition := VideoTransition{
|
|
from: f.targetLayers,
|
|
to: VideoLayers{spatial: 2, temporal: 0},
|
|
bandwidthDelta: 2,
|
|
}
|
|
transition := f.ProvisionalAllocateGetBestWeightedTransition()
|
|
require.Equal(t, expectedTransition, transition)
|
|
}
|
|
|
|
func TestForwarderAllocateNextHigher(t *testing.T) {
|
|
f := newForwarder(testutils.TestOpusCodec, webrtc.RTPCodecTypeAudio)
|
|
f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
|
|
emptyBitrates := Bitrates{}
|
|
bitrates := Bitrates{
|
|
{2, 3, 0, 0},
|
|
{4, 0, 0, 5},
|
|
{0, 7, 0, 0},
|
|
}
|
|
|
|
result, boosted := f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, VideoAllocationDefault, result) // no layer for audio
|
|
require.False(t, boosted)
|
|
|
|
f = newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
|
|
// when not in deficient state, does not boost
|
|
f.lastAllocation.state = VideoAllocationStateNone
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, VideoAllocationDefault, result)
|
|
require.False(t, boosted)
|
|
|
|
// if layers have not caught up, should not allocate next layer
|
|
f.lastAllocation.state = VideoAllocationStateDeficient
|
|
f.targetLayers.spatial = 0
|
|
expectedResult := VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 0,
|
|
bandwidthDelta: 0,
|
|
availableLayers: nil,
|
|
bitrates: emptyBitrates,
|
|
targetLayers: InvalidLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.False(t, boosted)
|
|
f.currentLayers.spatial = 0
|
|
|
|
f.targetLayers.temporal = 0
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.False(t, boosted)
|
|
f.currentLayers.temporal = 0
|
|
|
|
f.lastAllocation.bandwidthRequested = bitrates[0][0]
|
|
|
|
// empty bitrates cannot increase layer
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 2,
|
|
bandwidthDelta: 0,
|
|
availableLayers: nil,
|
|
bitrates: emptyBitrates,
|
|
targetLayers: InvalidLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, emptyBitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.False(t, boosted)
|
|
|
|
// move from (0, 0) -> (0, 1), i.e. a higher temporal layer is available in the same spatial layer
|
|
expectedTargetLayers := VideoLayers{
|
|
spatial: 0,
|
|
temporal: 1,
|
|
}
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 3,
|
|
bandwidthDelta: 1,
|
|
availableLayers: nil,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 3,
|
|
}
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
require.True(t, boosted)
|
|
|
|
// move from (0, 1) -> (1, 0), i.e. a higher spatial layer is available
|
|
f.currentLayers.temporal = 1
|
|
expectedTargetLayers = VideoLayers{
|
|
spatial: 1,
|
|
temporal: 0,
|
|
}
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 4,
|
|
bandwidthDelta: 1,
|
|
availableLayers: nil,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 2,
|
|
}
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
require.True(t, boosted)
|
|
|
|
// next higher, move from (1, 0) -> (1, 3), still deficient though
|
|
f.currentLayers.spatial = 1
|
|
f.currentLayers.temporal = 0
|
|
expectedTargetLayers = VideoLayers{
|
|
spatial: 1,
|
|
temporal: 3,
|
|
}
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 5,
|
|
bandwidthDelta: 1,
|
|
availableLayers: nil,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 1,
|
|
}
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
require.True(t, boosted)
|
|
|
|
// next higher, move from (1, 3) -> (2, 1), optimal allocation
|
|
f.currentLayers.temporal = 3
|
|
expectedTargetLayers = VideoLayers{
|
|
spatial: 2,
|
|
temporal: 1,
|
|
}
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateOptimal,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 7,
|
|
bandwidthDelta: 2,
|
|
availableLayers: nil,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
require.True(t, boosted)
|
|
|
|
// ask again, should return not boosted as there is no room to go higher
|
|
f.currentLayers.spatial = 2
|
|
f.currentLayers.temporal = 1
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
require.False(t, boosted)
|
|
|
|
// turn off everything, allocating next layer should result in streaming lowest layers
|
|
disable(f)
|
|
f.lastAllocation.state = VideoAllocationStateDeficient
|
|
f.lastAllocation.bandwidthRequested = 0
|
|
|
|
expectedTargetLayers = VideoLayers{
|
|
spatial: 0,
|
|
temporal: 0,
|
|
}
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeResuming,
|
|
bandwidthRequested: 2,
|
|
bandwidthDelta: 2,
|
|
availableLayers: nil,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 4,
|
|
}
|
|
result, boosted = f.AllocateNextHigher(ChannelCapacityInfinity, bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
require.True(t, boosted)
|
|
|
|
// no new available capacity cannot bump up layer
|
|
expectedResult = VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 2,
|
|
bandwidthDelta: 2,
|
|
availableLayers: nil,
|
|
bitrates: bitrates,
|
|
targetLayers: expectedTargetLayers,
|
|
distanceToDesired: 4,
|
|
}
|
|
result, boosted = f.AllocateNextHigher(0, bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, expectedTargetLayers, f.TargetLayers())
|
|
require.False(t, boosted)
|
|
}
|
|
|
|
func TestForwarderPause(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
|
|
availableLayers := []int32{0, 1, 2}
|
|
bitrates := Bitrates{
|
|
{1, 2, 3, 4},
|
|
{5, 6, 7, 8},
|
|
{9, 10, 11, 12},
|
|
}
|
|
|
|
f.availableLayers = availableLayers
|
|
f.ProvisionalAllocatePrepare(bitrates)
|
|
f.ProvisionalAllocate(bitrates[2][3], VideoLayers{spatial: 0, temporal: 0}, true)
|
|
// should have set target at (0, 0)
|
|
f.ProvisionalAllocateCommit()
|
|
|
|
expectedResult := VideoAllocation{
|
|
state: VideoAllocationStateDeficient,
|
|
change: VideoStreamingChangePausing,
|
|
bandwidthRequested: 0,
|
|
bandwidthDelta: 0 - bitrates[0][0],
|
|
availableLayers: availableLayers,
|
|
bitrates: bitrates,
|
|
targetLayers: InvalidLayers,
|
|
distanceToDesired: 12,
|
|
}
|
|
result := f.Pause(bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, InvalidLayers, f.TargetLayers())
|
|
}
|
|
|
|
func TestForwarderPauseMute(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
f.SetMaxSpatialLayer(DefaultMaxLayerSpatial)
|
|
f.SetMaxTemporalLayer(DefaultMaxLayerTemporal)
|
|
|
|
availableLayers := []int32{0, 1, 2}
|
|
bitrates := Bitrates{
|
|
{1, 2, 3, 4},
|
|
{5, 6, 7, 8},
|
|
{9, 10, 11, 12},
|
|
}
|
|
|
|
f.availableLayers = availableLayers
|
|
f.ProvisionalAllocatePrepare(bitrates)
|
|
f.ProvisionalAllocate(bitrates[2][3], VideoLayers{spatial: 0, temporal: 0}, true)
|
|
// should have set target at (0, 0)
|
|
f.ProvisionalAllocateCommit()
|
|
|
|
f.Mute(true)
|
|
expectedResult := VideoAllocation{
|
|
state: VideoAllocationStateMuted,
|
|
change: VideoStreamingChangeNone,
|
|
bandwidthRequested: 0,
|
|
bandwidthDelta: 0 - bitrates[0][0],
|
|
availableLayers: availableLayers,
|
|
bitrates: bitrates,
|
|
targetLayers: InvalidLayers,
|
|
distanceToDesired: 0,
|
|
}
|
|
result := f.Pause(bitrates)
|
|
require.Equal(t, expectedResult, result)
|
|
require.Equal(t, expectedResult, f.lastAllocation)
|
|
require.Equal(t, InvalidLayers, f.TargetLayers())
|
|
}
|
|
|
|
func TestForwarderGetTranslationParamsMuted(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
f.Mute(true)
|
|
|
|
params := &testutils.TestExtPacketParams{
|
|
SequenceNumber: 23333,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
}
|
|
extPkt, err := testutils.GetTestExtPacket(params)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, extPkt)
|
|
|
|
expectedTP := TranslationParams{
|
|
shouldDrop: true,
|
|
}
|
|
actualTP, err := f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedTP, *actualTP)
|
|
}
|
|
|
|
func TestForwarderGetTranslationParamsAudio(t *testing.T) {
|
|
f := newForwarder(testutils.TestOpusCodec, webrtc.RTPCodecTypeAudio)
|
|
|
|
params := &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23333,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
extPkt, _ := testutils.GetTestExtPacket(params)
|
|
|
|
// should lock onto the first packet
|
|
expectedTP := TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingContiguous,
|
|
sequenceNumber: 23333,
|
|
timestamp: 0xabcdef,
|
|
},
|
|
}
|
|
actualTP, err := f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
require.True(t, f.started)
|
|
require.Equal(t, f.lastSSRC, params.SSRC)
|
|
|
|
// send a duplicate, should be dropped
|
|
expectedTP = TranslationParams{
|
|
shouldDrop: true,
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// out-of-order packet not in cache should be dropped
|
|
params = &testutils.TestExtPacketParams{
|
|
SequenceNumber: 23332,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacket(params)
|
|
|
|
expectedTP = TranslationParams{
|
|
shouldDrop: true,
|
|
isDroppingRelevant: true,
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// padding only packet in order should be dropped
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23334,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacket(params)
|
|
|
|
expectedTP = TranslationParams{
|
|
shouldDrop: true,
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// in order packet should be forwarded
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23335,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacket(params)
|
|
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingContiguous,
|
|
sequenceNumber: 23334,
|
|
timestamp: 0xabcdef,
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// padding only packet after a gap should be forwarded
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23337,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacket(params)
|
|
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingGap,
|
|
sequenceNumber: 23336,
|
|
timestamp: 0xabcdef,
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// out-of-order should be forwarded using cache
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: false,
|
|
SequenceNumber: 23336,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacket(params)
|
|
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingOutOfOrder,
|
|
sequenceNumber: 23335,
|
|
timestamp: 0xabcdef,
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// switching source should lock onto the new source, but sequence number should be contiguous
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 123,
|
|
Timestamp: 0xfedcba,
|
|
SSRC: 0x87654321,
|
|
PayloadSize: 20,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacket(params)
|
|
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingContiguous,
|
|
sequenceNumber: 23337,
|
|
timestamp: 0xabcdf0,
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
require.Equal(t, f.lastSSRC, params.SSRC)
|
|
}
|
|
|
|
func TestForwarderGetTranslationParamsVideo(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
|
|
params := &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23333,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
vp8 := &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13467,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 233,
|
|
TIDPresent: 1,
|
|
TID: 1,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: false,
|
|
}
|
|
extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8)
|
|
|
|
// no target layers, should drop
|
|
expectedTP := TranslationParams{
|
|
shouldDrop: true,
|
|
}
|
|
actualTP, err := f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedTP, *actualTP)
|
|
|
|
// although target layer matches, not a key frame, so should drop and ask to send PLI
|
|
f.targetLayers = VideoLayers{
|
|
spatial: 0,
|
|
temporal: 1,
|
|
}
|
|
expectedTP = TranslationParams{
|
|
shouldDrop: true,
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedTP, *actualTP)
|
|
|
|
// should lock onto packet (target layer and key frame)
|
|
vp8 = &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13467,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 233,
|
|
TIDPresent: 1,
|
|
TID: 1,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8)
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingContiguous,
|
|
sequenceNumber: 23333,
|
|
timestamp: 0xabcdef,
|
|
},
|
|
vp8: &TranslationParamsVP8{
|
|
header: &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13467,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 233,
|
|
TIDPresent: 1,
|
|
TID: 1,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
},
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
require.True(t, f.started)
|
|
require.Equal(t, f.lastSSRC, params.SSRC)
|
|
|
|
// send a duplicate, should be dropped
|
|
expectedTP = TranslationParams{
|
|
shouldDrop: true,
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// out-of-order packet not in cache should be dropped
|
|
params = &testutils.TestExtPacketParams{
|
|
SequenceNumber: 23332,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8)
|
|
expectedTP = TranslationParams{
|
|
shouldDrop: true,
|
|
isDroppingRelevant: true,
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// padding only packet in order should be dropped
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23334,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8)
|
|
expectedTP = TranslationParams{
|
|
shouldDrop: true,
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// in order packet should be forwarded
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23335,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8)
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingContiguous,
|
|
sequenceNumber: 23334,
|
|
timestamp: 0xabcdef,
|
|
},
|
|
vp8: &TranslationParamsVP8{
|
|
header: &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13467,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 233,
|
|
TIDPresent: 1,
|
|
TID: 1,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
},
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// temporal layer higher than target, should be dropped
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23336,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
vp8 = &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13468,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 233,
|
|
TIDPresent: 1,
|
|
TID: 2,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8)
|
|
expectedTP = TranslationParams{
|
|
shouldDrop: true,
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// RTP sequence number and VP8 picture id should be contiguous after dropping higher temporal layer picture
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23337,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
vp8 = &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13469,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 234,
|
|
TIDPresent: 1,
|
|
TID: 0,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: false,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8)
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingContiguous,
|
|
sequenceNumber: 23335,
|
|
timestamp: 0xabcdef,
|
|
},
|
|
vp8: &TranslationParamsVP8{
|
|
header: &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13468,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 234,
|
|
TIDPresent: 1,
|
|
TID: 0,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: false,
|
|
},
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// padding only packet after a gap should be forwarded
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23339,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacket(params)
|
|
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingGap,
|
|
sequenceNumber: 23337,
|
|
timestamp: 0xabcdef,
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// out-of-order should be forwarded using cache, even if it is padding only
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: false,
|
|
SequenceNumber: 23338,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacket(params)
|
|
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingOutOfOrder,
|
|
sequenceNumber: 23336,
|
|
timestamp: 0xabcdef,
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 0)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
|
|
// switching SSRC (happens for new layer or new track source)
|
|
// should lock onto the new source, but sequence number should be contiguous
|
|
f.targetLayers = VideoLayers{
|
|
spatial: 1,
|
|
temporal: 1,
|
|
}
|
|
|
|
params = &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 123,
|
|
Timestamp: 0xfedcba,
|
|
SSRC: 0x87654321,
|
|
PayloadSize: 20,
|
|
}
|
|
vp8 = &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 45,
|
|
MBit: false,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 12,
|
|
TIDPresent: 1,
|
|
TID: 0,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 30,
|
|
HeaderSize: 5,
|
|
IsKeyFrame: true,
|
|
}
|
|
extPkt, _ = testutils.GetTestExtPacketVP8(params, vp8)
|
|
|
|
expectedTP = TranslationParams{
|
|
rtp: &TranslationParamsRTP{
|
|
snOrdering: SequenceNumberOrderingContiguous,
|
|
sequenceNumber: 23338,
|
|
timestamp: 0xabcdf0,
|
|
},
|
|
vp8: &TranslationParamsVP8{
|
|
header: &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13469,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 235,
|
|
TIDPresent: 1,
|
|
TID: 0,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 24,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
},
|
|
},
|
|
}
|
|
actualTP, err = f.GetTranslationParams(extPkt, 1)
|
|
require.NoError(t, err)
|
|
require.True(t, reflect.DeepEqual(expectedTP, *actualTP))
|
|
require.Equal(t, f.lastSSRC, params.SSRC)
|
|
}
|
|
|
|
func TestForwardGetSnTsForPadding(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
|
|
params := &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23333,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
vp8 := &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13467,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 233,
|
|
TIDPresent: 1,
|
|
TID: 13,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
}
|
|
extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8)
|
|
|
|
f.targetLayers = VideoLayers{
|
|
spatial: 0,
|
|
temporal: 1,
|
|
}
|
|
f.currentLayers = InvalidLayers
|
|
|
|
// send it through so that forwarder locks onto stream
|
|
_, _ = f.GetTranslationParams(extPkt, 0)
|
|
|
|
// pause stream and get padding, it should still work
|
|
disable(f)
|
|
|
|
// should get back frame end needed as the last packet did not have RTP marker set
|
|
snts, err := f.GetSnTsForPadding(5)
|
|
require.NoError(t, err)
|
|
|
|
numPadding := 5
|
|
clockRate := uint32(0)
|
|
frameRate := uint32(5)
|
|
var sntsExpected = make([]SnTs, numPadding)
|
|
for i := 0; i < numPadding; i++ {
|
|
sntsExpected[i] = SnTs{
|
|
sequenceNumber: 23333 + uint16(i) + 1,
|
|
timestamp: 0xabcdef + (uint32(i)*clockRate)/frameRate,
|
|
}
|
|
}
|
|
require.Equal(t, sntsExpected, snts)
|
|
|
|
// now that there is a marker, timestamp should jump on first padding when asked again
|
|
snts, err = f.GetSnTsForPadding(numPadding)
|
|
require.NoError(t, err)
|
|
|
|
for i := 0; i < numPadding; i++ {
|
|
sntsExpected[i] = SnTs{
|
|
sequenceNumber: 23338 + uint16(i) + 1,
|
|
timestamp: 0xabcdef + (uint32(i+1)*clockRate)/frameRate,
|
|
}
|
|
}
|
|
require.Equal(t, sntsExpected, snts)
|
|
}
|
|
|
|
func TestForwardGetSnTsForBlankFrames(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
|
|
params := &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23333,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
vp8 := &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13467,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 233,
|
|
TIDPresent: 1,
|
|
TID: 13,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
}
|
|
extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8)
|
|
|
|
f.targetLayers = VideoLayers{
|
|
spatial: 0,
|
|
temporal: 1,
|
|
}
|
|
f.currentLayers = InvalidLayers
|
|
|
|
// send it through so that forwarder locks onto stream
|
|
_, _ = f.GetTranslationParams(extPkt, 0)
|
|
|
|
// should get back frame end needed as the last packet did not have RTP marker set
|
|
snts, frameEndNeeded, err := f.GetSnTsForBlankFrames()
|
|
require.NoError(t, err)
|
|
require.True(t, frameEndNeeded)
|
|
|
|
// there should be one more than RTPBlankFramesMax as one would have been allocated to end previous frame
|
|
numPadding := RTPBlankFramesMax + 1
|
|
clockRate := testutils.TestVP8Codec.ClockRate
|
|
frameRate := uint32(30)
|
|
var sntsExpected = make([]SnTs, numPadding)
|
|
for i := 0; i < numPadding; i++ {
|
|
sntsExpected[i] = SnTs{
|
|
sequenceNumber: 23333 + uint16(i) + 1,
|
|
timestamp: 0xabcdef + (uint32(i)*clockRate)/frameRate,
|
|
}
|
|
}
|
|
require.Equal(t, sntsExpected, snts)
|
|
|
|
// now that there is a marker, timestamp should jump on first padding when asked again
|
|
// also number of padding should be RTPBlankFramesMax
|
|
snts, frameEndNeeded, err = f.GetSnTsForBlankFrames()
|
|
require.NoError(t, err)
|
|
require.False(t, frameEndNeeded)
|
|
|
|
numPadding = RTPBlankFramesMax
|
|
sntsExpected = sntsExpected[:numPadding]
|
|
for i := 0; i < numPadding; i++ {
|
|
sntsExpected[i] = SnTs{
|
|
sequenceNumber: 23340 + uint16(i) + 1,
|
|
timestamp: 0xabcdef + (uint32(i+1)*clockRate)/frameRate,
|
|
}
|
|
}
|
|
require.Equal(t, sntsExpected, snts)
|
|
}
|
|
|
|
func TestForwardGetPaddingVP8(t *testing.T) {
|
|
f := newForwarder(testutils.TestVP8Codec, webrtc.RTPCodecTypeVideo)
|
|
|
|
params := &testutils.TestExtPacketParams{
|
|
IsHead: true,
|
|
SequenceNumber: 23333,
|
|
Timestamp: 0xabcdef,
|
|
SSRC: 0x12345678,
|
|
PayloadSize: 20,
|
|
}
|
|
vp8 := &buffer.VP8{
|
|
FirstByte: 25,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13467,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 233,
|
|
TIDPresent: 1,
|
|
TID: 13,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
}
|
|
extPkt, _ := testutils.GetTestExtPacketVP8(params, vp8)
|
|
|
|
f.targetLayers = VideoLayers{
|
|
spatial: 0,
|
|
temporal: 1,
|
|
}
|
|
f.currentLayers = InvalidLayers
|
|
|
|
// send it through so that forwarder locks onto stream
|
|
_, _ = f.GetTranslationParams(extPkt, 0)
|
|
|
|
// getting padding with frame end needed, should repeat the last picture id
|
|
expectedVP8 := buffer.VP8{
|
|
FirstByte: 16,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13467,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 233,
|
|
TIDPresent: 1,
|
|
TID: 0,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 23,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
}
|
|
blankVP8 := f.GetPaddingVP8(true)
|
|
require.True(t, reflect.DeepEqual(expectedVP8, *blankVP8))
|
|
|
|
// getting padding with no frame end needed, should get next picture id
|
|
expectedVP8 = buffer.VP8{
|
|
FirstByte: 16,
|
|
PictureIDPresent: 1,
|
|
PictureID: 13468,
|
|
MBit: true,
|
|
TL0PICIDXPresent: 1,
|
|
TL0PICIDX: 234,
|
|
TIDPresent: 1,
|
|
TID: 0,
|
|
Y: 1,
|
|
KEYIDXPresent: 1,
|
|
KEYIDX: 24,
|
|
HeaderSize: 6,
|
|
IsKeyFrame: true,
|
|
}
|
|
blankVP8 = f.GetPaddingVP8(false)
|
|
require.True(t, reflect.DeepEqual(expectedVP8, *blankVP8))
|
|
}
|