Browse Source

fix 10-bit decoding with amd polaris (#2653)

* fix color conversion on amd polaris

* try software decode for polaris

* update changelog
pull/2654/head
Jason Dove 1 month ago committed by GitHub
parent
commit
bd7fd8984c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 24
      ErsatzTV.FFmpeg.Tests/Capabilities/Vaapi/VaapiCapabilityParserTests.cs
  3. 26
      ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs
  4. 43
      ErsatzTV.FFmpeg/Capabilities/Vaapi/VaapiCapabilityParser.cs
  5. 76
      ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs
  6. 2
      ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

1
CHANGELOG.md

@ -83,6 +83,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -83,6 +83,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add toggle to hide/show disabled channels in channel list
- Add disabled text color and `(D)` and `(H)` labels for disabled and hidden channels in channel list
- Graphics engine: fix subtitle path escaping and font loading
- Fix corrupt output (green artifacts) when decoding 10-bit content using AMD Polaris GPUs
### Changed
- Classic schedules: `Refresh` classic playouts from playout list; do not `Reset` them

24
ErsatzTV.FFmpeg.Tests/Capabilities/Vaapi/VaapiCapabilityParserTests.cs

@ -9,6 +9,16 @@ namespace ErsatzTV.FFmpeg.Tests.Capabilities.Vaapi; @@ -9,6 +9,16 @@ namespace ErsatzTV.FFmpeg.Tests.Capabilities.Vaapi;
[TestFixture]
public class VaapiCapabilityParserTests
{
private const string GenerationPolarisOutput = @"Trying display: drm
vainfo: VA-API version: 1.22 (libva 2.22.0)
vainfo: Driver version: Mesa Gallium driver 25.2.7-arch1.1 for AMD Radeon RX 550 / 550 Series (radeonsi, polaris12, ACO, DRM 3.64, 6.17.8-arch1-1)
vainfo: Supported config attributes per profile/entrypoint pair
VAProfileMPEG2Simple/VAEntrypointVLD
VAConfigAttribRTFormat : VA_RT_FORMAT_YUV420
VAConfigAttribMaxPictureWidth : 1920
VAConfigAttribMaxPictureHeight : 1088
";
private const string BriefOutput = @"Trying display: wayland
vainfo: VA-API version: 1.18 (libva 2.18.2)
vainfo: Driver version: Mesa Gallium driver 23.1.2 for AMD Radeon RX 6750 XT (navi22, LLVM 15.0.7, DRM 3.52, 6.3.8-arch1-1)
@ -201,4 +211,18 @@ VAProfileNone/VAEntrypointVideoProc @@ -201,4 +211,18 @@ VAProfileNone/VAEntrypointVideoProc
entrypoint.RateControlModes.Count.ShouldBeGreaterThan(0);
}
}
[Test]
public void Full_ShouldParseGeneration()
{
string generation = VaapiCapabilityParser.ParseGeneration(FullOutput);
generation.ShouldBe("navi22");
}
[Test]
public void Polaris_ShouldParseGeneration()
{
string generation = VaapiCapabilityParser.ParseGeneration(GenerationPolarisOutput);
generation.ShouldBe("polaris12");
}
}

26
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -29,6 +29,9 @@ public partial class HardwareCapabilitiesFactory( @@ -29,6 +29,9 @@ public partial class HardwareCapabilitiesFactory(
private static readonly CompositeFormat VaapiCacheKeyFormat =
CompositeFormat.Parse("ffmpeg.hardware.vaapi.{0}.{1}.{2}");
private static readonly CompositeFormat VaapiGenerationCacheKeyFormat =
CompositeFormat.Parse("ffmpeg.hardware.vaapi.generation.{0}.{1}.{2}");
private static readonly CompositeFormat QsvCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.qsv.{0}");
private static readonly CompositeFormat FFmpegCapabilitiesCacheKeyFormat = CompositeFormat.Parse("ffmpeg.{0}");
@ -467,12 +470,25 @@ public partial class HardwareCapabilitiesFactory( @@ -467,12 +470,25 @@ public partial class HardwareCapabilitiesFactory(
string display = vaapiDisplay.IfNone("drm");
string driver = vaapiDriver.IfNone(string.Empty);
string device = vaapiDevice.IfNone(string.Empty);
string generation = string.Empty;
var cacheKey = string.Format(CultureInfo.InvariantCulture, VaapiCacheKeyFormat, display, driver, device);
var generationCacheKey = string.Format(
CultureInfo.InvariantCulture,
VaapiGenerationCacheKeyFormat,
display,
driver,
device);
if (memoryCache.TryGetValue(cacheKey, out List<VaapiProfileEntrypoint>? profileEntrypoints) &&
profileEntrypoints is not null)
{
return new VaapiHardwareCapabilities(profileEntrypoints, logger);
if (memoryCache.TryGetValue(generationCacheKey, out string? cachedGeneration) &&
cachedGeneration is not null)
{
generation = cachedGeneration;
}
return new VaapiHardwareCapabilities(profileEntrypoints, generation, logger);
}
Option<string> output = await GetVaapiOutput(display, vaapiDriver, device);
@ -485,6 +501,7 @@ public partial class HardwareCapabilitiesFactory( @@ -485,6 +501,7 @@ public partial class HardwareCapabilitiesFactory(
foreach (string o in output)
{
profileEntrypoints = VaapiCapabilityParser.ParseFull(o);
generation = VaapiCapabilityParser.ParseGeneration(o);
}
if (profileEntrypoints is not null && profileEntrypoints.Count != 0)
@ -507,7 +524,8 @@ public partial class HardwareCapabilitiesFactory( @@ -507,7 +524,8 @@ public partial class HardwareCapabilitiesFactory(
}
memoryCache.Set(cacheKey, profileEntrypoints);
return new VaapiHardwareCapabilities(profileEntrypoints, logger);
memoryCache.Set(generationCacheKey, generation);
return new VaapiHardwareCapabilities(profileEntrypoints, generation, logger);
}
}
catch (Exception ex)
@ -541,7 +559,7 @@ public partial class HardwareCapabilitiesFactory( @@ -541,7 +559,7 @@ public partial class HardwareCapabilitiesFactory(
if (memoryCache.TryGetValue(cacheKey, out List<VaapiProfileEntrypoint>? profileEntrypoints) &&
profileEntrypoints is not null)
{
return new VaapiHardwareCapabilities(profileEntrypoints, logger);
return new VaapiHardwareCapabilities(profileEntrypoints, string.Empty, logger);
}
QsvOutput output = await GetQsvOutput(ffmpegPath, qsvDevice);
@ -583,7 +601,7 @@ public partial class HardwareCapabilitiesFactory( @@ -583,7 +601,7 @@ public partial class HardwareCapabilitiesFactory(
device);
memoryCache.Set(cacheKey, profileEntrypoints);
return new VaapiHardwareCapabilities(profileEntrypoints, logger);
return new VaapiHardwareCapabilities(profileEntrypoints, string.Empty, logger);
}
}
}

43
ErsatzTV.FFmpeg/Capabilities/Vaapi/VaapiCapabilityParser.cs

@ -2,7 +2,7 @@ using System.Text.RegularExpressions; @@ -2,7 +2,7 @@ using System.Text.RegularExpressions;
namespace ErsatzTV.FFmpeg.Capabilities.Vaapi;
public static class VaapiCapabilityParser
public static partial class VaapiCapabilityParser
{
public static List<VaapiProfileEntrypoint> Parse(string output)
{
@ -10,8 +10,7 @@ public static class VaapiCapabilityParser @@ -10,8 +10,7 @@ public static class VaapiCapabilityParser
foreach (string line in string.Join("", output).Split("\n"))
{
const string PROFILE_ENTRYPOINT_PATTERN = @"(VAProfile\w*).*(VAEntrypoint\w*)";
Match match = Regex.Match(line, PROFILE_ENTRYPOINT_PATTERN);
Match match = ProfileEntrypointRegex().Match(line);
if (match.Success)
{
profileEntrypoints.Add(
@ -33,9 +32,7 @@ public static class VaapiCapabilityParser @@ -33,9 +32,7 @@ public static class VaapiCapabilityParser
for (var i = 0; i < allLines.Length; i++)
{
string line = allLines[i];
const string PROFILE_ENTRYPOINT_PATTERN = @"(VAProfile\w*).*(VAEntrypoint\w*)";
const string PROFILE_RATE_CONTROL_PATTERN = @".*VA_RC_(\w*).*";
Match match = Regex.Match(line, PROFILE_ENTRYPOINT_PATTERN);
Match match = ProfileEntrypointRegex().Match(line);
if (match.Success)
{
profile = new VaapiProfileEntrypoint(match.Groups[1].Value.Trim(), match.Groups[2].Value.Trim());
@ -44,7 +41,7 @@ public static class VaapiCapabilityParser @@ -44,7 +41,7 @@ public static class VaapiCapabilityParser
else
{
// check for rate control
match = Regex.Match(line, PROFILE_RATE_CONTROL_PATTERN);
match = ProfileRateControlRegex().Match(line);
if (match.Success)
{
switch (match.Groups[1].Value.Trim().ToLowerInvariant())
@ -65,4 +62,36 @@ public static class VaapiCapabilityParser @@ -65,4 +62,36 @@ public static class VaapiCapabilityParser
return profileEntrypoints;
}
public static string ParseGeneration(string output)
{
string generation = string.Empty;
Match match = MiscGenerationRegex().Match(output);
if (match.Success)
{
generation = match.Groups[1].Value.Trim().ToLowerInvariant();
if (generation is "radeonsi")
{
match = RadeonSiGenerationRegex().Match(output);
if (match.Success)
{
generation = match.Groups[1].Value.Trim().ToLowerInvariant();
}
}
}
return generation;
}
[GeneratedRegex(@"(VAProfile\w*).*(VAEntrypoint\w*)")]
private static partial Regex ProfileEntrypointRegex();
[GeneratedRegex(@".*VA_RC_(\w*).*")]
private static partial Regex ProfileRateControlRegex();
[GeneratedRegex(@"Driver version:.*\(radeonsi, (\w+)")]
private static partial Regex RadeonSiGenerationRegex();
[GeneratedRegex(@"Driver version:.*\((\w+),")]
private static partial Regex MiscGenerationRegex();
}

76
ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs

@ -4,18 +4,13 @@ using Microsoft.Extensions.Logging; @@ -4,18 +4,13 @@ using Microsoft.Extensions.Logging;
namespace ErsatzTV.FFmpeg.Capabilities;
public class VaapiHardwareCapabilities : IHardwareCapabilities
public class VaapiHardwareCapabilities(
List<VaapiProfileEntrypoint> profileEntrypoints,
string generation,
ILogger logger)
: IHardwareCapabilities
{
private readonly ILogger _logger;
private readonly List<VaapiProfileEntrypoint> _profileEntrypoints;
public VaapiHardwareCapabilities(List<VaapiProfileEntrypoint> profileEntrypoints, ILogger logger)
{
_profileEntrypoints = profileEntrypoints;
_logger = logger;
}
public int EntrypointCount => _profileEntrypoints.Count;
public int EntrypointCount => profileEntrypoints.Count;
public FFmpegCapability CanDecode(
string videoFormat,
@ -25,121 +20,126 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -25,121 +20,126 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
{
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
bool isPolaris = generation.Contains("polaris", StringComparison.OrdinalIgnoreCase);
bool isHardware = (videoFormat, videoProfile.IfNone(string.Empty).ToLowerInvariant()) switch
{
// no hardware decoding of 10-bit h264
(VideoFormat.H264, _) when bitDepth == 10 => false,
// skip polaris hardware decoding 10-bit
(_, _) when bitDepth == 10 && isPolaris => false,
// no hardware decoding of h264 baseline profile
(VideoFormat.H264, "baseline" or "66") => false,
(VideoFormat.H264, "main" or "77") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.H264Main,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.H264, "high" or "100") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.H264High,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.H264, "high 10" or "110") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.H264High,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.H264, "baseline constrained" or "constrained baseline" or "578") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.H264ConstrainedBaseline,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Mpeg2Video, "main" or "4") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Mpeg2Main,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Mpeg2Video, "simple" or "5") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Mpeg2Simple,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Vc1, "simple" or "0") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Vc1Simple,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Vc1, "main" or "1") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Vc1Main,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Vc1, "advanced" or "3") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Vc1Advanced,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Hevc, "main" or "1") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.HevcMain,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Hevc, "main 10" or "2") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.HevcMain10,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Vp9, "profile 0" or "0") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Vp9Profile0,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Vp9, "profile 1" or "1") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Vp9Profile1,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Vp9, "profile 2" or "2") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Vp9Profile2,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Vp9, "profile 3" or "3") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Vp9Profile3,
VaapiEntrypoint: VaapiEntrypoint.Decode
}),
(VideoFormat.Av1, "main" or "0") =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Av1Profile0,
VaapiEntrypoint: VaapiEntrypoint.Decode
@ -151,7 +151,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -151,7 +151,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
if (!isHardware)
{
_logger.LogDebug(
logger.LogDebug(
"VAAPI does not support decoding {Format}/{Profile}, will use software decoder",
videoFormat,
videoProfile);
@ -173,35 +173,35 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -173,35 +173,35 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
VideoFormat.H264 when bitDepth == 10 => false,
VideoFormat.H264 =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.H264Main,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
}),
VideoFormat.Hevc when bitDepth == 10 =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.HevcMain10,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
}),
VideoFormat.Hevc =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.HevcMain,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
}),
VideoFormat.Av1 =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Av1Profile0,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
}),
VideoFormat.Mpeg2Video =>
_profileEntrypoints.Any(e => e is
profileEntrypoints.Any(e => e is
{
VaapiProfile: VaapiProfile.Mpeg2Main,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
@ -212,7 +212,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -212,7 +212,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
if (!isHardware)
{
_logger.LogDebug(
logger.LogDebug(
"VAAPI does not support encoding {Format} with bit depth {BitDepth}, will use software encoder",
videoFormat,
bitDepth);
@ -230,7 +230,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -230,7 +230,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
VideoFormat.H264 when bitDepth == 10 => None,
VideoFormat.H264 =>
_profileEntrypoints.Where(e => e is
profileEntrypoints.Where(e => e is
{
VaapiProfile: VaapiProfile.H264Main,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
@ -238,7 +238,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -238,7 +238,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
.HeadOrNone(),
VideoFormat.Hevc when bitDepth == 10 =>
_profileEntrypoints.Where(e => e is
profileEntrypoints.Where(e => e is
{
VaapiProfile: VaapiProfile.HevcMain10,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
@ -246,7 +246,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -246,7 +246,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
.HeadOrNone(),
VideoFormat.Hevc =>
_profileEntrypoints.Where(e => e is
profileEntrypoints.Where(e => e is
{
VaapiProfile: VaapiProfile.HevcMain,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower
@ -254,7 +254,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities @@ -254,7 +254,7 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities
.HeadOrNone(),
VideoFormat.Mpeg2Video =>
_profileEntrypoints.Where(e => e is
profileEntrypoints.Where(e => e is
{
VaapiProfile: VaapiProfile.Mpeg2Main,
VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower

2
ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs

@ -355,7 +355,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder @@ -355,7 +355,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder
if (currentState.FrameDataLocation == FrameDataLocation.Hardware)
{
result.Add(new VaapiFormatFilter(format));
result.Add(new VaapiFormatFilter(format));
}
else
{

Loading…
Cancel
Save