chore(pump): per-stage timing stats — render vs submit, 5s windows
Take-two forensics: rawvideo carries no per-frame timestamps — ffmpeg stamps frames by ARRIVAL at the declared fps, so a 2fps producer yields a 30x time-lapse, 0.6s-long '60fps' file with zero errors anywhere (explains the creator's accelerated webcam motion + truncated length observations). The pump now logs frames-produced-vs-target per 5s with the avg render/submit split — take three names the slow stage instead of us theorizing. FramePumpTests 9/9. (Multi-commit working unit: remaining declared files land in the next commit.)
This commit is contained in:
@@ -218,6 +218,30 @@ public sealed class FramePump : IDisposable
|
||||
var interval = TimeSpan.FromSeconds(1d / Math.Max(1, options.Fps));
|
||||
var lastTick = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
// Stage timing (2026-09-01, take two): rawvideo carries no per-frame
|
||||
// timestamps — ffmpeg stamps frames by ARRIVAL at the declared fps. A producer
|
||||
// slower than the declared rate yields a time-lapsed, short file (observed:
|
||||
// 39 frames in 27 wall-seconds ≈ 30x at 60fps) with no error anywhere.
|
||||
// Log the render/submit split every 5s so the next take names the stage.
|
||||
var renderSw = new System.Diagnostics.Stopwatch();
|
||||
var submitSw = new System.Diagnostics.Stopwatch();
|
||||
long renderTicks = 0, submitTicks = 0;
|
||||
int statFrames = 0;
|
||||
var statsNext = DateTime.UtcNow + TimeSpan.FromSeconds(5);
|
||||
void ReportStats()
|
||||
{
|
||||
if (DateTime.UtcNow < statsNext) return;
|
||||
var target = 5d / interval.TotalSeconds; // frames expected per window
|
||||
_log?.Invoke(statFrames == 0
|
||||
? "FramePump stats: NO frames produced in 5s (loop stalled?)"
|
||||
: $"FramePump stats: {statFrames}/{target:F0} frames per 5s, " +
|
||||
$"avg render {renderTicks / (double)System.Diagnostics.Stopwatch.Frequency * 1000 / statFrames:F1}ms, " +
|
||||
$"avg submit {submitTicks / (double)System.Diagnostics.Stopwatch.Frequency * 1000 / statFrames:F1}ms");
|
||||
renderTicks = submitTicks = 0;
|
||||
statFrames = 0;
|
||||
statsNext = DateTime.UtcNow + TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
@@ -239,6 +263,7 @@ public sealed class FramePump : IDisposable
|
||||
}
|
||||
|
||||
VideoFrame frame;
|
||||
renderSw.Restart();
|
||||
if (_transition is { Active: true } transition && _fromSceneProvider != null)
|
||||
{
|
||||
var fromScene = _fromSceneProvider();
|
||||
@@ -255,11 +280,18 @@ public sealed class FramePump : IDisposable
|
||||
frame = RenderScene(scene, compositorOptions, socialBarFrame, socialBarTop);
|
||||
lastTick.Restart();
|
||||
}
|
||||
renderSw.Stop();
|
||||
renderTicks += renderSw.ElapsedTicks;
|
||||
|
||||
IFfmpegEncoder? encoder;
|
||||
lock (_gate) encoder = _encoder;
|
||||
if (encoder == null) break;
|
||||
submitSw.Restart();
|
||||
await encoder.SubmitFrameAsync(frame, ct);
|
||||
submitSw.Stop();
|
||||
submitTicks += submitSw.ElapsedTicks;
|
||||
statFrames++;
|
||||
ReportStats();
|
||||
}
|
||||
await _pacingDelay(interval, ct);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user