From 7c5137a4afb3081aec189de2657d3453e01c019d Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Wed, 2 Jul 2025 00:34:07 +0000 Subject: [PATCH] remove some decode threading limits (#2103) --- CHANGELOG.md | 2 ++ .../PipelineBuilderBaseTests.cs | 4 +-- .../Pipeline/PipelineBuilderBase.cs | 30 ++++++++----------- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b7b04863..b9c8a07bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Try to mitigate inotify limit error by disabling automatic reloading of `appsettings.json` config files - Support `movie`, `musicvideo` and `episodedetails` top-level tags in other video NFO files - Note that no change has been made to the metadata tags that are actually parsed, but this should help with various types of content +- Remove some limits on multithreading that are no longer needed with latest ffmpeg + - Mixed transcoding (software decode, hardware filters/encode) can now use multiple decode threads ### Fixed - Fix QSV acceleration in docker with older Intel devices diff --git a/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs b/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs index dff1cfbe0..95f7a684a 100644 --- a/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs +++ b/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs @@ -115,7 +115,7 @@ public class PipelineBuilderBaseTests string command = PrintCommand(videoInputFile, audioInputFile, None, None, result); command.ShouldBe( - "-threads 1 -nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -readrate 1.0 -i /tmp/whatever.mkv -filter_complex [0:1]aresample=async=1:first_pts=0[a] -map 0:0 -map [a] -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -bf 0 -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -c:a aac -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -f mpegts -mpegts_flags +initial_discontinuity pipe:1"); + "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -readrate 1.0 -i /tmp/whatever.mkv -filter_complex [0:1]aresample=async=1:first_pts=0[a] -map 0:0 -map [a] -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -bf 0 -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -c:a aac -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -f mpegts -mpegts_flags +initial_discontinuity pipe:1"); } [Test] @@ -211,7 +211,7 @@ public class PipelineBuilderBaseTests string command = PrintCommand(videoInputFile, audioInputFile, None, None, result); command.ShouldBe( - "-threads 1 -nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -readrate 1.0 -i /tmp/whatever.mkv -filter_complex [0:1]aresample=async=1:first_pts=0[a] -map 0:0 -map [a] -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -bf 0 -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -c:a aac -ac 6 -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -f mpegts -mpegts_flags +initial_discontinuity pipe:1"); + "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -readrate 1.0 -i /tmp/whatever.mkv -filter_complex [0:1]aresample=async=1:first_pts=0[a] -map 0:0 -map [a] -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -bf 0 -sc_threshold 0 -video_track_timescale 90000 -b:v 2000k -maxrate:v 2000k -bufsize:v 4000k -c:v libx265 -tag:v hvc1 -x265-params log-level=error -c:a aac -ac 6 -b:a 320k -maxrate:a 320k -bufsize:a 640k -ar 48k -f mpegts -mpegts_flags +initial_discontinuity pipe:1"); } [Test] diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index 63f72c9ed..1d5920c5a 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -202,13 +202,12 @@ public abstract class PipelineBuilderBase : IPipelineBuilder false, videoStream.ColorParams.IsHdr); - SetThreadCount(ffmpegState, desiredState, pipelineSteps); SetSceneDetect(videoStream, ffmpegState, desiredState, pipelineSteps); SetFFReport(ffmpegState, pipelineSteps); SetStreamSeek(ffmpegState, videoInputFile, context, pipelineSteps); SetTimeLimit(ffmpegState, pipelineSteps); - FilterChain filterChain = BuildVideoPipeline( + (FilterChain filterChain, ffmpegState) = BuildVideoPipeline( videoInputFile, videoStream, ffmpegState, @@ -216,6 +215,8 @@ public abstract class PipelineBuilderBase : IPipelineBuilder context, pipelineSteps); + SetThreadCount(ffmpegState, pipelineSteps); + // don't double input files for concat segmenter (v2) parent or child if (_concatInputFile.IsNone && ffmpegState.OutputFormat is not OutputFormatKind.Nut) { @@ -483,7 +484,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder PipelineContext context, ICollection pipelineSteps); - private FilterChain BuildVideoPipeline( + private FilterChainAndState BuildVideoPipeline( VideoInputFile videoInputFile, VideoStream videoStream, FFmpegState ffmpegState, @@ -514,7 +515,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder : SetDecoder(videoInputFile, videoStream, ffmpegState, context); //SetStillImageInfiniteLoop(videoInputFile, videoStream, ffmpegState); - SetRealtimeInput(videoInputFile, ffmpegState, desiredState); + SetRealtimeInput(videoInputFile, desiredState); SetInfiniteLoop(videoInputFile, videoStream, ffmpegState, desiredState); SetFrameRateOutput(desiredState, pipelineSteps); SetVideoTrackTimescaleOutput(desiredState, pipelineSteps); @@ -539,7 +540,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder SetOutputTsOffset(ffmpegState, desiredState, pipelineSteps); - return filterChain; + return new FilterChainAndState(filterChain, ffmpegState); } protected abstract Option SetDecoder( @@ -707,7 +708,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder } } - private void SetRealtimeInput(VideoInputFile videoInputFile, FFmpegState ffmpegState, FrameState desiredState) + private void SetRealtimeInput(VideoInputFile videoInputFile, FrameState desiredState) { int initialBurst; if (!desiredState.Realtime) @@ -758,21 +759,12 @@ public abstract class PipelineBuilderBase : IPipelineBuilder } } - private void SetThreadCount(FFmpegState ffmpegState, FrameState desiredState, List pipelineSteps) + private void SetThreadCount(FFmpegState ffmpegState, List pipelineSteps) { - if (ffmpegState.DecoderHardwareAccelerationMode != HardwareAccelerationMode.None || - ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.None) - { - _logger.LogDebug( - "Forcing {Threads} ffmpeg thread when hardware acceleration is used", - 1); - - pipelineSteps.Insert(0, new ThreadCountOption(1)); - } - else if (ffmpegState.Start.Exists(s => s > TimeSpan.Zero) && desiredState.Realtime) + if (ffmpegState.DecoderHardwareAccelerationMode != HardwareAccelerationMode.None) { _logger.LogDebug( - "Forcing {Threads} ffmpeg thread due to buggy combination of stream seek and realtime output", + "Forcing {Threads} ffmpeg decoding thread when hardware acceleration is used", 1); pipelineSteps.Insert(0, new ThreadCountOption(1)); @@ -835,4 +827,6 @@ public abstract class PipelineBuilderBase : IPipelineBuilder private static void SetTimeLimit(FFmpegState ffmpegState, List pipelineSteps) => pipelineSteps.AddRange(ffmpegState.Finish.Map(finish => new TimeLimitOutputOption(finish))); + + private sealed record FilterChainAndState(FilterChain FilterChain, FFmpegState FFmpegState); }