Move probe observer to pacer (#3214)

* Probe ID pass

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* WIP

* clean up

* typo

* populate desired bytes

* correct num probes calculation

* debug log

* remove unused constant

* log channel observer

* debug

* clear isInProbe flag on end

* clear probe flag on reset

* re-arrange
This commit is contained in:
Raja Subramanian
2024-11-29 09:19:48 +05:30
committed by GitHub
parent 3372e6e285
commit 427ed23478
10 changed files with 442 additions and 405 deletions
-7
View File
@@ -1890,13 +1890,6 @@ func (p *ParticipantImpl) onDataMessage(kind livekit.DataPacket_Kind, data []byt
}
shouldForwardData = false
shouldForwardMetrics = true
// METRICS-TODO-QUESTIONS:
// 1. Should this record (and do processing/batching) metrics (i. e. publisher side) rather
// than forwarding and recording/processing/batching at every subscriber (in this case
// subscriber is defined as the other participants pushing this to edge client).
// 2. If the above is done, there could be two cadences, publisher side recording/processing/batching
// and pushing it to all subscribers on some cadence and subscribers have their own cadence of
// processing/batching and sending to edge clients.
p.metricTimestamper.Process(payload.Metrics)
case *livekit.DataPacket_RpcRequest:
if payload.RpcRequest == nil {
+9 -8
View File
@@ -452,13 +452,6 @@ func NewPCTransport(params TransportParams) (*PCTransport, error) {
lastNegotiate: time.Now(),
}
if params.IsSendSide {
t.streamAllocator = streamallocator.NewStreamAllocator(streamallocator.StreamAllocatorParams{
Config: params.CongestionControlConfig.StreamAllocator,
Logger: params.Logger.WithComponent(utils.ComponentCongestionControl),
}, params.CongestionControlConfig.Enabled, params.CongestionControlConfig.AllowPause)
t.streamAllocator.OnStreamStateChange(params.Handler.OnStreamStateChange)
t.streamAllocator.Start()
if params.CongestionControlConfig.UseSendSideBWE || params.UseSendSideBWE {
params.Logger.Infow("using send side BWE")
t.bwe = sendsidebwe.NewSendSideBWE(sendsidebwe.SendSideBWEParams{
@@ -473,7 +466,15 @@ func NewPCTransport(params TransportParams) (*PCTransport, error) {
})
t.pacer = pacer.NewPassThrough(params.Logger, nil)
}
t.streamAllocator.SetBWE(t.bwe)
t.streamAllocator = streamallocator.NewStreamAllocator(streamallocator.StreamAllocatorParams{
Config: params.CongestionControlConfig.StreamAllocator,
BWE: t.bwe,
Pacer: t.pacer,
Logger: params.Logger.WithComponent(utils.ComponentCongestionControl),
}, params.CongestionControlConfig.Enabled, params.CongestionControlConfig.AllowPause)
t.streamAllocator.OnStreamStateChange(params.Handler.OnStreamStateChange)
t.streamAllocator.Start()
}
if err := t.createPeerConnection(); err != nil {
+4 -6
View File
@@ -24,10 +24,6 @@ import (
"github.com/livekit/protocol/utils/mono"
)
const (
ChannelCapacityInfinity = 100 * 1000 * 1000 // 100 Mbps
)
// ---------------------------------------------------------------------------
type RemoteBWEConfig struct {
@@ -110,6 +106,7 @@ func (r *RemoteBWE) Reset() {
defer r.lock.Unlock()
r.channelObserver = r.newChannelObserverNonProbe()
r.isInProbe = false
}
func (r *RemoteBWE) Stop() {
@@ -205,7 +202,7 @@ func (r *RemoteBWE) estimateAvailableChannelCapacity(reason channelCongestionRea
"channel", r.channelObserver,
)
if estimateToCommit > commitThreshold {
ulgr.Debugw("remote bwe: channel congestion detected, skipping above commit threshold channel capacity update")
ulgr.Debugw("remote bwe: channel congestion detected, skipping above commit threshold channel capacity update")
return false
}
@@ -298,7 +295,7 @@ func (r *RemoteBWE) ProbingEnd(isNotFailing bool, isGoalReached bool) {
// the send side is in full control of bandwidth estimation.
//
r.params.Logger.Debugw(
"probe done",
"remote bwe: probe done",
"isNotFailing", isNotFailing,
"isGoalReached", isGoalReached,
"committedEstimate", r.committedChannelCapacity,
@@ -306,6 +303,7 @@ func (r *RemoteBWE) ProbingEnd(isNotFailing bool, isGoalReached bool) {
"channel", r.channelObserver,
)
r.channelObserver = r.newChannelObserverNonProbe()
r.isInProbe = false
if !isNotFailing {
return
}
+151 -206
View File
@@ -49,7 +49,7 @@
// are paused (could be due to downstream bandwidth
// constraints or the corresponding upstream tracks may
// have paused due to upstream bandwidth constraints).
// Another issue is not being to have tight control on
// Another issue is not being able to have tight control on
// probing window boundary as the packet forwarding path
// may not have a packet to forward. But, it should not
// be a major concern as long as some stream(s) is/are
@@ -126,14 +126,14 @@ import (
"github.com/gammazero/deque"
"go.uber.org/atomic"
"go.uber.org/zap/zapcore"
"github.com/livekit/protocol/logger"
)
type ProberListener interface {
OnSendProbe(bytesToSend int)
OnProbeClusterDone(info ProbeClusterInfo)
OnActiveChanged(isActive bool)
OnProbeClusterSwitch(probeClusterId ProbeClusterId, desiredBytes int)
}
type ProberParams struct {
@@ -146,11 +146,9 @@ type Prober struct {
clusterId atomic.Uint32
clustersMu sync.RWMutex
clusters deque.Deque[*Cluster]
activeCluster *Cluster
activeStateQueue []bool
activeStateQueueInProcess atomic.Bool
clustersMu sync.RWMutex
clusters deque.Deque[*Cluster]
activeCluster *Cluster
}
func NewProber(params ProberParams) *Prober {
@@ -168,33 +166,27 @@ func (p *Prober) IsRunning() bool {
return p.clusters.Len() > 0
}
func (p *Prober) Reset() {
reset := false
var info ProbeClusterInfo
func (p *Prober) Reset(info ProbeClusterInfo) {
p.clustersMu.Lock()
defer p.clustersMu.Unlock()
if p.activeCluster != nil {
p.params.Logger.Debugw("prober: resetting active cluster", "cluster", p.activeCluster.String())
reset = true
info = p.activeCluster.GetInfo()
if p.activeCluster.Id() == info.ProbeClusterId {
p.activeCluster.MarkCompleted(info)
p.params.Logger.Debugw("prober: resetting active cluster", "cluster", p.activeCluster)
}
}
p.clusters.Clear()
p.activeCluster = nil
p.activeStateQueue = append(p.activeStateQueue, false)
p.clustersMu.Unlock()
if reset {
if p.params.Listener != nil {
p.params.Listener.OnProbeClusterDone(info)
}
}
p.processActiveStateQueue()
}
func (p *Prober) AddCluster(mode ProbeClusterMode, desiredRateBps int, expectedRateBps int, minDuration time.Duration, maxDuration time.Duration) ProbeClusterId {
func (p *Prober) AddCluster(
mode ProbeClusterMode,
desiredRateBps int,
expectedRateBps int,
duration time.Duration,
) ProbeClusterId {
if desiredRateBps <= 0 {
return ProbeClusterIdInvalid
}
@@ -205,33 +197,38 @@ func (p *Prober) AddCluster(mode ProbeClusterMode, desiredRateBps int, expectedR
mode,
desiredRateBps,
expectedRateBps,
minDuration,
maxDuration,
duration,
p.params.Listener,
)
p.params.Logger.Debugw("cluster added", "cluster", cluster.String())
p.params.Logger.Debugw("cluster added", "cluster", cluster)
p.pushBackClusterAndMaybeStart(cluster)
return clusterId
}
func (p *Prober) PacketsSent(size int) {
func (p *Prober) ClusterDone(info ProbeClusterInfo) {
cluster := p.getFrontCluster()
if cluster == nil {
return
}
cluster.PacketsSent(size)
if cluster.Id() == info.ProbeClusterId {
cluster.MarkCompleted(info)
p.params.Logger.Debugw("cluster done", "cluster", cluster)
p.popFrontCluster(cluster)
}
}
func (p *Prober) ProbeSent(size int) {
cluster := p.getFrontCluster()
if cluster == nil {
return
func (p *Prober) GetActiveClusterId() ProbeClusterId {
p.clustersMu.RLock()
defer p.clustersMu.RUnlock()
if p.activeCluster != nil {
return p.activeCluster.Id()
}
cluster.ProbeSent(size)
return ProbeClusterIdInvalid
}
func (p *Prober) getFrontCluster() *Cluster {
@@ -268,12 +265,7 @@ func (p *Prober) popFrontCluster(cluster *Cluster) {
p.activeCluster = nil
}
if p.clusters.Len() == 0 {
p.activeStateQueue = append(p.activeStateQueue, false)
}
p.clustersMu.Unlock()
p.processActiveStateQueue()
}
func (p *Prober) pushBackClusterAndMaybeStart(cluster *Cluster) {
@@ -281,48 +273,19 @@ func (p *Prober) pushBackClusterAndMaybeStart(cluster *Cluster) {
p.clusters.PushBack(cluster)
if p.clusters.Len() == 1 {
p.activeStateQueue = append(p.activeStateQueue, true)
go p.run()
}
p.clustersMu.Unlock()
p.processActiveStateQueue()
}
func (p *Prober) processActiveStateQueue() {
if p.activeStateQueueInProcess.Swap(true) {
// processing queue
return
}
for {
p.clustersMu.Lock()
if len(p.activeStateQueue) == 0 {
p.clustersMu.Unlock()
break
}
isActive := p.activeStateQueue[0]
p.activeStateQueue = p.activeStateQueue[1:]
p.clustersMu.Unlock()
if p.params.Listener != nil {
p.params.Listener.OnActiveChanged(isActive)
}
}
p.activeStateQueueInProcess.Store(false)
}
func (p *Prober) run() {
// determine how long to sleep
cluster := p.getFrontCluster()
if cluster == nil {
return
}
timer := time.NewTimer(cluster.GetSleepDuration())
defer timer.Stop()
for {
<-timer.C
@@ -334,22 +297,6 @@ func (p *Prober) run() {
cluster.Process()
if cluster.IsFinished() {
p.params.Logger.Debugw("cluster finished", "cluster", cluster.String())
if p.params.Listener != nil {
p.params.Listener.OnProbeClusterDone(cluster.GetInfo())
}
p.popFrontCluster(cluster)
}
// determine how long to sleep
cluster := p.getFrontCluster()
if cluster == nil {
return
}
timer.Reset(cluster.GetSleepDuration())
}
}
@@ -362,7 +309,7 @@ const (
ProbeClusterIdInvalid ProbeClusterId = 0
cBucketDuration = 100 * time.Millisecond
cBytesPerProbe = 1000
cBytesPerProbe = 1100 // padding only packets are 255 bytes max + 20 byte header = 4 packets per probe
cMinProbeRateBps = 10000
)
@@ -389,33 +336,74 @@ func (p ProbeClusterMode) String() string {
// ---------------------------------------------------------------------------
type ProbeClusterInfo struct {
Id ProbeClusterId
BytesSent int
Duration time.Duration
ProbeClusterId ProbeClusterId
DesiredBytes int
StartTime int64
EndTime int64
BytesProbe int
BytesNonProbePrimary int
BytesNonProbeRTX int
}
type clusterBucket struct {
desiredBytes int
desiredElapsedTime time.Duration
sleepDuration time.Duration
var (
ProbeClusterInfoInvalid = ProbeClusterInfo{ProbeClusterId: ProbeClusterIdInvalid}
)
func (p ProbeClusterInfo) Bytes() int {
return p.BytesProbe + p.BytesNonProbePrimary + p.BytesNonProbeRTX
}
func (p ProbeClusterInfo) Duration() time.Duration {
return time.Duration(p.EndTime - p.StartTime)
}
func (p ProbeClusterInfo) MarshalLogObject(e zapcore.ObjectEncoder) error {
e.AddUint32("ProbeClusterId", uint32(p.ProbeClusterId))
e.AddInt("DesiredBytes", p.DesiredBytes)
e.AddTime("StartTime", time.Unix(0, p.StartTime))
e.AddTime("EndTime", time.Unix(0, p.EndTime))
e.AddDuration("Duration", p.Duration())
e.AddInt("BytesProbe", p.BytesProbe)
e.AddInt("BytesNonProbePrimary", p.BytesNonProbePrimary)
e.AddInt("BytesNonProbeRTX", p.BytesNonProbeRTX)
e.AddInt("Bytes", p.Bytes())
return nil
}
// ---------------------------------------------------------------------------
type clusterBucket struct {
desiredNumProbes int
desiredBytes int
sleepDuration time.Duration
}
func (c clusterBucket) MarshalLogObject(e zapcore.ObjectEncoder) error {
e.AddInt("desiredNumProbes", c.desiredNumProbes)
e.AddInt("desiredBytes", c.desiredBytes)
e.AddDuration("sleepDuration", c.sleepDuration)
return nil
}
// ---------------------------------------------------------------------------
type Cluster struct {
lock sync.RWMutex
id ProbeClusterId
mode ProbeClusterMode
listener ProberListener
desiredBytes int
minDuration time.Duration
maxDuration time.Duration
id ProbeClusterId
mode ProbeClusterMode
desiredRateBps int
expectedRateBps int
listener ProberListener
desiredBytes int
duration time.Duration
buckets []clusterBucket
bucketIdx int
bytesSentProbe int
bytesSentNonProbe int
startTime time.Time
numProbesSent int
isComplete bool
probeClusterInfo ProbeClusterInfo
}
func newCluster(
@@ -423,26 +411,26 @@ func newCluster(
mode ProbeClusterMode,
desiredRateBps int,
expectedRateBps int,
minDuration time.Duration,
maxDuration time.Duration,
duration time.Duration,
listener ProberListener,
) *Cluster {
c := &Cluster{
id: id,
mode: mode,
listener: listener,
minDuration: minDuration,
maxDuration: maxDuration,
id: id,
mode: mode,
desiredRateBps: desiredRateBps,
expectedRateBps: expectedRateBps,
listener: listener,
duration: duration,
}
c.initBuckets(desiredRateBps, expectedRateBps, minDuration)
c.initBuckets(desiredRateBps, expectedRateBps, duration)
c.desiredBytes = c.buckets[len(c.buckets)-1].desiredBytes
return c
}
func (c *Cluster) initBuckets(desiredRateBps int, expectedRateBps int, minDuration time.Duration) {
func (c *Cluster) initBuckets(desiredRateBps int, expectedRateBps int, duration time.Duration) {
// split into granular buckets
// NOTE: splitting even if mode is unitform
numBuckets := int((minDuration.Milliseconds() + cBucketDuration.Milliseconds() - 1) / cBucketDuration.Milliseconds())
numBuckets := int((duration.Milliseconds() + cBucketDuration.Milliseconds() - 1) / cBucketDuration.Milliseconds())
if numBuckets < 1 {
numBuckets = 1
}
@@ -451,7 +439,8 @@ func (c *Cluster) initBuckets(desiredRateBps int, expectedRateBps int, minDurati
baseProbeRateBps := (desiredRateBps - expectedRateBps + numBuckets - 1) / numBuckets
runningDesiredBytes := 0
runningDesiredElapsedTime := time.Duration(0)
runningExpectedBytes := 0
runningNumProbes := 0
c.buckets = make([]clusterBucket, 0, numBuckets)
for bucketIdx := 0; bucketIdx < numBuckets; bucketIdx++ {
@@ -466,27 +455,29 @@ func (c *Cluster) initBuckets(desiredRateBps int, expectedRateBps int, minDurati
}
bucketProbeRateBytesPerSec := (bucketProbeRateBps + 7) / 8
// pace based on bytes per probe
numProbesPerSec := (bucketProbeRateBytesPerSec + cBytesPerProbe - 1) / cBytesPerProbe
sleepDurationMicroSeconds := int(float64(1_000_000)/float64(numProbesPerSec) + 0.5)
runningDesiredBytes += (((bucketProbeRateBytesPerSec + expectedRateBytesPerSec) * int(cBucketDuration.Milliseconds())) + 999) / 1000
runningDesiredElapsedTime += cBucketDuration
runningExpectedBytes += ((expectedRateBytesPerSec * int(cBucketDuration.Milliseconds())) + 999) / 1000
numProbesNeeded := ((runningDesiredBytes - runningExpectedBytes) + cBytesPerProbe - 1) / cBytesPerProbe
numProbesInBucket := numProbesNeeded - runningNumProbes
if numProbesInBucket <= 0 {
numProbesInBucket = 1
}
runningNumProbes += numProbesInBucket
sleepDurationMicroSeconds := int(float64(cBucketDuration.Microseconds())/float64(numProbesInBucket) + 0.5)
c.buckets = append(c.buckets, clusterBucket{
desiredBytes: runningDesiredBytes,
desiredElapsedTime: runningDesiredElapsedTime,
sleepDuration: time.Duration(sleepDurationMicroSeconds) * time.Microsecond,
desiredNumProbes: runningNumProbes,
desiredBytes: runningDesiredBytes,
sleepDuration: time.Duration(sleepDurationMicroSeconds) * time.Microsecond,
})
}
}
func (c *Cluster) Start() {
c.lock.Lock()
defer c.lock.Unlock()
if c.startTime.IsZero() {
c.startTime = time.Now()
if c.listener != nil {
c.listener.OnProbeClusterSwitch(c.id, c.desiredBytes)
}
}
@@ -497,103 +488,57 @@ func (c *Cluster) GetSleepDuration() time.Duration {
return c.buckets[c.bucketIdx].sleepDuration
}
func (c *Cluster) PacketsSent(size int) {
func (c *Cluster) Id() ProbeClusterId {
return c.id
}
func (c *Cluster) MarkCompleted(info ProbeClusterInfo) {
c.lock.Lock()
defer c.lock.Unlock()
c.bytesSentNonProbe += size
}
func (c *Cluster) ProbeSent(size int) {
c.lock.Lock()
defer c.lock.Unlock()
c.bytesSentProbe += size
}
func (c *Cluster) IsFinished() bool {
c.lock.RLock()
defer c.lock.RUnlock()
// if already past deadline, end the cluster
timeElapsed := time.Since(c.startTime)
if timeElapsed > c.maxDuration {
return true
}
// do not end cluster until minDuration elapses even if rate is achieved.
// Ensures that the next cluster (if any) does not start early.
if (c.bytesSentProbe+c.bytesSentNonProbe) >= c.desiredBytes && timeElapsed >= c.minDuration {
return true
}
return false
}
func (c *Cluster) GetInfo() ProbeClusterInfo {
c.lock.RLock()
defer c.lock.RUnlock()
return ProbeClusterInfo{
Id: c.id,
BytesSent: c.bytesSentProbe + c.bytesSentNonProbe,
Duration: time.Since(c.startTime),
}
c.isComplete = true
c.probeClusterInfo = info
}
func (c *Cluster) Process() {
c.lock.RLock()
timeElapsed := time.Since(c.startTime)
// Calculate number of probe bytes that should have been sent since start.
// Overall goal is to send desired number of probe bytes in minDuration.
// However, it is possible that timeElapsed is more than minDuration due
// to scheduling variance. When overshooting time budget, use a capped
// short fall if there is a grace period given.
bytesShortFall := c.buckets[c.bucketIdx].desiredBytes - c.bytesSentProbe - c.bytesSentNonProbe
if bytesShortFall < 0 {
bytesShortFall = 0
c.lock.Lock()
if c.isComplete {
c.lock.Unlock()
return
}
// cap short fall to limit to 5 packets in an iteration
// 275 bytes per packet (255 max RTP padding payload + 20 bytes RTP header)
if bytesShortFall > (275 * 5) {
bytesShortFall = 275 * 5
}
// round up to packet size
bytesShortFall = ((bytesShortFall + 274) / 275) * 275
// move to next bucket if necessary
if timeElapsed > c.buckets[c.bucketIdx].desiredElapsedTime {
c.numProbesSent++
if c.numProbesSent >= c.buckets[c.bucketIdx].desiredNumProbes {
c.bucketIdx++
// stay in the last bucket till desired number of bytes are sent
if c.bucketIdx >= len(c.buckets) {
c.bucketIdx = len(c.buckets) - 1
}
}
c.lock.RUnlock()
c.lock.Unlock()
if bytesShortFall > 0 && c.listener != nil {
c.listener.OnSendProbe(bytesShortFall)
if c.listener != nil {
c.listener.OnSendProbe(cBytesPerProbe)
}
// STREAM-ALLOCATOR-TODO look at adapting sleep time based on how many bytes and how much time is left
}
func (c *Cluster) String() string {
activeTimeMs := int64(0)
if !c.startTime.IsZero() {
activeTimeMs = time.Since(c.startTime).Milliseconds()
func (c *Cluster) MarshalLogObject(e zapcore.ObjectEncoder) error {
if c != nil {
e.AddUint32("id", uint32(c.id))
e.AddString("mode", c.mode.String())
e.AddInt("desiredRateBps", c.desiredRateBps)
e.AddInt("expectedRateBps", c.expectedRateBps)
e.AddInt("desiredBytes", c.desiredBytes)
e.AddDuration("duration", c.duration)
e.AddArray("buckets", logger.ObjectSlice(c.buckets))
e.AddInt("bucketIdx", c.bucketIdx)
e.AddInt("numProbesSent", c.numProbesSent)
e.AddBool("isComplete", c.isComplete)
e.AddObject("probeClusterInfo", c.probeClusterInfo)
}
return fmt.Sprintf("id: %d, mode: %s, bytes: desired %d / probe %d / non-probe %d / remaining: %d, time(ms): active %d / min %d / max %d",
c.id,
c.mode,
c.desiredBytes,
c.bytesSentProbe,
c.bytesSentNonProbe,
c.desiredBytes-c.bytesSentProbe-c.bytesSentNonProbe,
activeTimeMs,
c.minDuration.Milliseconds(),
c.maxDuration.Milliseconds())
return nil
}
// ----------------------------------------------------------------------
+30 -59
View File
@@ -37,6 +37,7 @@ import (
"github.com/livekit/protocol/utils/mono"
"github.com/livekit/livekit-server/pkg/sfu/buffer"
"github.com/livekit/livekit-server/pkg/sfu/ccutils"
"github.com/livekit/livekit-server/pkg/sfu/connectionquality"
"github.com/livekit/livekit-server/pkg/sfu/pacer"
act "github.com/livekit/livekit-server/pkg/sfu/rtpextension/abscapturetime"
@@ -190,9 +191,6 @@ type DownTrackStreamAllocatorListener interface {
// stream resumed
OnResume(dt *DownTrack)
// packet(s) sent
OnPacketsSent(dt *DownTrack, size int)
/* STREAM-ALLOCATOR-DATA
// NACKs received
OnNACK(dt *DownTrack, nackInfos []NackInfo)
@@ -291,14 +289,13 @@ type DownTrack struct {
activePaddingOnMuteUpTrack atomic.Bool
streamAllocatorLock sync.RWMutex
streamAllocatorListener DownTrackStreamAllocatorListener
streamAllocatorReportGeneration int
streamAllocatorBytesCounter atomic.Uint32
streamAllocatorLock sync.RWMutex
streamAllocatorListener DownTrackStreamAllocatorListener
/* STREAM-ALLOCATOR-DATA
bytesSent atomic.Uint32
bytesRetransmitted atomic.Uint32
*/
probeClusterId atomic.Uint32
playoutDelay *PlayoutDelayController
@@ -627,48 +624,8 @@ func (d *DownTrack) getStreamAllocatorListener() DownTrackStreamAllocatorListene
return d.streamAllocatorListener
}
func (d *DownTrack) SetStreamAllocatorReportInterval(interval time.Duration) {
d.ClearStreamAllocatorReportInterval()
if interval == 0 {
return
}
d.streamAllocatorLock.Lock()
d.streamAllocatorBytesCounter.Store(0)
d.streamAllocatorReportGeneration++
gen := d.streamAllocatorReportGeneration
d.streamAllocatorLock.Unlock()
go func(generation int) {
timer := time.NewTimer(interval)
for {
<-timer.C
d.streamAllocatorLock.Lock()
if generation != d.streamAllocatorReportGeneration {
d.streamAllocatorLock.Unlock()
return
}
sal := d.streamAllocatorListener
bytes := d.streamAllocatorBytesCounter.Swap(0)
d.streamAllocatorLock.Unlock()
if sal != nil {
sal.OnPacketsSent(d, int(bytes))
}
timer.Reset(interval)
}
}(gen)
}
func (d *DownTrack) ClearStreamAllocatorReportInterval() {
d.streamAllocatorLock.Lock()
d.streamAllocatorReportGeneration++
d.streamAllocatorLock.Unlock()
func (d *DownTrack) SetProbeClusterId(probeClusterId ccutils.ProbeClusterId) {
d.probeClusterId.Store(uint32(probeClusterId))
}
// ID is the unique identifier for this Track. This should be unique for the
@@ -942,18 +899,21 @@ func (d *DownTrack) WriteRTP(extPkt *buffer.ExtPacket, layer int32) error {
)
}
headerSize := hdr.MarshalSize()
d.updateStats(updateStatsParams{
packetTime: extPkt.Arrival,
extSequenceNumber: tp.rtp.extSequenceNumber,
extTimestamp: tp.rtp.extTimestamp,
isOutOfOrder: extPkt.IsOutOfOrder,
headerSize: hdr.MarshalSize(),
headerSize: headerSize,
payloadSize: len(payload),
marker: hdr.Marker,
})
d.pacer.Enqueue(&pacer.Packet{
Header: hdr,
HeaderSize: headerSize,
Payload: payload,
ProbeClusterId: ccutils.ProbeClusterId(d.probeClusterId.Load()),
AbsSendTimeExtID: uint8(d.absSendTimeExtID),
TransportWideExtID: uint8(d.transportWideExtID),
WriteStream: d.writeStream,
@@ -986,7 +946,11 @@ func (d *DownTrack) WriteRTP(extPkt *buffer.ExtPacket, layer int32) error {
// WritePaddingRTP tries to write as many padding only RTP packets as necessary
// to satisfy given size to the DownTrack
func (d *DownTrack) WritePaddingRTP(bytesToSend int, paddingOnMute bool, forceMarker bool) int {
func (d *DownTrack) WritePaddingRTP(
bytesToSend int,
paddingOnMute bool,
forceMarker bool,
) int {
if !d.writable.Load() {
return 0
}
@@ -1070,7 +1034,10 @@ func (d *DownTrack) WritePaddingRTP(bytesToSend int, paddingOnMute bool, forceMa
})
d.pacer.Enqueue(&pacer.Packet{
Header: hdr,
HeaderSize: hdrSize,
Payload: payload,
ProbeClusterId: ccutils.ProbeClusterId(d.probeClusterId.Load()),
IsProbe: true,
AbsSendTimeExtID: uint8(d.absSendTimeExtID),
TransportWideExtID: uint8(d.transportWideExtID),
WriteStream: d.writeStream,
@@ -1227,8 +1194,6 @@ func (d *DownTrack) CloseWithFlush(flush bool) {
if onCloseHandler := d.getOnCloseHandler(); onCloseHandler != nil {
onCloseHandler(!flush)
}
d.ClearStreamAllocatorReportInterval()
}
func (d *DownTrack) SetMaxSpatialLayer(spatialLayer int32) {
@@ -1614,16 +1579,19 @@ func (d *DownTrack) writeBlankFrameRTP(duration float32, generation uint32) chan
return
}
headerSize := hdr.MarshalSize()
d.updateStats(updateStatsParams{
packetTime: mono.UnixNano(),
extSequenceNumber: snts[i].extSequenceNumber,
extTimestamp: snts[i].extTimestamp,
headerSize: hdr.MarshalSize(),
headerSize: headerSize,
payloadSize: len(payload),
})
d.pacer.Enqueue(&pacer.Packet{
Header: hdr,
HeaderSize: headerSize,
Payload: payload,
ProbeClusterId: ccutils.ProbeClusterId(d.probeClusterId.Load()),
AbsSendTimeExtID: uint8(d.absSendTimeExtID),
TransportWideExtID: uint8(d.transportWideExtID),
WriteStream: d.writeStream,
@@ -1964,18 +1932,21 @@ func (d *DownTrack) retransmitPackets(nacks []uint16) {
}
d.addDummyExtensions(&pkt.Header)
headerSize := pkt.Header.MarshalSize()
d.updateStats(updateStatsParams{
packetTime: mono.UnixNano(),
extSequenceNumber: epm.extSequenceNumber,
extTimestamp: epm.extTimestamp,
isOutOfOrder: true,
headerSize: pkt.Header.MarshalSize(),
headerSize: headerSize,
payloadSize: len(payload),
isRTX: true,
})
d.pacer.Enqueue(&pacer.Packet{
Header: &pkt.Header,
HeaderSize: headerSize,
Payload: payload,
ProbeClusterId: ccutils.ProbeClusterId(d.probeClusterId.Load()),
IsRTX: true,
AbsSendTimeExtID: uint8(d.absSendTimeExtID),
TransportWideExtID: uint8(d.transportWideExtID),
@@ -2178,18 +2149,21 @@ func (d *DownTrack) sendSilentFrameOnMuteForOpus() {
return
}
headerSize := hdr.MarshalSize()
d.updateStats(updateStatsParams{
packetTime: mono.UnixNano(),
extSequenceNumber: snts[i].extSequenceNumber,
extTimestamp: snts[i].extTimestamp,
headerSize: hdr.MarshalSize(),
headerSize: headerSize,
payloadSize: len(payload),
// although this is using empty frames, mark as padding as these are used to trigger Pion OnTrack only
isPadding: true,
})
d.pacer.Enqueue(&pacer.Packet{
Header: hdr,
HeaderSize: headerSize,
Payload: payload,
ProbeClusterId: ccutils.ProbeClusterId(d.probeClusterId.Load()),
AbsSendTimeExtID: uint8(d.absSendTimeExtID),
TransportWideExtID: uint8(d.transportWideExtID),
WriteStream: d.writeStream,
@@ -2235,9 +2209,6 @@ type updateStatsParams struct {
func (d *DownTrack) updateStats(params updateStatsParams) {
if !params.disableCounter {
// STREAM-ALLOCATOR-TODO: remove this stream allocator bytes counter once stream allocator changes fully to pull bytes counter
size := uint32(params.headerSize + params.payloadSize)
d.streamAllocatorBytesCounter.Add(size)
/* STREAM-ALLOCATOR-DATA
if params.isRTX {
d.bytesRetransmitted.Add(size)
+8 -3
View File
@@ -29,12 +29,15 @@ type Base struct {
logger logger.Logger
bwe bwe.BWE
*ProbeObserver
}
func NewBase(logger logger.Logger, bwe bwe.BWE) *Base {
return &Base{
logger: logger,
bwe: bwe,
logger: logger,
bwe: bwe,
ProbeObserver: NewProbeObserver(logger),
}
}
@@ -84,10 +87,11 @@ func (b *Base) patchRTPHeaderExtensions(p *Packet) error {
}
}
packetSize := p.HeaderSize + len(p.Payload)
if p.TransportWideExtID != 0 && b.bwe != nil {
twccSN := b.bwe.RecordPacketSendAndGetSequenceNumber(
sendingAt.UnixMicro(),
p.Header.MarshalSize()+len(p.Payload),
packetSize,
p.IsRTX,
)
twccExt := rtp.TransportCCExtension{
@@ -103,6 +107,7 @@ func (b *Base) patchRTPHeaderExtensions(p *Packet) error {
}
}
b.ProbeObserver.RecordPacket(packetSize, p.IsRTX, p.ProbeClusterId, p.IsProbe)
return nil
}
+12
View File
@@ -18,14 +18,18 @@ import (
"sync"
"time"
"github.com/livekit/livekit-server/pkg/sfu/ccutils"
"github.com/pion/rtp"
"github.com/pion/webrtc/v4"
)
type Packet struct {
Header *rtp.Header
HeaderSize int
Payload []byte
IsRTX bool
ProbeClusterId ccutils.ProbeClusterId
IsProbe bool
AbsSendTimeExtID uint8
TransportWideExtID uint8
WriteStream webrtc.TrackLocalWriter
@@ -39,6 +43,14 @@ type Pacer interface {
SetInterval(interval time.Duration)
SetBitrate(bitrate int)
SetPacerProbeObserverListener(listener PacerProbeObserverListener)
StartProbeCluster(probeClusterId ccutils.ProbeClusterId, desiredBytes int)
EndProbeCluster(probeClusterId ccutils.ProbeClusterId) ccutils.ProbeClusterInfo
}
type PacerProbeObserverListener interface {
OnPacerProbeObserverClusterComplete(probeClusterId ccutils.ProbeClusterId)
}
// ------------------------------------------------
+154
View File
@@ -0,0 +1,154 @@
// 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 pacer
import (
"sync"
"sync/atomic"
"github.com/livekit/livekit-server/pkg/sfu/ccutils"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils/mono"
)
type ProbeObserver struct {
logger logger.Logger
listener PacerProbeObserverListener
isInProbe atomic.Bool
lock sync.Mutex
clusterStartTime int64
activeProbeClusterId ccutils.ProbeClusterId
desiredProbeClusterBytes int
bytesNonProbePrimary int
bytesNonProbeRTX int
bytesProbe int
isActiveClusterDone bool
}
func NewProbeObserver(logger logger.Logger) *ProbeObserver {
return &ProbeObserver{
logger: logger,
}
}
func (po *ProbeObserver) SetPacerProbeObserverListener(listener PacerProbeObserverListener) {
po.listener = listener
}
func (po *ProbeObserver) StartProbeCluster(probeClusterId ccutils.ProbeClusterId, desiredBytes int) {
if po.isInProbe.Load() {
po.logger.Warnw(
"ignoring start of a new probe cluster when already active", nil,
"probeClusterId", probeClusterId,
"desiredBytes", desiredBytes,
)
return
}
po.lock.Lock()
defer po.lock.Unlock()
po.clusterStartTime = mono.UnixNano()
po.activeProbeClusterId = probeClusterId
po.desiredProbeClusterBytes = desiredBytes
po.bytesNonProbePrimary = 0
po.bytesNonProbeRTX = 0
po.bytesProbe = 0
po.isActiveClusterDone = false
po.isInProbe.Store(true)
}
func (po *ProbeObserver) EndProbeCluster(probeClusterId ccutils.ProbeClusterId) ccutils.ProbeClusterInfo {
if !po.isInProbe.Load() {
// probe not active
if probeClusterId != ccutils.ProbeClusterIdInvalid {
po.logger.Debugw(
"ignoring end of a probe cluster when not active",
"probeClusterId", probeClusterId,
)
}
return ccutils.ProbeClusterInfoInvalid
}
po.lock.Lock()
defer po.lock.Unlock()
if po.activeProbeClusterId != probeClusterId {
// probe cluster id not active
po.logger.Warnw(
"ignoring end of a probe cluster of a non-active one", nil,
"probeClusterId", probeClusterId,
"active", po.activeProbeClusterId,
)
return ccutils.ProbeClusterInfoInvalid
}
clusterInfo := ccutils.ProbeClusterInfo{
ProbeClusterId: po.activeProbeClusterId,
DesiredBytes: po.desiredProbeClusterBytes,
StartTime: po.clusterStartTime,
EndTime: mono.UnixNano(),
BytesProbe: po.bytesProbe,
BytesNonProbePrimary: po.bytesNonProbePrimary,
BytesNonProbeRTX: po.bytesNonProbeRTX,
}
po.activeProbeClusterId = ccutils.ProbeClusterIdInvalid
po.isInProbe.Store(false)
return clusterInfo
}
func (po *ProbeObserver) RecordPacket(size int, isRTX bool, probeClusterId ccutils.ProbeClusterId, isProbe bool) {
if !po.isInProbe.Load() {
return
}
po.lock.Lock()
if probeClusterId != po.activeProbeClusterId || po.isActiveClusterDone {
po.lock.Unlock()
return
}
if isProbe {
po.bytesProbe += size
} else {
if isRTX {
po.bytesNonProbeRTX += size
} else {
po.bytesNonProbePrimary += size
}
}
notify := false
var clusterId ccutils.ProbeClusterId
if !po.isActiveClusterDone && po.bytesProbe+po.bytesNonProbePrimary+po.bytesNonProbeRTX >= po.desiredProbeClusterBytes {
po.isActiveClusterDone = true
notify = true
clusterId = po.activeProbeClusterId
}
po.lock.Unlock()
if notify && po.listener != nil {
po.listener.OnPacerProbeObserverClusterComplete(clusterId)
}
}
// ------------------------------------------------
+17 -28
View File
@@ -20,6 +20,7 @@ import (
"github.com/livekit/livekit-server/pkg/sfu/bwe"
"github.com/livekit/livekit-server/pkg/sfu/ccutils"
"github.com/livekit/livekit-server/pkg/sfu/pacer"
"github.com/livekit/protocol/logger"
)
@@ -39,7 +40,6 @@ type ProbeControllerConfig struct {
MinBps int64 `yaml:"min_bps,omitempty"`
MinDuration time.Duration `yaml:"min_duration,omitempty"`
MaxDuration time.Duration `yaml:"max_duration,omitempty"`
DurationOverflowFactor float64 `yaml:"duration_overflow_factor,omitempty"`
DurationIncreaseFactor float64 `yaml:"duration_increase_factor,omitempty"`
}
@@ -58,7 +58,6 @@ var (
MinBps: 200_000,
MinDuration: 200 * time.Millisecond,
MaxDuration: 20 * time.Second,
DurationOverflowFactor: 1.25,
DurationIncreaseFactor: 1.5,
}
)
@@ -68,6 +67,8 @@ var (
type ProbeControllerParams struct {
Config ProbeControllerConfig
Prober *ccutils.Prober
BWE bwe.BWE
Pacer pacer.Pacer
Logger logger.Logger
}
@@ -75,7 +76,6 @@ type ProbeController struct {
params ProbeControllerParams
lock sync.RWMutex
bwe bwe.BWE
probeInterval time.Duration
lastProbeStartTime time.Time
probeGoalBps int64
@@ -98,13 +98,6 @@ func NewProbeController(params ProbeControllerParams) *ProbeController {
return p
}
func (p *ProbeController) SetBWE(bwe bwe.BWE) {
p.lock.Lock()
defer p.lock.Unlock()
p.bwe = bwe
}
func (p *ProbeController) Reset() {
p.lock.Lock()
defer p.lock.Unlock()
@@ -118,14 +111,15 @@ func (p *ProbeController) Reset() {
p.clearProbeLocked()
}
func (p *ProbeController) ProbeClusterDone(info ccutils.ProbeClusterInfo) {
func (p *ProbeController) ProbeClusterDone(probeClusterId ccutils.ProbeClusterId) {
p.lock.Lock()
defer p.lock.Unlock()
if p.probeClusterId != info.Id {
p.params.Logger.Debugw("not expected probe cluster", "probeClusterId", p.probeClusterId, "resetProbeClusterId", info.Id)
if p.probeClusterId != probeClusterId {
p.params.Logger.Debugw("not expected probe cluster", "probeClusterId", p.probeClusterId, "resetProbeClusterId", probeClusterId)
} else {
p.doneProbeClusterInfo = info
p.doneProbeClusterInfo = p.params.Pacer.EndProbeCluster(probeClusterId)
p.params.Prober.ClusterDone(p.doneProbeClusterInfo)
}
}
@@ -149,14 +143,14 @@ func (p *ProbeController) MaybeFinalizeProbe(
if (isComplete || p.abortedProbeClusterId != ccutils.ProbeClusterIdInvalid) &&
p.probeEndTime.IsZero() &&
p.doneProbeClusterInfo.Id != ccutils.ProbeClusterIdInvalid && p.doneProbeClusterInfo.Id == p.probeClusterId {
p.doneProbeClusterInfo.ProbeClusterId != ccutils.ProbeClusterIdInvalid && p.doneProbeClusterInfo.ProbeClusterId == p.probeClusterId {
// ensure any queueing due to probing is flushed
// STREAM-ALLOCATOR-TODO: ProbeControllerConfig.SettleWait should actually be a certain number of RTTs.
expectedDuration := float64(0.0)
if lowestEstimate != 0 {
expectedDuration = float64(p.doneProbeClusterInfo.BytesSent*8*1000) / float64(lowestEstimate)
expectedDuration = float64(p.doneProbeClusterInfo.Bytes()*8*1000) / float64(lowestEstimate)
}
queueTime := expectedDuration - float64(p.doneProbeClusterInfo.Duration.Milliseconds())
queueTime := expectedDuration - float64(p.doneProbeClusterInfo.Duration().Milliseconds())
if queueTime < 0.0 {
queueTime = 0.0
}
@@ -164,7 +158,7 @@ func (p *ProbeController) MaybeFinalizeProbe(
if queueWait > p.params.Config.SettleWaitMax {
queueWait = p.params.Config.SettleWaitMax
}
p.probeEndTime = p.lastProbeStartTime.Add(queueWait + p.doneProbeClusterInfo.Duration)
p.probeEndTime = p.lastProbeStartTime.Add(queueWait + p.doneProbeClusterInfo.Duration())
p.params.Logger.Debugw(
"setting probe end time",
"probeClusterId", p.probeClusterId,
@@ -223,7 +217,7 @@ func (p *ProbeController) InitProbe(probeGoalDeltaBps int64, expectedBandwidthUs
}
p.probeGoalBps = expectedBandwidthUsage + desiredIncreaseBps
p.doneProbeClusterInfo = ccutils.ProbeClusterInfo{Id: ccutils.ProbeClusterIdInvalid}
p.doneProbeClusterInfo = ccutils.ProbeClusterInfoInvalid
p.abortedProbeClusterId = ccutils.ProbeClusterIdInvalid
p.goalReachedProbeClusterId = ccutils.ProbeClusterIdInvalid
@@ -236,7 +230,6 @@ func (p *ProbeController) InitProbe(probeGoalDeltaBps int64, expectedBandwidthUs
int(p.probeGoalBps),
int(expectedBandwidthUsage),
p.probeDuration,
time.Duration(float64(p.probeDuration.Milliseconds())*p.params.Config.DurationOverflowFactor)*time.Millisecond,
)
p.pollProbe(p.probeClusterId, expectedBandwidthUsage)
@@ -245,11 +238,7 @@ func (p *ProbeController) InitProbe(probeGoalDeltaBps int64, expectedBandwidthUs
}
func (p *ProbeController) pollProbe(probeClusterId ccutils.ProbeClusterId, expectedBandwidthUsage int64) {
if p.bwe == nil {
return
}
p.bwe.ProbingStart(expectedBandwidthUsage)
p.params.BWE.ProbingStart(expectedBandwidthUsage)
go func() {
for {
@@ -261,7 +250,7 @@ func (p *ProbeController) pollProbe(probeClusterId ccutils.ProbeClusterId, expec
done := false
_, trend, _, highestEstimate := p.bwe.GetProbeStatus()
_, trend, _, highestEstimate := p.params.BWE.GetProbeStatus()
if !p.probeTrendObserved && trend != bwe.ChannelTrendNeutral {
p.probeTrendObserved = true
}
@@ -315,7 +304,7 @@ func (p *ProbeController) pollProbe(probeClusterId ccutils.ProbeClusterId, expec
func (p *ProbeController) clearProbeLocked() {
p.probeClusterId = ccutils.ProbeClusterIdInvalid
p.doneProbeClusterInfo = ccutils.ProbeClusterInfo{Id: ccutils.ProbeClusterIdInvalid}
p.doneProbeClusterInfo = ccutils.ProbeClusterInfoInvalid
p.abortedProbeClusterId = ccutils.ProbeClusterIdInvalid
p.goalReachedProbeClusterId = ccutils.ProbeClusterIdInvalid
}
@@ -343,7 +332,7 @@ func (p *ProbeController) increaseProbeDurationLocked() {
}
func (p *ProbeController) StopProbe() {
p.params.Prober.Reset()
p.params.Prober.Reset(p.params.Pacer.EndProbeCluster(p.probeClusterId))
}
func (p *ProbeController) AbortProbe() {
+57 -88
View File
@@ -32,6 +32,7 @@ import (
"github.com/livekit/livekit-server/pkg/sfu/buffer"
"github.com/livekit/livekit-server/pkg/sfu/bwe"
"github.com/livekit/livekit-server/pkg/sfu/ccutils"
"github.com/livekit/livekit-server/pkg/sfu/pacer"
"github.com/livekit/livekit-server/pkg/utils"
)
@@ -82,13 +83,13 @@ const (
streamAllocatorSignalEstimate
streamAllocatorSignalPeriodicPing
streamAllocatorSignalSendProbe
streamAllocatorSignalProbeClusterDone
streamAllocatorSignalResume
streamAllocatorSignalSetAllowPause
streamAllocatorSignalSetChannelCapacity
// STREAM-ALLOCATOR-DATA streamAllocatorSignalNACK
// STREAM-ALLOCATOR-DATA streamAllocatorSignalRTCPReceiverReport
streamAllocatorSignalCongestionStateChange
streamAllocatorSignalPacerProbeObserverClusterComplete
)
func (s streamAllocatorSignal) String() string {
@@ -105,8 +106,6 @@ func (s streamAllocatorSignal) String() string {
return "PERIODIC_PING"
case streamAllocatorSignalSendProbe:
return "SEND_PROBE"
case streamAllocatorSignalProbeClusterDone:
return "PROBE_CLUSTER_DONE"
case streamAllocatorSignalResume:
return "RESUME"
case streamAllocatorSignalSetAllowPause:
@@ -121,6 +120,8 @@ func (s streamAllocatorSignal) String() string {
*/
case streamAllocatorSignalCongestionStateChange:
return "CONGESTION_STATE_CHANGE"
case streamAllocatorSignalPacerProbeObserverClusterComplete:
return "PACER_PROBE_OBSERVER_CLUSTER_COMPLETE"
default:
return fmt.Sprintf("%d", int(s))
}
@@ -168,6 +169,8 @@ var (
type StreamAllocatorParams struct {
Config StreamAllocatorConfig
BWE bwe.BWE
Pacer pacer.Pacer
Logger logger.Logger
}
@@ -176,7 +179,6 @@ type StreamAllocator struct {
onStreamStateChange func(update *StreamStateUpdate) error
bwe bwe.BWE
sendSideBWEInterceptor cc.BandwidthEstimator
enabled bool
@@ -227,9 +229,14 @@ func NewStreamAllocator(params StreamAllocatorParams, enabled bool, allowPause b
s.probeController = NewProbeController(ProbeControllerParams{
Config: s.params.Config.ProbeController,
Prober: s.prober,
BWE: s.params.BWE,
Pacer: s.params.Pacer,
Logger: params.Logger,
})
s.params.BWE.SetBWEListener(s)
s.params.Pacer.SetPacerProbeObserverListener(s)
s.resetState()
return s
@@ -254,14 +261,6 @@ func (s *StreamAllocator) OnStreamStateChange(f func(update *StreamStateUpdate)
s.onStreamStateChange = f
}
func (s *StreamAllocator) SetBWE(bwe bwe.BWE) {
if bwe != nil {
bwe.SetBWEListener(s)
}
s.bwe = bwe
s.probeController.SetBWE(bwe)
}
func (s *StreamAllocator) SetSendSideBWEInterceptor(sendSideBWEInterceptor cc.BandwidthEstimator) {
if sendSideBWEInterceptor != nil {
sendSideBWEInterceptor.OnTargetBitrateChange(s.onTargetBitrateChange)
@@ -295,10 +294,7 @@ func (s *StreamAllocator) AddTrack(downTrack *sfu.DownTrack, params AddTrackPara
}
downTrack.SetStreamAllocatorListener(s)
if s.prober.IsRunning() {
// STREAM-ALLOCATOR-TODO: this can be changed to adapt to probe rate
downTrack.SetStreamAllocatorReportInterval(50 * time.Millisecond)
}
downTrack.SetProbeClusterId(s.prober.GetActiveClusterId())
s.maybePostEventAllocateTrack(downTrack)
}
@@ -346,9 +342,7 @@ func (s *StreamAllocator) SetChannelCapacity(channelCapacity int64) {
}
func (s *StreamAllocator) resetState() {
if s.bwe != nil {
s.bwe.Reset()
}
s.params.BWE.Reset()
s.probeController.Reset()
s.state = streamAllocatorStateStable
@@ -440,9 +434,7 @@ func (s *StreamAllocator) OnTransportCCFeedback(downTrack *sfu.DownTrack, fb *rt
s.sendSideBWEInterceptor.WriteRTCP([]rtcp.Packet{fb}, nil)
}
if s.bwe != nil {
s.bwe.HandleTWCCFeedback(fb)
}
s.params.BWE.HandleTWCCFeedback(fb)
}
// called when target bitrate changes (send side bandwidth estimation)
@@ -463,10 +455,7 @@ type congestionStateChangeData struct {
func (s *StreamAllocator) OnCongestionStateChange(congestionState bwe.CongestionState, estimatedAvailableChannelCapacity int64) {
s.postEvent(Event{
Signal: streamAllocatorSignalCongestionStateChange,
Data: congestionStateChangeData{
congestionState: congestionState,
estimatedAvailableChannelCapacity: estimatedAvailableChannelCapacity,
},
Data: congestionStateChangeData{congestionState, estimatedAvailableChannelCapacity},
})
}
@@ -522,11 +511,6 @@ func (s *StreamAllocator) OnResume(downTrack *sfu.DownTrack) {
})
}
// called by a video DownTrack to report packet send
func (s *StreamAllocator) OnPacketsSent(downTrack *sfu.DownTrack, size int) {
s.prober.PacketsSent(size)
}
/* STREAM-ALLOCATOR-DATA
// called by a video DownTrack when it processes NACKs
func (s *StreamAllocator) OnNACK(downTrack *sfu.DownTrack, nackInfos []sfu.NackInfo) {
@@ -556,24 +540,21 @@ func (s *StreamAllocator) OnSendProbe(bytesToSend int) {
})
}
// called when prober finishes a probe cluster, could be called when prober is reset which stops an active cluster
func (s *StreamAllocator) OnProbeClusterDone(info ccutils.ProbeClusterInfo) {
s.postEvent(Event{
Signal: streamAllocatorSignalProbeClusterDone,
Data: info,
})
// called when probe cluster changes
func (s *StreamAllocator) OnProbeClusterSwitch(probeClusterId ccutils.ProbeClusterId, desiredBytes int) {
s.params.Pacer.StartProbeCluster(probeClusterId, desiredBytes)
for _, t := range s.getTracks() {
t.DownTrack().SetProbeClusterId(probeClusterId)
}
}
// called when prober active state changes
func (s *StreamAllocator) OnActiveChanged(isActive bool) {
for _, t := range s.getTracks() {
if isActive {
// STREAM-ALLOCATOR-TODO: this can be changed to adapt to probe rate
t.DownTrack().SetStreamAllocatorReportInterval(50 * time.Millisecond)
} else {
t.DownTrack().ClearStreamAllocatorReportInterval()
}
}
// called when pacer probe observer observes a cluster completion
func (s *StreamAllocator) OnPacerProbeObserverClusterComplete(probeClusterId ccutils.ProbeClusterId) {
s.postEvent(Event{
Signal: streamAllocatorSignalPacerProbeObserverClusterComplete,
Data: probeClusterId,
})
}
// called to check if track should participate in BWE
@@ -652,8 +633,6 @@ func (s *StreamAllocator) postEvent(event Event) {
event.handleSignalPeriodicPing(event)
case streamAllocatorSignalSendProbe:
event.handleSignalSendProbe(event)
case streamAllocatorSignalProbeClusterDone:
event.handleSignalProbeClusterDone(event)
case streamAllocatorSignalResume:
event.handleSignalResume(event)
case streamAllocatorSignalSetAllowPause:
@@ -668,6 +647,8 @@ func (s *StreamAllocator) postEvent(event Event) {
*/
case streamAllocatorSignalCongestionStateChange:
s.handleSignalCongestionStateChange(event)
case streamAllocatorSignalPacerProbeObserverClusterComplete:
event.handleSignalPacerProbeObserverClusterComplete(event)
}
}, event)
}
@@ -705,29 +686,25 @@ func (s *StreamAllocator) handleSignalEstimate(event Event) {
// always update NACKs
packetDelta, repeatedNackDelta := s.getNackDelta()
if s.bwe != nil {
s.bwe.HandleREMB(
receivedEstimate,
s.probeController.DoesProbeNeedFinalize(), // waiting for goal reached OR aborted probe to finalize
s.getExpectedBandwidthUsage(),
packetDelta,
repeatedNackDelta,
)
}
s.params.BWE.HandleREMB(
receivedEstimate,
s.probeController.DoesProbeNeedFinalize(), // waiting for goal reached OR aborted probe to finalize
s.getExpectedBandwidthUsage(),
packetDelta,
repeatedNackDelta,
)
}
func (s *StreamAllocator) handleSignalPeriodicPing(Event) {
// finalize probe if necessary
if s.bwe != nil {
isValidSignal, trend, lowestEstimate, highestEstimate := s.bwe.GetProbeStatus()
isHandled, isNotFailing, isGoalReached := s.probeController.MaybeFinalizeProbe(
isValidSignal,
trend,
lowestEstimate,
)
if isHandled {
s.onProbeDone(isNotFailing, isGoalReached, highestEstimate)
}
isValidSignal, trend, lowestEstimate, highestEstimate := s.params.BWE.GetProbeStatus()
isHandled, isNotFailing, isGoalReached := s.probeController.MaybeFinalizeProbe(
isValidSignal,
trend,
lowestEstimate,
)
if isHandled {
s.onProbeDone(isNotFailing, isGoalReached, highestEstimate)
}
// probe if necessary and timing is right
@@ -756,15 +733,6 @@ func (s *StreamAllocator) handleSignalSendProbe(event Event) {
break
}
}
if bytesSent != 0 {
s.prober.ProbeSent(bytesSent)
}
}
func (s *StreamAllocator) handleSignalProbeClusterDone(event Event) {
info, _ := event.Data.(ccutils.ProbeClusterInfo)
s.probeController.ProbeClusterDone(info)
}
func (s *StreamAllocator) handleSignalResume(event Event) {
@@ -877,6 +845,11 @@ func (s *StreamAllocator) handleSignalCongestionStateChange(event Event) {
s.congestionState = cscd.congestionState
}
func (s *StreamAllocator) handleSignalPacerProbeObserverClusterComplete(event Event) {
probeClusterId, _ := event.Data.(ccutils.ProbeClusterId)
s.probeController.ProbeClusterDone(probeClusterId)
}
func (s *StreamAllocator) setState(state streamAllocatorState) {
if s.state == state {
return
@@ -889,14 +862,12 @@ func (s *StreamAllocator) setState(state streamAllocatorState) {
s.probeController.Reset()
// a fresh start after state transition to get clean data
if s.bwe != nil {
// BWE-TODO: ssbwe maybe should not reset like this as it might have useful state across
// BWE-TODO: state changes in this module, actually even remotebwe should also manage it
// BWE-TODO: internally, Reset should probably only be used if all managed tracks go away
// BWE-TODO: and we can get a clean start, mimicking existing behaviour till this can be
// BWE-TODO: evaluated more.
s.bwe.Reset()
}
// BWE-TODO: ssbwe maybe should not reset like this as it might have useful state across
// BWE-TODO: state changes in this module, actually even remotebwe should also manage it
// BWE-TODO: internally, Reset should probably only be used if all managed tracks go away
// BWE-TODO: and we can get a clean start, mimicking existing behaviour till this can be
// BWE-TODO: evaluated more.
s.params.BWE.Reset()
}
func (s *StreamAllocator) adjustState() {
@@ -1066,9 +1037,7 @@ func (s *StreamAllocator) allocateTrack(track *Track) {
}
func (s *StreamAllocator) onProbeDone(isNotFailing bool, isGoalReached bool, highestEstimate int64) {
if s.bwe != nil {
s.bwe.ProbingEnd(isNotFailing, isGoalReached)
}
s.params.BWE.ProbingEnd(isNotFailing, isGoalReached)
if !isNotFailing {
return