mirror of
https://github.com/livekit/livekit.git
synced 2026-09-11 20:15:41 +00:00
Comes from #4718. Remove #2 of original PR as the simulcast SR looks good to me.
189 lines
6.1 KiB
Go
189 lines
6.1 KiB
Go
// Copyright 2026 LiveKit, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package buffer
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/pion/rtp"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/livekit/protocol/logger"
|
|
|
|
dd "github.com/livekit/livekit-server/pkg/sfu/rtpextension/dependencydescriptor"
|
|
)
|
|
|
|
const ddTestExtID = uint8(1)
|
|
|
|
// single spatial layer, single temporal layer, one decode target, one chain
|
|
func newL1T1Structure(structureID int) *dd.FrameDependencyStructure {
|
|
return &dd.FrameDependencyStructure{
|
|
StructureId: structureID,
|
|
NumDecodeTargets: 1,
|
|
NumChains: 1,
|
|
DecodeTargetProtectedByChain: []int{0},
|
|
Templates: []*dd.FrameDependencyTemplate{
|
|
{ // key frame
|
|
DecodeTargetIndications: []dd.DecodeTargetIndication{dd.DecodeTargetSwitch},
|
|
ChainDiffs: []int{0},
|
|
},
|
|
{ // delta frame
|
|
DecodeTargetIndications: []dd.DecodeTargetIndication{dd.DecodeTargetRequired},
|
|
FrameDiffs: []int{1},
|
|
ChainDiffs: []int{1},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
type ddTestFeeder struct {
|
|
t *testing.T
|
|
parser *DependencyDescriptorParser
|
|
// structure the writer marshals against, i. e. what the publisher last sent
|
|
structure *dd.FrameDependencyStructure
|
|
seq uint16
|
|
}
|
|
|
|
func newDDTestFeeder(t *testing.T) *ddTestFeeder {
|
|
return &ddTestFeeder{
|
|
t: t,
|
|
parser: NewDependencyDescriptorParser(ddTestExtID, logger.GetLogger(), func(int32, int32) {}, false),
|
|
}
|
|
}
|
|
|
|
// keyFrame sends a key frame carrying `structure`. Passing the same structure id as
|
|
// the previous key frame models a publisher that repeats an unchanged structure,
|
|
// which is what screen content with frequent key frames does.
|
|
func (f *ddTestFeeder) keyFrame(frameNumber uint16, structure *dd.FrameDependencyStructure) (*ExtDependencyDescriptor, error) {
|
|
f.structure = structure
|
|
return f.feed(frameNumber, structure.Templates[0], structure)
|
|
}
|
|
|
|
func (f *ddTestFeeder) deltaFrame(frameNumber uint16) (*ExtDependencyDescriptor, error) {
|
|
return f.feed(frameNumber, f.structure.Templates[1], nil)
|
|
}
|
|
|
|
func (f *ddTestFeeder) feed(
|
|
frameNumber uint16,
|
|
template *dd.FrameDependencyTemplate,
|
|
attachedStructure *dd.FrameDependencyStructure,
|
|
) (*ExtDependencyDescriptor, error) {
|
|
f.t.Helper()
|
|
|
|
ddVal := &dd.DependencyDescriptor{
|
|
FirstPacketInFrame: true,
|
|
LastPacketInFrame: true,
|
|
FrameNumber: frameNumber,
|
|
FrameDependencies: template,
|
|
AttachedStructure: attachedStructure,
|
|
}
|
|
buf, err := (&dd.DependencyDescriptorExtension{Descriptor: ddVal, Structure: f.structure}).Marshal()
|
|
require.NoError(f.t, err)
|
|
|
|
f.seq++
|
|
pkt := &rtp.Packet{Header: rtp.Header{SequenceNumber: f.seq}}
|
|
require.NoError(f.t, pkt.SetExtension(ddTestExtID, buf))
|
|
|
|
extDD, _, err := f.parser.Parse(pkt)
|
|
return extDD, err
|
|
}
|
|
|
|
// A key frame that repeats the current structure must not start dropping frames that
|
|
// precede it. Upstream advanced the drop threshold on every structure bearing key
|
|
// frame, so with frequent key frames (screen content) a late or retransmitted frame
|
|
// arriving after one was discarded as "earlier than current structure".
|
|
func TestDependencyDescriptorParserLateFrameAfterRepeatedStructure(t *testing.T) {
|
|
f := newDDTestFeeder(t)
|
|
structure := newL1T1Structure(0)
|
|
|
|
_, err := f.keyFrame(0, structure)
|
|
require.NoError(t, err)
|
|
_, err = f.deltaFrame(1)
|
|
require.NoError(t, err)
|
|
_, err = f.deltaFrame(3)
|
|
require.NoError(t, err)
|
|
|
|
// key frame repeating the same structure id
|
|
_, err = f.keyFrame(4, structure)
|
|
require.NoError(t, err)
|
|
|
|
// frame 2 finally shows up (reordered or retransmitted). The structure has not
|
|
// changed, so it is still decodable and must be forwarded.
|
|
extDD, err := f.deltaFrame(2)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, extDD)
|
|
require.EqualValues(t, 2, extDD.ExtFrameNum)
|
|
}
|
|
|
|
// A key frame carrying a *different* structure does invalidate everything before it:
|
|
// earlier frames reference templates that no longer exist.
|
|
func TestDependencyDescriptorParserLateFrameAfterStructureChange(t *testing.T) {
|
|
f := newDDTestFeeder(t)
|
|
|
|
_, err := f.keyFrame(0, newL1T1Structure(0))
|
|
require.NoError(t, err)
|
|
_, err = f.deltaFrame(1)
|
|
require.NoError(t, err)
|
|
_, err = f.deltaFrame(3)
|
|
require.NoError(t, err)
|
|
|
|
_, err = f.keyFrame(4, newL1T1Structure(1))
|
|
require.NoError(t, err)
|
|
|
|
_, err = f.deltaFrame(2)
|
|
require.ErrorIs(t, err, ErrFrameEarlierThanKeyFrame)
|
|
}
|
|
|
|
// An out-of-order key frame must still be dropped: accepting it would regress
|
|
// structureExtFrameNum (ExtKeyFrameNum) and replay a stale structure update.
|
|
func TestDependencyDescriptorParserOutOfOrderKeyFrame(t *testing.T) {
|
|
f := newDDTestFeeder(t)
|
|
structure := newL1T1Structure(0)
|
|
|
|
_, err := f.keyFrame(0, structure)
|
|
require.NoError(t, err)
|
|
_, err = f.deltaFrame(1)
|
|
require.NoError(t, err)
|
|
_, err = f.keyFrame(4, structure)
|
|
require.NoError(t, err)
|
|
|
|
_, err = f.keyFrame(2, structure)
|
|
require.ErrorIs(t, err, ErrFrameEarlierThanKeyFrame)
|
|
}
|
|
|
|
// ExtKeyFrameNum keeps tracking every structure bearing key frame, unchanged by the
|
|
// split of the drop threshold into its own field.
|
|
func TestDependencyDescriptorParserExtKeyFrameNum(t *testing.T) {
|
|
f := newDDTestFeeder(t)
|
|
structure := newL1T1Structure(0)
|
|
|
|
extDD, err := f.keyFrame(0, structure)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, extDD.ExtKeyFrameNum)
|
|
|
|
extDD, err = f.deltaFrame(1)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 0, extDD.ExtKeyFrameNum)
|
|
|
|
// repeated structure still advances ExtKeyFrameNum
|
|
extDD, err = f.keyFrame(4, structure)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 4, extDD.ExtKeyFrameNum)
|
|
|
|
extDD, err = f.deltaFrame(5)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 4, extDD.ExtKeyFrameNum)
|
|
}
|