diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a4388b5e..d9e76fcec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - When items are missing from disk, they will be flagged and present in the `Media` > `Trash` page - The trash page can be used to permanently remove missing items from the database - When items reappear at the expected location on disk, they will be unflagged and removed from the trash +- Add basic Mac hardware acceleration using VideoToolbox ### Changed - Local libraries only: when items are missing from disk, they will be added to the trash and no longer removed from collections, etc. diff --git a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs index 44d0fb3a3..de9716435 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs @@ -94,6 +94,17 @@ namespace ErsatzTV.Core.Tests.FFmpeg { HardwareAccelerationKind.Vaapi }; + + public static string[] VideoToolboxCodecs = + { + "h264_videotoolbox", + "hevc_videotoolbox" + }; + + public static HardwareAccelerationKind[] VideoToolboxAcceleration = + { + HardwareAccelerationKind.VideoToolbox + }; } [Test, Combinatorial] @@ -108,10 +119,12 @@ namespace ErsatzTV.Core.Tests.FFmpeg bool pad, // [ValueSource(typeof(TestData), nameof(TestData.SoftwareCodecs))] string profileCodec, // [ValueSource(typeof(TestData), nameof(TestData.NoAcceleration))] HardwareAccelerationKind profileAcceleration) - [ValueSource(typeof(TestData), nameof(TestData.NvidiaCodecs))] string profileCodec, - [ValueSource(typeof(TestData), nameof(TestData.NvidiaAcceleration))] HardwareAccelerationKind profileAcceleration) + // [ValueSource(typeof(TestData), nameof(TestData.NvidiaCodecs))] string profileCodec, + // [ValueSource(typeof(TestData), nameof(TestData.NvidiaAcceleration))] HardwareAccelerationKind profileAcceleration) // [ValueSource(typeof(TestData), nameof(TestData.VaapiCodecs))] string profileCodec, // [ValueSource(typeof(TestData), nameof(TestData.VaapiAcceleration))] HardwareAccelerationKind profileAcceleration) + [ValueSource(typeof(TestData), nameof(TestData.VideoToolboxCodecs))] string profileCodec, + [ValueSource(typeof(TestData), nameof(TestData.VideoToolboxAcceleration))] HardwareAccelerationKind profileAcceleration) { string name = GetStringSha256Hash( $"{inputCodec}_{inputPixelFormat}_{pad}_{profileResolution}_{profileCodec}_{profileAcceleration}"); diff --git a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs index 0bd1fe3d6..b63b223e5 100644 --- a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs +++ b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs @@ -5,6 +5,7 @@ None = 0, Qsv = 1, Nvenc = 2, - Vaapi = 3 + Vaapi = 3, + VideoToolbox = 4 } } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs index 06fa4815a..75b1868d9 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs @@ -149,6 +149,7 @@ namespace ErsatzTV.Core.FFmpeg HardwareAccelerationKind.Nvenc => !isSong, HardwareAccelerationKind.Qsv => !isSong, + HardwareAccelerationKind.VideoToolbox => false, _ => false }; @@ -169,9 +170,11 @@ namespace ErsatzTV.Core.FFmpeg audioFilterQueue.Add($"apad=whole_dur={durationString}ms"); }); - bool usesHardwareFilters = acceleration != HardwareAccelerationKind.None && !isHardwareDecode && + bool usesHardwareFilters = acceleration != HardwareAccelerationKind.None && + acceleration != HardwareAccelerationKind.VideoToolbox && + !isHardwareDecode && (_deinterlace || _scaleToSize.IsSome); - + if (isSong) { switch (acceleration) diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs index 921386628..194d19bcb 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs @@ -108,6 +108,10 @@ namespace ErsatzTV.Core.FFmpeg _arguments.Add("-hwaccel_output_format"); _arguments.Add("vaapi"); break; + case HardwareAccelerationKind.VideoToolbox: + _arguments.Add("-hwaccel"); + _arguments.Add("videotoolbox"); + break; } _complexFilterBuilder = _complexFilterBuilder.WithHardwareAcceleration(hwAccel); diff --git a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs index ff4479dcc..1d28c5a7d 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs @@ -115,6 +115,9 @@ namespace ErsatzTV.Infrastructure.Health.Checks case "qsv": result.Add(HardwareAccelerationKind.Qsv); break; + case "videotoolbox": + result.Add(HardwareAccelerationKind.VideoToolbox); + break; } } diff --git a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs index c4825b22c..a4288d4ea 100644 --- a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs +++ b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs @@ -10,6 +10,7 @@ namespace ErsatzTV.Validators private static readonly List QsvEncoders = new() { "h264_qsv", "hevc_qsv", "mpeg2_qsv" }; private static readonly List NvencEncoders = new() { "h264_nvenc", "hevc_nvenc" }; private static readonly List VaapiEncoders = new() { "h264_vaapi", "hevc_vaapi", "mpeg2_vaapi" }; + private static readonly List VideoToolboxEncoders = new() { "h264_videotoolbox", "hevc_videotoolbox" }; public FFmpegProfileEditViewModelValidator() { @@ -53,11 +54,21 @@ namespace ErsatzTV.Validators .WithMessage("VAAPI codec is required (h264_vaapi, hevc_vaapi, mpeg2_vaapi)"); }); + When( + x => x.HardwareAcceleration == HardwareAccelerationKind.VideoToolbox, + () => + { + RuleFor(x => x.VideoCodec).Must(c => VideoToolboxEncoders.Contains(c)) + .WithMessage("VideoToolbox codec is required (h264_videotoolbox, hevc_videotoolbox)"); + }); + When( x => x.HardwareAcceleration == HardwareAccelerationKind.None, () => { - RuleFor(x => x.VideoCodec).Must(c => !QsvEncoders.Contains(c) && !NvencEncoders.Contains(c)) + RuleFor(x => x.VideoCodec).Must( + c => !QsvEncoders.Contains(c) && !NvencEncoders.Contains(c) && !VaapiEncoders.Contains(c) && + !VideoToolboxEncoders.Contains(c)) .WithMessage("Hardware acceleration is required for this codec"); }); }