Browse Source

check ffmpeg for available decoders, filters, encoders (#1183)

* check ffmpeg for available decoders, filters, encoders

* revert csproj change
pull/1184/head
Jason Dove 3 years ago committed by GitHub
parent
commit
4d57ece30d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/Streaming/HlsSessionWorker.cs
  3. 19
      ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs
  4. 13
      ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs
  5. 12
      ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs
  6. 48
      ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs
  7. 8
      ErsatzTV.FFmpeg/Capabilities/FFmpegCapability.cs
  8. 64
      ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs
  9. 11
      ErsatzTV.FFmpeg/Capabilities/IFFmpegCapabilities.cs
  10. 4
      ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs
  11. 3
      ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs
  12. 8
      ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs
  13. 52
      ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs
  14. 18
      ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs
  15. 10
      ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderAv1Cuvid.cs
  16. 2
      ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg4Cuvid.cs
  17. 30
      ErsatzTV.FFmpeg/Decoder/DecoderAv1.cs
  18. 27
      ErsatzTV.FFmpeg/Decoder/Qsv/DecoderAv1Qsv.cs
  19. 12
      ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs
  20. 10
      ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs
  21. 16
      ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs
  22. 40
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  23. 9
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs
  24. 19
      ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs
  25. 3
      ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs
  26. 14
      ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs
  27. 10
      ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs

3
CHANGELOG.md

@ -5,7 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added ### Added
- Use VP9 and AV1 hardware-accelerated decoders with VAAPI when available - Use AV1 hardware-accelerated decoder with VAAPI, QSV, NVIDIA when available
- Use VP9 hardware-accelerated decoder with VAAPI when available
### Fixed ### Fixed
- Align default docker image (no acceleration) with new images from [ErsatzTV-ffmpeg](https://github.com/jasongdove/ErsatzTV-ffmpeg) - Align default docker image (no acceleration) with new images from [ErsatzTV-ffmpeg](https://github.com/jasongdove/ErsatzTV-ffmpeg)

2
ErsatzTV.Application/Streaming/HlsSessionWorker.cs

@ -54,6 +54,8 @@ public class HlsSessionWorker : IHlsSessionWorker
{ {
lock (_sync) lock (_sync)
{ {
_logger.LogDebug("Keep alive - session worker for channel {ChannelNumber}", _channelNumber);
_lastAccess = DateTimeOffset.Now; _lastAccess = DateTimeOffset.Now;
_timer?.Stop(); _timer?.Stop();

19
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

@ -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;
@ -86,6 +87,7 @@ public class PipelineBuilderBaseTests
Option<int>.None); Option<int>.None);
var builder = new SoftwarePipelineBuilder( var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
@ -171,6 +173,7 @@ public class PipelineBuilderBaseTests
Option<int>.None); Option<int>.None);
var builder = new SoftwarePipelineBuilder( var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
@ -196,6 +199,7 @@ public class PipelineBuilderBaseTests
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 SoftwarePipelineBuilder( var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
None, None,
None, None,
@ -221,6 +225,7 @@ public class PipelineBuilderBaseTests
var concatInputFile = new ConcatInputFile("http://localhost:8080/iptv/channel/1.m3u8?mode=segmenter", resolution); var concatInputFile = new ConcatInputFile("http://localhost:8080/iptv/channel/1.m3u8?mode=segmenter", resolution);
var builder = new SoftwarePipelineBuilder( var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
None, None,
None, None,
@ -306,6 +311,7 @@ public class PipelineBuilderBaseTests
Option<int>.None); Option<int>.None);
var builder = new SoftwarePipelineBuilder( var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
@ -383,6 +389,7 @@ public class PipelineBuilderBaseTests
Option<int>.None); Option<int>.None);
var builder = new SoftwarePipelineBuilder( var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
@ -426,6 +433,7 @@ public class PipelineBuilderBaseTests
}); });
var pipelineBuilder = new SoftwarePipelineBuilder( var pipelineBuilder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
videoInputFile, videoInputFile,
Option<AudioInputFile>.None, Option<AudioInputFile>.None,
@ -463,4 +471,15 @@ public class PipelineBuilderBaseTests
return command; return command;
} }
public class DefaultFFmpegCapabilities : FFmpegCapabilities
{
public DefaultFFmpegCapabilities()
: base(
new System.Collections.Generic.HashSet<string>(),
new System.Collections.Generic.HashSet<string>(),
new System.Collections.Generic.HashSet<string>())
{
}
}
} }

13
ErsatzTV.FFmpeg/Capabilities/AmfHardwareCapabilities.cs

@ -4,21 +4,24 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public class AmfHardwareCapabilities : IHardwareCapabilities public class AmfHardwareCapabilities : IHardwareCapabilities
{ {
public bool CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) => false; public FFmpegCapability CanDecode(
string videoFormat,
Option<string> videoProfile,
Option<IPixelFormat> maybePixelFormat) => FFmpegCapability.Software;
public bool CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) public FFmpegCapability CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{ {
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
return (videoFormat, bitDepth) switch return (videoFormat, bitDepth) switch
{ {
// 10-bit hevc encoding is not yet supported by ffmpeg // 10-bit hevc encoding is not yet supported by ffmpeg
(VideoFormat.Hevc, 10) => false, (VideoFormat.Hevc, 10) => FFmpegCapability.Software,
// 10-bit h264 encoding is not support by any hardware // 10-bit h264 encoding is not support by any hardware
(VideoFormat.H264, 10) => false, (VideoFormat.H264, 10) => FFmpegCapability.Software,
_ => true _ => FFmpegCapability.Hardware
}; };
} }
} }

12
ErsatzTV.FFmpeg/Capabilities/DefaultHardwareCapabilities.cs

