Browse Source

try multiple qsv init options

pull/2691/head
Jason Dove 8 months ago
parent
commit
523c419c65
No known key found for this signature in database
  1. 1
      CHANGELOG.md
  2. 25
      ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs
  3. 53
      ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs
  4. 2
      ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs
  5. 8
      ErsatzTV.FFmpeg/Capabilities/Qsv/QsvInitMode.cs
  6. 56
      ErsatzTV.FFmpeg/Capabilities/QsvHardwareCapabilities.cs
  7. 20
      ErsatzTV.FFmpeg/GlobalOption/HardwareAcceleration/QsvHardwareAccelerationOption.cs
  8. 9
      ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

1
CHANGELOG.md

@ -29,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- A warning will be logged when this scenario is detected - A warning will be logged when this scenario is detected
- AMD VAAPI: work around buggy ffmpeg behavior where hevc_vaapi encoder with RadeonSI driver incorrectly outputs height of 1088 instead of 1080 - AMD VAAPI: work around buggy ffmpeg behavior where hevc_vaapi encoder with RadeonSI driver incorrectly outputs height of 1088 instead of 1080
- Optimize graphics engine to generate element frames in parallel and to eliminate redundant frame copies - Optimize graphics engine to generate element frames in parallel and to eliminate redundant frame copies
- QSV: try multiple variations of ffmpeg init syntax to find one that works for the specific GPU
## [25.9.0] - 2025-11-29 ## [25.9.0] - 2025-11-29
### Added ### Added

25
ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs

@ -102,14 +102,27 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
vaapiDisplays = ["drm"]; vaapiDisplays = ["drm"];
} }
List<QsvInitMode> qsvInitModes = [QsvInitMode.None];
if (_runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
qsvInitModes.Add(QsvInitMode.D3d11Va);
}
qsvInitModes.Add(QsvInitMode.Qsv);
foreach (string qsvDevice in vaapiDevices) foreach (string qsvDevice in vaapiDevices)
{ {
QsvOutput output = await _hardwareCapabilitiesFactory.GetQsvOutput(ffmpegPath.Value, qsvDevice); foreach (var qsvInitMode in qsvInitModes)
qsvCapabilities.AppendLine(CultureInfo.InvariantCulture, $"Checking device {qsvDevice}"); {
qsvCapabilities.AppendLine(CultureInfo.InvariantCulture, $"Exit Code: {output.ExitCode}"); QsvOutput output = await _hardwareCapabilitiesFactory.GetQsvOutput(
qsvCapabilities.AppendLine(); ffmpegPath.Value,
qsvCapabilities.AppendLine(output.Output); qsvDevice,
qsvCapabilities.AppendLine(); qsvInitMode);
qsvCapabilities.AppendLine(CultureInfo.InvariantCulture, $"Checking device {qsvDevice} with init mode {qsvInitMode}");
qsvCapabilities.AppendLine(CultureInfo.InvariantCulture, $"Exit Code: {output.ExitCode}");
qsvCapabilities.AppendLine();
qsvCapabilities.AppendLine(output.Output);
qsvCapabilities.AppendLine();
}
} }
if (_runtimeInfo.IsOSPlatform(OSPlatform.Linux)) if (_runtimeInfo.IsOSPlatform(OSPlatform.Linux))

