snapshot of https://github.com/livekit/livekit.git @ 08c5433449c106943231037d0b892895f6e92d3b (2026-09-15)
Some checks failed
Release to Docker / docker (push) Failing after 2s
Release Test Server to Docker / docker (push) Failing after 3s
Test / test (push) Failing after 16s

This commit is contained in:
upstream-mirror
2026-09-15 16:14:22 +05:00
commit 78704103fc
422 changed files with 154805 additions and 0 deletions

34
pkg/sfu/utils/debounce.go Normal file
View File

@@ -0,0 +1,34 @@
package utils
import (
"sync"
"time"
)
func NewDebouncer(after time.Duration) *Debouncer {
return &Debouncer{
after: after,
}
}
type Debouncer struct {
mu sync.Mutex
after time.Duration
timer *time.Timer
}
func (d *Debouncer) Add(f func()) {
d.mu.Lock()
defer d.mu.Unlock()
if d.timer != nil {
d.timer.Stop()
}
d.timer = time.AfterFunc(d.after, f)
}
func (d *Debouncer) SetDuration(after time.Duration) {
d.mu.Lock()
d.after = after
d.mu.Unlock()
}

View File

@@ -0,0 +1,129 @@
// Copyright 2023 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 utils
import (
"sync"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils"
)
type sender interface {
SubscriberID() livekit.ParticipantID
}
type DownTrackSpreaderParams struct {
Threshold int
Logger logger.Logger
}
type DownTrackSpreader[T sender] struct {
params DownTrackSpreaderParams
downTrackMu sync.RWMutex
downTracks map[livekit.ParticipantID]T
downTracksShadow []T
}
func NewDownTrackSpreader[T sender](params DownTrackSpreaderParams) *DownTrackSpreader[T] {
d := &DownTrackSpreader[T]{
params: params,
downTracks: make(map[livekit.ParticipantID]T),
}
return d
}
func (d *DownTrackSpreader[T]) GetDownTracks() []T {
d.downTrackMu.RLock()
defer d.downTrackMu.RUnlock()
return d.downTracksShadow
}
func (d *DownTrackSpreader[T]) ResetAndGetDownTracks() []T {
d.downTrackMu.Lock()
defer d.downTrackMu.Unlock()
downTracks := d.downTracksShadow
d.downTracks = make(map[livekit.ParticipantID]T)
d.downTracksShadow = nil
return downTracks
}
func (d *DownTrackSpreader[T]) Store(sender T) {
d.downTrackMu.Lock()
defer d.downTrackMu.Unlock()
d.downTracks[sender.SubscriberID()] = sender
d.shadowDownTracks()
}
func (d *DownTrackSpreader[T]) Free(subscriberID livekit.ParticipantID) {
d.downTrackMu.Lock()
defer d.downTrackMu.Unlock()
delete(d.downTracks, subscriberID)
d.shadowDownTracks()
}
func (d *DownTrackSpreader[T]) HasDownTrack(subscriberID livekit.ParticipantID) bool {
d.downTrackMu.RLock()
defer d.downTrackMu.RUnlock()
_, ok := d.downTracks[subscriberID]
return ok
}
func (d *DownTrackSpreader[T]) Broadcast(writer func(T)) {
d.downTrackMu.RLock()
downTracks := d.downTracksShadow
threshold := uint64(d.params.Threshold)
d.downTrackMu.RUnlock()
if len(downTracks) == 0 {
return
}
if threshold == 0 {
threshold = 1000000
}
// 100µs is enough to amortize the overhead and provide sufficient load balancing.
// WriteRTP takes about 50µs on average, so we write to 2 down tracks per loop.
step := uint64(2)
utils.ParallelExec(downTracks, threshold, step, writer)
}
func (d *DownTrackSpreader[T]) DownTrackCount() int {
d.downTrackMu.RLock()
defer d.downTrackMu.RUnlock()
return len(d.downTracksShadow)
}
func (d *DownTrackSpreader[T]) shadowDownTracks() {
d.downTracksShadow = make([]T, 0, len(d.downTracks))
for _, dt := range d.downTracks {
d.downTracksShadow = append(d.downTracksShadow, dt)
}
}
func (d *DownTrackSpreader[T]) SetThreshold(threshold int) {
d.downTrackMu.Lock()
d.params.Threshold = threshold
d.downTrackMu.Unlock()
}

