Browse Source

use nvenc to detect encoder capability (#2459)

pull/2460/head
Jason Dove 3 months ago committed by GitHub
parent
commit
b820b798cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 1
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  3. 67
      ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs
  4. 3
      ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaDevice.cs
  5. 132
      ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaHelper.cs
  6. 43
      ErsatzTV.FFmpeg/Capabilities/Nvidia/NvEncSharpRedirector.cs
  7. 121
      ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs
  8. 1
      ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj
  9. 1
      ErsatzTV.FFmpeg/Format/VideoProfile.cs
  10. 5
      ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs
  11. 9
      ErsatzTV/Pages/FFmpegEditor.razor
  12. 3
      ErsatzTV/Services/RunOnce/PlatformSettingsService.cs
  13. 8
      ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs
  14. 17
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

1
CHANGELOG.md

@ -74,6 +74,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -74,6 +74,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix extracted text subtitles getting into invalid state after media server deep scans
- Targeted deep scans will now extract text subtitles for the scanned show
- Fix playlist preview
- Use NvEnc API to detect encoder capability instead of heuristic based on GPU model/architecture
### Changed
- Filler presets: use separate text fields for `hours`, `minutes` and `seconds` duration

1
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -1114,6 +1114,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -1114,6 +1114,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
(VideoFormat.H264, VideoProfile.Main) => VideoProfile.Main,
(VideoFormat.H264, VideoProfile.High) => VideoProfile.High,
(VideoFormat.H264, VideoProfile.High10) => VideoProfile.High10,
(VideoFormat.H264, VideoProfile.High444p) => VideoProfile.High444p,
_ => Option<string>.None
};

