From 040785b0d7645697a04ed6393b8770ea4be0dfb4 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Sat, 26 Feb 2022 11:40:16 -0600 Subject: [PATCH] fix qsv scaling and watermarks with new transcoder (#656) * fix qsv scaling * fix qsv watermarks --- CHANGELOG.md | 1 + ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs | 11 +++++-- ErsatzTV.FFmpeg/Encoder/Qsv/EncoderHevcQsv.cs | 32 +++++++++++++++++++ .../Filter/AvailableOverlayFilters.cs | 3 +- .../Filter/Qsv/OverlayQsvFilter.cs | 14 ++++++++ ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs | 8 +++-- .../Filter/WatermarkPixelFormatFilter.cs | 1 + ErsatzTV.FFmpeg/PipelineBuilder.cs | 1 + 8 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 ErsatzTV.FFmpeg/Filter/Qsv/OverlayQsvFilter.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f428d95f..93c43f2d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Add improved but experimental transcoder logic, which can be toggled on and off in `Settings` - Fix `HLS Segmenter` bug when source video packet contains no duration (`N/A`) +- Fix green line at the bottom of some content scaled using QSV acceleration ### Added - Add configurable channel group (M3U) and categories (XMLTV) diff --git a/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs b/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs index 3002d6f5a..2b0f5893b 100644 --- a/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs +++ b/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs @@ -11,13 +11,20 @@ namespace ErsatzTV.FFmpeg.Encoder; public static class AvailableEncoders { - public static Option ForVideoFormat(FFmpegState ffmpegState, FrameState currentState, FrameState desiredState, ILogger logger) => + public static Option ForVideoFormat( + FFmpegState ffmpegState, + FrameState currentState, + FrameState desiredState, + Option maybeWatermarkInputFile, + ILogger logger) => (ffmpegState.HardwareAccelerationMode, desiredState.VideoFormat) switch { (HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) => new EncoderHevcNvenc(), (HardwareAccelerationMode.Nvenc, VideoFormat.H264) => new EncoderH264Nvenc(), - (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv(), + (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv( + currentState, + maybeWatermarkInputFile), (HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv(currentState), (HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi(currentState), diff --git a/ErsatzTV.FFmpeg/Encoder/Qsv/EncoderHevcQsv.cs b/ErsatzTV.FFmpeg/Encoder/Qsv/EncoderHevcQsv.cs index fac0fa5fe..623423367 100644 --- a/ErsatzTV.FFmpeg/Encoder/Qsv/EncoderHevcQsv.cs +++ b/ErsatzTV.FFmpeg/Encoder/Qsv/EncoderHevcQsv.cs @@ -1,9 +1,19 @@ using ErsatzTV.FFmpeg.Format; +using LanguageExt; namespace ErsatzTV.FFmpeg.Encoder.Qsv; public class EncoderHevcQsv : EncoderBase { + private readonly FrameState _currentState; + private readonly Option _maybeWatermarkInputFile; + + public EncoderHevcQsv(FrameState currentState, Option maybeWatermarkInputFile) + { + _currentState = currentState; + _maybeWatermarkInputFile = maybeWatermarkInputFile; + } + public override FrameState NextState(FrameState currentState) => currentState with { VideoFormat = VideoFormat.Hevc, @@ -12,4 +22,26 @@ public class EncoderHevcQsv : EncoderBase public override string Name => "hevc_qsv"; public override StreamKind Kind => StreamKind.Video; + + // need to upload if we're still in software and a watermark is used + public override string Filter + { + get + { + // only upload to hw if we need to overlay a watermark + if (_maybeWatermarkInputFile.IsSome && _currentState.FrameDataLocation == FrameDataLocation.Software) + { + // pixel format should already be converted to a supported format by QsvHardwareAccelerationOption + foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) + { + return $"format={pixelFormat.FFmpegName},hwupload=extra_hw_frames=64"; + } + + // default to nv12 + return "format=nv12,hwupload=extra_hw_frames=64"; + } + + return string.Empty; + } + } } diff --git a/ErsatzTV.FFmpeg/Filter/AvailableOverlayFilters.cs b/ErsatzTV.FFmpeg/Filter/AvailableOverlayFilters.cs index a8d0741d7..a8c5590f3 100644 --- a/ErsatzTV.FFmpeg/Filter/AvailableOverlayFilters.cs +++ b/ErsatzTV.FFmpeg/Filter/AvailableOverlayFilters.cs @@ -1,4 +1,5 @@ using ErsatzTV.FFmpeg.Filter.Cuda; +using ErsatzTV.FFmpeg.Filter.Qsv; using ErsatzTV.FFmpeg.State; namespace ErsatzTV.FFmpeg.Filter; @@ -12,7 +13,7 @@ public static class AvailableOverlayFilters accelMode switch { HardwareAccelerationMode.Nvenc => new OverlayCudaFilter(watermarkState, resolution), - // HardwareAccelerationMode.Qsv => new DeinterlaceQsvFilter(), + HardwareAccelerationMode.Qsv => new OverlayQsvFilter(watermarkState, resolution), // HardwareAccelerationMode.Vaapi => new DeinterlaceVaapiFilter(), _ => new OverlayFilter(watermarkState, resolution) }; diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/OverlayQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/OverlayQsvFilter.cs new file mode 100644 index 000000000..2971e1ca0 --- /dev/null +++ b/ErsatzTV.FFmpeg/Filter/Qsv/OverlayQsvFilter.cs @@ -0,0 +1,14 @@ +using ErsatzTV.FFmpeg.State; + +namespace ErsatzTV.FFmpeg.Filter.Qsv; + +public class OverlayQsvFilter : OverlayFilter +{ + public OverlayQsvFilter(WatermarkState watermarkState, FrameSize resolution) : base(watermarkState, resolution) + { + } + + public override FrameState NextState(FrameState currentState) => currentState; + + public override string Filter => $"overlay_qsv={Position}"; +} diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs index 11fa20b89..4fe963428 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs @@ -17,6 +17,8 @@ public class ScaleQsvFilter : BaseFilter { get { + // use vpp_qsv because scale_qsv sometimes causes green lines at the bottom + string scale = string.Empty; if (_currentState.ScaledSize == _scaledSize) @@ -24,7 +26,7 @@ public class ScaleQsvFilter : BaseFilter foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) { // don't need scaling, but still need pixel format - scale = $"scale_qsv=format={pixelFormat.FFmpegName}"; + scale = $"vpp_qsv=format={pixelFormat.FFmpegName}"; } } else @@ -35,8 +37,8 @@ public class ScaleQsvFilter : BaseFilter format = $":format={pixelFormat.FFmpegName}"; } - string targetSize = $"{_scaledSize.Width}:{_scaledSize.Height}"; - scale = $"scale_qsv={targetSize}{format}"; + string targetSize = $"w={_scaledSize.Width}:h={_scaledSize.Height}"; + scale = $"vpp_qsv={targetSize}{format}"; } if (_currentState.FrameDataLocation == FrameDataLocation.Hardware) diff --git a/ErsatzTV.FFmpeg/Filter/WatermarkPixelFormatFilter.cs b/ErsatzTV.FFmpeg/Filter/WatermarkPixelFormatFilter.cs index 34f85171c..f35160b30 100644 --- a/ErsatzTV.FFmpeg/Filter/WatermarkPixelFormatFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/WatermarkPixelFormatFilter.cs @@ -26,6 +26,7 @@ public class WatermarkPixelFormatFilter : BaseFilter Option maybeFormat = _ffmpegState.HardwareAccelerationMode switch { HardwareAccelerationMode.Nvenc => "yuva420p", + HardwareAccelerationMode.Qsv => "yuva420p", _ when _watermarkState.Opacity != 100 || hasFadePoints => "yuva420p|yuva444p|yuva422p|rgba|abgr|bgra|gbrap|ya8", _ => None diff --git a/ErsatzTV.FFmpeg/PipelineBuilder.cs b/ErsatzTV.FFmpeg/PipelineBuilder.cs index e56f28a01..10f1cc2bf 100644 --- a/ErsatzTV.FFmpeg/PipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/PipelineBuilder.cs @@ -387,6 +387,7 @@ public class PipelineBuilder ffmpegState, currentState, desiredState, + _watermarkInputFile, _logger)) { encoder = e;