diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs index 0800ca4b5..e690acf94 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs @@ -115,6 +115,10 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands await _configElementRepository.Delete(ConfigElementKey.FFmpegGlobalWatermarkId); } + await _configElementRepository.Upsert( + ConfigElementKey.FFmpegVaapiDriver, + (int)request.Settings.VaapiDriver); + return Unit.Default; } } diff --git a/ErsatzTV.Application/FFmpegProfiles/FFmpegSettingsViewModel.cs b/ErsatzTV.Application/FFmpegProfiles/FFmpegSettingsViewModel.cs index ad2de81df..339c759d3 100644 --- a/ErsatzTV.Application/FFmpegProfiles/FFmpegSettingsViewModel.cs +++ b/ErsatzTV.Application/FFmpegProfiles/FFmpegSettingsViewModel.cs @@ -1,4 +1,6 @@ -namespace ErsatzTV.Application.FFmpegProfiles +using ErsatzTV.Core.FFmpeg; + +namespace ErsatzTV.Application.FFmpegProfiles { public class FFmpegSettingsViewModel { @@ -8,5 +10,6 @@ public string PreferredLanguageCode { get; set; } public bool SaveReports { get; set; } public int? GlobalWatermarkId { get; set; } + public VaapiDriver VaapiDriver { get; set; } } } diff --git a/ErsatzTV.Application/FFmpegProfiles/Queries/GetFFmpegSettingsHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Queries/GetFFmpegSettingsHandler.cs index 8061797da..725fc00d4 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Queries/GetFFmpegSettingsHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Queries/GetFFmpegSettingsHandler.cs @@ -1,6 +1,7 @@ using System.Threading; using System.Threading.Tasks; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using MediatR; @@ -28,6 +29,8 @@ namespace ErsatzTV.Application.FFmpegProfiles.Queries await _configElementRepository.GetValue(ConfigElementKey.FFmpegPreferredLanguageCode); Option watermark = await _configElementRepository.GetValue(ConfigElementKey.FFmpegGlobalWatermarkId); + Option vaapiDriver = + await _configElementRepository.GetValue(ConfigElementKey.FFmpegVaapiDriver); var result = new FFmpegSettingsViewModel { @@ -36,6 +39,7 @@ namespace ErsatzTV.Application.FFmpegProfiles.Queries DefaultFFmpegProfileId = await defaultFFmpegProfileId.IfNoneAsync(0), SaveReports = await saveReports.IfNoneAsync(false), PreferredLanguageCode = await preferredLanguageCode.IfNoneAsync("eng"), + VaapiDriver = (VaapiDriver)await vaapiDriver.IfNoneAsync(0) }; foreach (int watermarkId in watermark) diff --git a/ErsatzTV.Application/Health/Queries/GetAllHealthCheckResultsHandler.cs b/ErsatzTV.Application/Health/Queries/GetAllHealthCheckResultsHandler.cs index 9cf19fa1d..4aeec64d8 100644 --- a/ErsatzTV.Application/Health/Queries/GetAllHealthCheckResultsHandler.cs +++ b/ErsatzTV.Application/Health/Queries/GetAllHealthCheckResultsHandler.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using ErsatzTV.Core.Health; @@ -13,9 +14,12 @@ namespace ErsatzTV.Application.Health.Queries public GetAllHealthCheckResultsHandler(IHealthCheckService healthCheckService) => _healthCheckService = healthCheckService; - public Task> Handle( + public async Task> Handle( GetAllHealthCheckResults request, - CancellationToken cancellationToken) => - _healthCheckService.PerformHealthChecks(); + CancellationToken cancellationToken) + { + List results = await _healthCheckService.PerformHealthChecks(); + return results.Filter(r => r.Status != HealthCheckStatus.NotApplicable).ToList(); + } } } diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index 890c357b5..3f383e665 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -99,6 +99,10 @@ namespace ErsatzTV.Application.Streaming.Queries watermarkId => dbContext.ChannelWatermarks .SelectOneAsync(w => w.Id, w => w.Id == watermarkId)); + Option maybeVaapiDriver = await dbContext.ConfigElements + .GetValue(ConfigElementKey.FFmpegVaapiDriver) + .MapT(i => (VaapiDriver)i); + return Right( await _ffmpegProcessService.ForPlayoutItem( ffmpegPath, @@ -108,7 +112,8 @@ namespace ErsatzTV.Application.Streaming.Queries playoutItemWithPath.Path, playoutItemWithPath.PlayoutItem.StartOffset, now, - maybeGlobalWatermark)); + maybeGlobalWatermark, + maybeVaapiDriver)); }, async error => { diff --git a/ErsatzTV.Core/Domain/ConfigElementKey.cs b/ErsatzTV.Core/Domain/ConfigElementKey.cs index 231cd3639..341a7047d 100644 --- a/ErsatzTV.Core/Domain/ConfigElementKey.cs +++ b/ErsatzTV.Core/Domain/ConfigElementKey.cs @@ -13,6 +13,7 @@ public static ConfigElementKey FFmpegSaveReports => new("ffmpeg.save_reports"); public static ConfigElementKey FFmpegPreferredLanguageCode => new("ffmpeg.preferred_language_code"); public static ConfigElementKey FFmpegGlobalWatermarkId => new("ffmpeg.global_watermark_id"); + public static ConfigElementKey FFmpegVaapiDriver => new("ffmpeg.vaapi_driver"); public static ConfigElementKey SearchIndexVersion => new("search_index.version"); public static ConfigElementKey HDHRTunerCount => new("hdhr.tuner_count"); public static ConfigElementKey ChannelsPageSize => new("pages.channels.page_size"); diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs index 8925f925e..3aa093c7e 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs @@ -43,6 +43,8 @@ namespace ErsatzTV.Core.FFmpeg private readonly bool _saveReports; private FFmpegComplexFilterBuilder _complexFilterBuilder = new(); private bool _isConcat; + private VaapiDriver _vaapiDriver; + private HardwareAccelerationKind _hwAccel; public FFmpegProcessBuilder(string ffmpegPath, bool saveReports) { @@ -50,6 +52,16 @@ namespace ErsatzTV.Core.FFmpeg _saveReports = saveReports; } + public FFmpegProcessBuilder WithVaapiDriver(Option maybeVaapiDriver) + { + foreach (VaapiDriver vaapiDriver in maybeVaapiDriver) + { + _vaapiDriver = vaapiDriver; + } + + return this; + } + public FFmpegProcessBuilder WithThreads(int threads) { _arguments.Add("-threads"); @@ -59,6 +71,8 @@ namespace ErsatzTV.Core.FFmpeg public FFmpegProcessBuilder WithHardwareAcceleration(HardwareAccelerationKind hwAccel) { + _hwAccel = hwAccel; + switch (hwAccel) { case HardwareAccelerationKind.Qsv: @@ -433,6 +447,19 @@ namespace ErsatzTV.Core.FFmpeg StandardOutputEncoding = Encoding.UTF8 }; + if (_hwAccel == HardwareAccelerationKind.Vaapi) + { + switch (_vaapiDriver) + { + case VaapiDriver.i965: + startInfo.EnvironmentVariables.Add("LIBVA_DRIVER_NAME", "i965"); + break; + case VaapiDriver.iHD: + startInfo.EnvironmentVariables.Add("LIBVA_DRIVER_NAME", "iHD"); + break; + } + } + if (_saveReports) { string fileName = _isConcat diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs index e2c5d0104..a6af725cb 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs @@ -34,7 +34,8 @@ namespace ErsatzTV.Core.FFmpeg string path, DateTimeOffset start, DateTimeOffset now, - Option globalWatermark) + Option globalWatermark, + Option maybeVaapiDriver) { MediaStream videoStream = await _ffmpegStreamSelector.SelectVideoStream(channel, version); Option maybeAudioStream = await _ffmpegStreamSelector.SelectAudioStream(channel, version); @@ -58,6 +59,7 @@ namespace ErsatzTV.Core.FFmpeg FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath, saveReports) .WithThreads(playbackSettings.ThreadCount) .WithHardwareAcceleration(playbackSettings.HardwareAcceleration) + .WithVaapiDriver(maybeVaapiDriver) .WithQuiet() .WithFormatFlags(playbackSettings.FormatFlags) .WithRealtimeOutput(playbackSettings.RealtimeOutput) diff --git a/ErsatzTV.Core/FFmpeg/VaapiDriver.cs b/ErsatzTV.Core/FFmpeg/VaapiDriver.cs new file mode 100644 index 000000000..8a6325211 --- /dev/null +++ b/ErsatzTV.Core/FFmpeg/VaapiDriver.cs @@ -0,0 +1,12 @@ +using System.Diagnostics.CodeAnalysis; + +namespace ErsatzTV.Core.FFmpeg +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum VaapiDriver + { + Default = 0, + iHD = 1, + i965 = 2 + } +} diff --git a/ErsatzTV.Core/Health/Checks/IVaapiDriverHealthCheck.cs b/ErsatzTV.Core/Health/Checks/IVaapiDriverHealthCheck.cs new file mode 100644 index 000000000..dffa83d0e --- /dev/null +++ b/ErsatzTV.Core/Health/Checks/IVaapiDriverHealthCheck.cs @@ -0,0 +1,6 @@ +namespace ErsatzTV.Core.Health.Checks +{ + public interface IVaapiDriverHealthCheck : IHealthCheck + { + } +} diff --git a/ErsatzTV.Core/Health/HealthCheckStatus.cs b/ErsatzTV.Core/Health/HealthCheckStatus.cs index 8d8c958bc..12d661c0f 100644 --- a/ErsatzTV.Core/Health/HealthCheckStatus.cs +++ b/ErsatzTV.Core/Health/HealthCheckStatus.cs @@ -2,9 +2,10 @@ { public enum HealthCheckStatus { - Pass, - Fail, - Warning, - Info + Pass = 0, + Fail = 1, + Warning = 2, + Info = 3, + NotApplicable = 4 } } diff --git a/ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs index 97eea9671..3d8a7e950 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs @@ -14,6 +14,9 @@ namespace ErsatzTV.Infrastructure.Health.Checks protected HealthCheckResult Result(HealthCheckStatus status, string message) => new(Title, status, message); + protected HealthCheckResult NotApplicableResult() => + new(Title, HealthCheckStatus.NotApplicable, string.Empty); + protected HealthCheckResult OkResult() => new(Title, HealthCheckStatus.Pass, string.Empty); diff --git a/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs new file mode 100644 index 000000000..fa98347fb --- /dev/null +++ b/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs @@ -0,0 +1,45 @@ +using System; +using System.Reflection; +using System.Threading.Tasks; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.FFmpeg; +using ErsatzTV.Core.Health; +using ErsatzTV.Core.Health.Checks; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; + +namespace ErsatzTV.Infrastructure.Health.Checks +{ + public class VaapiDriverHealthCheck : BaseHealthCheck, IVaapiDriverHealthCheck + { + private readonly IConfigElementRepository _configElementRepository; + + public VaapiDriverHealthCheck(IConfigElementRepository configElementRepository) => + _configElementRepository = configElementRepository; + + protected override string Title => "VAAPI Driver"; + + public async Task Check() + { + string version = Assembly.GetEntryAssembly()?.GetCustomAttribute() + ?.InformationalVersion ?? "unknown"; + + if (!version.Contains("docker", StringComparison.OrdinalIgnoreCase) || + !version.Contains("vaapi", StringComparison.OrdinalIgnoreCase)) + { + return NotApplicableResult(); + } + + Option maybeVaapiDriver = + await _configElementRepository.GetValue(ConfigElementKey.FFmpegVaapiDriver); + var vaapiDriver = (VaapiDriver)await maybeVaapiDriver.IfNoneAsync(0); + if (vaapiDriver == VaapiDriver.Default) + { + return InfoResult( + "Settings > FFmpeg Settings > VAAPI Driver is set to Default; selecting iHD (Gen 8+) or i965 (up to Gen 9) may offer better performance"); + } + + return OkResult(); + } + } +} diff --git a/ErsatzTV.Infrastructure/Health/HealthCheckService.cs b/ErsatzTV.Infrastructure/Health/HealthCheckService.cs index ddede362c..daaef72d4 100644 --- a/ErsatzTV.Infrastructure/Health/HealthCheckService.cs +++ b/ErsatzTV.Infrastructure/Health/HealthCheckService.cs @@ -18,7 +18,8 @@ namespace ErsatzTV.Infrastructure.Health IHardwareAccelerationHealthCheck hardwareAccelerationHealthCheck, IMovieMetadataHealthCheck movieMetadataHealthCheck, IEpisodeMetadataHealthCheck episodeMetadataHealthCheck, - IZeroDurationHealthCheck zeroDurationHealthCheck) + IZeroDurationHealthCheck zeroDurationHealthCheck, + IVaapiDriverHealthCheck vaapiDriverHealthCheck) { _checks = new List { @@ -27,7 +28,8 @@ namespace ErsatzTV.Infrastructure.Health hardwareAccelerationHealthCheck, movieMetadataHealthCheck, episodeMetadataHealthCheck, - zeroDurationHealthCheck + zeroDurationHealthCheck, + vaapiDriverHealthCheck }; } diff --git a/ErsatzTV/Pages/Settings.razor b/ErsatzTV/Pages/Settings.razor index 38dd9732d..87401618b 100644 --- a/ErsatzTV/Pages/Settings.razor +++ b/ErsatzTV/Pages/Settings.razor @@ -11,6 +11,7 @@ @using ErsatzTV.Application.Configuration.Commands @using ErsatzTV.Application.Watermarks @using ErsatzTV.Application.Watermarks.Queries +@using ErsatzTV.Core.FFmpeg @using Microsoft.AspNetCore.Components @inject IMediator _mediator @inject ISnackbar _snackbar @@ -57,6 +58,12 @@ @watermark.Name } + + @foreach (VaapiDriver driver in Enum.GetValues()) + { + @driver + } + diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index cea75ca27..53083d464 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -208,6 +208,7 @@ namespace ErsatzTV services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped();