TASK 18: local (record-only) recording pipeline + top-bar pills/light/Start-End button UX
Encoder dual-output: StreamEnabled/RecordEnabled/RecordPath options; per-output ffmpeg block (stream ->flv, record ->mp4); throw unless at least one output. RecordingFile: auto-name ty-<yyyymmdd>-<start hhmm>-0000.mp4, rename-on-stop to ty-...-<len hhmmss>.mp4 with numeric-suffix on collision. LayoutStore persists record folder. MainViewModel: REC/ON-AIR pills, status light, single Start<->End button (replaces Start/Stop variants), StopStream no longer signs out, explicit session timer. About overlay widened. Sign In label + spacing before Start. Tests: 244 passed / 246 (2 pre-existing: AudioPipeline gains-mute, RoundClip). Build 0 warnings. Scope check clean. Memory-map correction (derived-solution rule, 2026-08-29): image-shrink recipe was never recorded when first done, so it was re-derived. Added Derived-solution/recipes rule to AGENTS.md; MyMistakes.md now a permanent recipes registry (records the verified WPF-imaging shrink recipe); schema.md rows MyMistakes as registry; ai.md points to the rule. README hero image replaced: 3.2MB screenshot -> 1400x794 -> 188KB docs/ytLlive-preview.jpg (JPEG q82).
This commit is contained in:
@@ -12,40 +12,50 @@ public static class FfmpegArgs
|
||||
{
|
||||
public static IReadOnlyList<string> Build(EncoderOptions options, string videoEncoder)
|
||||
{
|
||||
var gop = options.GopSize;
|
||||
return
|
||||
[
|
||||
"-hide_banner",
|
||||
"-loglevel", "info",
|
||||
"-stats",
|
||||
"-stats_period", "0.5",
|
||||
var args = new List<string>
|
||||
{
|
||||
"-hide_banner", "-loglevel", "info",
|
||||
"-stats", "-stats_period", "0.5",
|
||||
"-re",
|
||||
"-f", "rawvideo",
|
||||
"-pix_fmt", "bgra",
|
||||
"-f", "rawvideo", "-pix_fmt", "bgra",
|
||||
"-video_size", $"{options.Width}x{options.Height}",
|
||||
"-framerate", options.Fps.ToString(),
|
||||
"-i", "pipe:0",
|
||||
"-f", "f32le",
|
||||
"-ar", options.AudioSampleRate.ToString(),
|
||||
"-f", "f32le", "-ar", options.AudioSampleRate.ToString(),
|
||||
"-ac", options.AudioChannels.ToString(),
|
||||
"-i", $@"\\.\pipe\{options.AudioPipeName}",
|
||||
"-map", "0:v",
|
||||
"-map", "1:a",
|
||||
"-c:v", videoEncoder,
|
||||
"-b:v", $"{options.BitrateKbps}k",
|
||||
"-maxrate", $"{options.BitrateKbps}k",
|
||||
"-bufsize", $"{options.BitrateKbps * 2}k",
|
||||
"-g", gop.ToString(),
|
||||
"-keyint_min", gop.ToString(),
|
||||
"-sc_threshold", "0",
|
||||
"-bf", "0",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-c:a", "aac",
|
||||
"-b:a", "128k",
|
||||
"-ar", options.AudioSampleRate.ToString(),
|
||||
"-ac", options.AudioChannels.ToString(),
|
||||
"-f", "flv",
|
||||
options.RtmpUrl,
|
||||
];
|
||||
};
|
||||
|
||||
// Each output is a self-contained block: its own -map, codec options,
|
||||
// format, and destination. Record+stream encodes the video twice off the
|
||||
// one raw input pipe (cheap on NVENC).
|
||||
if (options.StreamEnabled)
|
||||
{
|
||||
AddVideoTags(args, options, videoEncoder);
|
||||
args.AddRange(["-c:a", "aac", "-b:a", "128k",
|
||||
"-ar", options.AudioSampleRate.ToString(), "-ac", options.AudioChannels.ToString()]);
|
||||
args.AddRange(["-f", "flv", options.RtmpUrl]);
|
||||
}
|
||||
|
||||
if (options.RecordEnabled)
|
||||
{
|
||||
AddVideoTags(args, options, videoEncoder);
|
||||
args.AddRange(["-c:a", "aac",
|
||||
"-ar", options.AudioSampleRate.ToString(), "-ac", options.AudioChannels.ToString()]);
|
||||
args.AddRange(["-f", "mp4", options.RecordPath!]);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
private static void AddVideoTags(List<string> args, EncoderOptions options, string videoEncoder)
|
||||
{
|
||||
var gop = options.GopSize;
|
||||
args.AddRange(["-map", "0:v", "-map", "1:a"]);
|
||||
args.AddRange(["-c:v", videoEncoder]);
|
||||
args.AddRange(["-b:v", $"{options.BitrateKbps}k", "-maxrate", $"{options.BitrateKbps}k",
|
||||
"-bufsize", $"{options.BitrateKbps * 2}k"]);
|
||||
args.AddRange(["-g", gop.ToString(), "-keyint_min", gop.ToString(),
|
||||
"-sc_threshold", "0", "-bf", "0", "-pix_fmt", "yuv420p"]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user