53
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -32,7 +32,9 @@ public partial class HardwareCapabilitiesFactory(
private static readonly CompositeFormat VaapiGenerationCacheKeyFormat = private static readonly CompositeFormat VaapiGenerationCacheKeyFormat =
CompositeFormat.Parse("ffmpeg.hardware.vaapi.generation.{0}.{1}.{2}"); CompositeFormat.Parse("ffmpeg.hardware.vaapi.generation.{0}.{1}.{2}");
private static readonly CompositeFormat QsvVaapiCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.qsv.vaapi.{0}");
private static readonly CompositeFormat QsvCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.qsv.{0}"); private static readonly CompositeFormat QsvCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.qsv.{0}");
private static readonly CompositeFormat FFmpegCapabilitiesCacheKeyFormat = CompositeFormat.Parse("ffmpeg.{0}"); private static readonly CompositeFormat FFmpegCapabilitiesCacheKeyFormat = CompositeFormat.Parse("ffmpeg.{0}");
private static readonly string[] QsvArguments = private static readonly string[] QsvArguments =
@ -179,14 +181,14 @@ public partial class HardwareCapabilitiesFactory(
return output; return output;
} }
public async Task<QsvOutput> GetQsvOutput(string ffmpegPath, Option<string> qsvDevice) public async Task<QsvOutput> GetQsvOutput(string ffmpegPath, Option<string> qsvDevice, QsvInitMode qsvInitMode)
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{ {
return new QsvOutput(0, string.Empty); return new QsvOutput(0, string.Empty);
} }
var option = new QsvHardwareAccelerationOption(qsvDevice, FFmpegCapability.Software); var option = new QsvHardwareAccelerationOption(qsvDevice, FFmpegCapability.Software, qsvInitMode);
var arguments = option.GlobalOptions.ToList(); var arguments = option.GlobalOptions.ToList();
arguments.AddRange(QsvArguments); arguments.AddRange(QsvArguments);
@ -558,21 +560,50 @@ public partial class HardwareCapabilitiesFactory(
} }
string device = qsvDevice.IfNone(string.Empty); string device = qsvDevice.IfNone(string.Empty);
var cacheKey = string.Format(CultureInfo.InvariantCulture, QsvCacheKeyFormat, device); var qsvCacheKey = string.Format(CultureInfo.InvariantCulture, QsvCacheKeyFormat, device);
var qsvVaapiCacheKey = string.Format(CultureInfo.InvariantCulture, QsvVaapiCacheKeyFormat, device);
if (memoryCache.TryGetValue(cacheKey, out List<VaapiProfileEntrypoint>? profileEntrypoints) && List<VaapiProfileEntrypoint>? profileEntrypoints = null;
profileEntrypoints is not null) Option<VaapiHardwareCapabilities> vaapiCapabilities = None;
if (memoryCache.TryGetValue(qsvCacheKey, out QsvInitMode? qsvInitMode) &&
qsvInitMode is not null)
{ {
return new VaapiHardwareCapabilities(profileEntrypoints, string.Empty, logger); if (memoryCache.TryGetValue(qsvVaapiCacheKey, out profileEntrypoints) && profileEntrypoints is not null)
{
vaapiCapabilities = new VaapiHardwareCapabilities(profileEntrypoints, string.Empty, logger);
}
return new QsvHardwareCapabilities(vaapiCapabilities, qsvInitMode.Value);
} }
QsvOutput output = await GetQsvOutput(ffmpegPath, qsvDevice); var initModesToTest = new Queue<QsvInitMode>();
initModesToTest.Enqueue(QsvInitMode.None);
if (runtimeInfo.IsOSPlatform(OSPlatform.Windows))
{
initModesToTest.Enqueue(QsvInitMode.D3d11Va);
}
initModesToTest.Enqueue(QsvInitMode.Qsv);
QsvOutput output;
QsvInitMode initMode = initModesToTest.Dequeue();
do
{
output = await GetQsvOutput(ffmpegPath, qsvDevice, initMode);
if (output.ExitCode == 0)
{
break;
}
} while (initModesToTest.Count > 0);
if (output.ExitCode != 0) if (output.ExitCode != 0)
{ {
logger.LogWarning("QSV test failed; some hardware accelerated features will be unavailable"); logger.LogWarning("QSV test failed; some hardware accelerated features will be unavailable");
return new NoHardwareCapabilities(); return new NoHardwareCapabilities();
} }
memoryCache.Set(qsvCacheKey, initMode);
if (runtimeInfo.IsOSPlatform(OSPlatform.Linux)) if (runtimeInfo.IsOSPlatform(OSPlatform.Linux))
{ {
if (!memoryCache.TryGetValue("ffmpeg.vaapi_displays", out List<string>? vaapiDisplays)) if (!memoryCache.TryGetValue("ffmpeg.vaapi_displays", out List<string>? vaapiDisplays))
@ -589,7 +620,7 @@ public partial class HardwareCapabilitiesFactory(
if (vaapiOutput.IsNone) if (vaapiOutput.IsNone)
{ {
logger.LogWarning("Unable to determine QSV capabilities; please install vainfo"); logger.LogWarning("Unable to determine QSV capabilities; please install vainfo");
return new DefaultHardwareCapabilities(); return new QsvHardwareCapabilities(vaapiCapabilities, initMode);
} }
foreach (string o in vaapiOutput) foreach (string o in vaapiOutput)
@ -604,14 +635,14 @@ public partial class HardwareCapabilitiesFactory(
profileEntrypoints.Count, profileEntrypoints.Count,
device); device);
memoryCache.Set(cacheKey, profileEntrypoints); memoryCache.Set(qsvVaapiCacheKey, profileEntrypoints);
return new VaapiHardwareCapabilities(profileEntrypoints, string.Empty, logger); vaapiCapabilities = new VaapiHardwareCapabilities(profileEntrypoints, string.Empty, logger);
} }
} }
} }
// not sure how to check capabilities on windows // not sure how to check capabilities on windows
return new DefaultHardwareCapabilities(); return new QsvHardwareCapabilities(vaapiCapabilities, initMode);
} }
catch (Exception ex) catch (Exception ex)
{ {

2
ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs

@ -18,7 +18,7 @@ public interface IHardwareCapabilitiesFactory
Task<string> GetNvidiaOutput(string ffmpegPath); Task<string> GetNvidiaOutput(string ffmpegPath);
Task<QsvOutput> GetQsvOutput(string ffmpegPath, Option<string> qsvDevice); Task<QsvOutput> GetQsvOutput(string ffmpegPath, Option<string> qsvDevice, QsvInitMode qsvInitMode);
Task<Option<string>> GetVaapiOutput(string display, Option<string> vaapiDriver, string vaapiDevice); Task<Option<string>> GetVaapiOutput(string display, Option<string> vaapiDriver, string vaapiDevice);

8
ErsatzTV.FFmpeg/Capabilities/Qsv/QsvInitMode.cs

@ -0,0 +1,8 @@
namespace ErsatzTV.FFmpeg.Capabilities.Qsv;
public enum QsvInitMode
{
None = 0,
Qsv = 1,
D3d11Va = 2,
}

56
ErsatzTV.FFmpeg/Capabilities/QsvHardwareCapabilities.cs

@ -0,0 +1,56 @@
using ErsatzTV.FFmpeg.Capabilities.Qsv;
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Capabilities;
public class QsvHardwareCapabilities(Option<VaapiHardwareCapabilities> vaapiHardwareCapabilities, QsvInitMode initMode)
: IHardwareCapabilities
{
public QsvInitMode InitMode { get; } = initMode;
public FFmpegCapability CanDecode(
string videoFormat,
Option<string> videoProfile,
Option<IPixelFormat> maybePixelFormat,
bool isHdr)
{
foreach (var vaapi in vaapiHardwareCapabilities)
{
return vaapi.CanDecode(videoFormat, videoProfile, maybePixelFormat, isHdr);
}
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) => FFmpegCapability.Software,
_ => FFmpegCapability.Hardware
};
}
public FFmpegCapability CanEncode(
string videoFormat,
Option<string> videoProfile,
Option<IPixelFormat> maybePixelFormat)
{
foreach (var vaapi in vaapiHardwareCapabilities)
{
return vaapi.CanEncode(videoFormat, videoProfile, 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) => FFmpegCapability.Software,
_ => FFmpegCapability.Hardware
};
}
public Option<RateControlMode> GetRateControlMode(string videoFormat, Option<IPixelFormat> maybePixelFormat) =>
Option<RateControlMode>.None;
}

