Files
LlamaCasty/Services/Encoder/EncoderOptions.cs
T
gramps 5066d81aab feat(65): the clip mux — a third output on the same ffmpeg, drained off the frame path
Slice 2 of TASK 65. 602 green, 0 warnings. **Clips are still off**: no `ClipBuffer` is passed, so the
ffmpeg command line is byte-for-byte what it was before this commit, and a test asserts exactly that
(the original args are a strict prefix of the new ones).

- `FfmpegArgs` appends a fragmented-MP4 output to `pipe:1` only when `ClipBuffer` is set. One-second
  fragments (`-frag_duration 1000000`) rather than the 4 s GOP, so a clip can start inside the window
  instead of at the last keyframe. Stats already own stderr, so stdout was free.
- `Fmp4FragmentSplitter` turns that byte stream into whole fragments: `ftyp`+`moov` is the init
  segment, each `moof`+`mdat` is a fragment. It parses box headers itself, survives a box being cut
  anywhere across reads, and **resyncs instead of throwing** — a clip fault must never take the stream
  down.
- `FfmpegEncoder` reads it on its own task, never blocking the frame path. Any exception logs and
  disables clips for the session; the broadcast is untouched.
- `IEncoderProcess` gained `StandardOutputBytes` (the seam already had the text reader for probing).

Next slices: the replay layer + retention, then the Settings switch with disabled-but-remembered controls.
2026-11-10 12:25:19 -08:00

61 lines
2.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace ytLive.Services.Encoder;
/// <summary>
/// Everything the FFmpeg subprocess encoder needs for one go-live (TASK 4 ship
/// step 3). <see cref="RtmpUrl"/> is the FULL ingestion URL — the reusable
/// stream's ingest address plus its stream key (<c>rtmp://a.rtmp.youtube.com/live2/&lt;key&gt;</c>).
/// Resolution/FPS/bitrate come from the active quality tier; audio (TASK 9)
/// streams from the mixer through the named pipe <see cref="AudioPipeName"/>.
/// </summary>
public sealed class EncoderOptions
{
/// <summary>The audio pipe the mixer streams into; the args reference the
/// same name for ffmpeg's <c>-i</c>.</summary>
public const string DefaultAudioPipeName = "ytllive_audio";
public string RtmpUrl { get; init; } = string.Empty;
/// <summary>Final encoded output width and height.</summary>
public int Width { get; init; } = 1920;
public int Height { get; init; } = 1080;
/// <summary>Raw BGRA frame size from the compositor. Zero uses the final output size.</summary>
public int InputWidth { get; init; }
public int InputHeight { get; init; }
public int RawWidth => InputWidth > 0 ? InputWidth : Width;
public int RawHeight => InputHeight > 0 ? InputHeight : Height;
public int Fps { get; init; } = 60;
public int BitrateKbps { get; init; } = 8000;
/// <summary>Push to <see cref="RtmpUrl"/> (the ON-AIR side). Defaults on so
/// the pre-recording callers behave unchanged; the VM sets it from the ON-AIR
/// pill.</summary>
public bool StreamEnabled { get; init; } = true;
/// <summary>Write a local file at <see cref="RecordPath"/> (the REC side).
/// False unless the REC pill is on.</summary>
public bool RecordEnabled { get; init; }
/// <summary>Local MP4 output path for recording. Required when
/// <see cref="RecordEnabled"/> is true.</summary>
public string? RecordPath { get; init; }
/// <summary>H.264 encoder name for <c>-c:v</c>; the encoder probes and prefers
/// nvenc → qsv → amf → libopenh264 when not forced.</summary>
public string? VideoEncoder { get; init; }
public int AudioSampleRate { get; init; } = 48000;
public int AudioChannels { get; init; } = 2;
/// <summary>Name of the mixer's audio pipe (default <see cref="DefaultAudioPipeName"/>).</summary>
public string AudioPipeName { get; init; } = DefaultAudioPipeName;
/// <summary>TASK 65: when set, the encoder muxes a SECOND video output into this ring (fragmented
/// MP4 on stdout) for the replay buffer. Null = clips off, and the ffmpeg command line is exactly
/// what it was before the feature existed.</summary>
public Services.ClipRing? ClipBuffer { get; init; }
/// <summary>GOP in frames = Fps × 4s — the YouTube keyframe ≤ 4s compliance bound.</summary>
public int GopSize => Fps * 4;
}