detect nvidia capabilities (#853)

* fallback to software codecs for old nvidia cards

* update dependencies
This commit is contained in:
Jason Dove
2022-06-14 19:44:34 -05:00
committed by GitHub
parent 0c13b8ef1a
commit dd7f77751c
34 changed files with 376 additions and 106 deletions
+2
View File
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Fixed ### Fixed
- Fix content repeating for up to a minute near the top of every hour - Fix content repeating for up to a minute near the top of every hour
- Check whether hardware-accelerated hevc codecs are supported by the NVIDIA card
- Software codecs will be used if they are unsupported by the NVIDIA card
## [0.6.1-beta] - 2022-06-03 ## [0.6.1-beta] - 2022-06-03
### Fixed ### Fixed
@@ -69,7 +69,7 @@ public class
string originalPath = _imageCache.GetPathForImage(request.FileName, request.ArtworkKind, None); string originalPath = _imageCache.GetPathForImage(request.FileName, request.ArtworkKind, None);
Command process = _ffmpegProcessService.ResizeImage( Command process = await _ffmpegProcessService.ResizeImage(
ffmpegPath, ffmpegPath,
originalPath, originalPath,
withExtension, withExtension,
@@ -30,7 +30,7 @@ public class GetConcatProcessByChannelNumberHandler : FFmpegProcessHandler<GetCo
.GetValue<bool>(ConfigElementKey.FFmpegSaveReports) .GetValue<bool>(ConfigElementKey.FFmpegSaveReports)
.Map(result => result.IfNone(false)); .Map(result => result.IfNone(false));
Command process = _ffmpegProcessService.ConcatChannel( Command process = await _ffmpegProcessService.ConcatChannel(
ffmpegPath, ffmpegPath,
saveReports, saveReports,
channel, channel,
@@ -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.Capabilities;
using ErsatzTV.FFmpeg.State; using ErsatzTV.FFmpeg.State;
using FluentAssertions; using FluentAssertions;
using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Caching.Memory;
@@ -302,6 +303,9 @@ public class TranscodingTests
new FFmpegPlaybackSettingsCalculator(), new FFmpegPlaybackSettingsCalculator(),
new FakeStreamSelector(), new FakeStreamSelector(),
new Mock<ITempFilePool>().Object, new Mock<ITempFilePool>().Object,
new HardwareCapabilitiesFactory(
new MemoryCache(new MemoryCacheOptions()),
LoggerFactory.CreateLogger<HardwareCapabilitiesFactory>()),
LoggerFactory.CreateLogger<FFmpegLibraryProcessService>()); LoggerFactory.CreateLogger<FFmpegLibraryProcessService>());
var v = new MediaVersion var v = new MediaVersion
@@ -3,6 +3,7 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.FFmpeg; using ErsatzTV.FFmpeg;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Environment; using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.OutputFormat; using ErsatzTV.FFmpeg.OutputFormat;
@@ -16,6 +17,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
{ {
private readonly FFmpegProcessService _ffmpegProcessService; private readonly FFmpegProcessService _ffmpegProcessService;
private readonly IFFmpegStreamSelector _ffmpegStreamSelector; private readonly IFFmpegStreamSelector _ffmpegStreamSelector;
private readonly IHardwareCapabilitiesFactory _hardwareCapabilitiesFactory;
private readonly ILogger<FFmpegLibraryProcessService> _logger; private readonly ILogger<FFmpegLibraryProcessService> _logger;
private readonly FFmpegPlaybackSettingsCalculator _playbackSettingsCalculator; private readonly FFmpegPlaybackSettingsCalculator _playbackSettingsCalculator;
private readonly ITempFilePool _tempFilePool; private readonly ITempFilePool _tempFilePool;
@@ -25,12 +27,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
FFmpegPlaybackSettingsCalculator playbackSettingsCalculator, FFmpegPlaybackSettingsCalculator playbackSettingsCalculator,
IFFmpegStreamSelector ffmpegStreamSelector, IFFmpegStreamSelector ffmpegStreamSelector,
ITempFilePool tempFilePool, ITempFilePool tempFilePool,
IHardwareCapabilitiesFactory hardwareCapabilitiesFactory,
ILogger<FFmpegLibraryProcessService> logger) ILogger<FFmpegLibraryProcessService> logger)
{ {
_ffmpegProcessService = ffmpegProcessService; _ffmpegProcessService = ffmpegProcessService;
_playbackSettingsCalculator = playbackSettingsCalculator; _playbackSettingsCalculator = playbackSettingsCalculator;
_ffmpegStreamSelector = ffmpegStreamSelector; _ffmpegStreamSelector = ffmpegStreamSelector;
_tempFilePool = tempFilePool; _tempFilePool = tempFilePool;
_hardwareCapabilitiesFactory = hardwareCapabilitiesFactory;
_logger = logger; _logger = logger;
} }
@@ -214,6 +218,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
var ffmpegState = new FFmpegState( var ffmpegState = new FFmpegState(
saveReports, saveReports,
hwAccel, hwAccel,
hwAccel,
VaapiDriverName(hwAccel, vaapiDriver), VaapiDriverName(hwAccel, vaapiDriver),
VaapiDeviceName(hwAccel, vaapiDevice), VaapiDeviceName(hwAccel, vaapiDevice),
playbackSettings.StreamSeek, playbackSettings.StreamSeek,
@@ -231,6 +236,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
_logger.LogDebug("FFmpeg desired state {FrameState}", desiredState); _logger.LogDebug("FFmpeg desired state {FrameState}", desiredState);
var pipelineBuilder = new PipelineBuilder( var pipelineBuilder = new PipelineBuilder(
await _hardwareCapabilitiesFactory.GetHardwareCapabilities(ffmpegPath, hwAccel),
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
watermarkInputFile, watermarkInputFile,
@@ -333,6 +339,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
var ffmpegState = new FFmpegState( var ffmpegState = new FFmpegState(
false, false,
hwAccel, hwAccel,
hwAccel,
VaapiDriverName(hwAccel, vaapiDriver), VaapiDriverName(hwAccel, vaapiDriver),
VaapiDeviceName(hwAccel, vaapiDevice), VaapiDeviceName(hwAccel, vaapiDevice),
playbackSettings.StreamSeek, playbackSettings.StreamSeek,
@@ -359,6 +366,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
_logger.LogDebug("FFmpeg desired error state {FrameState}", desiredState); _logger.LogDebug("FFmpeg desired error state {FrameState}", desiredState);
var pipelineBuilder = new PipelineBuilder( var pipelineBuilder = new PipelineBuilder(
await _hardwareCapabilitiesFactory.GetHardwareCapabilities(ffmpegPath, hwAccel),
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
None, None,
@@ -372,7 +380,12 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
return GetCommand(ffmpegPath, videoInputFile, audioInputFile, None, None, pipeline); return GetCommand(ffmpegPath, videoInputFile, audioInputFile, None, None, pipeline);
} }
public Command ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host) public async Task<Command> ConcatChannel(
string ffmpegPath,
bool saveReports,
Channel channel,
string scheme,
string host)
{ {
var resolution = new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height); var resolution = new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height);
@@ -381,6 +394,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
resolution); resolution);
var pipelineBuilder = new PipelineBuilder( var pipelineBuilder = new PipelineBuilder(
await _hardwareCapabilitiesFactory.GetHardwareCapabilities(ffmpegPath, HardwareAccelerationMode.None),
None, None,
None, None,
None, None,
@@ -399,13 +413,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
public Command WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host) => public Command WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host) =>
_ffmpegProcessService.WrapSegmenter(ffmpegPath, saveReports, channel, scheme, host); _ffmpegProcessService.WrapSegmenter(ffmpegPath, saveReports, channel, scheme, host);
public Command ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height) public async Task<Command> ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height)
{ {
var videoInputFile = new VideoInputFile( var videoInputFile = new VideoInputFile(
inputFile, inputFile,
new List<VideoStream> { new(0, string.Empty, None, FrameSize.Unknown, None, true) }); new List<VideoStream> { new(0, string.Empty, None, FrameSize.Unknown, None, true) });
var pipelineBuilder = new PipelineBuilder( var pipelineBuilder = new PipelineBuilder(
await _hardwareCapabilitiesFactory.GetHardwareCapabilities(ffmpegPath, HardwareAccelerationMode.None),
videoInputFile, videoInputFile,
None, None,
None, None,
@@ -46,11 +46,11 @@ public interface IFFmpegProcessService
VaapiDriver vaapiDriver, VaapiDriver vaapiDriver,
string vaapiDevice); string vaapiDevice);
Command ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host); Task<Command> ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host);
Command WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host); Command WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host);
Command ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height); Task<Command> ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height);
Command ConvertToPng(string ffmpegPath, string inputFile, string outputFile); Command ConvertToPng(string ffmpegPath, string inputFile, string outputFile);
+23 -3
View File
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Encoder; using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.OutputFormat; using ErsatzTV.FFmpeg.OutputFormat;
@@ -54,6 +55,7 @@ public class PipelineGeneratorTests
var ffmpegState = new FFmpegState( var ffmpegState = new FFmpegState(
false, false,
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
HardwareAccelerationMode.None,
Option<string>.None, Option<string>.None,
Option<string>.None, Option<string>.None,
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1),
@@ -68,7 +70,15 @@ public class PipelineGeneratorTests
0, 0,
Option<int>.None); Option<int>.None);
var builder = new PipelineBuilder(videoInputFile, audioInputFile, None, None, "", "", _logger); var builder = new PipelineBuilder(
new DefaultHardwareCapabilities(),
videoInputFile,
audioInputFile,
None,
None,
"",
"",
_logger);
FFmpegPipeline result = builder.Build(ffmpegState, desiredState); FFmpegPipeline result = builder.Build(ffmpegState, desiredState);
result.PipelineSteps.Should().HaveCountGreaterThan(0); result.PipelineSteps.Should().HaveCountGreaterThan(0);
@@ -85,7 +95,7 @@ public class PipelineGeneratorTests
var resolution = new FrameSize(1920, 1080); var resolution = new FrameSize(1920, 1080);
var concatInputFile = new ConcatInputFile("http://localhost:8080/ffmpeg/concat/1", resolution); var concatInputFile = new ConcatInputFile("http://localhost:8080/ffmpeg/concat/1", resolution);
var builder = new PipelineBuilder(None, None, None, None, "", "", _logger); var builder = new PipelineBuilder(new DefaultHardwareCapabilities(), None, None, None, None, "", "", _logger);
FFmpegPipeline result = builder.Concat(concatInputFile, FFmpegState.Concat(false, "Some Channel")); FFmpegPipeline result = builder.Concat(concatInputFile, FFmpegState.Concat(false, "Some Channel"));
result.PipelineSteps.Should().HaveCountGreaterThan(0); result.PipelineSteps.Should().HaveCountGreaterThan(0);
@@ -132,6 +142,7 @@ public class PipelineGeneratorTests
var ffmpegState = new FFmpegState( var ffmpegState = new FFmpegState(
false, false,
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
HardwareAccelerationMode.None,
Option<string>.None, Option<string>.None,
Option<string>.None, Option<string>.None,
Option<TimeSpan>.None, Option<TimeSpan>.None,
@@ -146,7 +157,15 @@ public class PipelineGeneratorTests
0, 0,
Option<int>.None); Option<int>.None);
var builder = new PipelineBuilder(videoInputFile, audioInputFile, None, None, "", "", _logger); var builder = new PipelineBuilder(
new DefaultHardwareCapabilities(),
videoInputFile,
audioInputFile,
None,
None,
"",
"",
_logger);
FFmpegPipeline result = builder.Build(ffmpegState, desiredState); FFmpegPipeline result = builder.Build(ffmpegState, desiredState);
result.PipelineSteps.Should().HaveCountGreaterThan(0); result.PipelineSteps.Should().HaveCountGreaterThan(0);
@@ -172,6 +191,7 @@ public class PipelineGeneratorTests
}); });
var pipelineBuilder = new PipelineBuilder( var pipelineBuilder = new PipelineBuilder(
new DefaultHardwareCapabilities(),
videoInputFile, videoInputFile,
Option<AudioInputFile>.None, Option<AudioInputFile>.None,
Option<WatermarkInputFile>.None, Option<WatermarkInputFile>.None,
@@ -0,0 +1,7 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public class DefaultHardwareCapabilities : IHardwareCapabilities
{
public bool CanDecode(string videoFormat) => true;
public bool CanEncode(string videoFormat) => true;
}
@@ -0,0 +1,76 @@
using System.Text;
using System.Text.RegularExpressions;
using CliWrap;
using CliWrap.Buffered;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.FFmpeg.Capabilities;
public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
{
private const string CacheKey = "ffmpeg.hardware.nvidia.architecture";
private readonly ILogger<HardwareCapabilitiesFactory> _logger;
private readonly IMemoryCache _memoryCache;
public HardwareCapabilitiesFactory(IMemoryCache memoryCache, ILogger<HardwareCapabilitiesFactory> logger)
{
_memoryCache = memoryCache;
_logger = logger;
}
public async Task<IHardwareCapabilities> GetHardwareCapabilities(
string ffmpegPath,
HardwareAccelerationMode hardwareAccelerationMode) =>
hardwareAccelerationMode switch
{
HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath),
_ => new DefaultHardwareCapabilities()
};
private async Task<IHardwareCapabilities> GetNvidiaCapabilities(string ffmpegPath)
{
if (_memoryCache.TryGetValue(CacheKey, out int cachedArchitecture))
{
return new NvidiaHardwareCapabilities(cachedArchitecture);
}
string[] arguments =
{
"-f", "lavfi",
"-i", "nullsrc",
"-c:v", "h264_nvenc",
"-gpu", "list",
"-f", "null", "-"
};
BufferedCommandResult result = await Cli.Wrap(ffmpegPath)
.WithArguments(arguments)
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync(Encoding.UTF8);
string output = string.IsNullOrWhiteSpace(result.StandardOutput)
? result.StandardError
: result.StandardOutput;
Option<string> maybeLine = Optional(output.Split("\n").FirstOrDefault(x => x.Contains("GPU")));
foreach (string line in maybeLine)
{
const string PATTERN = @"SM\s+(\d\.\d)";
Match match = Regex.Match(line, PATTERN);
if (match.Success && int.TryParse(match.Groups[1].Value.Replace(".", string.Empty), out int architecture))
{
_logger.LogInformation("Detected NVIDIA GPU architecture SM {Architecture}", architecture);
_memoryCache.Set(CacheKey, architecture);
return new NvidiaHardwareCapabilities(architecture);
}
}
_logger.LogWarning(
"Error detecting NVIDIA GPU capabilities; some hardware accelerated features will be unavailable: {ExitCode}",
result.ExitCode);
return new NoHardwareCapabilities();
}
}
@@ -0,0 +1,7 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public interface IHardwareCapabilities
{
public bool CanDecode(string videoFormat);
public bool CanEncode(string videoFormat);
}
@@ -0,0 +1,8 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public interface IHardwareCapabilitiesFactory
{
Task<IHardwareCapabilities> GetHardwareCapabilities(
string ffmpegPath,
HardwareAccelerationMode hardwareAccelerationMode);
}
@@ -0,0 +1,7 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public class NoHardwareCapabilities : IHardwareCapabilities
{
public bool CanDecode(string videoFormat) => false;
public bool CanEncode(string videoFormat) => false;
}
@@ -0,0 +1,26 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Capabilities;
public class NvidiaHardwareCapabilities : IHardwareCapabilities
{
private readonly int _architecture;
public NvidiaHardwareCapabilities(int architecture) => _architecture = architecture;
public bool CanDecode(string videoFormat) =>
videoFormat switch
{
// pascal is required to decode hevc/vp9
VideoFormat.Hevc or VideoFormat.Vp9 => _architecture >= 60,
_ => true
};
public bool CanEncode(string videoFormat) =>
videoFormat switch
{
// pascal is required to encode hevc
VideoFormat.Hevc => _architecture >= 60,
_ => true
};
}
+13 -7
View File
@@ -1,4 +1,5 @@
using ErsatzTV.FFmpeg.Decoder.Cuvid; using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Decoder.Cuvid;
using ErsatzTV.FFmpeg.Decoder.Qsv; using ErsatzTV.FFmpeg.Decoder.Qsv;
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -8,16 +9,18 @@ namespace ErsatzTV.FFmpeg.Decoder;
public static class AvailableDecoders public static class AvailableDecoders
{ {
public static Option<IDecoder> ForVideoFormat( public static Option<IDecoder> ForVideoFormat(
IHardwareCapabilities hardwareCapabilities,
FFmpegState ffmpegState, FFmpegState ffmpegState,
FrameState currentState, FrameState currentState,
FrameState desiredState, FrameState desiredState,
Option<WatermarkInputFile> watermarkInputFile, Option<WatermarkInputFile> watermarkInputFile,
Option<SubtitleInputFile> subtitleInputFile, Option<SubtitleInputFile> subtitleInputFile,
ILogger logger) => ILogger logger) =>
(ffmpegState.HardwareAccelerationMode, currentState.VideoFormat, (ffmpegState.DecoderHardwareAccelerationMode, currentState.VideoFormat,
currentState.PixelFormat.Match(pf => pf.Name, () => string.Empty)) switch currentState.PixelFormat.Match(pf => pf.Name, () => string.Empty)) switch
{ {
(HardwareAccelerationMode.Nvenc, VideoFormat.Hevc, _) => new DecoderHevcCuvid(), (HardwareAccelerationMode.Nvenc, VideoFormat.Hevc, _)
when hardwareCapabilities.CanDecode(VideoFormat.Hevc) => 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)
@@ -27,12 +30,15 @@ public static class AvailableDecoders
(HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) when desiredState.Deinterlaced => (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) when desiredState.Deinterlaced =>
new DecoderMpeg2Video(), new DecoderMpeg2Video(),
(HardwareAccelerationMode.Nvenc, VideoFormat.H264, _) => new DecoderH264Cuvid(), (HardwareAccelerationMode.Nvenc, VideoFormat.H264, _)
when hardwareCapabilities.CanDecode(VideoFormat.H264) => new DecoderH264Cuvid(ffmpegState),
(HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Cuvid( (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Cuvid(
ffmpegState,
desiredState.Deinterlaced), desiredState.Deinterlaced),
(HardwareAccelerationMode.Nvenc, VideoFormat.Vc1, _) => new DecoderVc1Cuvid(), (HardwareAccelerationMode.Nvenc, VideoFormat.Vc1, _) => new DecoderVc1Cuvid(ffmpegState),
(HardwareAccelerationMode.Nvenc, VideoFormat.Vp9, _) => new DecoderVp9Cuvid(), (HardwareAccelerationMode.Nvenc, VideoFormat.Vp9, _)
(HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg4, _) => new DecoderMpeg4Cuvid(), when hardwareCapabilities.CanDecode(VideoFormat.Vp9) => new DecoderVp9Cuvid(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
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc, PixelFormat.YUV420P10LE) => new DecoderHevc(), (HardwareAccelerationMode.Qsv, VideoFormat.Hevc, PixelFormat.YUV420P10LE) => new DecoderHevc(),
@@ -2,16 +2,23 @@
public class DecoderH264Cuvid : DecoderBase public class DecoderH264Cuvid : DecoderBase
{ {
private readonly FFmpegState _ffmpegState;
public DecoderH264Cuvid(FFmpegState ffmpegState) => _ffmpegState = ffmpegState;
public override string Name => "h264_cuvid"; public override string Name => "h264_cuvid";
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; protected override FrameDataLocation OutputFrameDataLocation =>
_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None
? FrameDataLocation.Software
: FrameDataLocation.Hardware;
public override IList<string> InputOptions(InputFile inputFile) public override IList<string> InputOptions(InputFile inputFile)
{ {
IList<string> result = base.InputOptions(inputFile); IList<string> result = base.InputOptions(inputFile);
result.Add("-hwaccel_output_format"); result.Add("-hwaccel_output_format");
result.Add("cuda"); result.Add(_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.None ? "cuda" : "nv12");
return result; return result;
} }
@@ -2,16 +2,23 @@
public class DecoderHevcCuvid : DecoderBase public class DecoderHevcCuvid : DecoderBase
{ {
private readonly FFmpegState _ffmpegState;
public DecoderHevcCuvid(FFmpegState ffmpegState) => _ffmpegState = ffmpegState;
public override string Name => "hevc_cuvid"; public override string Name => "hevc_cuvid";
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; protected override FrameDataLocation OutputFrameDataLocation =>
_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None
? FrameDataLocation.Software
: FrameDataLocation.Hardware;
public override IList<string> InputOptions(InputFile inputFile) public override IList<string> InputOptions(InputFile inputFile)
{ {
IList<string> result = base.InputOptions(inputFile); IList<string> result = base.InputOptions(inputFile);
result.Add("-hwaccel_output_format"); result.Add("-hwaccel_output_format");
result.Add("cuda"); result.Add(_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.None ? "cuda" : "nv12");
return result; return result;
} }
@@ -3,20 +3,27 @@
public class DecoderMpeg2Cuvid : DecoderBase public class DecoderMpeg2Cuvid : DecoderBase
{ {
private readonly bool _contentIsInterlaced; private readonly bool _contentIsInterlaced;
private readonly FFmpegState _ffmpegState;
public DecoderMpeg2Cuvid(bool contentIsInterlaced) => _contentIsInterlaced = contentIsInterlaced; public DecoderMpeg2Cuvid(FFmpegState ffmpegState, bool contentIsInterlaced)
{
_ffmpegState = ffmpegState;
_contentIsInterlaced = contentIsInterlaced;
}
public override string Name => "mpeg2_cuvid"; public override string Name => "mpeg2_cuvid";
protected override FrameDataLocation OutputFrameDataLocation => protected override FrameDataLocation OutputFrameDataLocation =>
_contentIsInterlaced ? FrameDataLocation.Software : FrameDataLocation.Hardware; _contentIsInterlaced || _ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None
? FrameDataLocation.Software
: FrameDataLocation.Hardware;
public override IList<string> InputOptions(InputFile inputFile) public override IList<string> InputOptions(InputFile inputFile)
{ {
IList<string> result = base.InputOptions(inputFile); IList<string> result = base.InputOptions(inputFile);
result.Add("-hwaccel_output_format"); result.Add("-hwaccel_output_format");
result.Add("cuda"); result.Add(_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.None ? "cuda" : "nv12");
return result; return result;
} }
@@ -2,16 +2,23 @@
public class DecoderMpeg4Cuvid : DecoderBase public class DecoderMpeg4Cuvid : DecoderBase
{ {
private readonly FFmpegState _ffmpegState;
public DecoderMpeg4Cuvid(FFmpegState ffmpegState) => _ffmpegState = ffmpegState;
public override string Name => "mpeg4_cuvid"; public override string Name => "mpeg4_cuvid";
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; protected override FrameDataLocation OutputFrameDataLocation =>
_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None
? FrameDataLocation.Software
: FrameDataLocation.Hardware;
public override IList<string> InputOptions(InputFile inputFile) public override IList<string> InputOptions(InputFile inputFile)
{ {
IList<string> result = base.InputOptions(inputFile); IList<string> result = base.InputOptions(inputFile);
result.Add("-hwaccel_output_format"); result.Add("-hwaccel_output_format");
result.Add("cuda"); result.Add(_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.None ? "cuda" : "nv12");
return result; return result;
} }
@@ -2,16 +2,23 @@
public class DecoderVc1Cuvid : DecoderBase public class DecoderVc1Cuvid : DecoderBase
{ {
private readonly FFmpegState _ffmpegState;
public DecoderVc1Cuvid(FFmpegState ffmpegState) => _ffmpegState = ffmpegState;
public override string Name => "vc1_cuvid"; public override string Name => "vc1_cuvid";
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; protected override FrameDataLocation OutputFrameDataLocation =>
_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None
? FrameDataLocation.Software
: FrameDataLocation.Hardware;
public override IList<string> InputOptions(InputFile inputFile) public override IList<string> InputOptions(InputFile inputFile)
{ {
IList<string> result = base.InputOptions(inputFile); IList<string> result = base.InputOptions(inputFile);
result.Add("-hwaccel_output_format"); result.Add("-hwaccel_output_format");
result.Add("cuda"); result.Add(_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.None ? "cuda" : "nv12");
return result; return result;
} }
@@ -2,16 +2,23 @@
public class DecoderVp9Cuvid : DecoderBase public class DecoderVp9Cuvid : DecoderBase
{ {
private readonly FFmpegState _ffmpegState;
public DecoderVp9Cuvid(FFmpegState ffmpegState) => _ffmpegState = ffmpegState;
public override string Name => "vp9_cuvid"; public override string Name => "vp9_cuvid";
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; protected override FrameDataLocation OutputFrameDataLocation =>
_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None
? FrameDataLocation.Software
: FrameDataLocation.Hardware;
public override IList<string> InputOptions(InputFile inputFile) public override IList<string> InputOptions(InputFile inputFile)
{ {
IList<string> result = base.InputOptions(inputFile); IList<string> result = base.InputOptions(inputFile);
result.Add("-hwaccel_output_format"); result.Add("-hwaccel_output_format");
result.Add("cuda"); result.Add(_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.None ? "cuda" : "nv12");
return result; return result;
} }
+39 -29
View File
@@ -1,4 +1,5 @@
using ErsatzTV.FFmpeg.Encoder.Nvenc; using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Encoder.Nvenc;
using ErsatzTV.FFmpeg.Encoder.Qsv; using ErsatzTV.FFmpeg.Encoder.Qsv;
using ErsatzTV.FFmpeg.Encoder.Vaapi; using ErsatzTV.FFmpeg.Encoder.Vaapi;
using ErsatzTV.FFmpeg.Encoder.VideoToolbox; using ErsatzTV.FFmpeg.Encoder.VideoToolbox;
@@ -11,45 +12,54 @@ namespace ErsatzTV.FFmpeg.Encoder;
public static class AvailableEncoders public static class AvailableEncoders
{ {
public static Option<IEncoder> ForVideoFormat( public static Option<IEncoder> ForVideoFormat(
IHardwareCapabilities hardwareCapabilities,
FFmpegState ffmpegState, FFmpegState ffmpegState,
FrameState currentState, FrameState currentState,
FrameState desiredState, FrameState desiredState,
Option<WatermarkInputFile> maybeWatermarkInputFile, Option<WatermarkInputFile> maybeWatermarkInputFile,
Option<SubtitleInputFile> maybeSubtitleInputFile, Option<SubtitleInputFile> maybeSubtitleInputFile,
ILogger logger) => ILogger logger) =>
(ffmpegState.HardwareAccelerationMode, desiredState.VideoFormat) switch (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch
{ {
(HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) => new EncoderHevcNvenc( (HardwareAccelerationMode.Nvenc, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(VideoFormat.Hevc) =>
currentState, new EncoderHevcNvenc(
maybeWatermarkInputFile, currentState,
maybeSubtitleInputFile), maybeWatermarkInputFile,
(HardwareAccelerationMode.Nvenc, VideoFormat.H264) => new EncoderH264Nvenc( maybeSubtitleInputFile),
currentState, (HardwareAccelerationMode.Nvenc, VideoFormat.H264) when hardwareCapabilities.CanEncode(VideoFormat.H264) =>
maybeWatermarkInputFile, new EncoderH264Nvenc(
maybeSubtitleInputFile), currentState,
maybeWatermarkInputFile,
maybeSubtitleInputFile),
(HardwareAccelerationMode.Qsv, VideoFormat.Hevc) => new EncoderHevcQsv( (HardwareAccelerationMode.Qsv, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(VideoFormat.Hevc) =>
currentState, new EncoderHevcQsv(
maybeWatermarkInputFile, currentState,
maybeSubtitleInputFile), maybeWatermarkInputFile,
(HardwareAccelerationMode.Qsv, VideoFormat.H264) => new EncoderH264Qsv( maybeSubtitleInputFile),
currentState, (HardwareAccelerationMode.Qsv, VideoFormat.H264) when hardwareCapabilities.CanEncode(VideoFormat.H264) =>
maybeWatermarkInputFile, new EncoderH264Qsv(
maybeSubtitleInputFile), currentState,
maybeWatermarkInputFile,
maybeSubtitleInputFile),
(HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) => new EncoderHevcVaapi( (HardwareAccelerationMode.Vaapi, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(VideoFormat.Hevc) =>
currentState, new EncoderHevcVaapi(
maybeWatermarkInputFile, currentState,
maybeSubtitleInputFile), maybeWatermarkInputFile,
(HardwareAccelerationMode.Vaapi, VideoFormat.H264) => new EncoderH264Vaapi( maybeSubtitleInputFile),
currentState, (HardwareAccelerationMode.Vaapi, VideoFormat.H264) when hardwareCapabilities.CanEncode(VideoFormat.H264) =>
maybeWatermarkInputFile, new EncoderH264Vaapi(
maybeSubtitleInputFile), currentState,
maybeWatermarkInputFile,
maybeSubtitleInputFile),
(HardwareAccelerationMode.VideoToolbox, VideoFormat.Hevc) => new EncoderHevcVideoToolbox(), (HardwareAccelerationMode.VideoToolbox, VideoFormat.Hevc) when hardwareCapabilities.CanEncode(
(HardwareAccelerationMode.VideoToolbox, VideoFormat.H264) => new EncoderH264VideoToolbox(), VideoFormat.Hevc) => new EncoderHevcVideoToolbox(),
(HardwareAccelerationMode.VideoToolbox, VideoFormat.H264) when hardwareCapabilities.CanEncode(
VideoFormat.H264) => new EncoderH264VideoToolbox(),
(_, VideoFormat.Hevc) => new EncoderLibx265(), (_, VideoFormat.Hevc) => new EncoderLibx265(currentState),
(_, VideoFormat.H264) => new EncoderLibx264(), (_, VideoFormat.H264) => new EncoderLibx264(),
(_, VideoFormat.Mpeg2Video) => new EncoderMpeg2Video(), (_, VideoFormat.Mpeg2Video) => new EncoderMpeg2Video(),
+8 -1
View File
@@ -1,9 +1,16 @@
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Filter;
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Encoder; namespace ErsatzTV.FFmpeg.Encoder;
public class EncoderLibx265 : EncoderBase public class EncoderLibx265 : EncoderBase
{ {
private readonly FrameState _currentState;
public EncoderLibx265(FrameState currentState) => _currentState = currentState;
public override string Filter => new HardwareDownloadFilter(_currentState).Filter;
// TODO: is tag:v needed for mpegts? // TODO: is tag:v needed for mpegts?
public override IList<string> OutputOptions => new List<string> public override IList<string> OutputOptions => new List<string>
{ "-c:v", Name, "-tag:v", "hvc1", "-x265-params", "log-level=error" }; { "-c:v", Name, "-tag:v", "hvc1", "-x265-params", "log-level=error" };
+1
View File
@@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CliWrap" Version="3.4.4" /> <PackageReference Include="CliWrap" Version="3.4.4" />
<PackageReference Include="LanguageExt.Core" Version="4.2.2" /> <PackageReference Include="LanguageExt.Core" Version="4.2.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
</ItemGroup> </ItemGroup>
+3 -1
View File
@@ -4,7 +4,8 @@ namespace ErsatzTV.FFmpeg;
public record FFmpegState( public record FFmpegState(
bool SaveReport, bool SaveReport,
HardwareAccelerationMode HardwareAccelerationMode, HardwareAccelerationMode DecoderHardwareAccelerationMode,
HardwareAccelerationMode EncoderHardwareAccelerationMode,
Option<string> VaapiDriver, Option<string> VaapiDriver,
Option<string> VaapiDevice, Option<string> VaapiDevice,
Option<TimeSpan> Start, Option<TimeSpan> Start,
@@ -23,6 +24,7 @@ public record FFmpegState(
new( new(
saveReport, saveReport,
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
HardwareAccelerationMode.None,
Option<string>.None, Option<string>.None,
Option<string>.None, Option<string>.None,
Option<TimeSpan>.None, Option<TimeSpan>.None,
+8 -8
View File
@@ -148,7 +148,7 @@ public class ComplexFilter : IPipelineStep
} }
IPipelineFilterStep overlayFilter = AvailableWatermarkOverlayFilters.ForAcceleration( IPipelineFilterStep overlayFilter = AvailableWatermarkOverlayFilters.ForAcceleration(
_ffmpegState.HardwareAccelerationMode, _ffmpegState.EncoderHardwareAccelerationMode,
_currentState, _currentState,
watermarkInputFile.DesiredState, watermarkInputFile.DesiredState,
_resolution); _resolution);
@@ -164,16 +164,16 @@ public class ComplexFilter : IPipelineStep
// also wait to upload if a subtitle overlay is coming // also wait to upload if a subtitle overlay is coming
string uploadDownloadFilter = string.Empty; string uploadDownloadFilter = string.Empty;
if (_maybeSubtitleInputFile.IsNone && if (_maybeSubtitleInputFile.IsNone &&
(_ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Vaapi || (_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi ||
_ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.VideoToolbox && _ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.VideoToolbox &&
_currentState.VideoFormat == VideoFormat.Hevc)) _currentState.VideoFormat == VideoFormat.Hevc))
{ {
uploadDownloadFilter = new HardwareUploadFilter(_ffmpegState).Filter; uploadDownloadFilter = new HardwareUploadFilter(_ffmpegState).Filter;
} }
if (_maybeSubtitleInputFile.Map(s => !s.IsImageBased).IfNone(false) && if (_maybeSubtitleInputFile.Map(s => !s.IsImageBased).IfNone(false) &&
_ffmpegState.HardwareAccelerationMode != HardwareAccelerationMode.Vaapi && _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Vaapi &&
_ffmpegState.HardwareAccelerationMode != HardwareAccelerationMode.VideoToolbox) _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.VideoToolbox)
{ {
uploadDownloadFilter = new HardwareDownloadFilter(_currentState).Filter; uploadDownloadFilter = new HardwareDownloadFilter(_currentState).Filter;
} }
@@ -217,7 +217,7 @@ public class ComplexFilter : IPipelineStep
{ {
IPipelineFilterStep overlayFilter = IPipelineFilterStep overlayFilter =
AvailableSubtitleOverlayFilters.ForAcceleration( AvailableSubtitleOverlayFilters.ForAcceleration(
_ffmpegState.HardwareAccelerationMode, _ffmpegState.EncoderHardwareAccelerationMode,
_currentState); _currentState);
filter = overlayFilter.Filter; filter = overlayFilter.Filter;
} }
@@ -238,8 +238,8 @@ public class ComplexFilter : IPipelineStep
// vaapi uses software overlay and needs to upload // vaapi uses software overlay and needs to upload
// videotoolbox seems to require a hwupload for hevc // videotoolbox seems to require a hwupload for hevc
string uploadFilter = string.Empty; string uploadFilter = string.Empty;
if (_ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Vaapi if (_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi
|| _ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.VideoToolbox && || _ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.VideoToolbox &&
_currentState.VideoFormat == VideoFormat.Hevc) _currentState.VideoFormat == VideoFormat.Hevc)
{ {
uploadFilter = new HardwareUploadFilter(_ffmpegState).Filter; uploadFilter = new HardwareUploadFilter(_ffmpegState).Filter;
@@ -6,7 +6,7 @@ public class HardwareUploadFilter : BaseFilter
public HardwareUploadFilter(FFmpegState ffmpegState) => _ffmpegState = ffmpegState; public HardwareUploadFilter(FFmpegState ffmpegState) => _ffmpegState = ffmpegState;
public override string Filter => _ffmpegState.HardwareAccelerationMode switch public override string Filter => _ffmpegState.EncoderHardwareAccelerationMode switch
{ {
HardwareAccelerationMode.None => string.Empty, HardwareAccelerationMode.None => string.Empty,
HardwareAccelerationMode.Nvenc => "hwupload_cuda", HardwareAccelerationMode.Nvenc => "hwupload_cuda",
@@ -15,7 +15,7 @@ public class HardwareUploadFilter : BaseFilter
_ => "hwupload" _ => "hwupload"
}; };
public override FrameState NextState(FrameState currentState) => _ffmpegState.HardwareAccelerationMode switch public override FrameState NextState(FrameState currentState) => _ffmpegState.EncoderHardwareAccelerationMode switch
{ {
HardwareAccelerationMode.None => currentState, HardwareAccelerationMode.None => currentState,
_ => currentState with { FrameDataLocation = FrameDataLocation.Hardware } _ => currentState with { FrameDataLocation = FrameDataLocation.Hardware }
@@ -12,7 +12,7 @@ public class SubtitleHardwareUploadFilter : BaseFilter
} }
public override string Filter => public override string Filter =>
_ffmpegState.HardwareAccelerationMode switch _ffmpegState.EncoderHardwareAccelerationMode switch
{ {
HardwareAccelerationMode.None => string.Empty, HardwareAccelerationMode.None => string.Empty,
HardwareAccelerationMode.Nvenc => "hwupload_cuda", HardwareAccelerationMode.Nvenc => "hwupload_cuda",
@@ -10,7 +10,7 @@ public class SubtitlePixelFormatFilter : BaseFilter
{ {
get get
{ {
Option<string> maybeFormat = _ffmpegState.HardwareAccelerationMode switch Option<string> maybeFormat = _ffmpegState.EncoderHardwareAccelerationMode switch
{ {
HardwareAccelerationMode.Nvenc => "yuva420p", HardwareAccelerationMode.Nvenc => "yuva420p",
HardwareAccelerationMode.Qsv => "yuva420p", HardwareAccelerationMode.Qsv => "yuva420p",
@@ -11,7 +11,7 @@ public class WatermarkHardwareUploadFilter : BaseFilter
_ffmpegState = ffmpegState; _ffmpegState = ffmpegState;
} }
public override string Filter => _ffmpegState.HardwareAccelerationMode switch public override string Filter => _ffmpegState.EncoderHardwareAccelerationMode switch
{ {
HardwareAccelerationMode.None => string.Empty, HardwareAccelerationMode.None => string.Empty,
HardwareAccelerationMode.Nvenc => "hwupload_cuda", HardwareAccelerationMode.Nvenc => "hwupload_cuda",
@@ -19,7 +19,7 @@ public class WatermarkPixelFormatFilter : BaseFilter
{ {
bool hasFadePoints = _watermarkState.MaybeFadePoints.Map(fp => fp.Count).IfNone(0) > 0; bool hasFadePoints = _watermarkState.MaybeFadePoints.Map(fp => fp.Count).IfNone(0) > 0;
Option<string> maybeFormat = _ffmpegState.HardwareAccelerationMode switch Option<string> maybeFormat = _ffmpegState.EncoderHardwareAccelerationMode switch
{ {
HardwareAccelerationMode.Nvenc => "yuva420p", HardwareAccelerationMode.Nvenc => "yuva420p",
HardwareAccelerationMode.Qsv => "yuva420p", HardwareAccelerationMode.Qsv => "yuva420p",
+52 -24
View File
@@ -1,4 +1,5 @@
using ErsatzTV.FFmpeg.Decoder; using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Encoder; using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Environment; using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Filter; using ErsatzTV.FFmpeg.Filter;
@@ -18,6 +19,7 @@ public class PipelineBuilder
{ {
private readonly Option<AudioInputFile> _audioInputFile; private readonly Option<AudioInputFile> _audioInputFile;
private readonly string _fontsFolder; private readonly string _fontsFolder;
private readonly IHardwareCapabilities _hardwareCapabilities;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly List<IPipelineStep> _pipelineSteps; private readonly List<IPipelineStep> _pipelineSteps;
private readonly string _reportsFolder; private readonly string _reportsFolder;
@@ -26,6 +28,7 @@ public class PipelineBuilder
private readonly Option<WatermarkInputFile> _watermarkInputFile; private readonly Option<WatermarkInputFile> _watermarkInputFile;
public PipelineBuilder( public PipelineBuilder(
IHardwareCapabilities hardwareCapabilities,
Option<VideoInputFile> videoInputFile, Option<VideoInputFile> videoInputFile,
Option<AudioInputFile> audioInputFile, Option<AudioInputFile> audioInputFile,
Option<WatermarkInputFile> watermarkInputFile, Option<WatermarkInputFile> watermarkInputFile,
@@ -46,6 +49,7 @@ public class PipelineBuilder
new ClosedGopOutputOption() new ClosedGopOutputOption()
}; };
_hardwareCapabilities = hardwareCapabilities;
_videoInputFile = videoInputFile; _videoInputFile = videoInputFile;
_audioInputFile = audioInputFile; _audioInputFile = audioInputFile;
_watermarkInputFile = watermarkInputFile; _watermarkInputFile = watermarkInputFile;
@@ -200,7 +204,7 @@ public class PipelineBuilder
else else
{ {
Option<IPipelineStep> maybeAccel = AvailableHardwareAccelerationOptions.ForMode( Option<IPipelineStep> maybeAccel = AvailableHardwareAccelerationOptions.ForMode(
ffmpegState.HardwareAccelerationMode, ffmpegState.EncoderHardwareAccelerationMode,
ffmpegState.VaapiDevice, ffmpegState.VaapiDevice,
_logger); _logger);
@@ -209,24 +213,45 @@ public class PipelineBuilder
ffmpegState = ffmpegState with ffmpegState = ffmpegState with
{ {
// disable hw accel if we don't match anything // disable hw accel if we don't match anything
HardwareAccelerationMode = HardwareAccelerationMode.None DecoderHardwareAccelerationMode = HardwareAccelerationMode.None,
EncoderHardwareAccelerationMode = HardwareAccelerationMode.None
}; };
} }
foreach (IPipelineStep accel in maybeAccel) foreach (IPipelineStep accel in maybeAccel)
{ {
currentState = accel.NextState(currentState); bool canDecode = _hardwareCapabilities.CanDecode(currentState.VideoFormat);
_pipelineSteps.Add(accel); bool canEncode = _hardwareCapabilities.CanEncode(desiredState.VideoFormat);
// disable hw accel if decoder/encoder isn't supported
if (!canDecode || !canEncode)
{
ffmpegState = ffmpegState with
{
DecoderHardwareAccelerationMode = canDecode
? ffmpegState.DecoderHardwareAccelerationMode
: HardwareAccelerationMode.None,
EncoderHardwareAccelerationMode = canEncode
? ffmpegState.EncoderHardwareAccelerationMode
: HardwareAccelerationMode.None
};
}
if (canDecode || canEncode)
{
currentState = accel.NextState(currentState);
_pipelineSteps.Add(accel);
}
} }
// nvenc requires yuv420p background with yuva420p overlay // nvenc requires yuv420p background with yuva420p overlay
if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Nvenc && hasOverlay) if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Nvenc && hasOverlay)
{ {
desiredState = desiredState with { PixelFormat = new PixelFormatYuv420P() }; desiredState = desiredState with { PixelFormat = new PixelFormatYuv420P() };
} }
// qsv should stay nv12 // qsv should stay nv12
if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Qsv && hasOverlay) if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv && hasOverlay)
{ {
IPixelFormat pixelFormat = desiredState.PixelFormat.IfNone(new PixelFormatYuv420P()); IPixelFormat pixelFormat = desiredState.PixelFormat.IfNone(new PixelFormatYuv420P());
desiredState = desiredState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) }; desiredState = desiredState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) };
@@ -240,6 +265,7 @@ public class PipelineBuilder
} }
foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat( foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat(
_hardwareCapabilities,
ffmpegState, ffmpegState,
currentState, currentState,
desiredState, desiredState,
@@ -262,7 +288,7 @@ public class PipelineBuilder
if (videoStream.StillImage) if (videoStream.StillImage)
{ {
var option = new InfiniteLoopInputOption(ffmpegState.HardwareAccelerationMode); var option = new InfiniteLoopInputOption(ffmpegState.EncoderHardwareAccelerationMode);
_videoInputFile.Iter(f => f.AddOption(option)); _videoInputFile.Iter(f => f.AddOption(option));
} }
@@ -277,7 +303,7 @@ public class PipelineBuilder
if (desiredState.InfiniteLoop) if (desiredState.InfiniteLoop)
{ {
var option = new InfiniteLoopInputOption(ffmpegState.HardwareAccelerationMode); var option = new InfiniteLoopInputOption(ffmpegState.EncoderHardwareAccelerationMode);
_audioInputFile.Iter(f => f.AddOption(option)); _audioInputFile.Iter(f => f.AddOption(option));
_videoInputFile.Iter(f => f.AddOption(option)); _videoInputFile.Iter(f => f.AddOption(option));
} }
@@ -325,7 +351,7 @@ public class PipelineBuilder
if (desiredState.Deinterlaced && !currentState.Deinterlaced) if (desiredState.Deinterlaced && !currentState.Deinterlaced)
{ {
IPipelineFilterStep step = AvailableDeinterlaceFilters.ForAcceleration( IPipelineFilterStep step = AvailableDeinterlaceFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode, ffmpegState.EncoderHardwareAccelerationMode,
currentState, currentState,
desiredState, desiredState,
_watermarkInputFile, _watermarkInputFile,
@@ -335,7 +361,7 @@ public class PipelineBuilder
} }
// TODO: this is a software-only flow, will need to be different for hardware accel // TODO: this is a software-only flow, will need to be different for hardware accel
if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.None) if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.None)
{ {
if (currentState.ScaledSize != desiredState.ScaledSize || if (currentState.ScaledSize != desiredState.ScaledSize ||
currentState.PaddedSize != desiredState.PaddedSize) currentState.PaddedSize != desiredState.PaddedSize)
@@ -360,7 +386,7 @@ public class PipelineBuilder
else if (currentState.ScaledSize != desiredState.ScaledSize) else if (currentState.ScaledSize != desiredState.ScaledSize)
{ {
IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode, ffmpegState.EncoderHardwareAccelerationMode,
currentState, currentState,
desiredState.ScaledSize, desiredState.ScaledSize,
desiredState.PaddedSize); desiredState.PaddedSize);
@@ -382,7 +408,7 @@ public class PipelineBuilder
else if (currentState.PaddedSize != desiredState.PaddedSize) else if (currentState.PaddedSize != desiredState.PaddedSize)
{ {
IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode, ffmpegState.EncoderHardwareAccelerationMode,
currentState, currentState,
desiredState.ScaledSize, desiredState.ScaledSize,
desiredState.PaddedSize); desiredState.PaddedSize);
@@ -415,7 +441,7 @@ public class PipelineBuilder
currentState = formatFilter.NextState(currentState); currentState = formatFilter.NextState(currentState);
_videoInputFile.Iter(f => f.FilterSteps.Add(formatFilter)); _videoInputFile.Iter(f => f.FilterSteps.Add(formatFilter));
switch (ffmpegState.HardwareAccelerationMode) switch (ffmpegState.EncoderHardwareAccelerationMode)
{ {
case HardwareAccelerationMode.Nvenc: case HardwareAccelerationMode.Nvenc:
var uploadFilter = new HardwareUploadFilter(ffmpegState); var uploadFilter = new HardwareUploadFilter(ffmpegState);
@@ -426,13 +452,13 @@ public class PipelineBuilder
} }
else else
{ {
if (ffmpegState.HardwareAccelerationMode != HardwareAccelerationMode.Qsv) if (ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Qsv)
{ {
// the filter re-applies the current pixel format, so we have to set it first // the filter re-applies the current pixel format, so we have to set it first
currentState = currentState with { PixelFormat = desiredState.PixelFormat }; currentState = currentState with { PixelFormat = desiredState.PixelFormat };
IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode, ffmpegState.EncoderHardwareAccelerationMode,
currentState, currentState,
desiredState.ScaledSize, desiredState.ScaledSize,
desiredState.PaddedSize); desiredState.PaddedSize);
@@ -444,7 +470,7 @@ public class PipelineBuilder
} }
// nvenc custom logic // nvenc custom logic
if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Nvenc) if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Nvenc)
{ {
foreach (VideoInputFile videoInputFile in _videoInputFile) foreach (VideoInputFile videoInputFile in _videoInputFile)
{ {
@@ -461,7 +487,7 @@ public class PipelineBuilder
currentState = currentState with { PixelFormat = desiredState.PixelFormat }; currentState = currentState with { PixelFormat = desiredState.PixelFormat };
IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode, ffmpegState.EncoderHardwareAccelerationMode,
currentState, currentState,
desiredState.ScaledSize, desiredState.ScaledSize,
desiredState.PaddedSize); desiredState.PaddedSize);
@@ -488,7 +514,7 @@ public class PipelineBuilder
if (currentState.PixelFormat.Map(pf => pf.FFmpegName) != desiredPixelFormat.FFmpegName) if (currentState.PixelFormat.Map(pf => pf.FFmpegName) != desiredPixelFormat.FFmpegName)
{ {
// qsv doesn't seem to like this // qsv doesn't seem to like this
if (ffmpegState.HardwareAccelerationMode != HardwareAccelerationMode.Qsv) if (ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Qsv)
{ {
IPipelineStep step = new PixelFormatOutputOption(desiredPixelFormat); IPipelineStep step = new PixelFormatOutputOption(desiredPixelFormat);
currentState = step.NextState(currentState); currentState = step.NextState(currentState);
@@ -547,7 +573,7 @@ public class PipelineBuilder
{ {
// vaapi and videotoolbox use a software overlay, so we need to ensure the background is already in software // vaapi and videotoolbox use a software overlay, so we need to ensure the background is already in software
// though videotoolbox uses software decoders, so no need to download for that // though videotoolbox uses software decoders, so no need to download for that
if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Vaapi) if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi)
{ {
var downloadFilter = new HardwareDownloadFilter(currentState); var downloadFilter = new HardwareDownloadFilter(currentState);
currentState = downloadFilter.NextState(currentState); currentState = downloadFilter.NextState(currentState);
@@ -565,12 +591,12 @@ public class PipelineBuilder
// text-based subtitles are always added in software, so always try to download the background // text-based subtitles are always added in software, so always try to download the background
// nvidia needs some extra format help if the only filter will be the download filter // nvidia needs some extra format help if the only filter will be the download filter
if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Nvenc && if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Nvenc &&
currentState.FrameDataLocation == FrameDataLocation.Hardware && currentState.FrameDataLocation == FrameDataLocation.Hardware &&
_videoInputFile.Map(f => f.FilterSteps.Count).IfNone(1) == 0) _videoInputFile.Map(f => f.FilterSteps.Count).IfNone(1) == 0)
{ {
IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration( IPipelineFilterStep scaleFilter = AvailableScaleFilters.ForAcceleration(
ffmpegState.HardwareAccelerationMode, ffmpegState.EncoderHardwareAccelerationMode,
currentState, currentState,
desiredState.ScaledSize, desiredState.ScaledSize,
desiredState.PaddedSize); desiredState.PaddedSize);
@@ -588,7 +614,7 @@ public class PipelineBuilder
{ {
// vaapi and videotoolbox use a software overlay, so we need to ensure the background is already in software // vaapi and videotoolbox use a software overlay, so we need to ensure the background is already in software
// though videotoolbox uses software decoders, so no need to download for that // though videotoolbox uses software decoders, so no need to download for that
if (ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Vaapi) if (ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi)
{ {
var downloadFilter = new HardwareDownloadFilter(currentState); var downloadFilter = new HardwareDownloadFilter(currentState);
currentState = downloadFilter.NextState(currentState); currentState = downloadFilter.NextState(currentState);
@@ -607,7 +633,8 @@ public class PipelineBuilder
else if (watermarkInputFile.DesiredState.MaybeFadePoints.Map(fp => fp.Count > 0).IfNone(false)) else if (watermarkInputFile.DesiredState.MaybeFadePoints.Map(fp => fp.Count > 0).IfNone(false))
{ {
// looping is required to fade a static image in and out // looping is required to fade a static image in and out
watermarkInputFile.AddOption(new InfiniteLoopInputOption(ffmpegState.HardwareAccelerationMode)); watermarkInputFile.AddOption(
new InfiniteLoopInputOption(ffmpegState.EncoderHardwareAccelerationMode));
} }
} }
@@ -634,6 +661,7 @@ public class PipelineBuilder
if (_pipelineSteps.OfType<IEncoder>().All(e => e.Kind != StreamKind.Video)) if (_pipelineSteps.OfType<IEncoder>().All(e => e.Kind != StreamKind.Video))
{ {
foreach (IEncoder e in AvailableEncoders.ForVideoFormat( foreach (IEncoder e in AvailableEncoders.ForVideoFormat(
_hardwareCapabilities,
ffmpegState, ffmpegState,
currentState, currentState,
desiredState, desiredState,
@@ -14,12 +14,12 @@
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.6">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.2.32"> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.2.32">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+5 -5
View File
@@ -55,16 +55,16 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Bugsnag.AspNet.Core" Version="3.0.1" /> <PackageReference Include="Bugsnag.AspNet.Core" Version="3.0.1" />
<PackageReference Include="FluentValidation" Version="11.0.2" /> <PackageReference Include="FluentValidation" Version="11.0.3" />
<PackageReference Include="FluentValidation.AspNetCore" Version="11.0.2" /> <PackageReference Include="FluentValidation.AspNetCore" Version="11.0.3" />
<PackageReference Include="HtmlSanitizer" Version="7.1.512" /> <PackageReference Include="HtmlSanitizer" Version="7.1.512" />
<PackageReference Include="LanguageExt.Core" Version="4.2.2" /> <PackageReference Include="LanguageExt.Core" Version="4.2.2" />
<PackageReference Include="Markdig" Version="0.30.2" /> <PackageReference Include="Markdig" Version="0.30.2" />
<PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" /> <PackageReference Include="MediatR.Courier.DependencyInjection" Version="5.0.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" /> <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="10.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.6" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.5" /> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.5"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.6">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
+2
View File
@@ -32,6 +32,7 @@ using ErsatzTV.Core.Metadata.Nfo;
using ErsatzTV.Core.Plex; using ErsatzTV.Core.Plex;
using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Scheduling;
using ErsatzTV.Core.Trakt; using ErsatzTV.Core.Trakt;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.Formatters; using ErsatzTV.Formatters;
using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Data.Repositories; using ErsatzTV.Infrastructure.Data.Repositories;
@@ -396,6 +397,7 @@ public class Startup
services.AddScoped<IRuntimeInfo, RuntimeInfo>(); services.AddScoped<IRuntimeInfo, RuntimeInfo>();
services.AddScoped<IPlexPathReplacementService, PlexPathReplacementService>(); services.AddScoped<IPlexPathReplacementService, PlexPathReplacementService>();
services.AddScoped<IFFmpegStreamSelector, FFmpegStreamSelector>(); services.AddScoped<IFFmpegStreamSelector, FFmpegStreamSelector>();
services.AddScoped<IHardwareCapabilitiesFactory, HardwareCapabilitiesFactory>();
services.AddScoped<IFFmpegProcessService, FFmpegLibraryProcessService>(); services.AddScoped<IFFmpegProcessService, FFmpegLibraryProcessService>();
services.AddScoped<FFmpegProcessService>(); services.AddScoped<FFmpegProcessService>();