Files
livekit/pkg/rtc/audiolevel.go
hn8 dcfe7eaf4f Enhancement: audio speakers (#44)
* refactor: active speakers

1. Observe the loudest adjusted with active ratio instead of linear average of decibel values
2. Follow RFC6465 to convert audio level from decibel to linear value.
3. Quantize audio level for stable slice comparison
4. Switch moving average algorithm from MMA to EMA to have the same center of mass with SMA
5. Minor: remove seenSids map allocation
6. Minor: minimize division arithmetic

* Update pkg/rtc/audiolevel.go

Co-authored-by: David Zhao <david@davidzhao.com>
2021-07-09 09:46:24 -07:00

76 lines
1.9 KiB
Go

package rtc
import (
"math"
"sync/atomic"
)
const (
// number of audio frames for observe window
observeFrames = 25 // webrtc default opus frame size 20ms, 25*20=500ms matching default UpdateInterval
silentAudioLevel = 127
)
// keeps track of audio level for a participant
type AudioLevel struct {
levelThreshold uint8
currentLevel uint32
// min frames to be considered active
minActiveFrames uint32
// for Observe goroutine use
// keeps track of current activity
observeLevel uint8
activeFrames uint32
numFrames uint32
}
func NewAudioLevel(activeLevel uint8, minPercentile uint8) *AudioLevel {
l := &AudioLevel{
levelThreshold: activeLevel,
minActiveFrames: uint32(minPercentile) * observeFrames / 100,
currentLevel: silentAudioLevel,
observeLevel: silentAudioLevel,
}
return l
}
// Observes a new frame, must be called from the same thread
func (l *AudioLevel) Observe(level uint8) {
l.numFrames++
if level <= l.levelThreshold {
l.activeFrames++
if l.observeLevel > level {
l.observeLevel = level
}
}
if l.numFrames >= observeFrames {
// compute and reset
if l.activeFrames >= l.minActiveFrames {
const invObserveFrames = 1.0 / observeFrames
level := uint32(l.observeLevel) - uint32(20*math.Log10(float64(l.activeFrames)*invObserveFrames))
atomic.StoreUint32(&l.currentLevel, level)
} else {
atomic.StoreUint32(&l.currentLevel, silentAudioLevel)
}
l.observeLevel = silentAudioLevel
l.activeFrames = 0
l.numFrames = 0
}
}
// returns current audio level, 0 (loudest) to 127 (silent)
func (l *AudioLevel) GetLevel() (uint8, bool) {
level := uint8(atomic.LoadUint32(&l.currentLevel))
active := level != silentAudioLevel
return level, active
}
// convert decibel back to linear
func ConvertAudioLevel(level uint8) float32 {
const negInv20 = -1.0 / 20
return float32(math.Pow(10, float64(level)*negInv20))
}