97
pkg/sfu/utils/helpers.go Normal file
View File

@@ -0,0 +1,97 @@
// Copyright 2023 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 utils
import (
"errors"
"fmt"
"github.com/pion/interceptor"
"github.com/pion/rtp"
"github.com/pion/webrtc/v4"
"github.com/livekit/protocol/codecs/mime"
"github.com/livekit/protocol/livekit"
)
// Do a fuzzy find for a codec in the list of codecs
// Used for lookup up a codec in an existing list to find a match
func CodecParametersFuzzySearch(needle webrtc.RTPCodecParameters, haystack []webrtc.RTPCodecParameters) (webrtc.RTPCodecParameters, error) {
// First attempt to match on MimeType + SDPFmtpLine
for _, c := range haystack {
if mime.IsMimeTypeStringEqual(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) &&
c.RTPCodecCapability.SDPFmtpLine == needle.RTPCodecCapability.SDPFmtpLine {
return c, nil
}
}
// Fallback to just MimeType
for _, c := range haystack {
if mime.IsMimeTypeStringEqual(c.RTPCodecCapability.MimeType, needle.RTPCodecCapability.MimeType) {
return c, nil
}
}
return webrtc.RTPCodecParameters{}, webrtc.ErrCodecNotFound
}
// Given a CodecParameters find the RTX CodecParameters if one exists
func FindRTXPayloadType(needle webrtc.PayloadType, haystack []webrtc.RTPCodecParameters) webrtc.PayloadType {
aptStr := fmt.Sprintf("apt=%d", needle)
for _, c := range haystack {
if aptStr == c.SDPFmtpLine {
return c.PayloadType
}
}
return webrtc.PayloadType(0)
}
// GetHeaderExtensionID returns the ID of a header extension, or 0 if not found
func GetHeaderExtensionID(extensions []interceptor.RTPHeaderExtension, extension webrtc.RTPHeaderExtensionCapability) int {
for _, h := range extensions {
if extension.URI == h.URI {
return h.ID
}
}
return 0
}
var (
ErrInvalidRTPVersion = errors.New("invalid RTP version")
ErrRTPPayloadTypeMismatch = errors.New("RTP payload type mismatch")
ErrRTPSSRCMismatch = errors.New("RTP SSRC mismatch")
)
// ValidateRTPPacket checks for a valid RTP packet and returns an error if fields are incorrect
func ValidateRTPPacket(pkt *rtp.Packet, expectedPayloadType uint8, expectedSSRC uint32) error {
if pkt.Version != 2 {
return fmt.Errorf("%w, expected: 2, actual: %d", ErrInvalidRTPVersion, pkt.Version)
}
if expectedPayloadType != 0 && pkt.PayloadType != expectedPayloadType {
return fmt.Errorf("%w, expected: %d, actual: %d", ErrRTPPayloadTypeMismatch, expectedPayloadType, pkt.PayloadType)
}
if expectedSSRC != 0 && pkt.SSRC != expectedSSRC {
return fmt.Errorf("%w, expected: %d, actual: %d", ErrRTPSSRCMismatch, expectedSSRC, pkt.SSRC)
}
return nil
}
func IsSimulcastMode(m livekit.VideoLayer_Mode) bool {
return m == livekit.VideoLayer_ONE_SPATIAL_LAYER_PER_STREAM || m == livekit.VideoLayer_ONE_SPATIAL_LAYER_PER_STREAM_INCOMPLETE_RTCP_SR
}

200
pkg/sfu/utils/rangemap.go Normal file
View File

