|
- package aoiprxkit
-
- import (
- "math"
- "sync"
- "time"
- )
-
- type ChannelMeter struct {
- RMS float64 `json:"rms"`
- Peak float64 `json:"peak"`
- Latest float64 `json:"latest"`
- }
-
- type MeterSnapshot struct {
- UpdatedAt string `json:"updatedAt"`
- Source string `json:"source"`
- SampleRateHz int `json:"sampleRateHz"`
- Channels int `json:"channels"`
- Meters []ChannelMeter `json:"meters"`
- }
-
- // LiveMeter consumes PCM frames and publishes simple per-channel level data.
- type LiveMeter struct {
- mu sync.RWMutex
- latest MeterSnapshot
- subs map[chan MeterSnapshot]struct{}
- }
-
- func NewLiveMeter() *LiveMeter {
- return &LiveMeter{subs: make(map[chan MeterSnapshot]struct{})}
- }
-
- func (m *LiveMeter) Consume(frame PCMFrame) {
- if frame.Channels <= 0 || len(frame.Samples) == 0 {
- return
- }
- meters := make([]ChannelMeter, frame.Channels)
- fullScale := detectFullScale(frame.Samples)
- sums := make([]float64, frame.Channels)
- counts := make([]int, frame.Channels)
-
- for i, sample := range frame.Samples {
- ch := i % frame.Channels
- norm := float64(sample) / fullScale
- abs := math.Abs(norm)
- if abs > meters[ch].Peak {
- meters[ch].Peak = abs
- }
- meters[ch].Latest = norm
- sums[ch] += norm * norm
- counts[ch]++
- }
- for ch := range meters {
- if counts[ch] > 0 {
- meters[ch].RMS = math.Sqrt(sums[ch] / float64(counts[ch]))
- }
- }
-
- snap := MeterSnapshot{
- UpdatedAt: time.Now().UTC().Format(time.RFC3339Nano),
- Source: frame.Source,
- SampleRateHz: frame.SampleRateHz,
- Channels: frame.Channels,
- Meters: meters,
- }
-
- m.mu.Lock()
- m.latest = snap
- subs := make([]chan MeterSnapshot, 0, len(m.subs))
- for ch := range m.subs {
- subs = append(subs, ch)
- }
- m.mu.Unlock()
-
- for _, ch := range subs {
- select {
- case ch <- snap:
- default:
- }
- }
- }
-
- func detectFullScale(samples []int32) float64 {
- var maxAbs int64
- for _, s := range samples {
- v := int64(s)
- if v < 0 {
- v = -v
- }
- if v > maxAbs {
- maxAbs = v
- }
- }
- if maxAbs <= 8388608 {
- return 8388608.0
- }
- return 2147483648.0
- }
-
- func (m *LiveMeter) Snapshot() MeterSnapshot {
- m.mu.RLock()
- defer m.mu.RUnlock()
- return m.latest
- }
-
- func (m *LiveMeter) Subscribe() (<-chan MeterSnapshot, func()) {
- ch := make(chan MeterSnapshot, 8)
- m.mu.Lock()
- m.subs[ch] = struct{}{}
- latest := m.latest
- m.mu.Unlock()
-
- if latest.UpdatedAt != "" {
- ch <- latest
- }
-
- unsubscribe := func() {
- m.mu.Lock()
- if _, ok := m.subs[ch]; ok {
- delete(m.subs, ch)
- close(ch)
- }
- m.mu.Unlock()
- }
- return ch, unsubscribe
- }
|