diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e0b0f79..1cfd00591 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - When a title is specified, audio streams that contain that title (case-insensitive search) will be prioritized - This can be helpful for creating channels that use commentary tracks - External tooling exists to easily update title/name metadata if your audio streams don't already have this metadata +- Add `Amf` hardware acceleration option for AMD GPUs on Windows ## [0.6.6-beta] - 2022-08-17 ### Fixed diff --git a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs index 4a109ecc9..c8f093846 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs @@ -168,6 +168,11 @@ public class TranscodingTests HardwareAccelerationKind.VideoToolbox }; + public static HardwareAccelerationKind[] AmfAcceleration = + { + HardwareAccelerationKind.Amf + }; + public static HardwareAccelerationKind[] QsvAcceleration = { HardwareAccelerationKind.Qsv @@ -192,11 +197,11 @@ public class TranscodingTests [ValueSource(typeof(TestData), nameof(TestData.VideoFormats))] FFmpegProfileVideoFormat profileVideoFormat, // [ValueSource(typeof(TestData), nameof(TestData.NoAcceleration))] HardwareAccelerationKind profileAcceleration) - [ValueSource(typeof(TestData), nameof(TestData.NvidiaAcceleration))] - HardwareAccelerationKind profileAcceleration) + // [ValueSource(typeof(TestData), nameof(TestData.NvidiaAcceleration))] HardwareAccelerationKind profileAcceleration) // [ValueSource(typeof(TestData), nameof(TestData.VaapiAcceleration))] HardwareAccelerationKind profileAcceleration) // [ValueSource(typeof(TestData), nameof(TestData.QsvAcceleration))] HardwareAccelerationKind profileAcceleration) // [ValueSource(typeof(TestData), nameof(TestData.VideoToolboxAcceleration))] HardwareAccelerationKind profileAcceleration) + [ValueSource(typeof(TestData), nameof(TestData.AmfAcceleration))] HardwareAccelerationKind profileAcceleration) { if (inputFormat.Encoder is "mpeg1video" or "msmpeg4v2" or "msmpeg4v3") { diff --git a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs index 635fc4a6e..bc8d9426e 100644 --- a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs +++ b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs @@ -6,5 +6,6 @@ public enum HardwareAccelerationKind Qsv = 1, Nvenc = 2, Vaapi = 3, - VideoToolbox = 4 + VideoToolbox = 4, + Amf = 5 } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs index 33e841c87..03f89be55 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs @@ -169,6 +169,7 @@ public class FFmpegComplexFilterBuilder _videoDecoder.Contains("cuvid")), HardwareAccelerationKind.Qsv => !isSong, HardwareAccelerationKind.VideoToolbox => false, + HardwareAccelerationKind.Amf => false, _ => false }; @@ -200,6 +201,7 @@ public class FFmpegComplexFilterBuilder bool usesHardwareFilters = acceleration != HardwareAccelerationKind.None && acceleration != HardwareAccelerationKind.VideoToolbox && + acceleration != HardwareAccelerationKind.Amf && !isHardwareDecode && (_deinterlace || _scaleToSize.IsSome); diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 411362a77..08463af4b 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -627,6 +627,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService HardwareAccelerationKind.Qsv => HardwareAccelerationMode.Qsv, HardwareAccelerationKind.Vaapi => HardwareAccelerationMode.Vaapi, HardwareAccelerationKind.VideoToolbox => HardwareAccelerationMode.VideoToolbox, + HardwareAccelerationKind.Amf => HardwareAccelerationMode.Amf, _ => HardwareAccelerationMode.None }; } diff --git a/ErsatzTV.FFmpeg/Encoder/Amf/EncoderH264Amf.cs b/ErsatzTV.FFmpeg/Encoder/Amf/EncoderH264Amf.cs new file mode 100644 index 000000000..3fcc77304 --- /dev/null +++ b/ErsatzTV.FFmpeg/Encoder/Amf/EncoderH264Amf.cs @@ -0,0 +1,15 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Encoder.Amf; + +public class EncoderH264Amf : EncoderBase +{ + public override string Name => "h264_amf"; + public override StreamKind Kind => StreamKind.Video; + + public override FrameState NextState(FrameState currentState) => currentState with + { + VideoFormat = VideoFormat.H264, + FrameDataLocation = FrameDataLocation.Hardware + }; +} diff --git a/ErsatzTV.FFmpeg/Encoder/Amf/EncoderHevcAmf.cs b/ErsatzTV.FFmpeg/Encoder/Amf/EncoderHevcAmf.cs new file mode 100644 index 000000000..44828ce87 --- /dev/null +++ b/ErsatzTV.FFmpeg/Encoder/Amf/EncoderHevcAmf.cs @@ -0,0 +1,15 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Encoder.Amf; + +public class EncoderHevcAmf : EncoderBase +{ + public override string Name => "hevc_amf"; + public override StreamKind Kind => StreamKind.Video; + + public override FrameState NextState(FrameState currentState) => currentState with + { + VideoFormat = VideoFormat.Hevc, + FrameDataLocation = FrameDataLocation.Hardware + }; +} diff --git a/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs b/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs index cd3f8994e..b54c04a1d 100644 --- a/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs +++ b/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs @@ -1,4 +1,5 @@ using ErsatzTV.FFmpeg.Capabilities; +using ErsatzTV.FFmpeg.Encoder.Amf; using ErsatzTV.FFmpeg.Encoder.Nvenc; using ErsatzTV.FFmpeg.Encoder.Qsv; using ErsatzTV.FFmpeg.Encoder.Vaapi; @@ -72,6 +73,13 @@ public static class AvailableEncoders (HardwareAccelerationMode.VideoToolbox, VideoFormat.H264) when hardwareCapabilities.CanEncode( VideoFormat.H264, desiredState.PixelFormat) => new EncoderH264VideoToolbox(), + + (HardwareAccelerationMode.Amf, VideoFormat.Hevc) when hardwareCapabilities.CanEncode( + VideoFormat.Hevc, + desiredState.PixelFormat) => new EncoderHevcAmf(), + (HardwareAccelerationMode.Amf, VideoFormat.H264) when hardwareCapabilities.CanEncode( + VideoFormat.H264, + desiredState.PixelFormat) => new EncoderH264Amf(), (_, VideoFormat.Hevc) => new EncoderLibx265(currentState), (_, VideoFormat.H264) => new EncoderLibx264(), diff --git a/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs b/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs index ce25fc514..87cf32b83 100644 --- a/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs @@ -173,7 +173,8 @@ public class ComplexFilter : IPipelineStep if (_maybeSubtitleInputFile.Map(s => !s.IsImageBased).IfNone(false) && _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Vaapi && - _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.VideoToolbox) + _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.VideoToolbox && + _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Amf) { uploadDownloadFilter = new HardwareDownloadFilter(_currentState).Filter; } @@ -230,10 +231,9 @@ public class ComplexFilter : IPipelineStep if (filter != string.Empty) { - string tempVideoLabel = string.IsNullOrWhiteSpace(videoFilterComplex) && - string.IsNullOrWhiteSpace(watermarkFilterComplex) - ? $"[{videoLabel}]" - : videoLabel; + string tempVideoLabel = videoLabel.StartsWith('[') && videoLabel.EndsWith(']') + ? videoLabel + : $"[{videoLabel}]"; // vaapi uses software overlay and needs to upload // videotoolbox seems to require a hwupload for hevc diff --git a/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs b/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs index 154af760e..8640ddeb8 100644 --- a/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs @@ -25,6 +25,9 @@ public class SubtitleHardwareUploadFilter : BaseFilter // leave videotoolbox in software since we use a software overlay filter HardwareAccelerationMode.VideoToolbox => string.Empty, + // leave amf in software since we use a software overlay filter + HardwareAccelerationMode.Amf => string.Empty, + _ => "hwupload" }; diff --git a/ErsatzTV.FFmpeg/Filter/WatermarkHardwareUploadFilter.cs b/ErsatzTV.FFmpeg/Filter/WatermarkHardwareUploadFilter.cs index 51ce458cb..6c7861044 100644 --- a/ErsatzTV.FFmpeg/Filter/WatermarkHardwareUploadFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/WatermarkHardwareUploadFilter.cs @@ -24,6 +24,9 @@ public class WatermarkHardwareUploadFilter : BaseFilter // leave videotoolbox in software since we use a software overlay filter HardwareAccelerationMode.VideoToolbox => string.Empty, + // leave amf in software since we use a software overlay filter + HardwareAccelerationMode.Amf => string.Empty, + _ => "hwupload" }; diff --git a/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs b/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs index 3099cda4a..1c2c4d2fd 100644 --- a/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs +++ b/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs @@ -6,5 +6,6 @@ public enum HardwareAccelerationMode Qsv = 1, Nvenc = 2, Vaapi = 3, - VideoToolbox = 4 + VideoToolbox = 4, + Amf = 5 } diff --git a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AmfHardwareAccelerationOption.cs b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AmfHardwareAccelerationOption.cs new file mode 100644 index 000000000..3ac7bd1ea --- /dev/null +++ b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AmfHardwareAccelerationOption.cs @@ -0,0 +1,11 @@ +namespace ErsatzTV.FFmpeg.Option.HardwareAcceleration; + +public class AmfHardwareAccelerationOption : GlobalOption +{ + public override IList GlobalOptions => new List { "-hwaccel", "dxva2" }; + + public override FrameState NextState(FrameState currentState) => currentState with + { + FrameDataLocation = FrameDataLocation.Software + }; +} diff --git a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs index 9d2bbdcb2..7ca18783e 100644 --- a/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs +++ b/ErsatzTV.FFmpeg/Option/HardwareAcceleration/AvailableHardwareAccelerationOptions.cs @@ -14,6 +14,7 @@ public static class AvailableHardwareAccelerationOptions HardwareAccelerationMode.Qsv => new QsvHardwareAccelerationOption(), HardwareAccelerationMode.Vaapi => GetVaapiAcceleration(vaapiDevice, logger), HardwareAccelerationMode.VideoToolbox => new VideoToolboxHardwareAccelerationOption(), + HardwareAccelerationMode.Amf => new AmfHardwareAccelerationOption(), HardwareAccelerationMode.None => Option.None, _ => LogUnknownMode(mode, logger) }; diff --git a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs index be2509650..63ac0505b 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs @@ -1,8 +1,10 @@ using System.Reflection; +using System.Runtime.InteropServices; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Health; using ErsatzTV.Core.Health.Checks; using ErsatzTV.Core.Interfaces.Repositories; +using ErsatzTV.Core.Interfaces.Runtime; using ErsatzTV.Infrastructure.Data; using LanguageExt.UnsafeValueAccess; using Microsoft.EntityFrameworkCore; @@ -12,14 +14,17 @@ namespace ErsatzTV.Infrastructure.Health.Checks; public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAccelerationHealthCheck { private readonly IConfigElementRepository _configElementRepository; + private readonly IRuntimeInfo _runtimeInfo; private readonly IDbContextFactory _dbContextFactory; public HardwareAccelerationHealthCheck( IDbContextFactory dbContextFactory, - IConfigElementRepository configElementRepository) + IConfigElementRepository configElementRepository, + IRuntimeInfo runtimeInfo) { _dbContextFactory = dbContextFactory; _configElementRepository = configElementRepository; + _runtimeInfo = runtimeInfo; } public override string Title => "Hardware Acceleration"; @@ -91,7 +96,7 @@ public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAcceler return None; } - private static async Task> GetSupportedAccelerationKinds( + private async Task> GetSupportedAccelerationKinds( string ffmpegPath, CancellationToken cancellationToken) { @@ -120,6 +125,21 @@ public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAcceler } } + if (_runtimeInfo.IsOSPlatform(OSPlatform.Windows)) + { + string output2 = await GetProcessOutput( + ffmpegPath, + new[] { "-v", "quiet", "-encoders" }, + cancellationToken); + foreach (string method in output2.Split("\n").Map(s => s.Trim())) + { + if (method.Contains("_amf ")) + { + result.Add(HardwareAccelerationKind.Amf); + } + } + } + return result.ToList(); } } diff --git a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs index 125a095f4..c8eb0c54f 100644 --- a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs +++ b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs @@ -32,6 +32,12 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator AmfFormats = new() + { + FFmpegProfileVideoFormat.H264, + FFmpegProfileVideoFormat.Hevc + }; + public FFmpegProfileEditViewModelValidator() { RuleFor(x => x.Name).NotEmpty(); @@ -76,5 +82,13 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator x.VideoFormat).Must(c => VideoToolboxFormats.Contains(c)) .WithMessage("VideoToolbox supports formats (h264, hevc)"); }); + + When( + x => x.HardwareAcceleration == HardwareAccelerationKind.Amf, + () => + { + RuleFor(x => x.VideoFormat).Must(c => AmfFormats.Contains(c)) + .WithMessage("Amf supports formats (h264, hevc)"); + }); } }