From 27b923b462e6e7c80d352a4a1302cd403ccd0bf1 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Sun, 18 Sep 2022 21:02:09 -0500 Subject: [PATCH] qsv and vaapi scaling fixes (#966) * add qsv device option to ffmpeg profile * fix vaapi scaling * cleanup --- CHANGELOG.md | 3 + .../FFmpeg/FFmpegLibraryProcessService.cs | 5 +- .../Filter/AvailableScaleFilters.cs | 6 +- ErsatzTV.FFmpeg/Filter/SetDarFilter.cs | 14 ----- .../Filter/Vaapi/ScaleVaapiFilter.cs | 35 ++++++++++-- .../AvailableHardwareAccelerationOptions.cs | 6 +- .../QsvHardwareAccelerationOption.cs | 20 ++++++- ErsatzTV.FFmpeg/PipelineBuilder.cs | 23 -------- ErsatzTV/Pages/FFmpegEditor.razor | 55 ++++++++++++------- 9 files changed, 101 insertions(+), 66 deletions(-) delete mode 100644 ErsatzTV.FFmpeg/Filter/SetDarFilter.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 27b1f0fda..00c4d63f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix scaling logic for `Nvidia` acceleration and software mode - Attempt to position watermarks within content (not over added black padding) +### Added +- Add `QSV Device` option to ffmpeg profile on linux + ## [0.6.7-beta] - 2022-09-05 ### Fixed - When all audio streams are selected with `HLS Direct`, explicitly copy them without transcoding diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 8448df0d8..63ee27b0e 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -623,7 +623,10 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService } private static Option VaapiDeviceName(HardwareAccelerationMode accelerationMode, string vaapiDevice) => - accelerationMode == HardwareAccelerationMode.Vaapi ? vaapiDevice : Option.None; + accelerationMode == HardwareAccelerationMode.Vaapi || + OperatingSystem.IsLinux() && accelerationMode == HardwareAccelerationMode.Qsv + ? vaapiDevice + : Option.None; private static string GetVideoFormat(FFmpegPlaybackSettings playbackSettings) => playbackSettings.VideoFormat switch diff --git a/ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs b/ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs index 6f2d670b2..cc7fa5df6 100644 --- a/ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs +++ b/ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs @@ -28,7 +28,11 @@ public static class AvailableScaleFilters extraHardwareFrames, isAnamorphicEdgeCase, sampleAspectRatio), - HardwareAccelerationMode.Vaapi => new ScaleVaapiFilter(currentState, scaledSize, paddedSize), + HardwareAccelerationMode.Vaapi => new ScaleVaapiFilter( + currentState, + scaledSize, + paddedSize, + isAnamorphicEdgeCase), _ => new ScaleFilter(currentState, scaledSize, paddedSize, isAnamorphicEdgeCase) }; } diff --git a/ErsatzTV.FFmpeg/Filter/SetDarFilter.cs b/ErsatzTV.FFmpeg/Filter/SetDarFilter.cs deleted file mode 100644 index 1f59fac5f..000000000 --- a/ErsatzTV.FFmpeg/Filter/SetDarFilter.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace ErsatzTV.FFmpeg.Filter; - -public class SetDarFilter : BaseFilter -{ - private readonly string _displayAspectRatio; - - public SetDarFilter(string displayAspectRatio) - { - _displayAspectRatio = displayAspectRatio; - } - - public override string Filter => $"setdar=dar={_displayAspectRatio.Replace(':', '/')}"; - public override FrameState NextState(FrameState currentState) => currentState; -} diff --git a/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs b/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs index ac5b6f57a..8793db08a 100644 --- a/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs @@ -6,13 +6,19 @@ public class ScaleVaapiFilter : BaseFilter { private readonly FrameState _currentState; private readonly FrameSize _paddedSize; + private readonly bool _isAnamorphicEdgeCase; private readonly FrameSize _scaledSize; - public ScaleVaapiFilter(FrameState currentState, FrameSize scaledSize, FrameSize paddedSize) + public ScaleVaapiFilter( + FrameState currentState, + FrameSize scaledSize, + FrameSize paddedSize, + bool isAnamorphicEdgeCase) { _currentState = currentState; _scaledSize = scaledSize; _paddedSize = paddedSize; + _isAnamorphicEdgeCase = isAnamorphicEdgeCase; } public override string Filter @@ -31,14 +37,34 @@ public class ScaleVaapiFilter : BaseFilter } else { + string aspectRatio = string.Empty; + if (_scaledSize != _paddedSize) + { + aspectRatio = ":force_original_aspect_ratio=decrease"; + } + + string squareScale = string.Empty; + string targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; string format = string.Empty; foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) { format = $":format={pixelFormat.FFmpegName}"; } - string targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; - scale = $"scale_vaapi={targetSize}:force_divisible_by=2{format}"; + if (_isAnamorphicEdgeCase) + { + squareScale = $"scale_vaapi=iw:sar*ih{format},setsar=1,"; + } + else if (_currentState.IsAnamorphic) + { + squareScale = $"scale_vaapi=iw*sar:ih{format},setsar=1,"; + } + else + { + aspectRatio += ",setsar=1"; + } + + scale = $"{squareScale}scale_vaapi={targetSize}:force_divisible_by=2{format}{aspectRatio}"; } if (_currentState.FrameDataLocation == FrameDataLocation.Hardware) @@ -59,6 +85,7 @@ public class ScaleVaapiFilter : BaseFilter { ScaledSize = _scaledSize, PaddedSize = _scaledSize, - FrameDataLocation = FrameDataLocation.Hardware + FrameDataLocation = FrameDataLocation.Hardware, + IsAnamorphic = false // this filter always outputs square pixels }; } diff --git a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs index 7ca18783e..e5e72621e 100644 --- a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs +++ b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs @@ -6,13 +6,13 @@ public static class AvailableHardwareAccelerationOptions { public static Option ForMode( HardwareAccelerationMode mode, - Option vaapiDevice, + Option gpuDevice, ILogger logger) => mode switch { HardwareAccelerationMode.Nvenc => new CudaHardwareAccelerationOption(), - HardwareAccelerationMode.Qsv => new QsvHardwareAccelerationOption(), - HardwareAccelerationMode.Vaapi => GetVaapiAcceleration(vaapiDevice, logger), + HardwareAccelerationMode.Qsv => new QsvHardwareAccelerationOption(gpuDevice), + HardwareAccelerationMode.Vaapi => GetVaapiAcceleration(gpuDevice, logger), HardwareAccelerationMode.VideoToolbox => new VideoToolboxHardwareAccelerationOption(), HardwareAccelerationMode.Amf => new AmfHardwareAccelerationOption(), HardwareAccelerationMode.None => Option.None, diff --git a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/QsvHardwareAccelerationOption.cs b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/QsvHardwareAccelerationOption.cs index 64f5073da..984920eae 100644 --- a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/QsvHardwareAccelerationOption.cs +++ b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/QsvHardwareAccelerationOption.cs @@ -4,6 +4,13 @@ namespace ErsatzTV.FFmpeg.Option.HardwareAcceleration; public class QsvHardwareAccelerationOption : GlobalOption { + private readonly Option _qsvDevice; + + public QsvHardwareAccelerationOption(Option qsvDevice) + { + _qsvDevice = qsvDevice; + } + // TODO: read this from ffmpeg output private readonly List _supportedFFmpegFormats = new() { @@ -17,7 +24,7 @@ public class QsvHardwareAccelerationOption : GlobalOption { string[] initDevices = OperatingSystem.IsWindows() ? new[] { "-init_hw_device", "qsv=hw:hw,child_device_type=dxva2", "-filter_hw_device", "hw" } - : new[] { "-init_hw_device", "qsv=hw", "-filter_hw_device", "hw" }; + : new[] { "-init_hw_device", "qsv=hw:hw,child_device_type=vaapi", "-filter_hw_device", "hw" }; var result = new List { @@ -25,6 +32,17 @@ public class QsvHardwareAccelerationOption : GlobalOption "-hwaccel_output_format", "qsv" }; + if (OperatingSystem.IsLinux()) + { + foreach (string qsvDevice in _qsvDevice) + { + if (!string.IsNullOrWhiteSpace(qsvDevice)) + { + result.AddRange(new[] { "-qsv_device", qsvDevice }); + } + } + } + result.AddRange(initDevices); return result; diff --git a/ErsatzTV.FFmpeg/PipelineBuilder.cs b/ErsatzTV.FFmpeg/PipelineBuilder.cs index df7403bd2..4eca480d0 100644 --- a/ErsatzTV.FFmpeg/PipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/PipelineBuilder.cs @@ -385,13 +385,6 @@ public class PipelineBuilder IPipelineFilterStep padStep = new PadFilter(currentState, desiredState.PaddedSize); currentState = padStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(padStep)); - - // if (videoStream.DisplayAspectRatio == desiredState.DisplayAspectRatio) - // { - // IPipelineFilterStep darStep = new SetDarFilter(desiredState.DisplayAspectRatio); - // currentState = darStep.NextState(currentState); - // _videoInputFile.Iter(f => f.FilterSteps.Add(darStep)); - // } } } else if (currentState.ScaledSize != desiredState.ScaledSize) @@ -415,14 +408,6 @@ public class PipelineBuilder currentState = padStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(padStep)); } - - // if (videoStream.DisplayAspectRatio == desiredState.DisplayAspectRatio || - // ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv) - // { - // IPipelineFilterStep darStep = new SetDarFilter(desiredState.DisplayAspectRatio); - // currentState = darStep.NextState(currentState); - // _videoInputFile.Iter(f => f.FilterSteps.Add(darStep)); - // } } else if (currentState.PaddedSize != desiredState.PaddedSize) { @@ -444,14 +429,6 @@ public class PipelineBuilder currentState = padStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(padStep)); } - - // if (videoStream.DisplayAspectRatio == desiredState.DisplayAspectRatio || - // ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv) - // { - // IPipelineFilterStep darStep = new SetDarFilter(desiredState.DisplayAspectRatio); - // currentState = darStep.NextState(currentState); - // _videoInputFile.Iter(f => f.FilterSteps.Add(darStep)); - // } } if (hasOverlay && currentState.PixelFormat.Map(pf => pf.FFmpegName) != diff --git a/ErsatzTV/Pages/FFmpegEditor.razor b/ErsatzTV/Pages/FFmpegEditor.razor index 5bb4fb726..d5416720f 100644 --- a/ErsatzTV/Pages/FFmpegEditor.razor +++ b/ErsatzTV/Pages/FFmpegEditor.razor @@ -4,6 +4,8 @@ @using ErsatzTV.Application.Resolutions @using ErsatzTV.Core.FFmpeg @using ErsatzTV.Application.FFmpegProfiles +@using ErsatzTV.Infrastructure.Runtime +@using System.Runtime.InteropServices @implements IDisposable @inject NavigationManager _navigationManager @inject ILogger _logger @@ -60,25 +62,40 @@ } - - - @foreach (VaapiDriver driver in Enum.GetValues()) - { - @driver - } - - - - - @foreach (string device in _vaapiDevices) - { - @device - } - - - - - + @if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + @if (_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi) + { + + + @foreach (VaapiDriver driver in Enum.GetValues()) + { + @driver + } + + + } + @if (_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi || _model.HardwareAcceleration == HardwareAccelerationKind.Qsv) + { + + + @foreach (string device in _vaapiDevices) + { + @device + } + + + } + } + @if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv) + { + + + + }