c45cbc93b7
slice 9 made the DURATION right but content still hiccuped; aggregates (301/300, uniform file PTS) could not see it. Measured root cause: FfmpegEncoder.SubmitFrameAsync BLOCKED on WriteAsync(8.3MB)+FlushAsync when ffmpeg lagged the pipe, and the burst while-loop re-wrote that same stale composite per crossed slot — frozen runs. OBS shape (derivative, wrapped pre-1.0): the encoder queue in libobs/obs-encoder.c — encoder thread never couples back into the video thread; overflow = dropped data, never a frozen producer. https://github.com/obsproject/obs-studio/blob/master/libobs/obs-encoder.c - FfmpegEncoder: SubmitFrameAsync is now an enqueue (ArrayPool copy) into a bounded Channel (cap 120) drained by its own task; drop-newest + count when full; StopAsync flushes the queue then EOF (TryComplete). IFfmpegEncoder.DroppedFrames. - FramePump: ONE fresh composite per iteration (burst loop deleted); worst-submit stat, stall logger (>2x interval names the stage), dropped/stalls in stats. - Burned-in 6-digit dot-matrix frame counter (white box, bottom-right) on every composite — the clock-independent judge replacing the WSL ticker: +1/frame, jumps = counted drops. - ONE new test Backpressure_QueueOverflow_DropsFrames_AndNeverBlocks (slow-sink fake: submit never blocks, drops counted, stop flushes exactly submitted-minus-dropped). - Full suite 290 tests, 289 pass — sole failure the pre-existing compositor pixel test. - Docs same-commit: ai.md slice 10 (+ encoder/stop-note corrections), MyMistakes point 8, HANDOFF. Audio untouched (queued follow-up); web overlay still frozen pending timing closure.
31 lines
1.5 KiB
C#
31 lines
1.5 KiB
C#
using ytLive.Models;
|
|
|
|
namespace ytLive.Services.Encoder;
|
|
|
|
/// <summary>
|
|
/// The live encoder seam (TASK 4 ship step 3): start an FFmpeg subprocess that
|
|
/// encodes raw BGRA frames from stdin and pushes FLV to an RTMP ingestion URL,
|
|
/// raising parsed health stats from stderr. The frame producer (compositor →
|
|
/// capture managers, TASK 4 ship step 5) feeds <see cref="SubmitFrameAsync"/> at
|
|
/// capture rate; this service serializes writes, parses progress, and tears the
|
|
/// process down gracefully. Constructor-injected <see cref="IFfmpegLocator"/> +
|
|
/// process factory keep it hermetic (tests fake both).
|
|
/// </summary>
|
|
public interface IFfmpegEncoder : IDisposable
|
|
{
|
|
/// <summary>Fires on each parsed ffmpeg <c>-stats</c> progress line (~2 Hz).</summary>
|
|
event EventHandler<StreamHealth>? HealthUpdated;
|
|
|
|
/// <summary>Fires when the subprocess dies unexpectedly (non-zero exit while live).</summary>
|
|
event EventHandler<string>? ProcessFailed;
|
|
|
|
Task StartAsync(EncoderOptions options, CancellationToken cancellationToken = default);
|
|
Task SubmitFrameAsync(VideoFrame frame, CancellationToken cancellationToken = default);
|
|
Task StopAsync(CancellationToken cancellationToken = default);
|
|
|
|
/// <summary>Frames dropped by the encoder's bounded queue since start (the encoder
|
|
/// lagging the real-time capture rate). Zero in a healthy session; fed to the pump's
|
|
/// stats and mirrored by gaps in the burned-in frame counter of the recording.</summary>
|
|
int DroppedFrames { get; }
|
|
}
|