@ -4,29 +4,29 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public class DefaultHardwareCapabilities : IHardwareCapabilities public class DefaultHardwareCapabilities : IHardwareCapabilities
{ {
public bool CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) public FFmpegCapability CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{ {
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
return (videoFormat, bitDepth) switch return (videoFormat, bitDepth) switch
{ {
// 10-bit h264 decoding is likely not support by any hardware // 10-bit h264 decoding is likely not support by any hardware
(VideoFormat.H264, 10) => false, (VideoFormat.H264, 10) => FFmpegCapability.Software,
_ => true _ => FFmpegCapability.Hardware
}; };
} }
public bool CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) public FFmpegCapability CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{ {
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
return (videoFormat, bitDepth) switch return (videoFormat, bitDepth) switch
{ {
// 10-bit h264 encoding is not support by any hardware // 10-bit h264 encoding is not support by any hardware
(VideoFormat.H264, 10) => false, (VideoFormat.H264, 10) => FFmpegCapability.Software,
_ => true _ => FFmpegCapability.Hardware
}; };
} }
} }

48
ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs

@ -0,0 +1,48 @@
using ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Capabilities;
public class FFmpegCapabilities : IFFmpegCapabilities
{
private readonly IReadOnlySet<string> _ffmpegDecoders;
private readonly IReadOnlySet<string> _ffmpegFilters;
private readonly IReadOnlySet<string> _ffmpegEncoders;
public FFmpegCapabilities(
IReadOnlySet<string> ffmpegDecoders,
IReadOnlySet<string> ffmpegFilters,
IReadOnlySet<string> ffmpegEncoders)
{
_ffmpegDecoders = ffmpegDecoders;
_ffmpegFilters = ffmpegFilters;
_ffmpegEncoders = ffmpegEncoders;
}
public bool HasDecoder(string decoder) => _ffmpegDecoders.Contains(decoder);
public bool HasEncoder(string encoder) => _ffmpegEncoders.Contains(encoder);
public bool HasFilter(string filter) => _ffmpegFilters.Contains(filter);
public Option<IDecoder> SoftwareDecoderForVideoFormat(string videoFormat) =>
videoFormat switch
{
VideoFormat.Hevc => new DecoderHevc(),
VideoFormat.H264 => new DecoderH264(),
VideoFormat.Mpeg1Video => new DecoderMpeg1Video(),
VideoFormat.Mpeg2Video => new DecoderMpeg2Video(),
VideoFormat.Vc1 => new DecoderVc1(),
VideoFormat.MsMpeg4V2 => new DecoderMsMpeg4V2(),
VideoFormat.MsMpeg4V3 => new DecoderMsMpeg4V3(),
VideoFormat.Mpeg4 => new DecoderMpeg4(),
VideoFormat.Vp9 => new DecoderVp9(),
VideoFormat.Av1 => new DecoderAv1(_ffmpegDecoders),
VideoFormat.Undetermined => new DecoderImplicit(),
VideoFormat.Copy => new DecoderImplicit(),
VideoFormat.GeneratedImage => new DecoderImplicit(),
_ => Option<IDecoder>.None
};
}

8
ErsatzTV.FFmpeg/Capabilities/FFmpegCapability.cs

@ -0,0 +1,8 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public enum FFmpegCapability
{
None,
Software,
Hardware
}

64
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -1,3 +1,4 @@
using System.Collections.Immutable;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using CliWrap; using CliWrap;
@ -13,6 +14,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
private const string ArchitectureCacheKey = "ffmpeg.hardware.nvidia.architecture"; private const string ArchitectureCacheKey = "ffmpeg.hardware.nvidia.architecture";
private const string ModelCacheKey = "ffmpeg.hardware.nvidia.model"; private const string ModelCacheKey = "ffmpeg.hardware.nvidia.model";
private const string VaapiCacheKeyFormat = "ffmpeg.hardware.vaapi.{0}.{1}"; private const string VaapiCacheKeyFormat = "ffmpeg.hardware.vaapi.{0}.{1}";
private const string FFmpegCapabilitiesCacheKeyFormat = "ffmpeg.{0}";
private readonly ILogger<HardwareCapabilitiesFactory> _logger; private readonly ILogger<HardwareCapabilitiesFactory> _logger;
private readonly IMemoryCache _memoryCache; private readonly IMemoryCache _memoryCache;
@ -23,18 +25,62 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
_logger = logger; _logger = logger;
} }
public async Task<IFFmpegCapabilities> GetFFmpegCapabilities(string ffmpegPath)
{
IReadOnlySet<string> ffmpegDecoders = await GetFFmpegCapabilities(ffmpegPath, "decoders");
IReadOnlySet<string> ffmpegFilters = await GetFFmpegCapabilities(ffmpegPath, "filters");
IReadOnlySet<string> ffmpegEncoders = await GetFFmpegCapabilities(ffmpegPath, "encoders");
return new FFmpegCapabilities(ffmpegDecoders, ffmpegFilters, ffmpegEncoders);
}
public async Task<IHardwareCapabilities> GetHardwareCapabilities( public async Task<IHardwareCapabilities> GetHardwareCapabilities(
IFFmpegCapabilities ffmpegCapabilities,
string ffmpegPath, string ffmpegPath,
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
Option<string> vaapiDriver, Option<string> vaapiDriver,
Option<string> vaapiDevice) => Option<string> vaapiDevice)
hardwareAccelerationMode switch {
return hardwareAccelerationMode switch
{ {
HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath), HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath, ffmpegCapabilities),
HardwareAccelerationMode.Vaapi => await GetVaapiCapabilities(vaapiDriver, vaapiDevice), HardwareAccelerationMode.Vaapi => await GetVaapiCapabilities(vaapiDriver, vaapiDevice),
HardwareAccelerationMode.Amf => new AmfHardwareCapabilities(), HardwareAccelerationMode.Amf => new AmfHardwareCapabilities(),
_ => new DefaultHardwareCapabilities() _ => new DefaultHardwareCapabilities()
}; };
}
private async Task<IReadOnlySet<string>> GetFFmpegCapabilities(string ffmpegPath, string capabilities)
{
var cacheKey = string.Format(FFmpegCapabilitiesCacheKeyFormat, capabilities);
if (_memoryCache.TryGetValue(cacheKey, out IReadOnlySet<string>? cachedDecoders) &&
cachedDecoders is not null)
{
return cachedDecoders;
}
string[] arguments = { "-hide_banner", $"-{capabilities}" };
BufferedCommandResult result = await Cli.Wrap(ffmpegPath)
.WithArguments(arguments)
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync(Encoding.UTF8);
string output = string.IsNullOrWhiteSpace(result.StandardOutput)
? result.StandardError
: result.StandardOutput;
return output.Split("\n").Map(s => s.Trim())
.Bind(l => ParseFFmpegLine(l))
.ToImmutableHashSet();
}
private static Option<string> ParseFFmpegLine(string input)
{
const string PATTERN = @"^\s*?[A-Z\.]+\s+(\w+).*";
Match match = Regex.Match(input, PATTERN);
return match.Success ? match.Groups[1].Value : Option<string>.None;
}
private async Task<IHardwareCapabilities> GetVaapiCapabilities( private async Task<IHardwareCapabilities> GetVaapiCapabilities(
Option<string> vaapiDriver, Option<string> vaapiDriver,
@ -126,13 +172,19 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
return new NoHardwareCapabilities(); return new NoHardwareCapabilities();
} }
private async Task<IHardwareCapabilities> GetNvidiaCapabilities(string ffmpegPath) private async Task<IHardwareCapabilities> GetNvidiaCapabilities(
string ffmpegPath,
IFFmpegCapabilities ffmpegCapabilities)
{ {
if (_memoryCache.TryGetValue(ArchitectureCacheKey, out int cachedArchitecture) if (_memoryCache.TryGetValue(ArchitectureCacheKey, out int cachedArchitecture)
&& _memoryCache.TryGetValue(ModelCacheKey, out string? cachedModel) && _memoryCache.TryGetValue(ModelCacheKey, out string? cachedModel)
&& cachedModel is not null) && cachedModel is not null)
{ {
return new NvidiaHardwareCapabilities(cachedArchitecture, cachedModel); return new NvidiaHardwareCapabilities(
cachedArchitecture,
cachedModel,
ffmpegCapabilities,
_logger);
} }
string[] arguments = string[] arguments =
@ -169,7 +221,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
architecture); architecture);
_memoryCache.Set(ArchitectureCacheKey, architecture); _memoryCache.Set(ArchitectureCacheKey, architecture);
_memoryCache.Set(ModelCacheKey, model); _memoryCache.Set(ModelCacheKey, model);
return new NvidiaHardwareCapabilities(architecture, model); return new NvidiaHardwareCapabilities(architecture, model, ffmpegCapabilities, _logger);
} }
} }