@@ -0,0 +1,200 @@
// Copyright 2023 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 utils
import (
"errors"
"fmt"
"math"
"unsafe"
"go.uber.org/zap/zapcore"
)
const (
minRanges = 1
)
var (
errReversedOrder = errors.New("end <= start")
errKeyNotFound = errors.New("key not found")
errKeyTooOld = errors.New("key too old")
errKeyExcluded = errors.New("key excluded")
)
type rangeType interface {
uint32 | uint64
}
type valueType interface {
uint32 | uint64
}
// ---------------------------------------------------
type rangeVal[RT rangeType, VT valueType] struct {
start RT
end RT
value VT
}
func (r rangeVal[RT, VT]) MarshalLogObject(e zapcore.ObjectEncoder) error {
e.AddUint64("start", uint64(r.start))
e.AddUint64("end", uint64(r.end))
e.AddUint64("value", uint64(r.value))
return nil
}
// ---------------------------------------------------
type RangeMap[RT rangeType, VT valueType] struct {
halfRange RT
size int
ranges []rangeVal[RT, VT]
}
func NewRangeMap[RT rangeType, VT valueType](size int) *RangeMap[RT, VT] {
var t RT
r := &RangeMap[RT, VT]{
halfRange: 1 << ((unsafe.Sizeof(t) * 8) - 1),
size: int(math.Max(float64(size), float64(minRanges))),
}
r.initRanges(0, 0)
return r
}
func (r *RangeMap[RT, VT]) MarshalLogObject(e zapcore.ObjectEncoder) error {
e.AddInt("numRanges", len(r.ranges))
// just the last 10 ranges max
startIdx := max(len(r.ranges)-10, 0)
for i := startIdx; i < len(r.ranges); i++ {
e.AddObject(fmt.Sprintf("range[%d]", i), r.ranges[i])
}
return nil
}
func (r *RangeMap[RT, VT]) ClearAndResetValue(start RT, val VT) {
r.initRanges(start, val)
}
func (r *RangeMap[RT, VT]) DecValue(end RT, dec VT) {
lr := &r.ranges[len(r.ranges)-1]
if lr.start > end {
// modify existing value if end is in open range
lr.value -= dec
return
}
// close open range
lr.end = end
// start a new open one with decremented value
r.ranges = append(r.ranges, rangeVal[RT, VT]{
start: end + 1,
end: 0,
value: lr.value - dec,
})
r.prune()
}
func (r *RangeMap[RT, VT]) initRanges(start RT, val VT) {
r.ranges = []rangeVal[RT, VT]{
{
start: start,
end: 0,
value: val,
},
}
}
func (r *RangeMap[RT, VT]) ExcludeRange(startInclusive RT, endExclusive RT) error {
if endExclusive == startInclusive || endExclusive-startInclusive > r.halfRange {
return fmt.Errorf("%w, start %d, end %d", errReversedOrder, startInclusive, endExclusive)
}
lr := &r.ranges[len(r.ranges)-1]
if lr.start > startInclusive {
// start of open range is after start of exclusion range, cannot close the open range
return fmt.Errorf("%w, existingStart %d, newStart %d", errReversedOrder, lr.start, startInclusive)
}
newValue := lr.value + VT(endExclusive-startInclusive)
// if start of exclusion range matches start of open range, move the open range
if lr.start == startInclusive {
lr.start = endExclusive
lr.value = newValue
return nil
}
// close previous range
lr.end = startInclusive - 1
// start new open one after given exclusion range
r.ranges = append(r.ranges, rangeVal[RT, VT]{
start: endExclusive,
end: 0,
value: newValue,
})
r.prune()
return nil
}
func (r *RangeMap[RT, VT]) GetValue(key RT) (VT, error) {
numRanges := len(r.ranges)
if numRanges != 0 {
if key >= r.ranges[numRanges-1].start {
// in the open range
return r.ranges[numRanges-1].value, nil
}
if key < r.ranges[0].start {
// too old
return 0, errKeyTooOld
}
}
for idx := numRanges - 1; idx >= 0; idx-- {
rv := &r.ranges[idx]
if idx != numRanges-1 {
// open range checked above
if key-rv.start < r.halfRange && rv.end-key < r.halfRange {
return rv.value, nil
}
}
if idx > 0 {
rvPrev := &r.ranges[idx-1]
beforeDiff := key - rvPrev.end
afterDiff := rv.start - key
if beforeDiff > 0 && beforeDiff < r.halfRange && afterDiff > 0 && afterDiff < r.halfRange {
// in excluded range
return 0, errKeyExcluded
}
}
}
return 0, errKeyNotFound
}
func (r *RangeMap[RT, VT]) prune() {
if len(r.ranges) > r.size+1 { // +1 to accommodate the open range
r.ranges = r.ranges[len(r.ranges)-r.size-1:]
}
}

