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 2 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/). @@ -5,7 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### 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
- 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 @@ -54,6 +54,8 @@ public class HlsSessionWorker : IHlsSessionWorker
{
lock (_sync)
{
_logger.LogDebug("Keep alive - session worker for channel {ChannelNumber}", _channelNumber);
_lastAccess = DateTimeOffset.Now;
_timer?.Stop();

19
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.OutputFormat;
@ -86,6 +87,7 @@ public class PipelineBuilderBaseTests @@ -86,6 +87,7 @@ public class PipelineBuilderBaseTests
Option<int>.None);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None,
videoInputFile,
audioInputFile,
@ -171,6 +173,7 @@ public class PipelineBuilderBaseTests @@ -171,6 +173,7 @@ public class PipelineBuilderBaseTests
Option<int>.None);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None,
videoInputFile,
audioInputFile,
@ -196,6 +199,7 @@ public class PipelineBuilderBaseTests @@ -196,6 +199,7 @@ public class PipelineBuilderBaseTests
var concatInputFile = new ConcatInputFile("http://localhost:8080/ffmpeg/concat/1", resolution);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None,
None,
None,
@ -221,6 +225,7 @@ public class PipelineBuilderBaseTests @@ -221,6 +225,7 @@ public class PipelineBuilderBaseTests
var concatInputFile = new ConcatInputFile("http://localhost:8080/iptv/channel/1.m3u8?mode=segmenter", resolution);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None,
None,
None,
@ -306,6 +311,7 @@ public class PipelineBuilderBaseTests @@ -306,6 +311,7 @@ public class PipelineBuilderBaseTests
Option<int>.None);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None,
videoInputFile,
audioInputFile,
@ -383,6 +389,7 @@ public class PipelineBuilderBaseTests @@ -383,6 +389,7 @@ public class PipelineBuilderBaseTests
Option<int>.None);
var builder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None,
videoInputFile,
audioInputFile,
@ -426,6 +433,7 @@ public class PipelineBuilderBaseTests @@ -426,6 +433,7 @@ public class PipelineBuilderBaseTests
});
var pipelineBuilder = new SoftwarePipelineBuilder(
new DefaultFFmpegCapabilities(),
HardwareAccelerationMode.None,
videoInputFile,
Option<AudioInputFile>.None,
@ -463,4 +471,15 @@ public class PipelineBuilderBaseTests @@ -463,4 +471,15 @@ public class PipelineBuilderBaseTests
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; @@ -4,21 +4,24 @@ namespace ErsatzTV.FFmpeg.Capabilities;
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);
return (videoFormat, bitDepth) switch
{
// 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
(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; @@ -4,29 +4,29 @@ namespace ErsatzTV.FFmpeg.Capabilities;
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);
return (videoFormat, bitDepth) switch
{
// 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);
return (videoFormat, bitDepth) switch
{
// 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 @@ @@ -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 @@ @@ -0,0 +1,8 @@
namespace ErsatzTV.FFmpeg.Capabilities;
public enum FFmpegCapability
{
None,
Software,
Hardware
}

64
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
using System.Collections.Immutable;
using System.Text;
using System.Text.RegularExpressions;
using CliWrap;
@ -13,6 +14,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -13,6 +14,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
private const string ArchitectureCacheKey = "ffmpeg.hardware.nvidia.architecture";
private const string ModelCacheKey = "ffmpeg.hardware.nvidia.model";
private const string VaapiCacheKeyFormat = "ffmpeg.hardware.vaapi.{0}.{1}";
private const string FFmpegCapabilitiesCacheKeyFormat = "ffmpeg.{0}";
private readonly ILogger<HardwareCapabilitiesFactory> _logger;
private readonly IMemoryCache _memoryCache;
@ -23,18 +25,62 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -23,18 +25,62 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
_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(
IFFmpegCapabilities ffmpegCapabilities,
string ffmpegPath,
HardwareAccelerationMode hardwareAccelerationMode,
Option<string> vaapiDriver,
Option<string> vaapiDevice) =>
hardwareAccelerationMode switch
Option<string> vaapiDevice)
{
return hardwareAccelerationMode switch
{
HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath),
HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath, ffmpegCapabilities),
HardwareAccelerationMode.Vaapi => await GetVaapiCapabilities(vaapiDriver, vaapiDevice),
HardwareAccelerationMode.Amf => new AmfHardwareCapabilities(),
_ => 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(
Option<string> vaapiDriver,
@ -126,13 +172,19 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -126,13 +172,19 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
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)
&& _memoryCache.TryGetValue(ModelCacheKey, out string? cachedModel)
&& cachedModel is not null)
{
return new NvidiaHardwareCapabilities(cachedArchitecture, cachedModel);
return new NvidiaHardwareCapabilities(
cachedArchitecture,
cachedModel,
ffmpegCapabilities,
_logger);
}
string[] arguments =
@ -169,7 +221,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -169,7 +221,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
architecture);
_memoryCache.Set(ArchitectureCacheKey, architecture);
_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 @@ @@ -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; @@ -4,6 +4,6 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public interface IHardwareCapabilities
{
public bool CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat);
public bool CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat);
public FFmpegCapability CanDecode(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; @@ -2,7 +2,10 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public interface IHardwareCapabilitiesFactory
{
Task<IFFmpegCapabilities> GetFFmpegCapabilities(string ffmpegPath);
Task<IHardwareCapabilities> GetHardwareCapabilities(
IFFmpegCapabilities ffmpegCapabilities,
string ffmpegPath,
HardwareAccelerationMode hardwareAccelerationMode,
Option<string> vaapiDriver,

8
ErsatzTV.FFmpeg/Capabilities/NoHardwareCapabilities.cs

@ -4,9 +4,9 @@ namespace ErsatzTV.FFmpeg.Capabilities; @@ -4,9 +4,9 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public class NoHardwareCapabilities : 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) =>
false;
public FFmpegCapability CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat) =>
FFmpegCapability.Software;
}

