aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgnattu <gnattuoc@me.com>2024-12-09 16:17:49 +0800
committergnattu <gnattuoc@me.com>2024-12-09 16:17:49 +0800
commit0fc288936d10afc146780d118361f2e722768ee6 (patch)
treec8975b836146a8b0ba40eee5b7f2fb6842064c08
parent6691380c04d0545ba5cb72597ec4331dcf6cafcf (diff)
Enable VideoToolbox AV1 decode
This decoder differs from others provided by VideoToolbox in that it lacks any software fallback. To achieve consistent behavior with other VideoToolbox decoders, this PR implemented additional checking on the server to simulate the software fallback provided by VideoToolbox. The current fallback checking mechanism is a temporary solution. In the long term, it should be replaced with a more capable hardware capability checking system.
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs8
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs6
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/ApplePlatformHelper.cs85
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs10
5 files changed, 114 insertions, 0 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 9399679a4f..7dea5f8ebd 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -6610,6 +6610,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1 at the moment.
bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupported();
@@ -6643,6 +6644,13 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
}
+
+ if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ && isAv1SupportedSwFormatsVt
+ && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
+ {
+ return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
+ }
}
return null;
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index c767b4a519..a60f523408 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -76,6 +76,12 @@ namespace MediaBrowser.Controller.MediaEncoding
bool IsVaapiDeviceSupportVulkanDrmInterop { get; }
/// <summary>
+ /// Gets a value indicating whether av1 decoding is available via VideoToolbox.
+ /// </summary>
+ /// <value><c>true</c> if the av1 is available via VideoToolbox, <c>false</c> otherwise.</value>
+ bool IsVideoToolboxAv1DecodeAvailable { get; }
+
+ /// <summary>
/// Whether given encoder codec is supported.
/// </summary>
/// <param name="encoder">The encoder.</param>
diff --git a/MediaBrowser.MediaEncoding/Encoder/ApplePlatformHelper.cs b/MediaBrowser.MediaEncoding/Encoder/ApplePlatformHelper.cs
new file mode 100644
index 0000000000..ea2289bd7d
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/ApplePlatformHelper.cs
@@ -0,0 +1,85 @@
+#pragma warning disable CA1031
+
+using System;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Microsoft.Extensions.Logging;
+
+namespace MediaBrowser.MediaEncoding.Encoder;
+
+/// <summary>
+/// Helper class for Apple platform specific operations.
+/// </summary>
+public static class ApplePlatformHelper
+{
+ private static readonly string[] _av1DecodeBlacklistedCpuClass = ["M1", "M2"];
+
+ private static string GetSysctlValue(string name)
+ {
+ IntPtr length = IntPtr.Zero;
+ // Get length of the value
+ int osStatus = SysctlByName(name, IntPtr.Zero, ref length, IntPtr.Zero, 0);
+
+ if (osStatus != 0)
+ {
+ throw new NotSupportedException($"Failed to get sysctl value for {name} with error {osStatus}");
+ }
+
+ IntPtr buffer = Marshal.AllocHGlobal(length.ToInt32());
+ try
+ {
+ osStatus = SysctlByName(name, buffer, ref length, IntPtr.Zero, 0);
+ if (osStatus != 0)
+ {
+ throw new NotSupportedException($"Failed to get sysctl value for {name} with error {osStatus}");
+ }
+
+ return Marshal.PtrToStringAnsi(buffer) ?? string.Empty;
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
+
+ private static int SysctlByName(string name, IntPtr oldp, ref IntPtr oldlenp, IntPtr newp, uint newlen)
+ {
+ return NativeMethods.SysctlByName(System.Text.Encoding.ASCII.GetBytes(name), oldp, ref oldlenp, newp, newlen);
+ }
+
+ /// <summary>
+ /// Check if the current system has hardware acceleration for AV1 decoding.
+ /// </summary>
+ /// <param name="logger">The logger used for error logging.</param>
+ /// <returns>Boolean indicates the hwaccel support.</returns>
+ public static bool HasAv1HardwareAccel(ILogger logger)
+ {
+ if (!RuntimeInformation.OSArchitecture.Equals(Architecture.Arm64))
+ {
+ return false;
+ }
+
+ try
+ {
+ string cpuBrandString = GetSysctlValue("machdep.cpu.brand_string");
+ return !_av1DecodeBlacklistedCpuClass.Any(blacklistedCpuClass => cpuBrandString.Contains(blacklistedCpuClass, StringComparison.OrdinalIgnoreCase));
+ }
+ catch (NotSupportedException e)
+ {
+ logger.LogError("Error getting CPU brand string: {Message}", e.Message);
+ }
+ catch (Exception e)
+ {
+ logger.LogError("Unknown error occured: {Exception}", e);
+ }
+
+ return false;
+ }
+
+ private static class NativeMethods
+ {
+ [DllImport("libc", EntryPoint = "sysctlbyname", SetLastError = true)]
+ [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
+ internal static extern int SysctlByName(byte[] name, IntPtr oldp, ref IntPtr oldlenp, IntPtr newp, uint newlen);
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 23d9ca7ef5..776b2ab42c 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -437,6 +437,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
+ public bool CheckIsVideoToolboxAv1DecodeAvailable()
+ {
+ return ApplePlatformHelper.HasAv1HardwareAccel(_logger);
+ }
+
private IEnumerable<string> GetHwaccelTypes()
{
string? output = null;
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index a34238cd68..44b38f03bc 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -83,6 +83,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
private bool _isVaapiDeviceSupportVulkanDrmModifier = false;
private bool _isVaapiDeviceSupportVulkanDrmInterop = false;
+ private bool _isVideoToolboxAv1DecodeAvailable = false;
+
private static string[] _vulkanImageDrmFmtModifierExts =
{
"VK_EXT_image_drm_format_modifier",
@@ -153,6 +155,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc />
public bool IsVaapiDeviceSupportVulkanDrmInterop => _isVaapiDeviceSupportVulkanDrmInterop;
+ public bool IsVideoToolboxAv1DecodeAvailable => _isVideoToolboxAv1DecodeAvailable;
+
[GeneratedRegex(@"[^\/\\]+?(\.[^\/\\\n.]+)?$")]
private static partial Regex FfprobePathRegex();
@@ -255,6 +259,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger.LogInformation("VAAPI device {RenderNodePath} supports Vulkan DRM interop", options.VaapiDevice);
}
}
+
+ // Check if VideoToolbox supports AV1 decode
+ if (OperatingSystem.IsMacOS() && SupportsHwaccel("videotoolbox"))
+ {
+ _isVideoToolboxAv1DecodeAvailable = validator.CheckIsVideoToolboxAv1DecodeAvailable();
+ }
}
_logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty);