fix: clear pre-live capture ring backlog at StartLive (2.2s A/V sync lag)

AudioMixer.Start() begins mic/loopback capture at app startup to feed the
level meters, so both 2s ring buffers fill with pre-live audio. StartLive()
drained from the oldest tail sample, putting every recorded event ~2.2s late
in the audio track (confirmed by clap analysis + cross-correlation on two
takes: +2.11 to +2.22s).

Fix: AudioMixer.StartLive() now runs _micBuffer.Clear() + _loopbackBuffer.Clear()
immediately after the pipe starts, before the drain task runs. Recording now
begins at go-live; the <=10ms in-flight chunk evicted by Clear() is imperceptible.

Regression test: StartLive_DiscardsPreLiveBacklog_SoFirstAudioIsCurrent
saturates the loopback ring with stale 0.8 pre-live audio, then asserts the
wire carries fresh post-live 0.2 (max < 0.3).

Reference (external, per derivative-work rule): OBS 'Audio mixer' keeps its
buffers fed continuously and syncs the stream start timestamp at record time
rather than replaying pre-live capture; a go-live flush of the capture buffer
is the accepted pattern for live tools restarting a stream.

Docs: HANDOFF.md (fix shipped), MyMistakes.md (A/V sync measurement recipe).
This commit is contained in:
2026-09-14 11:24:12 -07:00
parent 5ba4d709ae
commit 11a7af2dc0
4 changed files with 230 additions and 148 deletions
+9
View File
@@ -159,6 +159,15 @@ public sealed class AudioMixer : IDisposable
if (_liveCts != null)
return;
// Capture runs from app STARTUP for the meters, so both rings carry up
// to a full ring depth (~2s) of pre-live backlog by the time the user
// hits record; draining it made every recorded event land ~2s late in
// the audio track (2026-09-14, two confirmed takes). Discard it so the
// recording starts at go-live. The writers keep running concurrently;
// Clear under the ring's lock, so only the in-flight chunk is dropped.
_micBuffer.Clear();
_loopbackBuffer.Clear();
var cts = new CancellationTokenSource();
_liveCts = cts;
_pipe = new NamedPipeAudioWriter();