diff --git a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs index ae9ad437..dd768e1a 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs @@ -10,6 +10,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; +using ErsatzTV.FFmpeg; using ErsatzTV.FFmpeg.Capabilities; using ErsatzTV.FFmpeg.State; using FluentAssertions; @@ -18,6 +19,7 @@ using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; using Serilog; +using MediaStream = ErsatzTV.Core.Domain.MediaStream; namespace ErsatzTV.Core.Tests.FFmpeg; @@ -303,9 +305,10 @@ public class TranscodingTests new FFmpegPlaybackSettingsCalculator(), new FakeStreamSelector(), new Mock().Object, - new HardwareCapabilitiesFactory( - new MemoryCache(new MemoryCacheOptions()), - LoggerFactory.CreateLogger()), + new FakeNvidiaCapabilitiesFactory(), + // new HardwareCapabilitiesFactory( + // new MemoryCache(new MemoryCacheOptions()), + // LoggerFactory.CreateLogger()), LoggerFactory.CreateLogger()); var v = new MediaVersion @@ -577,6 +580,14 @@ public class TranscodingTests subtitles.HeadOrNone().AsTask(); } + private class FakeNvidiaCapabilitiesFactory : IHardwareCapabilitiesFactory + { + public Task GetHardwareCapabilities( + string ffmpegPath, + HardwareAccelerationMode hardwareAccelerationMode) => + Task.FromResult(new NvidiaHardwareCapabilities(61)); + } + private static string ExecutableName(string baseName) => OperatingSystem.IsWindows() ? $"{baseName}.exe" : baseName; } diff --git a/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs index 1034ca64..417c7be6 100644 --- a/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs @@ -1,7 +1,9 @@ +using ErsatzTV.FFmpeg.Format; + namespace ErsatzTV.FFmpeg.Capabilities; public class DefaultHardwareCapabilities : IHardwareCapabilities { - public bool CanDecode(string videoFormat) => true; - public bool CanEncode(string videoFormat) => true; + public bool CanDecode(string videoFormat, Option maybePixelFormat) => true; + public bool CanEncode(string videoFormat, Option maybePixelFormat) => true; } diff --git a/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs index cf1d9dc2..92545c2a 100644 --- a/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs @@ -1,7 +1,9 @@ +using ErsatzTV.FFmpeg.Format; + namespace ErsatzTV.FFmpeg.Capabilities; public interface IHardwareCapabilities { - public bool CanDecode(string videoFormat); - public bool CanEncode(string videoFormat); + public bool CanDecode(string videoFormat, Option maybePixelFormat); + public bool CanEncode(string videoFormat, Option maybePixelFormat); } diff --git a/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs index b78383b4..a6410591 100644 --- a/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs @@ -1,7 +1,9 @@ +using ErsatzTV.FFmpeg.Format; + namespace ErsatzTV.FFmpeg.Capabilities; public class NoHardwareCapabilities : IHardwareCapabilities { - public bool CanDecode(string videoFormat) => false; - public bool CanEncode(string videoFormat) => false; + public bool CanDecode(string videoFormat, Option maybePixelFormat) => false; + public bool CanEncode(string videoFormat, Option maybePixelFormat) => false; } diff --git a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs index c8a8d990..c4c08e9f 100644 --- a/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs @@ -1,3 +1,4 @@ +using System.Numerics; using ErsatzTV.FFmpeg.Format; namespace ErsatzTV.FFmpeg.Capabilities; @@ -8,19 +9,41 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities public NvidiaHardwareCapabilities(int architecture) => _architecture = architecture; - public bool CanDecode(string videoFormat) => - videoFormat switch + public bool CanDecode(string videoFormat, Option maybePixelFormat) + { + int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); + + return videoFormat switch { - // pascal is required to decode hevc/vp9 - VideoFormat.Hevc or VideoFormat.Vp9 => _architecture >= 60, + // second gen maxwell is required to decode hevc + VideoFormat.Hevc => _architecture >= 52, + + // pascal is required to decode vp9 10-bit + VideoFormat.Vp9 when bitDepth == 10 => _architecture >= 60, + + // second gen maxwell is required to decode vp9 + VideoFormat.Vp9 => _architecture >= 52, + _ => true }; + } - public bool CanEncode(string videoFormat) => - videoFormat switch + public bool CanEncode(string videoFormat, Option maybePixelFormat) + { + int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); + + return videoFormat switch { - // pascal is required to encode hevc - VideoFormat.Hevc => _architecture >= 60, + // 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, + _ => true }; + } } diff --git a/ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs b/ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs index db41a4d7..8152efc9 100644 --- a/ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs +++ b/ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs @@ -20,7 +20,8 @@ public static class AvailableDecoders currentState.PixelFormat.Match(pf => pf.Name, () => string.Empty)) switch { (HardwareAccelerationMode.Nvenc, VideoFormat.Hevc, _) - when hardwareCapabilities.CanDecode(VideoFormat.Hevc) => new DecoderHevcCuvid(ffmpegState), + when hardwareCapabilities.CanDecode(VideoFormat.Hevc, currentState.PixelFormat) => + new DecoderHevcCuvid(ffmpegState), // nvenc doesn't support hardware decoding of 10-bit content (HardwareAccelerationMode.Nvenc, VideoFormat.H264, PixelFormat.YUV420P10LE or PixelFormat.YUV444P10LE) @@ -31,13 +32,15 @@ public static class AvailableDecoders new DecoderMpeg2Video(), (HardwareAccelerationMode.Nvenc, VideoFormat.H264, _) - when hardwareCapabilities.CanDecode(VideoFormat.H264) => new DecoderH264Cuvid(ffmpegState), + when hardwareCapabilities.CanDecode(VideoFormat.H264, currentState.PixelFormat) => + new DecoderH264Cuvid(ffmpegState), (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Cuvid( ffmpegState, desiredState.Deinterlaced), (HardwareAccelerationMode.Nvenc, VideoFormat.Vc1, _) => new DecoderVc1Cuvid(ffmpegState), (HardwareAccelerationMode.Nvenc, VideoFormat.Vp9, _) - when hardwareCapabilities.CanDecode(VideoFormat.Vp9) => new DecoderVp9Cuvid(ffmpegState), + when hardwareCapabilities.CanDecode(VideoFormat.Vp9, currentState.PixelFormat) => + new DecoderVp9Cuvid(ffmpegState), (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg4, _) => new DecoderMpeg4Cuvid(ffmpegState), // hevc_qsv decoder sometimes causes green lines with 10-bit content diff --git a/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs b/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs index a9226266..cd3f8994 100644 --- a/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs +++ b/ErsatzTV.FFmpeg/Encoder/AvailableEncoders.cs @@ -21,43 +21,57 @@ public static class AvailableEncoders ILogger logger) => (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch { - (HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(VideoFormat.Hevc) => + (HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) when hardwareCapabilities.CanEncode( + VideoFormat.Hevc, + desiredState.PixelFormat) => new EncoderHevcNvenc( currentState, maybeWatermarkInputFile, maybeSubtitleInputFile), - (HardwareAccelerationMode.Nvenc, VideoFormat.H264) when hardwareCapabilities.CanEncode(VideoFormat.H264) => + (HardwareAccelerationMode.Nvenc, VideoFormat.H264) when hardwareCapabilities.CanEncode( + VideoFormat.H264, + desiredState.PixelFormat) => new EncoderH264Nvenc( currentState, maybeWatermarkInputFile, maybeSubtitleInputFile), - (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(VideoFormat.Hevc) => + (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) when hardwareCapabilities.CanEncode( + VideoFormat.Hevc, + desiredState.PixelFormat) => new EncoderHevcQsv( currentState, maybeWatermarkInputFile, maybeSubtitleInputFile), - (HardwareAccelerationMode.Qsv, VideoFormat.H264) when hardwareCapabilities.CanEncode(VideoFormat.H264) => + (HardwareAccelerationMode.Qsv, VideoFormat.H264) when hardwareCapabilities.CanEncode( + VideoFormat.H264, + desiredState.PixelFormat) => new EncoderH264Qsv( currentState, maybeWatermarkInputFile, maybeSubtitleInputFile), - (HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(VideoFormat.Hevc) => + (HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) when hardwareCapabilities.CanEncode( + VideoFormat.Hevc, + desiredState.PixelFormat) => new EncoderHevcVaapi( currentState, maybeWatermarkInputFile, maybeSubtitleInputFile), - (HardwareAccelerationMode.Vaapi, VideoFormat.H264) when hardwareCapabilities.CanEncode(VideoFormat.H264) => + (HardwareAccelerationMode.Vaapi, VideoFormat.H264) when hardwareCapabilities.CanEncode( + VideoFormat.H264, + desiredState.PixelFormat) => new EncoderH264Vaapi( currentState, maybeWatermarkInputFile, maybeSubtitleInputFile), (HardwareAccelerationMode.VideoToolbox, VideoFormat.Hevc) when hardwareCapabilities.CanEncode( - VideoFormat.Hevc) => new EncoderHevcVideoToolbox(), + VideoFormat.Hevc, + desiredState.PixelFormat) => new EncoderHevcVideoToolbox(), (HardwareAccelerationMode.VideoToolbox, VideoFormat.H264) when hardwareCapabilities.CanEncode( - VideoFormat.H264) => new EncoderH264VideoToolbox(), + VideoFormat.H264, + desiredState.PixelFormat) => new EncoderH264VideoToolbox(), (_, VideoFormat.Hevc) => new EncoderLibx265(currentState), (_, VideoFormat.H264) => new EncoderLibx264(), diff --git a/ErsatzTV.FFmpeg/Format/IPixelFormat.cs b/ErsatzTV.FFmpeg/Format/IPixelFormat.cs index 93e9efae..c4f9b918 100644 --- a/ErsatzTV.FFmpeg/Format/IPixelFormat.cs +++ b/ErsatzTV.FFmpeg/Format/IPixelFormat.cs @@ -4,4 +4,5 @@ public interface IPixelFormat { string Name { get; } string FFmpegName { get; } + int BitDepth { get; } } diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatNv12.cs b/ErsatzTV.FFmpeg/Format/PixelFormatNv12.cs index cbed1cd3..bda70cab 100644 --- a/ErsatzTV.FFmpeg/Format/PixelFormatNv12.cs +++ b/ErsatzTV.FFmpeg/Format/PixelFormatNv12.cs @@ -7,4 +7,5 @@ public class PixelFormatNv12 : IPixelFormat public string Name { get; } public string FFmpegName => "nv12"; + public int BitDepth => 8; } diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatUnknown.cs b/ErsatzTV.FFmpeg/Format/PixelFormatUnknown.cs index 756085ae..9b5b945a 100644 --- a/ErsatzTV.FFmpeg/Format/PixelFormatUnknown.cs +++ b/ErsatzTV.FFmpeg/Format/PixelFormatUnknown.cs @@ -4,4 +4,5 @@ public class PixelFormatUnknown : IPixelFormat { public string Name => "unknown"; public string FFmpegName => "unknown"; + public int BitDepth => 8; } diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatYuv420P.cs b/ErsatzTV.FFmpeg/Format/PixelFormatYuv420P.cs index 4a6df868..5c42a172 100644 --- a/ErsatzTV.FFmpeg/Format/PixelFormatYuv420P.cs +++ b/ErsatzTV.FFmpeg/Format/PixelFormatYuv420P.cs @@ -4,4 +4,5 @@ public class PixelFormatYuv420P : IPixelFormat { public string Name => PixelFormat.YUV420P; public string FFmpegName => FFmpegFormat.YUV420P; + public int BitDepth => 8; } diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatYuv420P10Le.cs b/ErsatzTV.FFmpeg/Format/PixelFormatYuv420P10Le.cs index 14e2fec9..70ba347c 100644 --- a/ErsatzTV.FFmpeg/Format/PixelFormatYuv420P10Le.cs +++ b/ErsatzTV.FFmpeg/Format/PixelFormatYuv420P10Le.cs @@ -4,4 +4,5 @@ public class PixelFormatYuv420P10Le : IPixelFormat { public string Name => PixelFormat.YUV420P10LE; public string FFmpegName => FFmpegFormat.P010LE; + public int BitDepth => 10; } diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatYuv444P.cs b/ErsatzTV.FFmpeg/Format/PixelFormatYuv444P.cs index 7246a188..915f4874 100644 --- a/ErsatzTV.FFmpeg/Format/PixelFormatYuv444P.cs +++ b/ErsatzTV.FFmpeg/Format/PixelFormatYuv444P.cs @@ -4,4 +4,5 @@ public class PixelFormatYuv444P : IPixelFormat { public string Name => PixelFormat.YUV444P; public string FFmpegName => FFmpegFormat.YUV444P; + public int BitDepth => 8; } diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatYuvJ420P.cs b/ErsatzTV.FFmpeg/Format/PixelFormatYuvJ420P.cs index dafe2c16..4e2c6848 100644 --- a/ErsatzTV.FFmpeg/Format/PixelFormatYuvJ420P.cs +++ b/ErsatzTV.FFmpeg/Format/PixelFormatYuvJ420P.cs @@ -6,4 +6,5 @@ public class PixelFormatYuvJ420P : IPixelFormat // always convert this to yuv420p in filter chains public string FFmpegName => FFmpegFormat.YUV420P; + public int BitDepth => 8; } diff --git a/ErsatzTV.FFmpeg/PipelineBuilder.cs b/ErsatzTV.FFmpeg/PipelineBuilder.cs index 8045f270..f25996f1 100644 --- a/ErsatzTV.FFmpeg/PipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/PipelineBuilder.cs @@ -220,8 +220,10 @@ public class PipelineBuilder foreach (IPipelineStep accel in maybeAccel) { - bool canDecode = _hardwareCapabilities.CanDecode(currentState.VideoFormat); - bool canEncode = _hardwareCapabilities.CanEncode(desiredState.VideoFormat); + bool canDecode = _hardwareCapabilities.CanDecode(currentState.VideoFormat, videoStream.PixelFormat); + bool canEncode = _hardwareCapabilities.CanEncode( + desiredState.VideoFormat, + desiredState.PixelFormat); // disable hw accel if decoder/encoder isn't supported if (!canDecode || !canEncode)