67
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -5,6 +5,7 @@ using System.Text; @@ -5,6 +5,7 @@ using System.Text;
using System.Text.RegularExpressions;
using CliWrap;
using CliWrap.Buffered;
using ErsatzTV.FFmpeg.Capabilities.Nvidia;
using ErsatzTV.FFmpeg.Capabilities.Qsv;
using ErsatzTV.FFmpeg.Capabilities.Vaapi;
using ErsatzTV.FFmpeg.Capabilities.VideoToolbox;
@ -18,8 +19,7 @@ namespace ErsatzTV.FFmpeg.Capabilities; @@ -18,8 +19,7 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
{
private const string ArchitectureCacheKey = "ffmpeg.hardware.nvidia.architecture";
private const string ModelCacheKey = "ffmpeg.hardware.nvidia.model";
private const string CudaDeviceKey = "ffmpeg.hardware.cuda.device";
private static readonly CompositeFormat
VaapiCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.vaapi.{0}.{1}.{2}");
@ -104,7 +104,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -104,7 +104,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
return hardwareAccelerationMode switch
{
HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath, ffmpegCapabilities),
HardwareAccelerationMode.Nvenc => GetNvidiaCapabilities(ffmpegCapabilities),
HardwareAccelerationMode.Qsv => await GetQsvCapabilities(ffmpegPath, vaapiDevice),
HardwareAccelerationMode.Vaapi => await GetVaapiCapabilities(vaapiDisplay, vaapiDriver, vaapiDevice),
HardwareAccelerationMode.VideoToolbox => new VideoToolboxHardwareCapabilities(ffmpegCapabilities, _logger),
@ -122,6 +122,24 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -122,6 +122,24 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
return string.Empty;
}
Option<List<CudaDevice>> maybeDevices = CudaHelper.GetDevices();
foreach (List<CudaDevice> devices in maybeDevices.Where(list => list.Count > 0))
{
var sb = new StringBuilder();
foreach (CudaDevice device in devices)
{
sb.AppendLine(
CultureInfo.InvariantCulture,
$"GPU #{device.Handle} < {device.Model} > has Compute SM {device.Version.Major}.{device.Version.Minor}");
sb.AppendLine(CudaHelper.GetDeviceDetails(device));
}
return sb.ToString();
}
// if we don't have a list of cuda devices, fall back to ffmpeg check
string[] arguments =
{
"-f", "lavfi",
@ -497,41 +515,24 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -497,41 +515,24 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
}
}
private async Task<IHardwareCapabilities> GetNvidiaCapabilities(
string ffmpegPath,
IFFmpegCapabilities ffmpegCapabilities)
private IHardwareCapabilities GetNvidiaCapabilities(IFFmpegCapabilities ffmpegCapabilities)
{
if (_memoryCache.TryGetValue(ArchitectureCacheKey, out int cachedArchitecture)
&& _memoryCache.TryGetValue(ModelCacheKey, out string? cachedModel)
&& cachedModel is not null)
if (_memoryCache.TryGetValue(CudaDeviceKey, out CudaDevice? cudaDevice) && cudaDevice is not null)
{
return new NvidiaHardwareCapabilities(
cachedArchitecture,
cachedModel,
ffmpegCapabilities,
_logger);
return new NvidiaHardwareCapabilities(cudaDevice, ffmpegCapabilities, _logger);
}
string output = await GetNvidiaOutput(ffmpegPath);
Option<string> maybeLine = Optional(output.Split("\n").FirstOrDefault(x => x.Contains("GPU")));
foreach (string line in maybeLine)
Option<List<CudaDevice>> maybeDevices = CudaHelper.GetDevices();
foreach (CudaDevice firstDevice in maybeDevices.Map(list => list.HeadOrNone()))
{
const string ARCHITECTURE_PATTERN = @"SM\s+(\d+\.\d+)";
Match match = Regex.Match(line, ARCHITECTURE_PATTERN);
if (match.Success && int.TryParse(match.Groups[1].Value.Replace(".", string.Empty), out int architecture))
{
const string MODEL_PATTERN = @"(GTX\s+[0-9a-zA-Z]+[\sTtIi]+)";
Match modelMatch = Regex.Match(line, MODEL_PATTERN);
string model = modelMatch.Success ? modelMatch.Groups[1].Value.Trim() : "unknown";
_logger.LogDebug(
"Detected NVIDIA GPU model {Model} architecture SM {Architecture}",
model,
architecture);
_memoryCache.Set(ArchitectureCacheKey, architecture);
_memoryCache.Set(ModelCacheKey, model);
return new NvidiaHardwareCapabilities(architecture, model, ffmpegCapabilities, _logger);
}
_logger.LogDebug(
"Detected NVIDIA GPU model {Model} architecture SM {Major}.{Minor}",
firstDevice.Model,
firstDevice.Version.Major,
firstDevice.Version.Minor);
_memoryCache.Set(CudaDeviceKey, firstDevice);
return new NvidiaHardwareCapabilities(firstDevice, ffmpegCapabilities, _logger);
}
_logger.LogWarning(

3
ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaDevice.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.FFmpeg.Capabilities.Nvidia;
public record CudaDevice(int Handle, string Model, Version Version);

132
ErsatzTV.FFmpeg/Capabilities/Nvidia/CudaHelper.cs

@ -0,0 +1,132 @@ @@ -0,0 +1,132 @@
using System.Globalization;
using System.Text;
using ErsatzTV.FFmpeg.Format;
using Lennox.NvEncSharp;
namespace ErsatzTV.FFmpeg.Capabilities.Nvidia;
internal static class CudaHelper
{
private static bool _success;
private static bool _initialized;
private static readonly Lock Lock = new();
private static readonly Dictionary<string, Guid> AllCodecs = new()
{
[VideoFormat.H264] = NvEncCodecGuids.H264,
[VideoFormat.Hevc] = NvEncCodecGuids.Hevc
};
private static bool EnsureInit()
{
if (_initialized)
{
return _success;
}
lock (Lock)
{
if (_initialized)
{
return _success;
}
try
{
LibNvEnc.TryInitialize(out string? error);
if (string.IsNullOrEmpty(error))
{
LibCuda.Initialize();
_success = true;
}
}
catch (LibNvEncException)
{
_success = false;
}
_initialized = true;
}
return _success;
}
internal static Option<List<CudaDevice>> GetDevices()
{
var result = new List<CudaDevice>();
if (!EnsureInit())
{
return Option<List<CudaDevice>>.None;
}
foreach (var description in CuDevice.GetDescriptions())
{
var device = description.Device;
string name = device.GetName();
int nullIndex = name.IndexOf('\0');
if (nullIndex > 0)
{
name = name[..nullIndex];
}
int major = device.GetAttribute(CuDeviceAttribute.ComputeCapabilityMajor);
int minor = device.GetAttribute(CuDeviceAttribute.ComputeCapabilityMinor);
result.Add(new CudaDevice(device.Handle, name, new Version(major, minor)));
}
return result;
}
internal static string GetDeviceDetails(CudaDevice device)
{
var sb = new StringBuilder();
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 encoder = LibNvEnc.OpenEncoder(ref sessionParams);
try
{
sb.AppendLine(" Encoding:");
IReadOnlyList<Guid> codecGuids = encoder.GetEncodeGuids();
foreach ((string codecName, Guid codecGuid) in AllCodecs)
{
if (codecGuids.Contains(codecGuid))
{
sb.AppendLine(CultureInfo.InvariantCulture, $" - Supports {codecName} 8-bit");
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");
}
}
}
}
finally
{
encoder.DestroyEncoder();
}
}
catch (Exception)
{
// do nothing
}
return sb.ToString();
}
}

