fix(audio): add per-5s live-loop telemetry to name the silent-recording stage

The recorded audio is full-length silent AAC (−91dB, 1124 frames/23.95s) —
the named pipe carried ~9.1MB of zeros the entire take. The mixer's loop
ran, the pipe connected, ffmpeg read it, and the file is full-length
silence. Sources started fine ('Audio: using system default mic...' logged),
no failure callbacks fired, and the existing integration test
(Mix_WithFiltersDuckAndGain_Lands_On_AudioPipe) proves the loop→pipe path
carries real audio when fed — the fault is capture-side.

Added permanent per-5s live-loop telemetry to startup.log so the next take
names the exact stage without new code:

  Audio live: pipe connected= dropped writes= micLevel= loopLevel= drained
  N/N samples peakMix N (per 5s)

- AudioMixer.FillAndMix now returns (MicRms, MicDrained, LoopDrained) for
  the accumulation; StartLive/StopLive log start/stop lines.
- NamedPipeAudioWriter.DroppedWrites: nonzero = audio dropped before ffmpeg
  connected (names 'pipe never connected' stage).

Likely root cause: WASAPI loopback captures the default render endpoint —
if audio plays on a non-default device the recording is silently silent.
The fix requires device enumeration + selection (slice 13 candidate).

Suite 290/291 — same sole pre-existing compositor pixel failure.
This commit is contained in:
2026-09-10 10:55:13 -07:00
parent 5e78065c2d
commit f3d578c81b
4 changed files with 128 additions and 67 deletions
+15 -3
View File
@@ -896,9 +896,21 @@ seam:** `Func<Scene?>`, `Func<SceneElement, VideoFrame?>` resolver, `Func<Compos
(4) **capture-cost telemetry**: first 30 captures per session log elapsed ms to startup.log —
PNG-encode+decode cost decides whether ~30Hz stays or drops to ~20Hz; the FramePump drops frames
(never time-lapses, slice 10) if UI-thread GC churn starves it, so the cost is observable.
Full suite 290/291 passing, the sole failure the pre-existing compositor pixel test. The web-overlay
transparency verification (the first-capture diagnostic bound to about:blank) and the audio-silence
item remain open push-gate items — both untouched by this slice.
Full suite 290/291 passing, the sole failure the pre-existing compositor pixel test. The web-overlay
transparency verification (the first-capture diagnostic bound to about:blank) and the audio-silence
item remain open push-gate items — both untouched by this slice.
- **Slice 12 (audio diagnostics, 2026-09-10):** the recorded audio is full-length silent AAC
(−91dB, 1124 frames / 23.95s in the latest take) — the named-pipe delivered ~9.1MB of zeros to
ffmpeg for the entire recording, so the loop ran and connected, but both WASAPI capture sources
delivered nothing. Sources started fine (`Audio: using system default mic...` logged), no failure
callbacks fired, and the mixer's existing integration test (`Mix_WithFiltersDuckAndGain_Lands_On_AudioPipe`)
proves the loop→pipe math is sound. The fault is capture-side: either the default render endpoint
carried nothing (audio played on a non-default device — common), or both endpoints were held
exclusive, or genuinely nothing played. Added permanent per-5s live-loop telemetry to startup.log
(`Audio live: pipe connected=, dropped writes=, micLevel=, loopLevel=, drained, peakMix`) and
`NamedPipeAudioWriter.DroppedWrites` — names the exact stage on the next take without new
code. `AudioMixer.FillAndMix` now returns `(MicRms, MicDrained, LoopDrained)` for the telemetry
accumulation. Full suite 290/291, same pre-existing sole failure.
- **Stop ordering matters:** `StopAsync` stops the encoder — since slice 10 it FLUSHES the pending
queue (`Channel.TryComplete` → drain writes the leftovers, closes stdin → EOF → ffmpeg finalizes+exits;
an accepted frame is never lost) — **before** awaiting the loop. The old reverse-order deadlock was