FfmpegEncoder resolved the encoder name into its OWN StreamHealth instance, while the view model read CurrentHealth — a different object. So every recorded session was filed as "unknown", and the rate history's encoder key (evidence about NVENC says nothing about QSV) had nothing to key on. The name now travels on the encoder interface and the pump forwards it: IFfmpegEncoder.VideoEncoderName -> FramePump.EncoderName. Caught while adding it: reading _framePump.EncoderName from RefreshRateProposal throws, because that call runs at MainViewModel ctor line 354 and the pump is built at line 468. Eight tests failed with the same NullReferenceException, which took a while to see because the harness's test output is buffered — the first honest look at per-test logging named it in one line. Guarded; the history falls back to the last session's encoder for that tier, which is exactly the pre-first-session case the fallback already existed for. Tests: FramePumpTests gains the encoder-name assertion (null before start, h264_nvenc after), and the three fake encoders implement the new member. 675 pass, 0 warnings.
36 lines
1.8 KiB
C#
36 lines
1.8 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;
|
|
|
|
/// <summary>Which encoder the probe chose for this session ("h264_nvenc", "libopenh264", …). Null
|
|
/// before the first start. The rate history is keyed by it: evidence about one encoder says nothing
|
|
/// about another.</summary>
|
|
string? VideoEncoderName { get; }
|
|
|
|
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; }
|
|
}
|