fix(media): play at the clip's real rate — the pipe was copying a frame per read
Reported as "the default video and the green screen video are playing at a hideously slow rate… I thought the playback timing issues were resolved?!" They were not: two independent faults, both in the shared clip pipe. 1. QUADRATIC COPY IN THE FRAME READER. `RawVideoFrameReader` re-concatenated the whole pending buffer on every `Feed`, and the pipe feeds it 64 KB at a time. A 1080p BGRA frame is 8 MB, so one frame cost ~127 copies of a growing buffer — roughly half a gigabyte of memcpy per frame. Measured on a 30 fps clip: 6 frames in 3 seconds (2 fps). The reader now fills one preallocated frame buffer and hands that array to the VideoFrame, so a frame is copied exactly once. Pipe reads are 256 KB. Same clip: 29 fps. 2. PACING SLEPT ON TOP OF THE WORK. A fixed 1/fps sleep after each frame adds the frame's own cost, so 20 ms of work on a 30 fps clip yields 1/(33+20) ≈ 19 fps — measured 20.9. Pacing is now deadline-based (`Helpers/FramePacer.cs`): wait only what is left of the frame's interval, resync when a frame overruns instead of bursting, never sleep longer than one interval. The alert clip shares this pipe, so it gained both fixes. Tests: `FramePacerTests` (subtract work, no drift, late → resync, zero duration, reset); `ChromaKeyRealClipTests.RealClip_PlaysAtItsNativeRate` (throughput floor of 15 fps on the real 30 fps clip — the old code sat at 2); the two pacing assertions now check "at most one interval" rather than an exact sleep, which is what deadline pacing means. Full suite: 574 passed, 0 failed, clean build.
This commit is contained in:
@@ -44,11 +44,13 @@ public class AlertClipDecoderTests
|
||||
await completed.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
Assert.Equal(3, forwarded);
|
||||
// One pacing delay per emitted frame, each = 1/100s (the probed rate).
|
||||
Assert.Equal(forwarded, delays.Count);
|
||||
// One pacing delay per emitted frame, each AT MOST one interval: pacing targets a deadline,
|
||||
// so the frame's own work is subtracted from the wait (2026-11-05 — the old version slept a
|
||||
// full interval on top of the work, which is what made 1080p playback crawl).
|
||||
Assert.NotEmpty(delays); // the decoder paces at all
|
||||
foreach (var d in delays)
|
||||
Assert.True(Math.Abs((d - TimeSpan.FromMilliseconds(10)).TotalMilliseconds) < 0.001,
|
||||
$"expected ~10ms, got {d.TotalMilliseconds}ms");
|
||||
Assert.True(d > TimeSpan.Zero && d <= TimeSpan.FromMilliseconds(10),
|
||||
$"expected a wait of at most 10ms, got {d.TotalMilliseconds}ms");
|
||||
}
|
||||
|
||||
private sealed class FakeDecodeProcess : IDecodeProcess
|
||||
|
||||
Reference in New Issue
Block a user