namespace ErsatzTV.Core.FFmpeg; /// /// Heavy transcode features detected from a cold-start's FFmpeg arguments, so each HLS /// cold-start latency sample is self-describing (#350). Only cleanly-detectable signals are /// surfaced: subtitle burn-in (the libass subtitles= text filter) and the hardware /// acceleration family. Watermark, HDR->SDR, and image-based subtitle burn-in are /// intentionally NOT flagged here: in the argument string they all reduce to overlay=, /// indistinguishable from one another, so a boolean would mislead the very measurement it /// serves. Inspect the full ffmpeg hls arguments debug log for those details. /// Detection is a case-sensitive substring scan of the whole argument string (which includes /// the input media path), so a media filename literally containing e.g. subtitles= or /// _amf could false-positive; this is telemetry-only (no behavior impact) and low-probability. /// public readonly record struct ColdStartFeatures(bool SubtitleBurnIn, string HardwareAcceleration) { // Encoder/filter suffix -> accel family. Matching any of these substrings means the // pipeline is that hardware family (the encoder codec, e.g. h264_vaapi, dominates // cold-start encoder init cost; VAAPI/QSV/CUDA filters carry the same suffix). private static readonly (string Token, string Family)[] HardwareFamilies = [ ("_vaapi", "vaapi"), ("_nvenc", "nvenc"), ("_qsv", "qsv"), ("_videotoolbox", "videotoolbox"), ("_amf", "amf"), ("_rkmpp", "rkmpp") ]; public static ColdStartFeatures FromFFmpegArguments(string arguments) { if (string.IsNullOrWhiteSpace(arguments)) { return new ColdStartFeatures(false, "unknown"); } bool subtitleBurnIn = arguments.Contains("subtitles=", StringComparison.Ordinal); return new ColdStartFeatures(subtitleBurnIn, DetectHardwareAcceleration(arguments)); } private static string DetectHardwareAcceleration(string arguments) { foreach ((string token, string family) in HardwareFamilies) { if (arguments.Contains(token, StringComparison.Ordinal)) { return family; } } // No hardware encoder/filter present: fall back to the decode accel (a hardware // decode + software encode still pays an init cost worth attributing). const string HwAccelFlag = "-hwaccel "; int index = arguments.IndexOf(HwAccelFlag, StringComparison.Ordinal); if (index >= 0) { string rest = arguments[(index + HwAccelFlag.Length)..].TrimStart(); int end = rest.IndexOf(' '); string value = end >= 0 ? rest[..end] : rest; if (value.Length > 0) { return value; } } return "software"; } }