11
ErsatzTV.FFmpeg/Capabilities/IFFmpegCapabilities.cs

@ -0,0 +1,11 @@
using ErsatzTV.FFmpeg.Decoder;
namespace ErsatzTV.FFmpeg.Capabilities;
public interface IFFmpegCapabilities
{
bool HasDecoder(string decoder);
bool HasEncoder(string encoder);
bool HasFilter(string filter);
Option<IDecoder> SoftwareDecoderForVideoFormat(string videoFormat);
}

4
ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs

@ -4,6 +4,6 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public interface IHardwareCapabilities public interface IHardwareCapabilities
{ {
public bool CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat); public FFmpegCapability CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat);
public bool CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat); public FFmpegCapability CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat);
} }

3
ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs

@ -2,7 +2,10 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public interface IHardwareCapabilitiesFactory public interface IHardwareCapabilitiesFactory
{ {
Task<IFFmpegCapabilities> GetFFmpegCapabilities(string ffmpegPath);
Task<IHardwareCapabilities> GetHardwareCapabilities( Task<IHardwareCapabilities> GetHardwareCapabilities(
IFFmpegCapabilities ffmpegCapabilities,
string ffmpegPath, string ffmpegPath,
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
Option<string> vaapiDriver, Option<string> vaapiDriver,

8
ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs

@ -4,9 +4,9 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public class NoHardwareCapabilities : IHardwareCapabilities public class NoHardwareCapabilities : IHardwareCapabilities
{ {
public bool CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) => public FFmpegCapability CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) =>
false; FFmpegCapability.Software;
public bool CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) => public FFmpegCapability CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) =>
false; FFmpegCapability.Software;
} }

52
ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs

@ -1,4 +1,5 @@
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.FFmpeg.Capabilities; namespace ErsatzTV.FFmpeg.Capabilities;
@ -7,18 +8,26 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
private readonly int _architecture; private readonly int _architecture;
private readonly List<string> _maxwellGm206 = new() { "GTX 750", "GTX 950", "GTX 960", "GTX 965M" }; private readonly List<string> _maxwellGm206 = new() { "GTX 750", "GTX 950", "GTX 960", "GTX 965M" };
private readonly string _model; private readonly string _model;
private readonly IFFmpegCapabilities _ffmpegCapabilities;
private readonly ILogger _logger;
public NvidiaHardwareCapabilities(int architecture, string model) public NvidiaHardwareCapabilities(
int architecture,
string model,
IFFmpegCapabilities ffmpegCapabilities,
ILogger logger)
{ {
_architecture = architecture; _architecture = architecture;
_model = model; _model = model;
_ffmpegCapabilities = ffmpegCapabilities;
_logger = logger;
} }
public bool CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) public FFmpegCapability CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{ {
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
return videoFormat switch bool isHardware = videoFormat switch
{ {
// some second gen maxwell can decode hevc, otherwise pascal is required // some second gen maxwell can decode hevc, otherwise pascal is required
VideoFormat.Hevc => _architecture == 52 && _maxwellGm206.Contains(_model) || _architecture >= 60, VideoFormat.Hevc => _architecture == 52 && _maxwellGm206.Contains(_model) || _architecture >= 60,
@ -38,18 +47,38 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
VideoFormat.Mpeg4 => true, VideoFormat.Mpeg4 => true,
// ampere is required for av1 decoding
VideoFormat.Av1 => _architecture >= 86,
// generated images are decoded into software // generated images are decoded into software
VideoFormat.GeneratedImage => false, VideoFormat.GeneratedImage => false,
_ => false _ => false
}; };
if (isHardware)
{
return videoFormat switch
{
VideoFormat.Mpeg2Video => CheckHardwareCodec("mpeg2_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Mpeg4 => CheckHardwareCodec("mpeg4_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Vc1 => CheckHardwareCodec("vc1_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.H264 => CheckHardwareCodec("h264_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Hevc => CheckHardwareCodec("hevc_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Vp9 => CheckHardwareCodec("hevc_cuvid", _ffmpegCapabilities.HasDecoder),
VideoFormat.Av1 => CheckHardwareCodec("av1_cuvid", _ffmpegCapabilities.HasDecoder),
_ => FFmpegCapability.Software
};
}
return FFmpegCapability.Software;
} }
public bool CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) public FFmpegCapability CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{ {
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
return videoFormat switch bool isHardware = videoFormat switch
{ {
// pascal is required to encode 10-bit hevc // pascal is required to encode 10-bit hevc
VideoFormat.Hevc when bitDepth == 10 => _architecture >= 60, VideoFormat.Hevc when bitDepth == 10 => _architecture >= 60,
@ -62,5 +91,18 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
_ => true _ => true
}; };
return isHardware ? FFmpegCapability.Hardware : FFmpegCapability.Software;
}
private FFmpegCapability CheckHardwareCodec(string codec, Func<string, bool> check)
{
if (check(codec))
{
return FFmpegCapability.Hardware;
}
_logger.LogWarning("FFmpeg does not contain codec {Codec}; will fall back to software codec", codec);
return FFmpegCapability.Software;
} }
} }

