Files
upstream-livekit/pkg/sfu/datachannel/bitrate.go
upstream-mirror 78704103fc
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
snapshot of https://github.com/livekit/livekit.git @ 08c5433449c106943231037d0b892895f6e92d3b (2026-09-15)
2026-09-15 16:14:22 +05:00

176 lines
4.3 KiB
Go

package datachannel
import (
"sync"
"time"
"github.com/gammazero/deque"
"github.com/livekit/protocol/utils/mono"
)
const (
BitrateDuration = 2 * time.Second
BitrateWindow = 100 * time.Millisecond
)
// BitrateMode selects how the sliding-window rate is computed.
type BitrateMode int
const (
// BitrateModeBusyOnly divides drained bytes by backlogged ("busy") time
// only; idle gaps are excluded from the denominator. All drained bytes count.
BitrateModeBusyOnly BitrateMode = iota
// BitrateModeExcludeIdleDrain is like BitrateModeBusyOnly but also excludes
// bytes drained across a non-backlogged interval from the numerator.
BitrateModeExcludeIdleDrain
// BitrateModeWallClock divides all bytes by elapsed wall-clock time. For
// consumers with no send buffer, e.g. a receiver measuring its incoming rate.
BitrateModeWallClock
)
func (m BitrateMode) String() string {
switch m {
case BitrateModeBusyOnly:
return "busyOnly"
case BitrateModeExcludeIdleDrain:
return "excludeIdleDrain"
case BitrateModeWallClock:
return "wallClock"
default:
return "unknown"
}
}
// BitrateCalculator calculates bitrate over sliding window
type BitrateCalculator struct {
lock sync.Mutex
windowDuration time.Duration
duration time.Duration
windows deque.Deque[bitrateWindow]
active bitrateWindow
bytes int
busy time.Duration
lastBufferedAmount int
last time.Time
start time.Time
mode BitrateMode
}
// NewBitrateCalculator creates a calculator. See BitrateMode for how mode
// governs the numerator and denominator.
func NewBitrateCalculator(duration time.Duration, window time.Duration, mode BitrateMode) *BitrateCalculator {
windowCnt := int((duration + (window - 1)) / window)
if windowCnt == 0 {
windowCnt = 1
}
now := mono.Now()
c := &BitrateCalculator{
duration: duration,
windowDuration: window,
start: now,
active: bitrateWindow{start: now},
mode: mode,
}
c.windows.SetBaseCap(windowCnt + 1)
return c
}
func (c *BitrateCalculator) AddBytes(bytes int, bufferedAmout int, ts time.Time) {
c.lock.Lock()
defer c.lock.Unlock()
// bufferedAmout is sampled right after the write, so it includes the bytes
// just enqueued; subtracting them gives the buffer occupancy before the
// write. Between writes the buffer only drains, so a non-zero pre-write
// level means it stayed backlogged for the whole interval.
backlogged := bufferedAmout-bytes > 0
var busy time.Duration
if !c.last.IsZero() && backlogged {
if busy = ts.Sub(c.last); busy < 0 {
busy = 0
}
}
c.last = ts
// bytes that actually drained since the previous sample
bytes -= bufferedAmout - c.lastBufferedAmount
if bytes < 0 {
// it is possible that internal buffering (non-data like DCEP packet from webrtc) caused bytes to be negative
bytes = 0
}
c.lastBufferedAmount = bufferedAmout
if c.mode == BitrateModeExcludeIdleDrain && !backlogged {
// drain across a non-backlogged interval finished at an unknown point,
// so it is not a reliable rate sample
bytes = 0
}
if ts.Sub(c.active.start) >= c.windowDuration {
c.windows.PushBack(c.active)
c.active.start = ts
c.active.bytes = 0
c.active.busy = 0
for c.windows.Len() > 0 {
// pop expired windows
if w := c.windows.Front(); ts.Sub(w.start) > (c.duration + c.windowDuration) {
c.bytes -= w.bytes
c.busy -= w.busy
c.windows.PopFront()
} else {
c.start = w.start
break
}
}
if c.windows.Len() == 0 {
c.start = ts
c.bytes = 0
c.busy = 0
}
}
c.bytes += bytes
c.active.bytes += bytes
c.busy += busy
c.active.busy += busy
}
func (c *BitrateCalculator) Bitrate(ts time.Time) (int, bool) {
return c.bitrate(ts, false)
}
func (c *BitrateCalculator) ForceBitrate(ts time.Time) (int, bool) {
return c.bitrate(ts, true)
}
func (c *BitrateCalculator) bitrate(ts time.Time, force bool) (int, bool) {
c.lock.Lock()
defer c.lock.Unlock()
// busy modes divide by backlogged time; wall-clock mode by elapsed time
denom := c.busy
if c.mode == BitrateModeWallClock {
denom = ts.Sub(c.start)
}
if denom < c.windowDuration {
if force {
denom = c.windowDuration
} else {
return 0, false
}
}
return c.bytes * 8 * 1000 / int(denom.Milliseconds()), true
}
type bitrateWindow struct {
start time.Time
bytes int
busy time.Duration
}