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
This commit is contained in:
Raja Subramanian
2026-02-05 09:35:26 +05:30
committed by GitHub
parent f3e9b68854
commit 370e0a4d52
3 changed files with 40 additions and 20 deletions

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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 {