diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f5d984..7bce3eeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add environment variable `ETV_DISABLE_VULKAN` - Any non-empty value will disable use of Vulkan acceleration and force software tonemapping - This may be needed with misbehaving NVIDIA drivers on Windows +- Add health check error when invalid VAAPI device and VAAPI driver combination is used in an active ffmpeg profile + - This makes it obvious when hardware acceleration will not work as configured ### Changed - Start to make UI minimally responsive (functional on smaller screens) diff --git a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs index 9f4ffd5e..0375f483 100644 --- a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs @@ -15,6 +15,8 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities _logger = logger; } + public int EntrypointCount => _profileEntrypoints.Count; + public FFmpegCapability CanDecode( string videoFormat, Option videoProfile, diff --git a/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs index 15f5bbe7..5d7e39a7 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs @@ -1,35 +1,81 @@ -using ErsatzTV.Core.Domain; +using System.Collections.Immutable; +using ErsatzTV.Core.Domain; using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Health; using ErsatzTV.Core.Health.Checks; +using ErsatzTV.FFmpeg; +using ErsatzTV.FFmpeg.Capabilities; using ErsatzTV.Infrastructure.Data; +using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Infrastructure.Health.Checks; -public class VaapiDriverHealthCheck : BaseHealthCheck, IVaapiDriverHealthCheck +public class VaapiDriverHealthCheck( + IHardwareCapabilitiesFactory hardwareCapabilitiesFactory, + IDbContextFactory dbContextFactory) + : BaseHealthCheck, IVaapiDriverHealthCheck { - private readonly IDbContextFactory _dbContextFactory; - - public VaapiDriverHealthCheck(IDbContextFactory dbContextFactory) => - _dbContextFactory = dbContextFactory; - public override string Title => "VAAPI Driver"; public async Task Check(CancellationToken cancellationToken) { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); - List profiles = await dbContext.FFmpegProfiles - .Filter(p => p.HardwareAcceleration == HardwareAccelerationKind.Vaapi) + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + + List channels = await dbContext.Channels + .AsNoTracking() + .ToListAsync(cancellationToken); + + var channelFFmpegProfiles = channels + .Map(c => c.FFmpegProfileId) + .ToImmutableHashSet(); + + List ffmpegProfiles = await dbContext.FFmpegProfiles + .AsNoTracking() + .Include(p => p.Resolution) .ToListAsync(cancellationToken); - if (profiles.Count == 0) + var activeFFmpegProfiles = ffmpegProfiles + .Filter(f => channelFFmpegProfiles.Contains(f.Id)) + .ToList(); + + if (activeFFmpegProfiles.Count == 0) { return NotApplicableResult(); } + Option maybeFFmpegPath = await dbContext.ConfigElements.GetValue(ConfigElementKey.FFmpegPath); + if (maybeFFmpegPath.IsNone) + { + return NotApplicableResult(); + } + + foreach (string ffmpegPath in maybeFFmpegPath) + { + IFFmpegCapabilities ffmpegCapabilities = await hardwareCapabilitiesFactory.GetFFmpegCapabilities(ffmpegPath); + foreach (FFmpegProfile profile in activeFFmpegProfiles) + { + Option vaapiDriver = VaapiDriverName(profile.VaapiDriver); - var defaultProfiles = profiles + IHardwareCapabilities capabilities = await hardwareCapabilitiesFactory.GetHardwareCapabilities( + ffmpegCapabilities, + ffmpegPath, + HardwareAccelerationMode.Vaapi, + profile.VaapiDisplay, + vaapiDriver, + profile.VaapiDevice + ); + + if (capabilities is VaapiHardwareCapabilities { EntrypointCount: 0 } or NoHardwareCapabilities) + { + return FailResult( + $"FFmpeg Profile {profile.Name} is using device and driver combination ({profile.VaapiDevice} and {profile.VaapiDriver}) that reports no capabilities. Hardware Acceleration WILL NOT WORK as configured."); + } + } + } + + + var defaultProfiles = activeFFmpegProfiles .Filter(p => p.VaapiDriver == VaapiDriver.Default) .ToList(); @@ -38,4 +84,14 @@ public class VaapiDriverHealthCheck : BaseHealthCheck, IVaapiDriverHealthCheck $"{defaultProfiles.Count} FFmpeg Profile{(defaultProfiles.Count > 1 ? "s are" : " is")} set to use Default VAAPI Driver; selecting iHD (Gen 8+) or i965 (up to Gen 9) may offer better performance with Intel iGPU") : OkResult(); } + + private static Option VaapiDriverName(VaapiDriver driver) => + driver switch + { + VaapiDriver.i965 => "i965", + VaapiDriver.iHD => "iHD", + VaapiDriver.RadeonSI => "radeonsi", + VaapiDriver.Nouveau => "nouveau", + _ => Option.None + }; }