43
ErsatzTV.FFmpeg/Capabilities/Nvidia/NvEncSharpRedirector.cs

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
using System.Reflection;
using System.Runtime.InteropServices;
namespace ErsatzTV.FFmpeg.Capabilities.Nvidia;
public static class NvEncSharpRedirector
{
static NvEncSharpRedirector()
{
NativeLibrary.SetDllImportResolver(typeof(Lennox.NvEncSharp.LibCuda).Assembly, Resolver);
}
private static IntPtr Resolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (libraryName.Equals("nvEncodeAPI64.dll", StringComparison.OrdinalIgnoreCase))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return NativeLibrary.Load("libnvidia-encode.so", assembly, searchPath);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return NativeLibrary.Load("nvEncodeAPI64.dll", assembly, searchPath);
}
if (libraryName.Equals("nvEncodeAPI.dll", StringComparison.OrdinalIgnoreCase))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return NativeLibrary.Load("libnvidia-encode.so", assembly, searchPath);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return NativeLibrary.Load("nvEncodeAPI.dll", assembly, searchPath);
}
if (libraryName.Equals("nvcuda.dll", StringComparison.OrdinalIgnoreCase))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return NativeLibrary.Load("libcuda.so", assembly, searchPath);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return NativeLibrary.Load("nvcuda.dll", assembly, searchPath);
}
return IntPtr.Zero;
}
public static void Init() { }
}

121
ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs

@ -1,30 +1,32 @@ @@ -1,30 +1,32 @@
using ErsatzTV.FFmpeg.Capabilities.Nvidia;
using ErsatzTV.FFmpeg.Format;
using Lennox.NvEncSharp;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.FFmpeg.Capabilities;
public class NvidiaHardwareCapabilities : IHardwareCapabilities
{
private readonly int _architecture;
private readonly CudaDevice _cudaDevice;
private readonly IFFmpegCapabilities _ffmpegCapabilities;
private readonly ILogger _logger;
private readonly List<string> _maxwellGm206 = new() { "GTX 750", "GTX 950", "GTX 960", "GTX 965M" };
private readonly string _model;
private readonly List<string> _maxwellGm206 = ["GTX 750", "GTX 950", "GTX 960", "GTX 965M"];
private readonly Version _maxwell = new(5, 2);
private readonly Version _pascal = new(6, 0);
private readonly Version _ampere = new(8, 6);
public NvidiaHardwareCapabilities(
int architecture,
string model,
CudaDevice cudaDevice,
IFFmpegCapabilities ffmpegCapabilities,
ILogger logger)
{
_architecture = architecture;
_model = model;
_cudaDevice = cudaDevice;
_ffmpegCapabilities = ffmpegCapabilities;
_logger = logger;
}
// this fails with some 1650 cards, so let's try greater than 75
public bool HevcBFrames => _architecture > 75;
public bool HevcBFrames => _cudaDevice.Version >= new Version(7, 5);
public FFmpegCapability CanDecode(
string videoFormat,
@ -39,13 +41,13 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -39,13 +41,13 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
bool isHardware = videoFormat switch
{
// some second gen maxwell can decode hevc, otherwise pascal is required
VideoFormat.Hevc => _architecture == 52 && _maxwellGm206.Contains(_model) || _architecture >= 60,
VideoFormat.Hevc => _cudaDevice.Version == _maxwell && _maxwellGm206.Contains(_cudaDevice.Model) || _cudaDevice.Version >= _pascal,
// pascal is required to decode vp9 10-bit
VideoFormat.Vp9 when bitDepth == 10 => !isHdr && _architecture >= 60,
VideoFormat.Vp9 when bitDepth == 10 => !isHdr && _cudaDevice.Version >= _pascal,
// some second gen maxwell can decode vp9, otherwise pascal is required
VideoFormat.Vp9 => !isHdr && _architecture == 52 && _maxwellGm206.Contains(_model) || _architecture >= 60,
VideoFormat.Vp9 => !isHdr && _cudaDevice.Version == _maxwell && _maxwellGm206.Contains(_cudaDevice.Model) || _cudaDevice.Version >= _pascal,
// no hardware decoding of 10-bit h264
VideoFormat.H264 => bitDepth < 10,
@ -58,7 +60,7 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -58,7 +60,7 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
VideoFormat.Mpeg4 => false,
// ampere is required for av1 decoding
VideoFormat.Av1 => _architecture >= 86,
VideoFormat.Av1 => _cudaDevice.Version >= _ampere,
// generated images are decoded into software
VideoFormat.GeneratedImage => false,
@ -93,21 +95,92 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -93,21 +95,92 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
{
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
bool isHardware = videoFormat switch
try
{
// pascal is required to encode 10-bit hevc
VideoFormat.Hevc when bitDepth == 10 => _architecture >= 60,
// second gen maxwell is required to encode hevc
VideoFormat.Hevc => _architecture >= 52,
// nvidia cannot encode 10-bit h264
VideoFormat.H264 when bitDepth == 10 => false,
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
};
_ => true
};
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<Guid> 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<Guid> 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();
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Unexpected error checking NvEnc capabilities; falling back to software");
}
return isHardware ? FFmpegCapability.Hardware : FFmpegCapability.Software;
return FFmpegCapability.Software;
}
public Option<RateControlMode> GetRateControlMode(string videoFormat, Option<IPixelFormat> maybePixelFormat) =>

1
ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj

@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
<PackageReference Include="CliWrap" Version="3.9.0" />
<PackageReference Include="Hardware.Info" Version="101.1.0" />
<PackageReference Include="LanguageExt.Core" Version="4.4.9" />
<PackageReference Include="Lennox.NvEncSharp" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.9" />
</ItemGroup>

1
ErsatzTV.FFmpeg/Format/VideoProfile.cs

@ -5,4 +5,5 @@ public static class VideoProfile @@ -5,4 +5,5 @@ public static class VideoProfile
public const string Main = "main";
public const string High = "high";
public const string High10 = "high10";
public const string High444p = "high444p";
}

5
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