52
ErsatzTV.FFmpeg/Capabilities/NvidiaHardwareCapabilities.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.FFmpeg.Format;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.FFmpeg.Capabilities;
@ -7,18 +8,26 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -7,18 +8,26 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
private readonly int _architecture;
private readonly List<string> _maxwellGm206 = new() { "GTX 750", "GTX 950", "GTX 960", "GTX 965M" };
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;
_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);
return videoFormat switch
bool isHardware = videoFormat switch
{
// some second gen maxwell can decode hevc, otherwise pascal is required
VideoFormat.Hevc => _architecture == 52 && _maxwellGm206.Contains(_model) || _architecture >= 60,
@ -38,18 +47,38 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -38,18 +47,38 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
VideoFormat.Mpeg4 => true,
// ampere is required for av1 decoding
VideoFormat.Av1 => _architecture >= 86,
// generated images are decoded into software
VideoFormat.GeneratedImage => 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);
return videoFormat switch
bool isHardware = videoFormat switch
{
// pascal is required to encode 10-bit hevc
VideoFormat.Hevc when bitDepth == 10 => _architecture >= 60,
@ -62,5 +91,18 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities @@ -62,5 +91,18 @@ public class NvidiaHardwareCapabilities : IHardwareCapabilities
_ => 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 @@ -15,11 +15,11 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
_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);
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
(VideoFormat.H264, _) when bitDepth == 10 => false,
@ -95,22 +95,22 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -95,22 +95,22 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
_ => false
};
if (!result)
if (!isHardware)
{
_logger.LogDebug(
"VAAPI does not support decoding {Format}/{Profile}, will use software decoder",
videoFormat,
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);
bool result = videoFormat switch
bool isHardware = videoFormat switch
{
// vaapi cannot encode 10-bit h264
VideoFormat.H264 when bitDepth == 10 => false,
@ -142,7 +142,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -142,7 +142,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
_ => false
};
if (!result)
if (!isHardware)
{
_logger.LogDebug(
"VAAPI does not support encoding {Format} with bit depth {BitDepth}, will use software encoder",
@ -150,6 +150,6 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -150,6 +150,6 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
bitDepth);
}
return result;
return isHardware ? FFmpegCapability.Hardware : FFmpegCapability.Software;
}
}

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