18
ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs

@ -15,11 +15,11 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
_logger = logger; _logger = logger;
} }
public bool CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) public FFmpegCapability CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{ {
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
bool result = (videoFormat, videoProfile.IfNone(string.Empty).ToLowerInvariant()) switch bool isHardware = (videoFormat, videoProfile.IfNone(string.Empty).ToLowerInvariant()) switch
{ {
// no hardware decoding of 10-bit h264 // no hardware decoding of 10-bit h264
(VideoFormat.H264, _) when bitDepth == 10 => false, (VideoFormat.H264, _) when bitDepth == 10 => false,
@ -95,22 +95,22 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
_ => false _ => false
}; };
if (!result) if (!isHardware)
{ {
_logger.LogDebug( _logger.LogDebug(
"VAAPI does not support decoding {Format}/{Profile}, will use software decoder", "VAAPI does not support decoding {Format}/{Profile}, will use software decoder",
videoFormat, videoFormat,
videoProfile); videoProfile);
} }
return result; return isHardware ? FFmpegCapability.Hardware : FFmpegCapability.Software;
} }
public bool CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) public FFmpegCapability CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{ {
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
bool result = videoFormat switch bool isHardware = videoFormat switch
{ {
// vaapi cannot encode 10-bit h264 // vaapi cannot encode 10-bit h264
VideoFormat.H264 when bitDepth == 10 => false, VideoFormat.H264 when bitDepth == 10 => false,
@ -142,7 +142,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
_ => false _ => false
}; };
if (!result) if (!isHardware)
{ {
_logger.LogDebug( _logger.LogDebug(
"VAAPI does not support encoding {Format} with bit depth {BitDepth}, will use software encoder", "VAAPI does not support encoding {Format} with bit depth {BitDepth}, will use software encoder",
@ -150,6 +150,6 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
bitDepth); bitDepth);
} }
return result; return isHardware ? FFmpegCapability.Hardware : FFmpegCapability.Software;
} }
} }

10
ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderAv1Cuvid.cs

@ -0,0 +1,10 @@
namespace ErsatzTV.FFmpeg.Decoder.Cuvid;
public class DecoderAv1Cuvid : CuvidDecoder
{
public DecoderAv1Cuvid(HardwareAccelerationMode hardwareAccelerationMode) : base(hardwareAccelerationMode)
{
}
public override string Name => "av1_cuvid";
}

2
ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg4Cuvid.cs

@ -7,6 +7,4 @@ public class DecoderMpeg4Cuvid : CuvidDecoder
} }
public override string Name => "mpeg4_cuvid"; public override string Name => "mpeg4_cuvid";
} }

30
ErsatzTV.FFmpeg/Decoder/DecoderAv1.cs

@ -0,0 +1,30 @@
namespace ErsatzTV.FFmpeg.Decoder;
public class DecoderAv1 : DecoderBase
{
// ReSharper disable IdentifierTypo
private const string Libdav1d = "libdav1d";
private const string Libaomav1 = "libaom-av1";
private readonly IReadOnlySet<string> _ffmpegDecoders;
public DecoderAv1(IReadOnlySet<string> ffmpegDecoders)
{
_ffmpegDecoders = ffmpegDecoders;
}
public override string Name
{
get
{
if (_ffmpegDecoders.Contains(Libdav1d))
{
return Libdav1d;
}
return _ffmpegDecoders.Contains(Libaomav1) ? Libaomav1 : "av1";
}
}
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software;
}

27
ErsatzTV.FFmpeg/Decoder/Qsv/DecoderAv1Qsv.cs

@ -0,0 +1,27 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Decoder.Qsv;
public class DecoderAv1Qsv : DecoderBase
{
public override string Name => "av1_qsv";
protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware;
public override FrameState NextState(FrameState currentState)
{
FrameState nextState = base.NextState(currentState);
return currentState.PixelFormat.Match(
pixelFormat =>
{
if (pixelFormat.BitDepth == 10)
{
return nextState with { PixelFormat = new PixelFormatP010() };
}
return nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) };
},
() => nextState);
}
}

12
ErsatzTV.FFmpeg/Option/HardwareAcceleration/VaapiHardwareAccelerationOption.cs

