Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 5s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 5s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m6s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 7s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 14m27s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 19m10s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13m24s
Adds one Information-level structured log per HLS tune-in cold-start so the real driver breakdown can be measured on prod before optimizing the transcode pipeline (measure-before-optimize). Log-only; no transcode behavior change. - WaitForPlaylistSegments returns a PlaylistSegmentsResult: Phase A (process startup -> playlist exists) vs Phase B (segment fill), segments reached, deadline-expired. - StartFFmpegSessionHandler emits one summary: total = setup + startup + fill, plus cleanly-detectable feature flags (subtitle burn-in, hwaccel family). - ColdStartFeatures: pure, unit-tested args->features helper (14 cases). Watermark / HDR->SDR / image-subtitle burn-in are deliberately not flagged (all reduce to overlay= in the args, indistinguishable); the full ffmpeg arguments remain available at Debug. Refs #350 (instrumentation slice; optimization deferred pending real data). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
69 lines
2.9 KiB
C#
69 lines
2.9 KiB
C#
namespace ErsatzTV.Core.FFmpeg;
|
|
|
|
/// <summary>
|
|
/// 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 <c>subtitles=</c> text filter) and the hardware
|
|
/// acceleration family. Watermark, HDR->SDR, and <em>image</em>-based subtitle burn-in are
|
|
/// intentionally NOT flagged here: in the argument string they all reduce to <c>overlay=</c>,
|
|
/// indistinguishable from one another, so a boolean would mislead the very measurement it
|
|
/// serves. Inspect the full <c>ffmpeg hls arguments</c> 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. <c>subtitles=</c> or
|
|
/// <c>_amf</c> could false-positive; this is telemetry-only (no behavior impact) and low-probability.
|
|
/// </summary>
|
|
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";
|
|
}
|
|
}
|