adjust nvidia capabilities (#864)
* adjust nvidia capabilities logic * fallback to software encoding for 10-bit h264 * cleanup * more tweaks
This commit is contained in:
@@ -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
|
{
|
||||||
{
|
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
|
||||||
// pascal is required to decode hevc/vp9
|
|
||||||
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user