fix(audio): the track starts with the session, not with the first write
Third measurement, and it names the start-up defect the creator heard as "started off wrong, then corrected" (ty-20261112-1222-748-0000-2.mp4): wall clock (audio start -> audio stop) 12.614 s video: 754 frames 12.588 s correct audio: 585 packets 12.477 s 137 ms short pipe connected 160 ms after the session opened first 150 ms of the audio track digital silence (the mic delay line's head) The rate is fixed — 12.5 s of show, no accumulating drift — but starting the clock at connect made the audio stream begin 160 ms after the video stream, so every word sat 160 ms late and the track came out that much shorter. The silence at the head is the 175 ms mic delay doing its job, not a fault. Now the clock runs from session open. Time that passes before ffmpeg connects stays owed, and it is paid the moment the pipe connects — out of the capture ring, so it is the audio that was missed rather than padding. Debt larger than one 200 ms chunk continues to be paid over the following ticks. 674 pass, 0 warnings.
This commit is contained in:
@@ -402,7 +402,12 @@ public sealed class AudioMixer : IDisposable
|
||||
// any scheduler error into a permanent A/V rate error (measured 2026-11-12: 2.1% fast, 224 ms
|
||||
// of drift in ten seconds); asking the clock how much is owed and producing exactly that keeps
|
||||
// the track the same length as the show.
|
||||
Stopwatch? clock = null;
|
||||
// The clock runs from the moment the session opens, NOT from the first successful write. Starting
|
||||
// it at connect made the track begin that much later than the picture: measured 2026-11-12, the
|
||||
// writes began 160 ms after ffmpeg started reading and the audio stream came out 137 ms shorter
|
||||
// than the video, so every word sat 160 ms late. Owed time is paid out of the capture ring when
|
||||
// the pipe connects, which is the audio that was missed — real audio, not padding.
|
||||
var clock = Stopwatch.StartNew();
|
||||
long framesProduced = 0;
|
||||
var maxChunkFrames = micChunk.Length;
|
||||
|
||||
@@ -411,15 +416,14 @@ public sealed class AudioMixer : IDisposable
|
||||
var nextTick = DateTime.UtcNow + _mixInterval;
|
||||
try
|
||||
{
|
||||
// The clock starts when ffmpeg is actually reading. Before that there is nowhere for
|
||||
// audio to go, and time spent waiting must not become a shorter track.
|
||||
// Nowhere for audio to go until ffmpeg is reading: wait without producing, so the debt
|
||||
// stays owed and is paid from the ring the moment the pipe connects.
|
||||
if (!pipe.IsConnected)
|
||||
{
|
||||
await Task.Delay(1, cancellationToken).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
clock ??= Stopwatch.StartNew();
|
||||
var frames = AudioClock.FramesDue(
|
||||
clock.Elapsed.TotalSeconds, framesProduced, OutputSampleRate, maxChunkFrames);
|
||||
if (frames <= 0)
|
||||
|
||||
Reference in New Issue
Block a user