View File

@@ -0,0 +1,361 @@
// Copyright 2023 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 utils
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestRangeMapUint32(t *testing.T) {
r := NewRangeMap[uint32, uint32](2)
// getting value for any key should be 0 default
value, err := r.GetValue(33333)
require.NoError(t, err)
require.Equal(t, uint32(0), value)
expectedRanges := []rangeVal[uint32, uint32]{
{
start: 0,
end: 0,
value: 0,
},
}
require.Equal(t, expectedRanges, r.ranges)
// add an exclusion, should create a new range
err = r.ExcludeRange(10, 11)
require.NoError(t, err)
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 0,
end: 9,
value: 0,
},
{
start: 11,
end: 0,
value: 1,
},
}
require.Equal(t, expectedRanges, r.ranges)
// getting value in old range should return 0
value, err = r.GetValue(6)
require.NoError(t, err)
require.Equal(t, uint32(0), value)
// newer should return 1
value, err = r.GetValue(11)
require.NoError(t, err)
require.Equal(t, uint32(1), value)
// excluded range should return error
_, err = r.GetValue(10)
require.ErrorIs(t, err, errKeyExcluded)
// out-of-order exclusion should return error
err = r.ExcludeRange(9, 10)
require.ErrorIs(t, err, errReversedOrder)
// flipped exclusion should return error
err = r.ExcludeRange(12, 11)
require.ErrorIs(t, err, errReversedOrder)
err = r.ExcludeRange(11, 11)
require.ErrorIs(t, err, errReversedOrder)
// add adjacent exclusion range of length = 1
err = r.ExcludeRange(11, 12)
require.NoError(t, err)
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 0,
end: 9,
value: 0,
},
{
start: 12,
end: 0,
value: 2,
},
}
require.Equal(t, expectedRanges, r.ranges)
// excluded range should return error, now is excluded because exclusion range could be extended
_, err = r.GetValue(11)
require.ErrorIs(t, err, errKeyExcluded)
// getting value in old range should return 0
_, err = r.GetValue(6)
require.NoError(t, err)
// newer should return 2
value, err = r.GetValue(12)
require.NoError(t, err)
require.Equal(t, uint32(2), value)
// add adjacent exclusion range of length = 10
err = r.ExcludeRange(12, 22)
require.NoError(t, err)
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 0,
end: 9,
value: 0,
},
{
start: 22,
end: 0,
value: 12,
},
}
require.Equal(t, expectedRanges, r.ranges)
// excluded range should return error, now is excluded because exclusion range could be extended
_, err = r.GetValue(15)
require.ErrorIs(t, err, errKeyExcluded)
// newer should return 12
value, err = r.GetValue(25)
require.NoError(t, err)
require.Equal(t, uint32(12), value)
// add a disjoint exclusion of length = 4
err = r.ExcludeRange(26, 30)
require.NoError(t, err)
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 0,
end: 9,
value: 0,
},
{
start: 22,
end: 25,
value: 12,
},
{
start: 30,
end: 0,
value: 16,
},
}
require.Equal(t, expectedRanges, r.ranges)
// get a value from newly closed range [22, 25]
value, err = r.GetValue(23)
require.NoError(t, err)
require.Equal(t, uint32(12), value)
// add a disjoint exclusion of length = 1
err = r.ExcludeRange(50, 51)
require.NoError(t, err)
// previously first range would have been pruned due to size limitations
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 22,
end: 25,
value: 12,
},
{
start: 30,
end: 49,
value: 16,
},
{
start: 51,
end: 0,
value: 17,
},
}
require.Equal(t, expectedRanges, r.ranges)
// excluded range should return error
_, err = r.GetValue(50)
require.ErrorIs(t, err, errKeyExcluded)
_, err = r.GetValue(28)
require.ErrorIs(t, err, errKeyExcluded)
_, err = r.GetValue(17)
require.ErrorIs(t, err, errKeyTooOld)
// previously valid, but aged out key should return error
_, err = r.GetValue(5)
require.ErrorIs(t, err, errKeyTooOld)
// valid range access should return values
value, err = r.GetValue(24)
require.NoError(t, err)
require.Equal(t, uint32(12), value)
value, err = r.GetValue(34)
require.NoError(t, err)
require.Equal(t, uint32(16), value)
value, err = r.GetValue(49)
require.NoError(t, err)
require.Equal(t, uint32(16), value)
value, err = r.GetValue(55555555)
require.NoError(t, err)
require.Equal(t, uint32(17), value)
// reset
r.ClearAndResetValue(24, 23)
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 24,
end: 0,
value: 23,
},
}
require.Equal(t, expectedRanges, r.ranges)
value, err = r.GetValue(55555555)
require.NoError(t, err)
require.Equal(t, uint32(23), value)
// decrement value and ensure that any key after start in ClearAndResetValue above returns that value
// (as given end is higher than open range start, open range should be closed and a new range added)
r.DecValue(34, 12)
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 24,
end: 34,
value: 23,
},
{
start: 35,
end: 0,
value: 11,
},
}
require.Equal(t, expectedRanges, r.ranges)
value, err = r.GetValue(55555555)
require.NoError(t, err)
require.Equal(t, uint32(11), value)
// add an exclusion and then decrement value
err = r.ExcludeRange(40, 45)
require.NoError(t, err)
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 24,
end: 34,
value: 23,
},
{
start: 35,
end: 39,
value: 11,
},
{
start: 45,
end: 0,
value: 16,
},
}
require.Equal(t, expectedRanges, r.ranges)
// before first range access
_, err = r.GetValue(5)
require.ErrorIs(t, err, errKeyTooOld)
// first range access
value, err = r.GetValue(25)
require.NoError(t, err)
require.Equal(t, uint32(23), value)
// second range access
value, err = r.GetValue(35)
require.NoError(t, err)
require.Equal(t, uint32(11), value)
// open range access
value, err = r.GetValue(55555555)
require.NoError(t, err)
require.Equal(t, uint32(16), value)
r.DecValue(66, 6)
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 35,
end: 39,
value: 11,
},
{
start: 45,
end: 66,
value: 16,
},
{
start: 67,
end: 0,
value: 10,
},
}
require.Equal(t, expectedRanges, r.ranges)
// aged out range access
_, err = r.GetValue(25)
require.ErrorIs(t, err, errKeyTooOld)
// access closed range before decrementing value
value, err = r.GetValue(66)
require.NoError(t, err)
require.Equal(t, uint32(16), value)
// open range access
value, err = r.GetValue(67)
require.NoError(t, err)
require.Equal(t, uint32(10), value)
// decrement with old end and check that open range gets decremented
r.DecValue(66, 6)
expectedRanges = []rangeVal[uint32, uint32]{
{
start: 35,
end: 39,
value: 11,
},
{
start: 45,
end: 66,
value: 16,
},
{
start: 67,
end: 0,
value: 4,
},
}
require.Equal(t, expectedRanges, r.ranges)
// open range access should get decremented value
value, err = r.GetValue(67)
require.NoError(t, err)
require.Equal(t, uint32(4), value)
}