@ -311,7 +311,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder @@ -311,7 +311,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
(HardwareAccelerationMode.Nvenc, VideoFormat.H264) =>
new EncoderH264Nvenc(desiredState.VideoProfile, desiredState.VideoPreset),
(_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState)
// don't pass NVENC profile down to libx264
(_, _) => GetSoftwareEncoder(ffmpegState, currentState, desiredState with { VideoProfile = Option<string>.None })
};
foreach (IEncoder encoder in maybeEncoder)
@ -391,7 +392,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder @@ -391,7 +392,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None)
{
_logger.LogDebug("Using software encoder");
//_logger.LogDebug("Using software encoder");
if ((context.HasSubtitleOverlay || context.HasWatermark || context.HasGraphicsEngine) &&
currentState.FrameDataLocation == FrameDataLocation.Hardware)

9
ErsatzTV/Pages/FFmpegEditor.razor

@ -79,7 +79,14 @@ @@ -79,7 +79,14 @@
Clearable="true">
<MudSelectItem Value="@VideoProfile.Main">main</MudSelectItem>
<MudSelectItem Value="@VideoProfile.High">high</MudSelectItem>
<MudSelectItem Value="@VideoProfile.High10">high10</MudSelectItem>
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Nvenc)
{
<MudSelectItem Value="@VideoProfile.High444p">high444p</MudSelectItem>
}
else
{
<MudSelectItem Value="@VideoProfile.High10">high10</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">

3
ErsatzTV/Services/RunOnce/PlatformSettingsService.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using System.Runtime.InteropServices;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Capabilities.Nvidia;
using ErsatzTV.FFmpeg.Runtime;
using Microsoft.Extensions.Caching.Memory;
@ -16,6 +17,8 @@ public class PlatformSettingsService(IServiceScopeFactory serviceScopeFactory) : @@ -16,6 +17,8 @@ public class PlatformSettingsService(IServiceScopeFactory serviceScopeFactory) :
IRuntimeInfo runtimeInfo = scope.ServiceProvider.GetRequiredService<IRuntimeInfo>();
if (runtimeInfo != null && runtimeInfo.IsOSPlatform(OSPlatform.Linux))
{
NvEncSharpRedirector.Init();
if (Directory.Exists("/dev/dri"))
{
ILocalFileSystem localFileSystem = scope.ServiceProvider.GetRequiredService<ILocalFileSystem>();

8
ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs

@ -127,11 +127,17 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator<FFmpegProfi @@ -127,11 +127,17 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator<FFmpegProfi
.WithMessage("Mpeg2Video does not support 10-bit content"));
When(
x => x.VideoFormat == FFmpegProfileVideoFormat.H264 && x.BitDepth == FFmpegProfileBitDepth.TenBit,
x => x.HardwareAcceleration != HardwareAccelerationKind.Nvenc && x.VideoFormat == FFmpegProfileVideoFormat.H264 && x.BitDepth == FFmpegProfileBitDepth.TenBit,
() => RuleFor(x => x.VideoProfile)
.Must(vp => vp == VideoProfile.High10)
.WithMessage("VideoProfile must be high10 with 10-bit h264"));
When(
x => x.HardwareAcceleration == HardwareAccelerationKind.Nvenc && x.VideoFormat == FFmpegProfileVideoFormat.H264 && x.BitDepth == FFmpegProfileBitDepth.TenBit,
() => RuleFor(x => x.VideoProfile)
.Must(vp => vp == VideoProfile.High444p)
.WithMessage("VideoProfile must be high444p with NVIDIA 10-bit h264"));
When(
x => x.VideoFormat == FFmpegProfileVideoFormat.H264 && x.BitDepth == FFmpegProfileBitDepth.EightBit,
() => RuleFor(x => x.VideoProfile)

17
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -7,6 +7,8 @@ namespace ErsatzTV.ViewModels; @@ -7,6 +7,8 @@ namespace ErsatzTV.ViewModels;
public class FFmpegProfileEditViewModel
{
private string _videoProfile;
public FFmpegProfileEditViewModel()
{
}
@ -62,7 +64,20 @@ public class FFmpegProfileEditViewModel @@ -62,7 +64,20 @@ public class FFmpegProfileEditViewModel
public int VideoBitrate { get; set; }
public int VideoBufferSize { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; }
public string VideoProfile { get; set; }
public string VideoProfile
{
get =>
(HardwareAcceleration, VideoFormat, BitDepth) switch
{
(HardwareAccelerationKind.Nvenc, FFmpegProfileVideoFormat.H264, FFmpegProfileBitDepth.TenBit) => FFmpeg
.Format.VideoProfile.High444p,
(_, FFmpegProfileVideoFormat.H264, _) => _videoProfile,
_ => string.Empty
};
set => _videoProfile = value;
}
public string VideoPreset { get; set; }
public bool AllowBFrames { get; set; }
public FFmpegProfileBitDepth BitDepth { get; set; }

Loading…
Cancel
Save