diff --git a/ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaDevice.cs b/ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaDevice.cs index a0d2eeda9..da927d5ec 100644 --- a/ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaDevice.cs +++ b/ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaDevice.cs @@ -1,3 +1,5 @@ namespace ErsatzTV.FFmpeg.Capabilities.Nvidia; -public record CudaDevice(int Handle, string Model, Version Version); +public record CudaDevice(int Handle, string Model, Version Version, IReadOnlyList Codecs); + +public record CudaCodec(string Name, Guid CodecGuid, IReadOnlyList ProfileGuids, IReadOnlyList BitDepths); diff --git a/ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaHelper.cs b/ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaHelper.cs index 93767830f..ebda24f1d 100644 --- a/ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaHelper.cs +++ b/ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaHelper.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using System.Globalization; using System.Text; using ErsatzTV.FFmpeg.Format; @@ -62,69 +63,89 @@ internal static class CudaHelper foreach (var description in CuDevice.GetDescriptions()) { - var device = description.Device; - - string name = device.GetName(); - int nullIndex = name.IndexOf('\0'); - if (nullIndex > 0) + try { - name = name[..nullIndex]; - } + var device = description.Device; - int major = device.GetAttribute(CuDeviceAttribute.ComputeCapabilityMajor); - int minor = device.GetAttribute(CuDeviceAttribute.ComputeCapabilityMinor); + string name = device.GetName(); + int nullIndex = name.IndexOf('\0'); + if (nullIndex > 0) + { + name = name[..nullIndex]; + } - result.Add(new CudaDevice(device.Handle, name, new Version(major, minor))); - } + int major = device.GetAttribute(CuDeviceAttribute.ComputeCapabilityMajor); + int minor = device.GetAttribute(CuDeviceAttribute.ComputeCapabilityMinor); - return result; - } + using var context = device.CreateContext(); + var sessionParams = new NvEncOpenEncodeSessionExParams + { + Version = LibNvEnc.NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER, + ApiVersion = LibNvEnc.NVENCAPI_VERSION, + Device = context.Handle, + DeviceType = NvEncDeviceType.Cuda + }; + + var codecs = new List(); + var encoder = LibNvEnc.OpenEncoder(ref sessionParams); + try + { + IReadOnlyList codecGuids = encoder.GetEncodeGuids(); + foreach ((string codecName, Guid codecGuid) in AllCodecs) + { + if (codecGuids.Contains(codecGuid)) + { + IReadOnlyList codecProfileGuids = encoder.GetEncodeProfileGuids(codecGuid); - internal static string GetDeviceDetails(CudaDevice device) - { - var sb = new StringBuilder(); + var bitDepths = new List { 8 }; - try - { - var dev = CuDevice.GetDevice(device.Handle); - using var context = dev.CreateContext(); - var sessionParams = new NvEncOpenEncodeSessionExParams - { - Version = LibNvEnc.NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER, - ApiVersion = LibNvEnc.NVENCAPI_VERSION, - Device = context.Handle, - DeviceType = NvEncDeviceType.Cuda - }; + var cap = new NvEncCapsParam { CapsToQuery = NvEncCaps.Support10bitEncode }; + var capsVal = 0; + encoder.GetEncodeCaps(codecGuid, ref cap, ref capsVal); + if (capsVal > 0) + { + bitDepths.Add(10); + } - var encoder = LibNvEnc.OpenEncoder(ref sessionParams); - try - { - sb.AppendLine(" Encoding:"); - IReadOnlyList codecGuids = encoder.GetEncodeGuids(); - foreach ((string codecName, Guid codecGuid) in AllCodecs) - { - if (codecGuids.Contains(codecGuid)) - { - sb.AppendLine(CultureInfo.InvariantCulture, $" - Supports {codecName} 8-bit"); + var cudaCodec = new CudaCodec( + codecName, + codecGuid, + codecProfileGuids, + bitDepths.ToImmutableList()); - var cap = new NvEncCapsParam { CapsToQuery = NvEncCaps.Support10bitEncode }; - var capsVal = 0; - encoder.GetEncodeCaps(codecGuid, ref cap, ref capsVal); - if (capsVal > 0) - { - sb.AppendLine(CultureInfo.InvariantCulture, $" - Supports {codecName} 10-bit"); + codecs.Add(cudaCodec); } } } + finally + { + encoder.DestroyEncoder(); + } + + result.Add(new CudaDevice(device.Handle, name, new Version(major, minor), codecs)); } - finally + catch (Exception) { - encoder.DestroyEncoder(); + // do nothing } } - catch (Exception) + + return result; + } + + internal static string GetDeviceDetails(CudaDevice device) + { + var sb = new StringBuilder(); + + sb.AppendLine(" Encoding:"); + foreach (CudaCodec cudaCodec in device.Codecs) { - // do nothing + sb.AppendLine(CultureInfo.InvariantCulture, $" - Supports {cudaCodec.Name} 8-bit"); + + if (cudaCodec.BitDepths.Contains(10)) + { + sb.AppendLine(CultureInfo.InvariantCulture, $" - Supports {cudaCodec.Name} 10-bit"); + } } return sb.ToString(); diff --git a/ErsatzTV.FFmpeg/Capabilities/Nvidia/NvEncSharpRedirector.cs b/ErsatzTV.FFmpeg/Capabilities/Nvidia/NvEncSharpRedirector.cs index ff141ce64..c1e5268e8 100644 --- a/ErsatzTV.FFmpeg/Capabilities/Nvidia/NvEncSharpRedirector.cs +++ b/ErsatzTV.FFmpeg/Capabilities/Nvidia/NvEncSharpRedirector.cs @@ -15,7 +15,7 @@ public static class NvEncSharpRedirector if (libraryName.Equals("nvEncodeAPI64.dll", StringComparison.OrdinalIgnoreCase)) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return NativeLibrary.Load("libnvidia-encode.so", assembly, searchPath); + return NativeLibrary.Load("libnvidia-encode.so.1", assembly, searchPath); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return NativeLibrary.Load("nvEncodeAPI64.dll", assembly, searchPath); } @@ -23,7 +23,7 @@ public static class NvEncSharpRedirector if (libraryName.Equals("nvEncodeAPI.dll", StringComparison.OrdinalIgnoreCase)) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return NativeLibrary.Load("libnvidia-encode.so", assembly, searchPath); + return NativeLibrary.Load("libnvidia-encode.so.1", assembly, searchPath); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return NativeLibrary.Load("nvEncodeAPI.dll", assembly, searchPath); } @@ -31,7 +31,7 @@ public static class NvEncSharpRedirector if (libraryName.Equals("nvcuda.dll", StringComparison.OrdinalIgnoreCase)) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - return NativeLibrary.Load("libcuda.so", assembly, searchPath); + return NativeLibrary.Load("libcuda.so.1", assembly, searchPath); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return NativeLibrary.Load("nvcuda.dll", assembly, searchPath); } diff --git a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs index 57ade22e3..5e267a01c 100644 --- a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs @@ -95,92 +95,55 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities { int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); - try + _logger.LogDebug( + "Checking NvEnc {Format} / {Profile} / {BitDepth}-bit", + videoFormat, + videoProfile, + bitDepth); + + var codec = _cudaDevice.Codecs.FirstOrDefault(c => c.Name.Equals( + videoFormat, + StringComparison.OrdinalIgnoreCase)); + + if (codec == null) { - var dev = CuDevice.GetDevice(0); - using var context = dev.CreateContext(); - var sessionParams = new NvEncOpenEncodeSessionExParams - { - Version = LibNvEnc.NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER, - ApiVersion = LibNvEnc.NVENCAPI_VERSION, - Device = context.Handle, - DeviceType = NvEncDeviceType.Cuda - }; + _logger.LogWarning("NvEnc {Format} is not supported; will use software encode", videoFormat); + return FFmpegCapability.Software; + } - var encoder = LibNvEnc.OpenEncoder(ref sessionParams); - try - { - _logger.LogDebug( - "Checking NvEnc {Format} / {Profile} / {BitDepth}-bit", - videoFormat, - videoProfile, - bitDepth); - - var codecGuid = videoFormat switch - { - VideoFormat.Hevc => NvEncCodecGuids.Hevc, - _ => NvEncCodecGuids.H264 - }; - - IReadOnlyList codecGuids = encoder.GetEncodeGuids(); - if (!codecGuids.Contains(codecGuid)) - { - _logger.LogWarning("NvEnc {Format} is not supported; will use software encode", videoFormat); - return FFmpegCapability.Software; - } - - var profileGuid = (videoFormat, videoProfile.IfNone(string.Empty), bitDepth) switch - { - (VideoFormat.Hevc, _, 8) => NvEncProfileGuids.HevcMain, - (VideoFormat.Hevc, _, 10) => NvEncProfileGuids.HevcMain10, - - (VideoFormat.H264, _, 10) => NvEncProfileGuids.H264High444, - (VideoFormat.H264, VideoProfile.High, _) => NvEncProfileGuids.H264High, - // high10 is for libx264, nvenc needs high444 - (VideoFormat.H264, VideoProfile.High10, _) => NvEncProfileGuids.H264High444, - - _ => NvEncProfileGuids.H264Main - }; - - IReadOnlyList profileGuids = encoder.GetEncodeProfileGuids(codecGuid); - if (!profileGuids.Contains(profileGuid)) - { - _logger.LogWarning( - "NvEnc {Format} / {Profile} is not supported; will use software encode", - videoFormat, - videoProfile); - return FFmpegCapability.Software; - } - - if (bitDepth == 10) - { - var cap = new NvEncCapsParam { CapsToQuery = NvEncCaps.Support10bitEncode }; - var capsVal = 0; - encoder.GetEncodeCaps(codecGuid, ref cap, ref capsVal); - if (capsVal == 0) - { - _logger.LogWarning( - "NvEnc {Format} / {Profile} / {BitDepth}-bit is not supported; will use software encode", - videoFormat, - videoProfile, - bitDepth); - return FFmpegCapability.Software; - } - } - - return FFmpegCapability.Hardware; - } - finally - { - encoder.DestroyEncoder(); - } + var profileGuid = (videoFormat, videoProfile.IfNone(string.Empty), bitDepth) switch + { + (VideoFormat.Hevc, _, 8) => NvEncProfileGuids.HevcMain, + (VideoFormat.Hevc, _, 10) => NvEncProfileGuids.HevcMain10, + + (VideoFormat.H264, _, 10) => NvEncProfileGuids.H264High444, + (VideoFormat.H264, VideoProfile.High, _) => NvEncProfileGuids.H264High, + // high10 is for libx264, nvenc needs high444 + (VideoFormat.H264, VideoProfile.High10, _) => NvEncProfileGuids.H264High444, + + _ => NvEncProfileGuids.H264Main + }; + + if (!codec.ProfileGuids.Contains(profileGuid)) + { + _logger.LogWarning( + "NvEnc {Format} / {Profile} is not supported; will use software encode", + videoFormat, + videoProfile); + return FFmpegCapability.Software; } - catch (Exception ex) + + if (!codec.BitDepths.Contains(bitDepth)) { - _logger.LogWarning(ex, "Unexpected error checking NvEnc capabilities; falling back to software"); + _logger.LogWarning( + "NvEnc {Format} / {Profile} / {BitDepth}-bit is not supported; will use software encode", + videoFormat, + videoProfile, + bitDepth); + return FFmpegCapability.Software; } - return FFmpegCapability.Software; + return FFmpegCapability.Hardware; } public Option GetRateControlMode(string videoFormat, Option maybePixelFormat) =>