forked from livekit/livekit
/
audiolevel.go
98 lines (82 loc) · 2.68 KB
/
audiolevel.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package audio
import (
"math"
"go.uber.org/atomic"
)
const (
silentAudioLevel = 127
negInv20 = -1.0 / 20
)
type AudioLevelParams struct {
ActiveLevel uint8
MinPercentile uint8
ObserveDuration uint32
SmoothIntervals uint32
}
// keeps track of audio level for a participant
type AudioLevel struct {
params AudioLevelParams
// min duration within an observe duration window to be considered active
minActiveDuration uint32
smoothFactor float64
activeThreshold float64
smoothedLevel atomic.Float64
loudestObservedLevel uint8
activeDuration uint32 // ms
observedDuration uint32 // ms
}
func NewAudioLevel(params AudioLevelParams) *AudioLevel {
l := &AudioLevel{
params: params,
minActiveDuration: uint32(params.MinPercentile) * params.ObserveDuration / 100,
smoothFactor: 1,
activeThreshold: ConvertAudioLevel(float64(params.ActiveLevel)),
loudestObservedLevel: silentAudioLevel,
}
if l.params.SmoothIntervals > 0 {
// exponential moving average (EMA), same center of mass with simple moving average (SMA)
l.smoothFactor = float64(2) / (float64(l.params.SmoothIntervals + 1))
}
return l
}
// Observes a new frame, must be called from the same thread
func (l *AudioLevel) Observe(level uint8, durationMs uint32) {
l.observedDuration += durationMs
if level <= l.params.ActiveLevel {
l.activeDuration += durationMs
if l.loudestObservedLevel > level {
l.loudestObservedLevel = level
}
}
if l.observedDuration >= l.params.ObserveDuration {
// compute and reset
if l.activeDuration >= l.minActiveDuration {
// adjust loudest observed level by how much of the window was active.
// 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.ObserveDuration))
adjustedLevel := float64(l.loudestObservedLevel) - activityWeight
linearLevel := ConvertAudioLevel(adjustedLevel)
// exponential smoothing to dampen transients
smoothedLevel := l.smoothedLevel.Load()
smoothedLevel += (linearLevel - smoothedLevel) * l.smoothFactor
l.smoothedLevel.Store(smoothedLevel)
} else {
l.smoothedLevel.Store(0)
}
l.loudestObservedLevel = silentAudioLevel
l.activeDuration = 0
l.observedDuration = 0
}
}
// returns current soothed audio level
func (l *AudioLevel) GetLevel() (float64, bool) {
smoothedLevel := l.smoothedLevel.Load()
active := smoothedLevel >= l.activeThreshold
return smoothedLevel, active
}
// convert decibel back to linear
func ConvertAudioLevel(level float64) float64 {
return math.Pow(10, level*negInv20)
}