20
ErsatzTV.FFmpeg/GlobalOption/HardwareAcceleration/QsvHardwareAccelerationOption.cs

@ -1,22 +1,32 @@
using ErsatzTV.FFmpeg.Capabilities; using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Capabilities.Qsv;
using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.GlobalOption.HardwareAcceleration; namespace ErsatzTV.FFmpeg.GlobalOption.HardwareAcceleration;
public class QsvHardwareAccelerationOption(Option<string> device, FFmpegCapability decodeCapability) : GlobalOption public class QsvHardwareAccelerationOption(
Option<string> device,
FFmpegCapability decodeCapability,
QsvInitMode qsvInitMode) : GlobalOption
{ {
// TODO: read this from ffmpeg output // TODO: read this from ffmpeg output
private readonly List<string> _supportedFFmpegFormats = new() private readonly List<string> _supportedFFmpegFormats =
{ [
FFmpegFormat.NV12, FFmpegFormat.NV12,
FFmpegFormat.P010LE FFmpegFormat.P010LE
}; ];
public override string[] GlobalOptions public override string[] GlobalOptions
{ {
get get
{ {
string[] initDevices = ["-init_hw_device", "qsv=hw", "-filter_hw_device", "hw"]; string[] initDevices = qsvInitMode switch
{
QsvInitMode.None => [],
QsvInitMode.Qsv => ["-init_hw_device", "qsv=hw", "-filter_hw_device", "hw"],
QsvInitMode.D3d11Va => ["-init_hw_device", "d3d11va=hw:,vendor=0x8086", "-filter_hw_device", "hw"],
_ => throw new NotSupportedException()
};
var result = new List<string> var result = new List<string>
{ {

9
ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs

@ -1,5 +1,6 @@
using System.Globalization; using System.Globalization;
using ErsatzTV.FFmpeg.Capabilities; using ErsatzTV.FFmpeg.Capabilities;
using ErsatzTV.FFmpeg.Capabilities.Qsv;
using ErsatzTV.FFmpeg.Decoder; using ErsatzTV.FFmpeg.Decoder;
using ErsatzTV.FFmpeg.Decoder.Qsv; using ErsatzTV.FFmpeg.Decoder.Qsv;
using ErsatzTV.FFmpeg.Encoder; using ErsatzTV.FFmpeg.Encoder;
@ -95,7 +96,13 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder
// give a bogus value so no cuda devices are visible to ffmpeg // give a bogus value so no cuda devices are visible to ffmpeg
pipelineSteps.Add(new CudaVisibleDevicesVariable("999")); pipelineSteps.Add(new CudaVisibleDevicesVariable("999"));
pipelineSteps.Add(new QsvHardwareAccelerationOption(ffmpegState.VaapiDevice, decodeCapability)); QsvInitMode initMode = QsvInitMode.Qsv;
if (_hardwareCapabilities is QsvHardwareCapabilities qsvHardwareCapabilities)
{
initMode = qsvHardwareCapabilities.InitMode;
}
pipelineSteps.Add(new QsvHardwareAccelerationOption(ffmpegState.VaapiDevice, decodeCapability, initMode));
// disable hw accel if decoder/encoder isn't supported // disable hw accel if decoder/encoder isn't supported
return ffmpegState with return ffmpegState with

Loading…
Cancel
Save