adjust nvidia capabilities (#864)

* adjust nvidia capabilities logic

* fallback to software encoding for 10-bit h264

* cleanup

* more tweaks
This commit is contained in:
Jason Dove
2022-06-17 10:50:36 -05:00
committed by GitHub
parent 100eb14408
commit 3204da8e43
15 changed files with 99 additions and 33 deletions
+14 -3
View File
@@ -10,6 +10,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Interfaces.Images;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Metadata; using ErsatzTV.Core.Metadata;
using ErsatzTV.FFmpeg;
using ErsatzTV.FFmpeg.Capabilities; using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.State; using ErsatzTV.FFmpeg.State;
using FluentAssertions; using FluentAssertions;
@@ -18,6 +19,7 @@ using Microsoft.Extensions.Logging;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using Serilog; using Serilog;
using MediaStream = ErsatzTV.Core.Domain.MediaStream;
namespace ErsatzTV.Core.Tests.FFmpeg; namespace ErsatzTV.Core.Tests.FFmpeg;
@@ -303,9 +305,10 @@ public class TranscodingTests
new FFmpegPlaybackSettingsCalculator(), new FFmpegPlaybackSettingsCalculator(),
new FakeStreamSelector(), new FakeStreamSelector(),
new Mock<ITempFilePool>().Object, new Mock<ITempFilePool>().Object,
new HardwareCapabilitiesFactory( new FakeNvidiaCapabilitiesFactory(),
new MemoryCache(new MemoryCacheOptions()), // new HardwareCapabilitiesFactory(
LoggerFactory.CreateLogger<HardwareCapabilitiesFactory>()), // new MemoryCache(new MemoryCacheOptions()),
// LoggerFactory.CreateLogger<HardwareCapabilitiesFactory>()),
LoggerFactory.CreateLogger<FFmpegLibraryProcessService>()); LoggerFactory.CreateLogger<FFmpegLibraryProcessService>());
var v = new MediaVersion var v = new MediaVersion
@@ -577,6 +580,14 @@ public class TranscodingTests
subtitles.HeadOrNone().AsTask(); subtitles.HeadOrNone().AsTask();
} }
private class FakeNvidiaCapabilitiesFactory : IHardwareCapabilitiesFactory
{
public Task<IHardwareCapabilities> GetHardwareCapabilities(
string ffmpegPath,
HardwareAccelerationMode hardwareAccelerationMode) =>
Task.FromResult<IHardwareCapabilities>(new NvidiaHardwareCapabilities(61));
}
private static string ExecutableName(string baseName) => private static string ExecutableName(string baseName) =>
OperatingSystem.IsWindows() ? $"{baseName}.exe" : baseName; OperatingSystem.IsWindows() ? $"{baseName}.exe" : baseName;
} }
@@ -1,7 +1,9 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Capabilities; namespace ErsatzTV.FFmpeg.Capabilities;
public class DefaultHardwareCapabilities : IHardwareCapabilities public class DefaultHardwareCapabilities : IHardwareCapabilities
{ {
public bool CanDecode(string videoFormat) => true; public bool CanDecode(string videoFormat, Option<IPixelFormat> maybePixelFormat) => true;
public bool CanEncode(string videoFormat) => true; public bool CanEncode(string videoFormat, Option<IPixelFormat> maybePixelFormat) => true;
} }
@@ -1,7 +1,9 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Capabilities; namespace ErsatzTV.FFmpeg.Capabilities;
public interface IHardwareCapabilities public interface IHardwareCapabilities
{ {
public bool CanDecode(string videoFormat); public bool CanDecode(string videoFormat, Option<IPixelFormat> maybePixelFormat);
public bool CanEncode(string videoFormat); public bool CanEncode(string videoFormat, Option<IPixelFormat> maybePixelFormat);
} }
@@ -1,7 +1,9 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Capabilities; namespace ErsatzTV.FFmpeg.Capabilities;
public class NoHardwareCapabilities : IHardwareCapabilities public class NoHardwareCapabilities : IHardwareCapabilities
{ {
public bool CanDecode(string videoFormat) => false; public bool CanDecode(string videoFormat, Option<IPixelFormat> maybePixelFormat) => false;
public bool CanEncode(string videoFormat) => false; public bool CanEncode(string videoFormat, Option<IPixelFormat> maybePixelFormat) => false;
} }
@@ -1,3 +1,4 @@
using System.Numerics;
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Capabilities; namespace ErsatzTV.FFmpeg.Capabilities;
@@ -8,19 +9,41 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
public NvidiaHardwareCapabilities(int architecture) => _architecture = architecture; public NvidiaHardwareCapabilities(int architecture) => _architecture = architecture;
public bool CanDecode(string videoFormat) => public bool CanDecode(string videoFormat, Option<IPixelFormat> maybePixelFormat)
videoFormat switch
{ {
// pascal is required to decode hevc/vp9 int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
VideoFormat.Hevc or VideoFormat.Vp9 => _architecture >= 60,
_ => true
};
public bool CanEncode(string videoFormat) => return videoFormat switch
videoFormat switch
{ {
// pascal is required to encode hevc // second gen maxwell is required to decode hevc
VideoFormat.Hevc => _architecture >= 60, 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 _ => true
}; };
}
public bool CanEncode(string videoFormat, Option<IPixelFormat> maybePixelFormat)
{
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
return videoFormat switch
{
// 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
};
}
} }
+6 -3
View File
@@ -20,7 +20,8 @@ public static class AvailableDecoders
currentState.PixelFormat.Match(pf => pf.Name, () => string.Empty)) switch currentState.PixelFormat.Match(pf => pf.Name, () => string.Empty)) switch
{ {
(HardwareAccelerationMode.Nvenc, VideoFormat.Hevc, _) (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 // nvenc doesn't support hardware decoding of 10-bit content
(HardwareAccelerationMode.Nvenc, VideoFormat.H264, PixelFormat.YUV420P10LE or PixelFormat.YUV444P10LE) (HardwareAccelerationMode.Nvenc, VideoFormat.H264, PixelFormat.YUV420P10LE or PixelFormat.YUV444P10LE)
@@ -31,13 +32,15 @@ public static class AvailableDecoders
new DecoderMpeg2Video(), new DecoderMpeg2Video(),
(HardwareAccelerationMode.Nvenc, VideoFormat.H264, _) (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( (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Cuvid(
ffmpegState, ffmpegState,
desiredState.Deinterlaced), desiredState.Deinterlaced),
(HardwareAccelerationMode.Nvenc, VideoFormat.Vc1, _) => new DecoderVc1Cuvid(ffmpegState), (HardwareAccelerationMode.Nvenc, VideoFormat.Vc1, _) => new DecoderVc1Cuvid(ffmpegState),
(HardwareAccelerationMode.Nvenc, VideoFormat.Vp9, _) (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), (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg4, _) => new DecoderMpeg4Cuvid(ffmpegState),
// hevc_qsv decoder sometimes causes green lines with 10-bit content // hevc_qsv decoder sometimes causes green lines with 10-bit content
+22 -8
View File
@@ -21,43 +21,57 @@ public static class AvailableEncoders
ILogger logger) => ILogger logger) =>
(ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch (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( new EncoderHevcNvenc(
currentState, currentState,
maybeWatermarkInputFile, maybeWatermarkInputFile,
maybeSubtitleInputFile), maybeSubtitleInputFile),
(HardwareAccelerationMode.Nvenc, VideoFormat.H264) when hardwareCapabilities.CanEncode(VideoFormat.H264) => (HardwareAccelerationMode.Nvenc, VideoFormat.H264) when hardwareCapabilities.CanEncode(
VideoFormat.H264,
desiredState.PixelFormat) =>
new EncoderH264Nvenc( new EncoderH264Nvenc(
currentState, currentState,
maybeWatermarkInputFile, maybeWatermarkInputFile,
maybeSubtitleInputFile), maybeSubtitleInputFile),
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(VideoFormat.Hevc) => (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(
VideoFormat.Hevc,
desiredState.PixelFormat) =>
new EncoderHevcQsv( new EncoderHevcQsv(
currentState, currentState,
maybeWatermarkInputFile, maybeWatermarkInputFile,
maybeSubtitleInputFile), maybeSubtitleInputFile),
(HardwareAccelerationMode.Qsv, VideoFormat.H264) when hardwareCapabilities.CanEncode(VideoFormat.H264) => (HardwareAccelerationMode.Qsv, VideoFormat.H264) when hardwareCapabilities.CanEncode(
VideoFormat.H264,
desiredState.PixelFormat) =>
new EncoderH264Qsv( new EncoderH264Qsv(
currentState, currentState,
maybeWatermarkInputFile, maybeWatermarkInputFile,
maybeSubtitleInputFile), maybeSubtitleInputFile),
(HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(VideoFormat.Hevc) => (HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(
VideoFormat.Hevc,
desiredState.PixelFormat) =>
new EncoderHevcVaapi( new EncoderHevcVaapi(
currentState, currentState,
maybeWatermarkInputFile, maybeWatermarkInputFile,
maybeSubtitleInputFile), maybeSubtitleInputFile),
(HardwareAccelerationMode.Vaapi, VideoFormat.H264) when hardwareCapabilities.CanEncode(VideoFormat.H264) => (HardwareAccelerationMode.Vaapi, VideoFormat.H264) when hardwareCapabilities.CanEncode(
VideoFormat.H264,
desiredState.PixelFormat) =>
new EncoderH264Vaapi( new EncoderH264Vaapi(
currentState, currentState,
maybeWatermarkInputFile, maybeWatermarkInputFile,
maybeSubtitleInputFile), maybeSubtitleInputFile),
(HardwareAccelerationMode.VideoToolbox, VideoFormat.Hevc) when hardwareCapabilities.CanEncode( (HardwareAccelerationMode.VideoToolbox, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(
VideoFormat.Hevc) => new EncoderHevcVideoToolbox(), VideoFormat.Hevc,
desiredState.PixelFormat) => new EncoderHevcVideoToolbox(),
(HardwareAccelerationMode.VideoToolbox, VideoFormat.H264) when hardwareCapabilities.CanEncode( (HardwareAccelerationMode.VideoToolbox, VideoFormat.H264) when hardwareCapabilities.CanEncode(
VideoFormat.H264) => new EncoderH264VideoToolbox(), VideoFormat.H264,
desiredState.PixelFormat) => new EncoderH264VideoToolbox(),
(_, VideoFormat.Hevc) => new EncoderLibx265(currentState), (_, VideoFormat.Hevc) => new EncoderLibx265(currentState),
(_, VideoFormat.H264) => new EncoderLibx264(), (_, VideoFormat.H264) => new EncoderLibx264(),
+1
View File
@@ -4,4 +4,5 @@ public interface IPixelFormat
{ {
string Name { get; } string Name { get; }
string FFmpegName { get; } string FFmpegName { get; }
int BitDepth { get; }
} }
@@ -7,4 +7,5 @@ public class PixelFormatNv12 : IPixelFormat
public string Name { get; } public string Name { get; }
public string FFmpegName => "nv12"; public string FFmpegName => "nv12";
public int BitDepth => 8;
} }
@@ -4,4 +4,5 @@ public class PixelFormatUnknown : IPixelFormat
{ {
public string Name => "unknown"; public string Name => "unknown";
public string FFmpegName => "unknown"; public string FFmpegName => "unknown";
public int BitDepth => 8;
} }
@@ -4,4 +4,5 @@ public class PixelFormatYuv420P : IPixelFormat
{ {
public string Name => PixelFormat.YUV420P; public string Name => PixelFormat.YUV420P;
public string FFmpegName => FFmpegFormat.YUV420P; public string FFmpegName => FFmpegFormat.YUV420P;
public int BitDepth => 8;
} }
@@ -4,4 +4,5 @@ public class PixelFormatYuv420P10Le : IPixelFormat
{ {
public string Name => PixelFormat.YUV420P10LE; public string Name => PixelFormat.YUV420P10LE;
public string FFmpegName => FFmpegFormat.P010LE; public string FFmpegName => FFmpegFormat.P010LE;
public int BitDepth => 10;
} }
@@ -4,4 +4,5 @@ public class PixelFormatYuv444P : IPixelFormat
{ {
public string Name => PixelFormat.YUV444P; public string Name => PixelFormat.YUV444P;
public string FFmpegName => FFmpegFormat.YUV444P; public string FFmpegName => FFmpegFormat.YUV444P;
public int BitDepth => 8;
} }
@@ -6,4 +6,5 @@ public class PixelFormatYuvJ420P : IPixelFormat
// always convert this to yuv420p in filter chains // always convert this to yuv420p in filter chains
public string FFmpegName => FFmpegFormat.YUV420P; public string FFmpegName => FFmpegFormat.YUV420P;
public int BitDepth => 8;
} }
+4 -2
View File
@@ -220,8 +220,10 @@ public class PipelineBuilder
foreach (IPipelineStep accel in maybeAccel) foreach (IPipelineStep accel in maybeAccel)
{ {
bool canDecode = _hardwareCapabilities.CanDecode(currentState.VideoFormat); bool canDecode = _hardwareCapabilities.CanDecode(currentState.VideoFormat, videoStream.PixelFormat);
bool canEncode = _hardwareCapabilities.CanEncode(desiredState.VideoFormat); bool canEncode = _hardwareCapabilities.CanEncode(
desiredState.VideoFormat,
desiredState.PixelFormat);
// disable hw accel if decoder/encoder isn't supported // disable hw accel if decoder/encoder isn't supported
if (!canDecode || !canEncode) if (!canDecode || !canEncode)