// 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 streamtracker import ( "sync" "time" "go.uber.org/atomic" "github.com/livekit/livekit-server/pkg/sfu/buffer" dd "github.com/livekit/livekit-server/pkg/sfu/dependencydescriptor" ) type StreamTrackerDependencyDescriptor struct { lock sync.RWMutex paused bool generation atomic.Uint32 params StreamTrackerParams maxSpatialLayer int32 maxTemporalLayer int32 onStatusChanged [buffer.DefaultMaxLayerSpatial + 1]func(status StreamStatus) onBitrateAvailable [buffer.DefaultMaxLayerSpatial + 1]func() lastBitrateReport time.Time bytesForBitrate [buffer.DefaultMaxLayerSpatial + 1][buffer.DefaultMaxLayerTemporal + 1]int64 bitrate [buffer.DefaultMaxLayerSpatial + 1][buffer.DefaultMaxLayerTemporal + 1]int64 isStopped bool } func NewStreamTrackerDependencyDescriptor(params StreamTrackerParams) *StreamTrackerDependencyDescriptor { return &StreamTrackerDependencyDescriptor{ params: params, maxSpatialLayer: buffer.InvalidLayerSpatial, maxTemporalLayer: buffer.InvalidLayerTemporal, } } func (s *StreamTrackerDependencyDescriptor) Start() { } func (s *StreamTrackerDependencyDescriptor) Stop() { s.lock.Lock() defer s.lock.Unlock() if s.isStopped { return } s.isStopped = true // bump generation to trigger exit of worker s.generation.Inc() } func (s *StreamTrackerDependencyDescriptor) OnStatusChanged(layer int32, f func(status StreamStatus)) { s.lock.Lock() s.onStatusChanged[layer] = f s.lock.Unlock() } func (s *StreamTrackerDependencyDescriptor) OnBitrateAvailable(layer int32, f func()) { s.lock.Lock() s.onBitrateAvailable[layer] = f s.lock.Unlock() } func (s *StreamTrackerDependencyDescriptor) Status(layer int32) StreamStatus { s.lock.RLock() defer s.lock.RUnlock() if layer > s.maxSpatialLayer { return StreamStatusStopped } return StreamStatusActive } func (s *StreamTrackerDependencyDescriptor) BitrateTemporalCumulative(layer int32) []int64 { s.lock.RLock() defer s.lock.RUnlock() if layer > s.maxSpatialLayer { brs := make([]int64, len(s.bitrate[0])) return brs } return s.bitrate[layer][:] } func (s *StreamTrackerDependencyDescriptor) Reset() { } func (s *StreamTrackerDependencyDescriptor) resetLocked() { // bump generation to trigger exit of current worker s.generation.Inc() for i := 0; i < len(s.bytesForBitrate); i++ { for j := 0; j < len(s.bytesForBitrate[i]); j++ { s.bytesForBitrate[i][j] = 0 } } for i := 0; i < len(s.bitrate); i++ { for j := 0; j < len(s.bitrate[i]); j++ { s.bitrate[i][j] = 0 } } } func (s *StreamTrackerDependencyDescriptor) SetPaused(paused bool) { s.lock.Lock() if s.paused == paused { s.lock.Unlock() return } s.paused = paused if !paused { s.resetLocked() } else { s.lastBitrateReport = time.Now() go s.worker(s.generation.Inc()) } s.lock.Unlock() } func (s *StreamTrackerDependencyDescriptor) Observe(temporalLayer int32, pktSize int, payloadSize int, hasMarker bool, ts uint32, ddVal *buffer.ExtDependencyDescriptor) { s.lock.Lock() if s.isStopped || s.paused || payloadSize == 0 || ddVal == nil { s.lock.Unlock() return } var notifyFns []func(status StreamStatus) var notifyStatus StreamStatus if mask := ddVal.Descriptor.ActiveDecodeTargetsBitmask; mask != nil && ddVal.ActiveDecodeTargetsUpdated { var maxSpatial, maxTemporal int32 for _, dt := range ddVal.DecodeTargets { if *mask&(1<