mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
5.9 KiB
182 lines
5.9 KiB
using System.Runtime.InteropServices; |
|
using System.Text; |
|
using Microsoft.Extensions.Logging; |
|
|
|
namespace ErsatzTV.FFmpeg.Capabilities.VideoToolbox; |
|
|
|
internal static partial class VideoToolboxUtil |
|
{ |
|
private const string CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; |
|
private const string VideoToolbox = "/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox"; |
|
private const string LibSystem = "/usr/lib/libSystem.dylib"; |
|
|
|
[LibraryImport(CoreFoundation)] |
|
private static partial long CFArrayGetCount(IntPtr array); |
|
|
|
[LibraryImport(CoreFoundation)] |
|
private static partial IntPtr CFArrayGetValueAtIndex(IntPtr array, int index); |
|
|
|
[LibraryImport(CoreFoundation)] |
|
private static partial IntPtr CFDictionaryGetValue(IntPtr dict, IntPtr key); |
|
|
|
[LibraryImport(CoreFoundation)] |
|
private static partial IntPtr CFStringGetLength(IntPtr theString); |
|
|
|
[LibraryImport(CoreFoundation)] |
|
private static partial IntPtr CFStringGetCStringPtr(IntPtr theString, uint encoding); |
|
|
|
[LibraryImport(CoreFoundation, StringMarshalling = StringMarshalling.Utf8)] |
|
[return: MarshalAs(UnmanagedType.I1)] |
|
private static partial bool CFStringGetCString(IntPtr theString, byte[] buffer, long bufferSize, uint encoding); |
|
|
|
[LibraryImport(CoreFoundation)] |
|
private static partial void CFRelease(IntPtr cf); |
|
|
|
[LibraryImport(VideoToolbox)] |
|
private static partial int VTCopyVideoEncoderList(IntPtr options, out IntPtr listOfEncoders); |
|
|
|
[LibraryImport(VideoToolbox)] |
|
[return: MarshalAs(UnmanagedType.I1)] |
|
private static partial bool VTIsHardwareDecodeSupported(uint codecType); |
|
|
|
[LibraryImport(LibSystem, StringMarshalling = StringMarshalling.Utf8)] |
|
private static partial IntPtr dlopen(string path, int mode); |
|
|
|
[LibraryImport(LibSystem, StringMarshalling = StringMarshalling.Utf8)] |
|
private static partial IntPtr dlsym(IntPtr handle, string symbol); |
|
|
|
[LibraryImport(LibSystem)] |
|
private static partial int dlclose(IntPtr handle); |
|
|
|
private static IntPtr GetCFString(string frameworkPath, string symbolName) |
|
{ |
|
IntPtr frameworkHandle = dlopen(frameworkPath, 0); // RTLD_NOW |
|
if (frameworkHandle == IntPtr.Zero) |
|
{ |
|
return IntPtr.Zero; |
|
} |
|
|
|
try |
|
{ |
|
IntPtr symbol = dlsym(frameworkHandle, symbolName); |
|
return Marshal.ReadIntPtr(symbol); |
|
} |
|
finally |
|
{ |
|
_ = dlclose(frameworkHandle); |
|
} |
|
} |
|
|
|
private static string? CFStringToString(IntPtr cfString) |
|
{ |
|
if (cfString == IntPtr.Zero) |
|
{ |
|
return null; |
|
} |
|
|
|
const uint kCFStringEncodingUTF8 = 0x08000100; |
|
|
|
IntPtr cStringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8); |
|
if (cStringPtr != IntPtr.Zero) |
|
{ |
|
return Marshal.PtrToStringAnsi(cStringPtr); |
|
} |
|
|
|
long length = CFStringGetLength(cfString); |
|
if (length == 0) |
|
{ |
|
return string.Empty; |
|
} |
|
|
|
long maxSize = length * 4 + 1; |
|
byte[] buffer = new byte[maxSize]; |
|
if (CFStringGetCString(cfString, buffer, maxSize, kCFStringEncodingUTF8)) |
|
{ |
|
int terminator = Array.IndexOf(buffer, (byte)0); |
|
int actualLength = terminator >= 0 ? terminator : buffer.Length; |
|
return Encoding.UTF8.GetString(buffer, 0, actualLength); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
private static uint FourCCToUInt32(string fourCC) |
|
{ |
|
if (fourCC.Length != 4) |
|
{ |
|
throw new ArgumentException("FourCC must be 4 characters long.", nameof(fourCC)); |
|
} |
|
|
|
return ((uint)fourCC[0] << 24) | ((uint)fourCC[1] << 16) | ((uint)fourCC[2] << 8) | (uint)fourCC[3]; |
|
} |
|
|
|
internal static List<string> GetAvailableEncoders(ILogger logger) |
|
{ |
|
var encoderNames = new List<string>(); |
|
|
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|
{ |
|
return encoderNames; |
|
} |
|
|
|
IntPtr kVTVideoEncoderList_EncoderName = GetCFString(VideoToolbox, "kVTVideoEncoderList_EncoderName"); |
|
if (kVTVideoEncoderList_EncoderName == IntPtr.Zero) |
|
{ |
|
logger.LogWarning("Failed to load kVTVideoEncoderList_EncoderName symbol."); |
|
return encoderNames; |
|
} |
|
|
|
IntPtr encoderList = IntPtr.Zero; |
|
try |
|
{ |
|
int status = VTCopyVideoEncoderList(IntPtr.Zero, out encoderList); |
|
|
|
if (status != 0 || encoderList == IntPtr.Zero) |
|
{ |
|
logger.LogWarning("VTCopyVideoEncoderList failed with status: {Status}", status); |
|
return encoderNames; |
|
} |
|
|
|
var count = (int)CFArrayGetCount(encoderList); |
|
for (int i = 0; i < count; i++) |
|
{ |
|
IntPtr encoderDict = CFArrayGetValueAtIndex(encoderList, i); |
|
if (encoderDict == IntPtr.Zero) |
|
{ |
|
continue; |
|
} |
|
|
|
IntPtr encoderNameCfString = CFDictionaryGetValue(encoderDict, kVTVideoEncoderList_EncoderName); |
|
string? encoderName = CFStringToString(encoderNameCfString); |
|
|
|
if (!string.IsNullOrEmpty(encoderName)) |
|
{ |
|
encoderNames.Add(encoderName); |
|
} |
|
} |
|
} |
|
finally |
|
{ |
|
if (encoderList != IntPtr.Zero) |
|
{ |
|
CFRelease(encoderList); |
|
} |
|
} |
|
|
|
return encoderNames; |
|
} |
|
|
|
internal static bool IsHardwareDecoderSupported(string codecFourCC, ILogger logger) |
|
{ |
|
try |
|
{ |
|
uint codecType = FourCCToUInt32(codecFourCC); |
|
return VTIsHardwareDecodeSupported(codecType); |
|
} |
|
catch (Exception ex) |
|
{ |
|
logger.LogWarning(ex, "Unexpected error checking decoder support for {CodecFourCC}", codecFourCC); |
|
return false; |
|
} |
|
} |
|
}
|
|
|