Browse Source

Add buffered duration metric for audio stream stats

tags/v0.9.0
Jan Svabenik 1 month ago
parent
commit
1d20e798d1
3 changed files with 45 additions and 12 deletions
  1. +3
    -0
      docs/API.md
  2. +26
    -12
      internal/audio/stream.go
  3. +16
    -0
      internal/audio/stream_test.go

+ 3
- 0
docs/API.md View File

@@ -298,6 +298,7 @@ Requires `--audio-stdin`, `--audio-http`, or another configured stream source to
"available": 12000, "available": 12000,
"capacity": 131072, "capacity": 131072,
"buffered": 0.09, "buffered": 0.09,
"bufferedDurationSeconds": 0.27,
"written": 890000, "written": 890000,
"underruns": 0, "underruns": 0,
"overflows": 0 "overflows": 0
@@ -366,6 +367,7 @@ The stream uses a lock-free ring buffer (default: 2 seconds at input rate). Buff
"available": 12000, "available": 12000,
"capacity": 131072, "capacity": 131072,
"buffered": 0.09, "buffered": 0.09,
"bufferedDurationSeconds": 0.27,
"written": 890000, "written": 890000,
"underruns": 0, "underruns": 0,
"overflows": 0 "overflows": 0
@@ -376,5 +378,6 @@ The stream uses a lock-free ring buffer (default: 2 seconds at input rate). Buff
- **underruns**: DSP consumed faster than audio arrived (silence inserted) - **underruns**: DSP consumed faster than audio arrived (silence inserted)
- **overflows**: Audio arrived faster than DSP consumed (data dropped) - **overflows**: Audio arrived faster than DSP consumed (data dropped)
- **buffered**: Fill ratio (0.0 = empty, 1.0 = full) - **buffered**: Fill ratio (0.0 = empty, 1.0 = full)
- **bufferedDurationSeconds**: Approximate seconds of audio queued in the buffer (`available` frames divided by the sample rate)


When no audio is streaming, the transmitter falls back to the configured tone generator or silence. When no audio is streaming, the transmitter falls back to the configured tone generator or silence.

+ 26
- 12
internal/audio/stream.go View File

@@ -109,24 +109,38 @@ func (s *StreamSource) Buffered() float64 {


// Stats returns diagnostic counters. // Stats returns diagnostic counters.
func (s *StreamSource) Stats() StreamStats { func (s *StreamSource) Stats() StreamStats {
available := s.Available()
buffered := 0.0
if s.size > 0 {
buffered = float64(available) / float64(s.size)
}
return StreamStats{ return StreamStats{
Available: s.Available(),
Capacity: s.size,
Buffered: s.Buffered(),
Written: s.Written.Load(),
Underruns: s.Underruns.Load(),
Overflows: s.Overflows.Load(),
Available: available,
Capacity: s.size,
Buffered: buffered,
BufferedDurationSeconds: s.bufferedDurationSeconds(available),
Written: s.Written.Load(),
Underruns: s.Underruns.Load(),
Overflows: s.Overflows.Load(),
} }
} }


// StreamStats exposes runtime telemetry for the stream buffer. // StreamStats exposes runtime telemetry for the stream buffer.
type StreamStats struct { type StreamStats struct {
Available int `json:"available"`
Capacity int `json:"capacity"`
Buffered float64 `json:"buffered"`
Written uint64 `json:"written"`
Underruns uint64 `json:"underruns"`
Overflows uint64 `json:"overflows"`
Available int `json:"available"`
Capacity int `json:"capacity"`
Buffered float64 `json:"buffered"`
BufferedDurationSeconds float64 `json:"bufferedDurationSeconds"`
Written uint64 `json:"written"`
Underruns uint64 `json:"underruns"`
Overflows uint64 `json:"overflows"`
}

func (s *StreamSource) bufferedDurationSeconds(available int) float64 {
if s.SampleRate <= 0 {
return 0
}
return float64(available) / float64(s.SampleRate)
} }


// --- StreamResampler --- // --- StreamResampler ---


+ 16
- 0
internal/audio/stream_test.go View File

@@ -205,6 +205,22 @@ func TestStreamSource_ConcurrentSPSC(t *testing.T) {
} }
} }


func TestStreamSource_StatsBufferedDuration(t *testing.T) {
rate := 48000
s := NewStreamSource(128, rate)
for i := 0; i < 24; i++ {
s.WriteFrame(NewFrame(0, 0))
}
stats := s.Stats()
if stats.BufferedDurationSeconds <= 0 {
t.Fatalf("expected buffered duration > 0, got %.6f", stats.BufferedDurationSeconds)
}
expected := float64(stats.Available) / float64(rate)
if math.Abs(stats.BufferedDurationSeconds-expected) > 1e-9 {
t.Fatalf("buffered duration %.9f != expected %.9f", stats.BufferedDurationSeconds, expected)
}
}

// --- StreamResampler tests --- // --- StreamResampler tests ---


func TestStreamResampler_1to1(t *testing.T) { func TestStreamResampler_1to1(t *testing.T) {


Loading…
Cancel
Save