From 370e0a4d521d43117d15e8fa2e654f1053a1b169 Mon Sep 17 00:00:00 2001 From: Raja Subramanian Date: Thu, 5 Feb 2026 09:35:26 +0530 Subject: [PATCH] Set up audio config in audio level module when config is updated. (#4290) * Set up audio config in audio level module when config is updated. It is possible to get audio config after bind (bind is where the audio level module is created) for remote tracks. So, split out setting audio level config in audio level module and invoke it when config is updated. * coderabbit review * prevent divide-by-0 * not active before config --- pkg/sfu/audio/audiolevel.go | 40 ++++++++++++++++++++++---------- pkg/sfu/audio/audiolevel_test.go | 13 ++++++----- pkg/sfu/buffer/buffer_base.go | 7 ++++-- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/pkg/sfu/audio/audiolevel.go b/pkg/sfu/audio/audiolevel.go index 5f56079fe..b97e0a4eb 100644 --- a/pkg/sfu/audio/audiolevel.go +++ b/pkg/sfu/audio/audiolevel.go @@ -51,13 +51,15 @@ var ( // -------------------------------------- type AudioLevelParams struct { - Config AudioLevelConfig ClockRate uint32 } // keeps track of audio level for a participant type AudioLevel struct { params AudioLevelParams + + config AudioLevelConfig + // min duration within an observe duration window to be considered active minActiveDuration uint32 smoothFactor float64 @@ -76,20 +78,26 @@ type AudioLevel struct { } func NewAudioLevel(params AudioLevelParams) *AudioLevel { - l := &AudioLevel{ + return &AudioLevel{ params: params, - minActiveDuration: uint32(params.Config.MinPercentile) * params.Config.UpdateInterval / 100, smoothFactor: 1, - activeThreshold: ConvertAudioLevel(float64(params.Config.ActiveLevel)), loudestObservedLevel: silentAudioLevel, } +} - if l.params.Config.SmoothIntervals > 0 { +func (l *AudioLevel) SetConfig(config AudioLevelConfig) { + l.lock.Lock() + defer l.lock.Unlock() + + l.config = config + l.minActiveDuration = uint32(l.config.MinPercentile) * l.config.UpdateInterval / 100 + if l.config.SmoothIntervals > 0 { // exponential moving average (EMA), same center of mass with simple moving average (SMA) - l.smoothFactor = float64(2) / (float64(l.params.Config.SmoothIntervals + 1)) + l.smoothFactor = float64(2) / (float64(l.config.SmoothIntervals + 1)) + } else { + l.smoothFactor = 1 } - - return l + l.activeThreshold = ConvertAudioLevel(float64(l.config.ActiveLevel)) } // Observes a new frame @@ -101,18 +109,22 @@ func (l *AudioLevel) Observe(level uint8, durationMs uint32, arrivalTime int64) } func (l *AudioLevel) observeLocked(level uint8, durationMs uint32, arrivalTime int64) { + if l.config.UpdateInterval == 0 { + return + } + l.lastObservedAt = arrivalTime l.observedDuration += durationMs - if level <= l.params.Config.ActiveLevel { + if level <= l.config.ActiveLevel { l.activeDuration += durationMs if l.loudestObservedLevel > level { l.loudestObservedLevel = level } } - if l.observedDuration >= l.params.Config.UpdateInterval { + if l.observedDuration >= l.config.UpdateInterval { smoothedLevel := float64(0.0) // compute and reset if l.activeDuration >= l.minActiveDuration { @@ -120,7 +132,7 @@ func (l *AudioLevel) observeLocked(level uint8, durationMs uint32, arrivalTime i // Weight will be 0 if active the entire duration // > 0 if active for longer than observe duration // < 0 if active for less than observe duration - activityWeight := 20 * math.Log10(float64(l.activeDuration)/float64(l.params.Config.UpdateInterval)) + activityWeight := 20 * math.Log10(float64(l.activeDuration)/float64(l.config.UpdateInterval)) adjustedLevel := float64(l.loudestObservedLevel) - activityWeight linearLevel := ConvertAudioLevel(adjustedLevel) @@ -153,13 +165,17 @@ func (l *AudioLevel) GetLevel(now int64) (float64, bool) { l.lock.Lock() defer l.lock.Unlock() + if l.config.UpdateInterval == 0 { + return 0.0, false + } + l.resetIfStaleLocked(now) return l.smoothedLevel, l.smoothedLevel >= l.activeThreshold } func (l *AudioLevel) resetIfStaleLocked(arrivalTime int64) { - if (arrivalTime-l.lastObservedAt)/1e6 < int64(2*l.params.Config.UpdateInterval) { + if (arrivalTime-l.lastObservedAt)/1e6 < int64(2*l.config.UpdateInterval) { return } diff --git a/pkg/sfu/audio/audiolevel_test.go b/pkg/sfu/audio/audiolevel_test.go index 81afd85e6..7876b287b 100644 --- a/pkg/sfu/audio/audiolevel_test.go +++ b/pkg/sfu/audio/audiolevel_test.go @@ -126,14 +126,15 @@ func TestAudioLevel(t *testing.T) { } func createAudioLevel(activeLevel uint8, minPercentile uint8, observeDuration uint32) *AudioLevel { - return NewAudioLevel(AudioLevelParams{ - Config: AudioLevelConfig{ - ActiveLevel: activeLevel, - MinPercentile: minPercentile, - UpdateInterval: observeDuration, - }, + al := NewAudioLevel(AudioLevelParams{ ClockRate: 48000, }) + al.SetConfig(AudioLevelConfig{ + ActiveLevel: activeLevel, + MinPercentile: minPercentile, + UpdateInterval: observeDuration, + }) + return al } func observeSamples(a *AudioLevel, level uint8, count int, baseTime time.Time) { diff --git a/pkg/sfu/buffer/buffer_base.go b/pkg/sfu/buffer/buffer_base.go index 55b7ede68..b288296d2 100644 --- a/pkg/sfu/buffer/buffer_base.go +++ b/pkg/sfu/buffer/buffer_base.go @@ -331,9 +331,9 @@ func (b *BufferBase) BindLocked(rtpParameters webrtc.RTPParameters, codec webrtc case sdp.AudioLevelURI: b.audioLevelExtID = uint8(ext.ID) b.audioLevel = audio.NewAudioLevel(audio.AudioLevelParams{ - Config: b.audioLevelConfig, ClockRate: b.clockRate, }) + b.audioLevel.SetConfig(b.audioLevelConfig) case act.AbsCaptureTimeURI: b.absCaptureTimeExtID = uint8(ext.ID) @@ -434,6 +434,9 @@ func (b *BufferBase) SetAudioLevelConfig(audioLevelConfig audio.AudioLevelConfig defer b.Unlock() b.audioLevelConfig = audioLevelConfig + if b.audioLevel != nil { + b.audioLevel.SetConfig(b.audioLevelConfig) + } } func (b *BufferBase) SetStreamRestartDetection(enable bool) { @@ -512,9 +515,9 @@ func (b *BufferBase) restartStreamLocked(reason string, isDetected bool) { if b.audioLevel != nil { b.audioLevel = audio.NewAudioLevel(audio.AudioLevelParams{ - Config: b.audioLevelConfig, ClockRate: b.clockRate, }) + b.audioLevel.SetConfig(b.audioLevelConfig) } if b.ddExtID != 0 {