From b16215fcd60ec7aa019cb7f8ae916fe1e8f7b773 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Thu, 14 Sep 2023 19:28:15 -0500 Subject: [PATCH] improve hls throttle (#1434) * throttle using ffmpeg option * update ffmpeg version --- .../Streaming/HlsSessionWorker.cs | 2 -- ...layoutItemProcessByChannelNumberHandler.cs | 15 +--------- .../PipelineBuilderBaseTests.cs | 14 ++++----- ...eInputOption.cs => ReadrateInputOption.cs} | 29 +++++++++++++++++-- .../Pipeline/PipelineBuilderBase.cs | 13 +++++---- .../Health/Checks/FFmpegVersionHealthCheck.cs | 4 +-- 6 files changed, 44 insertions(+), 33 deletions(-) rename ErsatzTV.FFmpeg/InputOption/{RealtimeInputOption.cs => ReadrateInputOption.cs} (50%) diff --git a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs index 93fc11657..387b4dc97 100644 --- a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs +++ b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs @@ -19,8 +19,6 @@ namespace ErsatzTV.Application.Streaming; public class HlsSessionWorker : IHlsSessionWorker { - public static readonly TimeSpan WorkAheadDuration = TimeSpan.FromMinutes(3); - private static readonly SemaphoreSlim Slim = new(1, 1); private static int _workAheadCount; private readonly IMediator _mediator; diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index 317e18321..e8d6b20df 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -178,19 +178,6 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< TimeSpan outPoint = playoutItemWithPath.PlayoutItem.OutPoint; DateTimeOffset effectiveNow = request.StartAtZero ? start : now; TimeSpan duration = finish - effectiveNow; - var isComplete = true; - - // _logger.LogDebug("PRE Start: {Start}, Finish {Finish}", start, finish); - // _logger.LogDebug("PRE in: {In}, out: {Out}", inPoint, outPoint); - if (!request.HlsRealtime && duration > HlsSessionWorker.WorkAheadDuration) - { - finish = effectiveNow + HlsSessionWorker.WorkAheadDuration; - outPoint = finish - start + inPoint; - isComplete = false; - - // _logger.LogDebug("POST Start: {Start}, Finish {Finish}", start, finish); - // _logger.LogDebug("POST in: {In}, out: {Out}", inPoint, outPoint); - } Command process = await _ffmpegProcessService.ForPlayoutItem( ffmpegPath, @@ -223,7 +210,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< playoutItemWithPath.PlayoutItem.DisableWatermarks, _ => { }); - var result = new PlayoutItemProcessModel(process, duration, finish, isComplete); + var result = new PlayoutItemProcessModel(process, duration, finish, true); return Right(result); } diff --git a/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs b/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs index 1934938f5..60b11e09b 100644 --- a/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs +++ b/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs @@ -106,7 +106,7 @@ public class PipelineBuilderBaseTests string command = PrintCommand(videoInputFile, audioInputFile, None, None, result); command.Should().Be( - "-threads 1 -nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -re -i /tmp/whatever.mkv -map 0:1 -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -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"); + "-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 -map 0:1 -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -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] @@ -194,7 +194,7 @@ public class PipelineBuilderBaseTests string command = PrintCommand(videoInputFile, audioInputFile, None, None, result); command.Should().Be( - "-threads 1 -nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -ss 00:00:01 -c:v h264 -re -i /tmp/whatever.mkv -map 0:1 -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -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"); + "-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 -map 0:1 -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -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] @@ -220,7 +220,7 @@ public class PipelineBuilderBaseTests string command = PrintCommand(None, None, None, concatInputFile, result); command.Should().Be( - "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -f concat -safe 0 -protocol_whitelist file,http,tcp,https,tcp,tls -probesize 32 -re -stream_loop -1 -i http://localhost:8080/ffmpeg/concat/1 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -sc_threshold 0 -c copy -map_metadata -1 -metadata service_provider=\"ErsatzTV\" -metadata service_name=\"Some Channel\" -f mpegts -mpegts_flags +initial_discontinuity pipe:1"); + "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -f concat -safe 0 -protocol_whitelist file,http,tcp,https,tcp,tls -probesize 32 -readrate 1.0 -stream_loop -1 -i http://localhost:8080/ffmpeg/concat/1 -muxdelay 0 -muxpreload 0 -movflags +faststart -flags cgop -sc_threshold 0 -c copy -map_metadata -1 -metadata service_provider=\"ErsatzTV\" -metadata service_name=\"Some Channel\" -f mpegts -mpegts_flags +initial_discontinuity pipe:1"); } [Test] @@ -248,7 +248,7 @@ public class PipelineBuilderBaseTests string command = PrintCommand(None, None, None, concatInputFile, result); command.Should().Be( - "-nostdin -threads 1 -hide_banner -loglevel error -nostats -fflags +genpts+discardcorrupt+igndts -re -i http://localhost:8080/iptv/channel/1.m3u8?mode=segmenter -map 0 -c copy -metadata service_provider=\"ErsatzTV\" -metadata service_name=\"Some Channel\" -f mpegts pipe:1"); + "-nostdin -threads 1 -hide_banner -loglevel error -nostats -fflags +genpts+discardcorrupt+igndts -readrate 1.0 -i http://localhost:8080/iptv/channel/1.m3u8?mode=segmenter -map 0 -c copy -metadata service_provider=\"ErsatzTV\" -metadata service_name=\"Some Channel\" -f mpegts pipe:1"); } [Test] @@ -334,13 +334,13 @@ public class PipelineBuilderBaseTests result.PipelineSteps.Should().HaveCountGreaterThan(0); result.PipelineSteps.Should().Contain(ps => ps is EncoderCopyVideo); result.PipelineSteps.Should().Contain(ps => ps is EncoderCopyAudio); - videoInputFile.InputOptions.Should().Contain(io => io is RealtimeInputOption); + videoInputFile.InputOptions.Should().Contain(io => io is ReadrateInputOption); string command = PrintCommand(videoInputFile, audioInputFile, None, None, result); // 0.4.0 reference: "-nostdin -threads 1 -hide_banner -loglevel error -nostats -fflags +genpts+discardcorrupt+igndts -re -ss 00:14:33.6195516 -i /tmp/whatever.mkv -map 0:0 -map 0:a -c:v copy -flags cgop -sc_threshold 0 -c:a copy -movflags +faststart -muxdelay 0 -muxpreload 0 -metadata service_provider="ErsatzTV" -metadata service_name="ErsatzTV" -t 00:06:39.6934484 -f mpegts -mpegts_flags +initial_discontinuity pipe:1" command.Should().Be( - "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -re -i /tmp/whatever.mkv -map 0:1 -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart+frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov+delay_moov -flags cgop -sc_threshold 0 -c:v copy -c:a copy -f mp4 pipe:1"); + "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -readrate 1.0 -i /tmp/whatever.mkv -map 0:1 -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart+frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov+delay_moov -flags cgop -sc_threshold 0 -c:v copy -c:a copy -f mp4 pipe:1"); } [Test] @@ -420,7 +420,7 @@ public class PipelineBuilderBaseTests string command = PrintCommand(videoInputFile, audioInputFile, None, None, result); command.Should().Be( - "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -re -i /tmp/whatever.mkv -map 0:a -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart+frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov+delay_moov -flags cgop -sc_threshold 0 -c:v copy -c:a copy -f mp4 pipe:1"); + "-nostdin -hide_banner -nostats -loglevel error -fflags +genpts+discardcorrupt+igndts -readrate 1.0 -i /tmp/whatever.mkv -map 0:a -map 0:0 -muxdelay 0 -muxpreload 0 -movflags +faststart+frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov+delay_moov -flags cgop -sc_threshold 0 -c:v copy -c:a copy -f mp4 pipe:1"); } [Test] diff --git a/ErsatzTV.FFmpeg/InputOption/RealtimeInputOption.cs b/ErsatzTV.FFmpeg/InputOption/ReadrateInputOption.cs similarity index 50% rename from ErsatzTV.FFmpeg/InputOption/RealtimeInputOption.cs rename to ErsatzTV.FFmpeg/InputOption/ReadrateInputOption.cs index 6d41f8cf6..368453292 100644 --- a/ErsatzTV.FFmpeg/InputOption/RealtimeInputOption.cs +++ b/ErsatzTV.FFmpeg/InputOption/ReadrateInputOption.cs @@ -1,14 +1,37 @@ -using ErsatzTV.FFmpeg.Environment; +using System.Globalization; +using ErsatzTV.FFmpeg.Environment; namespace ErsatzTV.FFmpeg.InputOption; -public class RealtimeInputOption : IInputOption +public class ReadrateInputOption : IInputOption { + private readonly int _initialBurstSeconds; + + public ReadrateInputOption(int initialBurstSeconds = 0) + { + _initialBurstSeconds = initialBurstSeconds; + } + public IList EnvironmentVariables => Array.Empty(); public IList GlobalOptions => Array.Empty(); - public IList InputOptions(InputFile inputFile) => new List { "-re" }; + public IList InputOptions(InputFile inputFile) + { + var result = new List { "-readrate", "1.0" }; + + if (_initialBurstSeconds > 0) + { + result.AddRange( + new[] + { + "-readrate_initial_burst", + _initialBurstSeconds.ToString(CultureInfo.InvariantCulture) + }); + } + + return result; + } public IList FilterOptions => Array.Empty(); public IList OutputOptions => Array.Empty(); diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index 9b9da7d56..b1345d37b 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -84,7 +84,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder }; concatInputFile.AddOption(new ConcatInputFormat()); - concatInputFile.AddOption(new RealtimeInputOption()); + concatInputFile.AddOption(new ReadrateInputOption()); concatInputFile.AddOption(new InfiniteLoopInputOption(HardwareAccelerationMode.None)); foreach (int threadCount in ffmpegState.ThreadCount) @@ -130,7 +130,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder new EncoderCopyAll() }; - concatInputFile.AddOption(new RealtimeInputOption()); + concatInputFile.AddOption(new ReadrateInputOption()); SetMetadataServiceProvider(ffmpegState, pipelineSteps); SetMetadataServiceName(ffmpegState, pipelineSteps); @@ -598,11 +598,14 @@ public abstract class PipelineBuilderBase : IPipelineBuilder private void SetRealtimeInput(VideoInputFile videoInputFile, FrameState desiredState) { - if (desiredState.Realtime) + var initialBurst = 0; + if (!desiredState.Realtime) { - _audioInputFile.Iter(a => a.AddOption(new RealtimeInputOption())); - videoInputFile.AddOption(new RealtimeInputOption()); + initialBurst = 180; } + + _audioInputFile.Iter(a => a.AddOption(new ReadrateInputOption(initialBurst))); + videoInputFile.AddOption(new ReadrateInputOption(initialBurst)); } private static void SetStillImageInfiniteLoop( diff --git a/ErsatzTV.Infrastructure/Health/Checks/FFmpegVersionHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/FFmpegVersionHealthCheck.cs index ce81dd0b6..79debb83b 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/FFmpegVersionHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/FFmpegVersionHealthCheck.cs @@ -8,8 +8,8 @@ namespace ErsatzTV.Infrastructure.Health.Checks; public class FFmpegVersionHealthCheck : BaseHealthCheck, IFFmpegVersionHealthCheck { - private const string BundledVersion = "6.0"; - private const string BundledVersionVaapi = "6.0"; + private const string BundledVersion = "N-112071-g00a837c70c"; + private const string BundledVersionVaapi = "N-112071-g00a837c70c"; private readonly IConfigElementRepository _configElementRepository; public FFmpegVersionHealthCheck(IConfigElementRepository configElementRepository) =>