TASK 21 slice 2b: native-FPS pacing in MediaVideoSource (probe -> delay seam)
- MediaVideoSource takes optional IFrameRateProbe? + Func<TimeSpan,CancellationToken,Task>? delay seams (default Task.Delay); probes FPS once in RunAsync, delays by 1/fps after each emitted frame. No probe/unknown fps -> no pacing (ffmpeg pipe backpressure throttles). - Test: MediaVideoSource_PacesFramesByProbedFps (fake probe returns 1000fps + recording delay; one delay per frame ~= 1ms). Media tests 5/5, build 0 warnings. Derivative reference: per-frame delay pacing of decoded output, standard in media playback.
This commit is contained in:
@@ -90,6 +90,41 @@ public class MediaVideoSourceTests
|
||||
public void Dispose() => _stream.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MediaVideoSource_PacesFramesByProbedFps()
|
||||
{
|
||||
const int w = 2, h = 1; // frame bytes = 8
|
||||
var payload = new byte[16]; // two frames
|
||||
var delays = new List<TimeSpan>();
|
||||
|
||||
using var source = new MediaVideoSource(
|
||||
"clip.mp4", w, h,
|
||||
new FakeLocator(),
|
||||
new FakeDecodeProcess(payload),
|
||||
frameRateProbe: new FakeFrameRateProbe(1000.0),
|
||||
delay: (_d, _) => { delays.Add(_d); return Task.CompletedTask; });
|
||||
|
||||
var completed = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
source.Completed += () => completed.TrySetResult(true);
|
||||
|
||||
await source.StartAsync();
|
||||
await completed.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
|
||||
// One pacing delay per emitted frame, each = 1/1000s.
|
||||
Assert.Equal(2, delays.Count);
|
||||
foreach (var d in delays)
|
||||
Assert.True(Math.Abs((d - TimeSpan.FromMilliseconds(1)).TotalMilliseconds) < 0.001,
|
||||
$"expected ~1ms, got {d.TotalMilliseconds}ms");
|
||||
}
|
||||
|
||||
private sealed class FakeFrameRateProbe : IFrameRateProbe
|
||||
{
|
||||
private readonly double? _fps;
|
||||
public FakeFrameRateProbe(double? fps) => _fps = fps;
|
||||
public Task<double?> ProbeAsync(string path, CancellationToken ct = default)
|
||||
=> Task.FromResult(_fps);
|
||||
}
|
||||
|
||||
private sealed class FakeLocator : IFfmpegLocator
|
||||
{
|
||||
public Task<string> LocateAsync(CancellationToken ct = default) => Task.FromResult("ffmpeg.exe");
|
||||
|
||||
Reference in New Issue
Block a user