@ -0,0 +1,10 @@ @@ -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 @@ -7,6 +7,4 @@ public class DecoderMpeg4Cuvid : CuvidDecoder
}
public override string Name => "mpeg4_cuvid";
}

30
ErsatzTV.FFmpeg/Decoder/DecoderAv1.cs

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

10
ErsatzTV.FFmpeg/Pipeline/AmfPipelineBuilder.cs

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

16
ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs

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

40
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Diagnostics;
using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Environment;
@ -14,6 +15,7 @@ namespace ErsatzTV.FFmpeg.Pipeline; @@ -14,6 +15,7 @@ namespace ErsatzTV.FFmpeg.Pipeline;
public abstract class PipelineBuilderBase : IPipelineBuilder
{
private readonly IFFmpegCapabilities _ffmpegCapabilities;
private readonly HardwareAccelerationMode _hardwareAccelerationMode;
private readonly Option<VideoInputFile> _videoInputFile;
private readonly Option<AudioInputFile> _audioInputFile;
@ -24,6 +26,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -24,6 +26,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
private readonly ILogger _logger;
protected PipelineBuilderBase(
IFFmpegCapabilities ffmpegCapabilities,
HardwareAccelerationMode hardwareAccelerationMode,
Option<VideoInputFile> videoInputFile,
Option<AudioInputFile> audioInputFile,
@ -33,6 +36,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -33,6 +36,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
string fontsFolder,
ILogger logger)
{
_ffmpegCapabilities = ffmpegCapabilities;
_hardwareAccelerationMode = hardwareAccelerationMode;
_videoInputFile = videoInputFile;
_audioInputFile = audioInputFile;
@ -218,7 +222,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -218,7 +222,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
return new FFmpegPipeline(pipelineSteps);
}
private Option<IDecoder> LogUnknownDecoder(
private void LogUnknownDecoder(
HardwareAccelerationMode hardwareAccelerationMode,
string videoFormat,
string pixelFormat)
@ -228,7 +232,6 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -228,7 +232,6 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
hardwareAccelerationMode,
videoFormat,
pixelFormat);
return Option<IDecoder>.None;
}
private Option<IEncoder> LogUnknownEncoder(HardwareAccelerationMode hardwareAccelerationMode, string videoFormat)
@ -428,29 +431,20 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -428,29 +431,20 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
FFmpegState ffmpegState,
PipelineContext context);
protected Option<IDecoder> GetSoftwareDecoder(VideoStream videoStream) =>
videoStream.Codec 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.Undetermined => new DecoderImplicit(),
VideoFormat.Copy => new DecoderImplicit(),
VideoFormat.GeneratedImage => new DecoderImplicit(),
_ => LogUnknownDecoder(
protected Option<IDecoder> GetSoftwareDecoder(VideoStream videoStream)
{
Option<IDecoder> maybeDecoder = _ffmpegCapabilities.SoftwareDecoderForVideoFormat(videoStream.Codec);
if (maybeDecoder.IsNone)
{
LogUnknownDecoder(
HardwareAccelerationMode.None,
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) =>
desiredState.VideoFormat switch
{

9
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs

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

19
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

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

3
ErsatzTV.FFmpeg/Pipeline/SoftwarePipelineBuilder.cs

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

14
ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

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

10
ErsatzTV.FFmpeg/Pipeline/VideoToolboxPipelineBuilder.cs

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

Loading…
Cancel
Save