A stored qsvExtraHardwareFrames of 0 reached FFmpeg as hwupload=extra_hw_frames=0, leaving the QSV upload pool no headroom for frames in flight through the filter graph. Any input that is not throttled then exhausts it: the graph fails with -12 (Cannot allocate memory), h264_qsv reports "Could not open encoder before EOF", and zero segments are written. Measured against the deployed FFmpeg 8.1.2, one real logged command, only the marked tokens differing: readrate 1.05, no burst + frames 0 -> exit 0, 14 segments readrate 1.05 + burst 2/4/8 + frames 0 -> exit 244, ENOMEM, 0 segments no readrate at all + frames 0 -> exit 244, ENOMEM, 0 segments readrate 1.05 + burst 8 + frames 64 -> exit 0, 14 segments no readrate at all + frames 64 -> exit 0, 14 segments So the defect predates #350's cold-start burst: a work-ahead start takes no -readrate and was already failing on a profile with 0. The burst removed the throttle on every realtime session, turning an intermittent failure into a near-deterministic one, which is how it surfaced. Input throttling was doing load-bearing allocation-bounding work that nobody had written down -- which is why the FFmpeg-level benchmark in #350 and the argument-generation tests in #516 were both green and neither could see it. Fixed at FFmpegState.QsvExtraHardwareFrames, the single point every QSV upload site reads, so one guard covers HardwareUploadFilter, HardwareUploadQsvFilter, WatermarkHardwareUploadFilter, ScaleQsvFilter, DeinterlaceQsvFilter and SubtitleScaleQsvFilter rather than six call sites that can drift apart. Values above the floor are still honored. Tests negative-controlled: reverting the floor fails exactly the three sub-minimum cases, with the build verified succeeded first. Refs #350, #516, #519.
73 lines
2.7 KiB
C#
73 lines
2.7 KiB
C#
using ErsatzTV.FFmpeg.OutputFormat;
|
|
|
|
namespace ErsatzTV.FFmpeg;
|
|
|
|
public record FFmpegState(
|
|
bool SaveReport,
|
|
HardwareAccelerationMode DecoderHardwareAccelerationMode,
|
|
HardwareAccelerationMode EncoderHardwareAccelerationMode,
|
|
Option<string> VaapiDriver,
|
|
Option<string> VaapiDevice,
|
|
Option<TimeSpan> Start,
|
|
Option<TimeSpan> Finish,
|
|
bool DoNotMapMetadata,
|
|
Option<string> MetadataServiceProvider,
|
|
Option<string> MetadataServiceName,
|
|
Option<string> MetadataAudioLanguage,
|
|
Option<string> MetadataSubtitleLanguage,
|
|
Option<string> MetadataSubtitleTitle,
|
|
OutputFormatKind OutputFormat,
|
|
Option<string> HlsPlaylistPath,
|
|
Option<string> HlsSegmentTemplate,
|
|
Option<string> HlsInitTemplate,
|
|
Option<string> HlsSegmentOptions,
|
|
TimeSpan PtsOffset,
|
|
Option<int> ThreadCount,
|
|
Option<int> MaybeQsvExtraHardwareFrames,
|
|
bool IsSongWithProgress,
|
|
bool IsHdrTonemap,
|
|
string TonemapAlgorithm,
|
|
bool IsTroubleshooting,
|
|
bool QsvPreferNativeDecoder = false)
|
|
{
|
|
// the QSV upload pool needs headroom for the frames in flight through the filter graph.
|
|
// extra_hw_frames=0 leaves none, so any input that is not throttled exhausts it: the graph
|
|
// fails with -12 (Cannot allocate memory), h264_qsv reports "Could not open encoder before
|
|
// EOF", and the output file gets no packets at all. Input throttling was the only thing
|
|
// hiding it — a work-ahead start (no -readrate) and #350's cold-start burst both remove that
|
|
// throttle, so the channel simply dies. A stored 0 is therefore treated as "no pool
|
|
// configured" rather than honored literally (ersatztv#529)
|
|
public const int MinimumQsvExtraHardwareFrames = 64;
|
|
|
|
public int QsvExtraHardwareFrames =>
|
|
Math.Max(MaybeQsvExtraHardwareFrames.IfNone(MinimumQsvExtraHardwareFrames), MinimumQsvExtraHardwareFrames);
|
|
|
|
public static FFmpegState Concat(bool saveReport, string channelName) =>
|
|
new(
|
|
saveReport,
|
|
HardwareAccelerationMode.None,
|
|
HardwareAccelerationMode.None,
|
|
Option<string>.None,
|
|
Option<string>.None,
|
|
Option<TimeSpan>.None,
|
|
Option<TimeSpan>.None,
|
|
true, // do not map metadata
|
|
"ErsatzTV",
|
|
channelName,
|
|
Option<string>.None,
|
|
Option<string>.None,
|
|
Option<string>.None,
|
|
OutputFormatKind.MpegTs,
|
|
Option<string>.None,
|
|
Option<string>.None,
|
|
Option<string>.None,
|
|
Option<string>.None,
|
|
TimeSpan.Zero,
|
|
Option<int>.None,
|
|
Option<int>.None,
|
|
false,
|
|
false,
|
|
"linear",
|
|
false);
|
|
}
|