@ -1,14 +1,16 @@
namespace ErsatzTV.FFmpeg.Option.HardwareAcceleration; using ErsatzTV.FFmpeg.Capabilities;
namespace ErsatzTV.FFmpeg.Option.HardwareAcceleration;
public class VaapiHardwareAccelerationOption : GlobalOption public class VaapiHardwareAccelerationOption : GlobalOption
{ {
private readonly string _vaapiDevice; private readonly string _vaapiDevice;
private readonly bool _canDecode; private readonly FFmpegCapability _decodeCapability;
public VaapiHardwareAccelerationOption(string vaapiDevice, bool canDecode) public VaapiHardwareAccelerationOption(string vaapiDevice, FFmpegCapability decodeCapability)
{ {
_vaapiDevice = vaapiDevice; _vaapiDevice = vaapiDevice;
_canDecode = canDecode; _decodeCapability = decodeCapability;
} }
public override IList<string> GlobalOptions public override IList<string> GlobalOptions
@ -17,7 +19,7 @@ public class VaapiHardwareAccelerationOption : GlobalOption
{ {
var result = new List<string> { "-vaapi_device", _vaapiDevice }; var result = new List<string> { "-vaapi_device", _vaapiDevice };
if (_canDecode) if (_decodeCapability == FFmpegCapability.Hardware)
{ {
result.InsertRange(0, new[] { "-hwaccel", "vaapi" }); result.InsertRange(0, new[] { "-hwaccel", "vaapi" });
} }

10
ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs

@ -15,6 +15,7 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder
private readonly ILogger _logger; private readonly ILogger _logger;
public AmfPipelineBuilder( public AmfPipelineBuilder(
IFFmpegCapabilities ffmpegCapabilities,
IHardwareCapabilities hardwareCapabilities, IHardwareCapabilities hardwareCapabilities,
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
Option<VideoInputFile> videoInputFile, Option<VideoInputFile> videoInputFile,
@ -24,6 +25,7 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder
string reportsFolder, string reportsFolder,
string fontsFolder, string fontsFolder,
ILogger logger) : base( ILogger logger) : base(
ffmpegCapabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
@ -44,11 +46,11 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder
PipelineContext context, PipelineContext context,
ICollection<IPipelineStep> pipelineSteps) ICollection<IPipelineStep> pipelineSteps)
{ {
bool canDecode = _hardwareCapabilities.CanDecode( FFmpegCapability decodeCapability = _hardwareCapabilities.CanDecode(
videoStream.Codec, videoStream.Codec,
desiredState.VideoProfile, desiredState.VideoProfile,
videoStream.PixelFormat); videoStream.PixelFormat);
bool canEncode = _hardwareCapabilities.CanEncode( FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode(
desiredState.VideoFormat, desiredState.VideoFormat,
desiredState.VideoProfile, desiredState.VideoProfile,
desiredState.PixelFormat); desiredState.PixelFormat);
@ -58,10 +60,10 @@ public class AmfPipelineBuilder : SoftwarePipelineBuilder
// disable hw accel if decoder/encoder isn't supported // disable hw accel if decoder/encoder isn't supported
return ffmpegState with return ffmpegState with
{ {
DecoderHardwareAccelerationMode = canDecode DecoderHardwareAccelerationMode = decodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.Amf ? HardwareAccelerationMode.Amf
: HardwareAccelerationMode.None, : HardwareAccelerationMode.None,
EncoderHardwareAccelerationMode = canEncode EncoderHardwareAccelerationMode = encodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.Amf ? HardwareAccelerationMode.Amf
: HardwareAccelerationMode.None : HardwareAccelerationMode.None
}; };

16
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

@ -19,6 +19,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
private readonly ILogger _logger; private readonly ILogger _logger;
public NvidiaPipelineBuilder( public NvidiaPipelineBuilder(
IFFmpegCapabilities ffmpegCapabilities,
IHardwareCapabilities hardwareCapabilities, IHardwareCapabilities hardwareCapabilities,
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
Option<VideoInputFile> videoInputFile, Option<VideoInputFile> videoInputFile,
@ -28,6 +29,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
string reportsFolder, string reportsFolder,
string fontsFolder, string fontsFolder,
ILogger logger) : base( ILogger logger) : base(
ffmpegCapabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
@ -48,11 +50,11 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
PipelineContext context, PipelineContext context,
ICollection<IPipelineStep> pipelineSteps) ICollection<IPipelineStep> pipelineSteps)
{ {
bool canDecode = _hardwareCapabilities.CanDecode( FFmpegCapability decodeCapability = _hardwareCapabilities.CanDecode(
videoStream.Codec, videoStream.Codec,
desiredState.VideoProfile, desiredState.VideoProfile,
videoStream.PixelFormat); videoStream.PixelFormat);
bool canEncode = _hardwareCapabilities.CanEncode( FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode(
desiredState.VideoFormat, desiredState.VideoFormat,
desiredState.VideoProfile, desiredState.VideoProfile,
desiredState.PixelFormat); desiredState.PixelFormat);
@ -60,10 +62,10 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
// mpeg2_cuvid seems to have issues when yadif_cuda is used, so just use software decoding // mpeg2_cuvid seems to have issues when yadif_cuda is used, so just use software decoding
if (context.ShouldDeinterlace && videoStream.Codec == VideoFormat.Mpeg2Video) if (context.ShouldDeinterlace && videoStream.Codec == VideoFormat.Mpeg2Video)
{ {
canDecode = false; decodeCapability = FFmpegCapability.Software;
} }
if (canDecode || canEncode) if (decodeCapability == FFmpegCapability.Hardware || encodeCapability == FFmpegCapability.Hardware)
{ {
pipelineSteps.Add(new CudaHardwareAccelerationOption()); pipelineSteps.Add(new CudaHardwareAccelerationOption());
} }
@ -71,10 +73,10 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
// disable hw accel if decoder/encoder isn't supported // disable hw accel if decoder/encoder isn't supported
return ffmpegState with return ffmpegState with
{ {
DecoderHardwareAccelerationMode = canDecode DecoderHardwareAccelerationMode = decodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.Nvenc ? HardwareAccelerationMode.Nvenc
: HardwareAccelerationMode.None, : HardwareAccelerationMode.None,
EncoderHardwareAccelerationMode = canEncode EncoderHardwareAccelerationMode = encodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.Nvenc ? HardwareAccelerationMode.Nvenc
: HardwareAccelerationMode.None : HardwareAccelerationMode.None
}; };
@ -97,6 +99,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder
(HardwareAccelerationMode.Nvenc, VideoFormat.Vp9) => new DecoderVp9Cuvid(HardwareAccelerationMode.Nvenc), (HardwareAccelerationMode.Nvenc, VideoFormat.Vp9) => new DecoderVp9Cuvid(HardwareAccelerationMode.Nvenc),
(HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg4) => (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg4) =>
new DecoderMpeg4Cuvid(HardwareAccelerationMode.Nvenc), new DecoderMpeg4Cuvid(HardwareAccelerationMode.Nvenc),
(HardwareAccelerationMode.Nvenc, VideoFormat.Av1) =>
new DecoderAv1Cuvid(HardwareAccelerationMode.Nvenc),
_ => GetSoftwareDecoder(videoStream) _ => GetSoftwareDecoder(videoStream)
}; };

40
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -1,4 +1,5 @@
using System.Diagnostics; using System.Diagnostics;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Decoder; using ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Encoder; using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Environment; using ErsatzTV.FFmpeg.Environment;
@ -14,6 +15,7 @@ namespace ErsatzTV.FFmpeg.Pipeline;
public abstract class PipelineBuilderBase : IPipelineBuilder public abstract class PipelineBuilderBase : IPipelineBuilder
{ {
private readonly IFFmpegCapabilities _ffmpegCapabilities;
private readonly HardwareAccelerationMode _hardwareAccelerationMode; private readonly HardwareAccelerationMode _hardwareAccelerationMode;
private readonly Option<VideoInputFile> _videoInputFile; private readonly Option<VideoInputFile> _videoInputFile;
private readonly Option<AudioInputFile> _audioInputFile; private readonly Option<AudioInputFile> _audioInputFile;
@ -24,6 +26,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
private readonly ILogger _logger; private readonly ILogger _logger;
protected PipelineBuilderBase( protected PipelineBuilderBase(
IFFmpegCapabilities ffmpegCapabilities,
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
Option<VideoInputFile> videoInputFile, Option<VideoInputFile> videoInputFile,
Option<AudioInputFile> audioInputFile, Option<AudioInputFile> audioInputFile,
@ -33,6 +36,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
string fontsFolder, string fontsFolder,
ILogger logger) ILogger logger)
{ {
_ffmpegCapabilities = ffmpegCapabilities;
_hardwareAccelerationMode = hardwareAccelerationMode; _hardwareAccelerationMode = hardwareAccelerationMode;
_videoInputFile = videoInputFile; _videoInputFile = videoInputFile;
_audioInputFile = audioInputFile; _audioInputFile = audioInputFile;
@ -218,7 +222,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
return new FFmpegPipeline(pipelineSteps); return new FFmpegPipeline(pipelineSteps);
} }
private Option<IDecoder> LogUnknownDecoder( private void LogUnknownDecoder(
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
string videoFormat, string videoFormat,
string pixelFormat) string pixelFormat)
@ -228,7 +232,6 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
hardwareAccelerationMode, hardwareAccelerationMode,
videoFormat, videoFormat,
pixelFormat); pixelFormat);
return Option<IDecoder>.None;
} }
private Option<IEncoder> LogUnknownEncoder(HardwareAccelerationMode hardwareAccelerationMode, string videoFormat) private Option<IEncoder> LogUnknownEncoder(HardwareAccelerationMode hardwareAccelerationMode, string videoFormat)
@ -428,29 +431,20 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
FFmpegState ffmpegState, FFmpegState ffmpegState,
PipelineContext context); PipelineContext context);
protected Option<IDecoder> GetSoftwareDecoder(VideoStream videoStream) => protected Option<IDecoder> GetSoftwareDecoder(VideoStream videoStream)
videoStream.Codec switch {
{ Option<IDecoder> maybeDecoder = _ffmpegCapabilities.SoftwareDecoderForVideoFormat(videoStream.Codec);
VideoFormat.Hevc => new DecoderHevc(), if (maybeDecoder.IsNone)
VideoFormat.H264 => new DecoderH264(), {
VideoFormat.Mpeg1Video => new DecoderMpeg1Video(), LogUnknownDecoder(
VideoFormat.Mpeg2Video => new DecoderMpeg2Video(),
VideoFormat.Vc1 => new DecoderVc1(),
VideoFormat.MsMpeg4V2 => new DecoderMsMpeg4V2(),
VideoFormat.MsMpeg4V3 => new DecoderMsMpeg4V3(),
VideoFormat.Mpeg4 => new DecoderMpeg4(),
VideoFormat.Vp9 => new DecoderVp9(),
VideoFormat.Undetermined => new DecoderImplicit(),
VideoFormat.Copy => new DecoderImplicit(),
VideoFormat.GeneratedImage => new DecoderImplicit(),
_ => LogUnknownDecoder(
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
videoStream.Codec, videoStream.Codec,
videoStream.PixelFormat.Match(pf => pf.Name, () => string.Empty)) videoStream.PixelFormat.Match(pf => pf.Name, () => string.Empty));
}; }
return maybeDecoder;
}
protected Option<IEncoder> GetSoftwareEncoder(FrameState currentState, FrameState desiredState) => protected Option<IEncoder> GetSoftwareEncoder(FrameState currentState, FrameState desiredState) =>
desiredState.VideoFormat switch desiredState.VideoFormat switch
{ {

9
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs

@ -32,7 +32,10 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
string fontsFolder, string fontsFolder,
string ffmpegPath) string ffmpegPath)
{ {
IFFmpegCapabilities ffmpegCapabilities = await _hardwareCapabilitiesFactory.GetFFmpegCapabilities(ffmpegPath);
IHardwareCapabilities capabilities = await _hardwareCapabilitiesFactory.GetHardwareCapabilities( IHardwareCapabilities capabilities = await _hardwareCapabilitiesFactory.GetHardwareCapabilities(
ffmpegCapabilities,
ffmpegPath, ffmpegPath,
hardwareAccelerationMode, hardwareAccelerationMode,
vaapiDriver, vaapiDriver,
@ -41,6 +44,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
return hardwareAccelerationMode switch return hardwareAccelerationMode switch
{ {
HardwareAccelerationMode.Nvenc when capabilities is not NoHardwareCapabilities => new NvidiaPipelineBuilder( HardwareAccelerationMode.Nvenc when capabilities is not NoHardwareCapabilities => new NvidiaPipelineBuilder(
ffmpegCapabilities,
capabilities, capabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
@ -51,6 +55,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
fontsFolder, fontsFolder,
_logger), _logger),
HardwareAccelerationMode.Vaapi when capabilities is not NoHardwareCapabilities => new VaapiPipelineBuilder( HardwareAccelerationMode.Vaapi when capabilities is not NoHardwareCapabilities => new VaapiPipelineBuilder(
ffmpegCapabilities,
capabilities, capabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
@ -61,6 +66,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
fontsFolder, fontsFolder,
_logger), _logger),
HardwareAccelerationMode.Qsv => new QsvPipelineBuilder( HardwareAccelerationMode.Qsv => new QsvPipelineBuilder(
ffmpegCapabilities,
capabilities, capabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
@ -71,6 +77,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
fontsFolder, fontsFolder,
_logger), _logger),
HardwareAccelerationMode.VideoToolbox => new VideoToolboxPipelineBuilder( HardwareAccelerationMode.VideoToolbox => new VideoToolboxPipelineBuilder(
ffmpegCapabilities,
capabilities, capabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
@ -81,6 +88,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
fontsFolder, fontsFolder,
_logger), _logger),
HardwareAccelerationMode.Amf => new AmfPipelineBuilder( HardwareAccelerationMode.Amf => new AmfPipelineBuilder(
ffmpegCapabilities,
capabilities, capabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
@ -91,6 +99,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
fontsFolder, fontsFolder,
_logger), _logger),
_ => new SoftwarePipelineBuilder( _ => new SoftwarePipelineBuilder(
ffmpegCapabilities,
HardwareAccelerationMode.None, HardwareAccelerationMode.None,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,

19
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

@ -19,6 +19,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
private readonly ILogger _logger; private readonly ILogger _logger;
public QsvPipelineBuilder( public QsvPipelineBuilder(
IFFmpegCapabilities ffmpegCapabilities,
IHardwareCapabilities hardwareCapabilities, IHardwareCapabilities hardwareCapabilities,
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
Option<VideoInputFile> videoInputFile, Option<VideoInputFile> videoInputFile,
@ -28,6 +29,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
string reportsFolder, string reportsFolder,
string fontsFolder, string fontsFolder,
ILogger logger) : base( ILogger logger) : base(
ffmpegCapabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
@ -48,31 +50,33 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
PipelineContext context, PipelineContext context,
ICollection<IPipelineStep> pipelineSteps) ICollection<IPipelineStep> pipelineSteps)
{ {
bool canDecode = _hardwareCapabilities.CanDecode( FFmpegCapability decodeCapability = _hardwareCapabilities.CanDecode(
videoStream.Codec, videoStream.Codec,
desiredState.VideoProfile, desiredState.VideoProfile,
videoStream.PixelFormat); videoStream.PixelFormat);
bool canEncode = _hardwareCapabilities.CanEncode( FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode(
desiredState.VideoFormat, desiredState.VideoFormat,
desiredState.VideoProfile, desiredState.VideoProfile,
desiredState.PixelFormat); desiredState.PixelFormat);
pipelineSteps.Add(new QsvHardwareAccelerationOption(ffmpegState.VaapiDevice)); pipelineSteps.Add(new QsvHardwareAccelerationOption(ffmpegState.VaapiDevice));
bool isHevcOrH264 = videoStream.Codec is VideoFormat.Hevc or VideoFormat.H264;
bool is10Bit = videoStream.PixelFormat.Map(pf => pf.BitDepth).IfNone(8) == 10;
// 10-bit hevc/h264 qsv decoders have issues, so use software // 10-bit hevc/h264 qsv decoders have issues, so use software
if (canDecode && videoStream.Codec is VideoFormat.Hevc or VideoFormat.H264 && if (decodeCapability == FFmpegCapability.Hardware && isHevcOrH264 && is10Bit)
videoStream.PixelFormat.Map(pf => pf.BitDepth).IfNone(8) == 10)
{ {
canDecode = false; decodeCapability = FFmpegCapability.Software;
} }
// disable hw accel if decoder/encoder isn't supported // disable hw accel if decoder/encoder isn't supported
return ffmpegState with return ffmpegState with
{ {
DecoderHardwareAccelerationMode = canDecode DecoderHardwareAccelerationMode = decodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.Qsv ? HardwareAccelerationMode.Qsv
: HardwareAccelerationMode.None, : HardwareAccelerationMode.None,
EncoderHardwareAccelerationMode = canEncode EncoderHardwareAccelerationMode = encodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.Qsv ? HardwareAccelerationMode.Qsv
: HardwareAccelerationMode.None : HardwareAccelerationMode.None
}; };
@ -91,6 +95,7 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
(HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video) => new DecoderMpeg2Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Mpeg2Video) => new DecoderMpeg2Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Vc1) => new DecoderVc1Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Vc1) => new DecoderVc1Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Vp9) => new DecoderVp9Qsv(), (HardwareAccelerationMode.Qsv, VideoFormat.Vp9) => new DecoderVp9Qsv(),
(HardwareAccelerationMode.Qsv, VideoFormat.Av1) => new DecoderAv1Qsv(),
_ => GetSoftwareDecoder(videoStream) _ => GetSoftwareDecoder(videoStream)
}; };

3
ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs

@ -1,3 +1,4 @@
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Decoder; using ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Encoder; using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Filter; using ErsatzTV.FFmpeg.Filter;
@ -13,6 +14,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase
private readonly ILogger _logger; private readonly ILogger _logger;
public SoftwarePipelineBuilder( public SoftwarePipelineBuilder(
IFFmpegCapabilities ffmpegCapabilities,
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
Option<VideoInputFile> videoInputFile, Option<VideoInputFile> videoInputFile,
Option<AudioInputFile> audioInputFile, Option<AudioInputFile> audioInputFile,
@ -21,6 +23,7 @@ public class SoftwarePipelineBuilder : PipelineBuilderBase
string reportsFolder, string reportsFolder,
string fontsFolder, string fontsFolder,
ILogger logger) : base( ILogger logger) : base(
ffmpegCapabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,

14
ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

@ -19,6 +19,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
private readonly ILogger _logger; private readonly ILogger _logger;
public VaapiPipelineBuilder( public VaapiPipelineBuilder(
IFFmpegCapabilities ffmpegCapabilities,
IHardwareCapabilities hardwareCapabilities, IHardwareCapabilities hardwareCapabilities,
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
Option<VideoInputFile> videoInputFile, Option<VideoInputFile> videoInputFile,
@ -28,6 +29,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
string reportsFolder, string reportsFolder,
string fontsFolder, string fontsFolder,
ILogger logger) : base( ILogger logger) : base(
ffmpegCapabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
@ -48,18 +50,18 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
PipelineContext context, PipelineContext context,
ICollection<IPipelineStep> pipelineSteps) ICollection<IPipelineStep> pipelineSteps)
{ {
bool canDecode = _hardwareCapabilities.CanDecode( FFmpegCapability decodeCapability = _hardwareCapabilities.CanDecode(
videoStream.Codec, videoStream.Codec,
desiredState.VideoProfile, desiredState.VideoProfile,
videoStream.PixelFormat); videoStream.PixelFormat);
bool canEncode = _hardwareCapabilities.CanEncode( FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode(
desiredState.VideoFormat, desiredState.VideoFormat,
desiredState.VideoProfile, desiredState.VideoProfile,
desiredState.PixelFormat); desiredState.PixelFormat);
foreach (string vaapiDevice in ffmpegState.VaapiDevice) foreach (string vaapiDevice in ffmpegState.VaapiDevice)
{ {
pipelineSteps.Add(new VaapiHardwareAccelerationOption(vaapiDevice, canDecode)); pipelineSteps.Add(new VaapiHardwareAccelerationOption(vaapiDevice, decodeCapability));
foreach (string driverName in ffmpegState.VaapiDriver) foreach (string driverName in ffmpegState.VaapiDriver)
{ {
@ -70,16 +72,16 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
// use software decoding with an extensive pipeline // use software decoding with an extensive pipeline
if (context is { HasSubtitleOverlay: true, HasWatermark: true }) if (context is { HasSubtitleOverlay: true, HasWatermark: true })
{ {
canDecode = false; decodeCapability = FFmpegCapability.Software;
} }
// disable hw accel if decoder/encoder isn't supported // disable hw accel if decoder/encoder isn't supported
return ffmpegState with return ffmpegState with
{ {
DecoderHardwareAccelerationMode = canDecode DecoderHardwareAccelerationMode = decodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.Vaapi ? HardwareAccelerationMode.Vaapi
: HardwareAccelerationMode.None, : HardwareAccelerationMode.None,
EncoderHardwareAccelerationMode = canEncode EncoderHardwareAccelerationMode = encodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.Vaapi ? HardwareAccelerationMode.Vaapi
: HardwareAccelerationMode.None : HardwareAccelerationMode.None
}; };

10
ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs

@ -16,6 +16,7 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder
private readonly ILogger _logger; private readonly ILogger _logger;
public VideoToolboxPipelineBuilder( public VideoToolboxPipelineBuilder(
IFFmpegCapabilities ffmpegCapabilities,
IHardwareCapabilities hardwareCapabilities, IHardwareCapabilities hardwareCapabilities,
HardwareAccelerationMode hardwareAccelerationMode, HardwareAccelerationMode hardwareAccelerationMode,
Option<VideoInputFile> videoInputFile, Option<VideoInputFile> videoInputFile,
@ -25,6 +26,7 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder
string reportsFolder, string reportsFolder,
string fontsFolder, string fontsFolder,
ILogger logger) : base( ILogger logger) : base(
ffmpegCapabilities,
hardwareAccelerationMode, hardwareAccelerationMode,
videoInputFile, videoInputFile,
audioInputFile, audioInputFile,
@ -45,11 +47,11 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder
PipelineContext context, PipelineContext context,
ICollection<IPipelineStep> pipelineSteps) ICollection<IPipelineStep> pipelineSteps)
{ {
bool canDecode = _hardwareCapabilities.CanDecode( FFmpegCapability decodeCapability = _hardwareCapabilities.CanDecode(
videoStream.Codec, videoStream.Codec,
desiredState.VideoProfile, desiredState.VideoProfile,
videoStream.PixelFormat); videoStream.PixelFormat);
bool canEncode = _hardwareCapabilities.CanEncode( FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode(
desiredState.VideoFormat, desiredState.VideoFormat,
desiredState.VideoProfile, desiredState.VideoProfile,
desiredState.PixelFormat); desiredState.PixelFormat);
@ -59,10 +61,10 @@ public class VideoToolboxPipelineBuilder : SoftwarePipelineBuilder
// disable hw accel if decoder/encoder isn't supported // disable hw accel if decoder/encoder isn't supported
return ffmpegState with return ffmpegState with
{ {
DecoderHardwareAccelerationMode = canDecode DecoderHardwareAccelerationMode = decodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.VideoToolbox ? HardwareAccelerationMode.VideoToolbox
: HardwareAccelerationMode.None, : HardwareAccelerationMode.None,
EncoderHardwareAccelerationMode = canEncode EncoderHardwareAccelerationMode = encodeCapability == FFmpegCapability.Hardware
? HardwareAccelerationMode.VideoToolbox ? HardwareAccelerationMode.VideoToolbox
: HardwareAccelerationMode.None : HardwareAccelerationMode.None
}; };

Loading…
Cancel
Save