validate hardware accel, use hw accel for error messages (#1471)
* only display supported hw accels in ffmpeg profile editor * qsv capability improvements * qsv fixes * update changelog
This commit is contained in:
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Include `inputstream.ffmpegdirect` properties in channels.m3u when requested by Kodi
|
- Include `inputstream.ffmpegdirect` properties in channels.m3u when requested by Kodi
|
||||||
- Log playout item title and path when starting a stream
|
- Log playout item title and path when starting a stream
|
||||||
- This will help with media server libraries where the URL passed to ffmpeg doesn't indicate which file is streaming
|
- This will help with media server libraries where the URL passed to ffmpeg doesn't indicate which file is streaming
|
||||||
|
- Add QSV Capabilities to Troubleshooting page
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix playout bug that caused some schedule items with fixed start times to be pushed to the next day
|
- Fix playout bug that caused some schedule items with fixed start times to be pushed to the next day
|
||||||
@@ -25,6 +26,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Note that ffmpeg is still *always* required for playback to work
|
- Note that ffmpeg is still *always* required for playback to work
|
||||||
- Fix PGS subtitle pixel format with Intel VAAPI
|
- Fix PGS subtitle pixel format with Intel VAAPI
|
||||||
- Fix some cases where `Copy` button would fail to copy to clipboard
|
- Fix some cases where `Copy` button would fail to copy to clipboard
|
||||||
|
- Fix some cases where ffmpeg process would remain running after properly closing ErsatzTV
|
||||||
|
- Fix QSV HLS segment duration
|
||||||
|
- This behavior caused extremely slow QSV stream starts
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Upgrade ffmpeg to 6.1, which is now *required* for all installs
|
- Upgrade ffmpeg to 6.1, which is now *required* for all installs
|
||||||
@@ -36,6 +40,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- Jellyfin collection scanning will no longer happen after every (automatic or forced) library scan
|
- Jellyfin collection scanning will no longer happen after every (automatic or forced) library scan
|
||||||
- Automatic/periodic scans will check collections one time after all libraries have been scanned
|
- Automatic/periodic scans will check collections one time after all libraries have been scanned
|
||||||
- There is a new table in the `Media` > `Libraries` page with a button to manually re-scan Jellyfin collections as needed
|
- There is a new table in the `Media` > `Libraries` page with a button to manually re-scan Jellyfin collections as needed
|
||||||
|
- In FFmpeg Profile editor, only display hardware acceleration kinds that are supported by the configured ffmpeg
|
||||||
|
- Test QSV acceleration if configured, and fallback to software mode if test fails
|
||||||
|
- Detect QSV capabilities on Linux (supported decoders, encoders)
|
||||||
|
|
||||||
## [0.8.2-beta] - 2023-09-14
|
## [0.8.2-beta] - 2023-09-14
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="MediatR" Version="12.1.1" />
|
<PackageReference Include="MediatR" Version="12.1.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.7.30">
|
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.7.30">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.FFmpegProfiles;
|
||||||
|
|
||||||
|
public record GetSupportedHardwareAccelerationKinds : IRequest<List<HardwareAccelerationKind>>;
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.FFmpeg;
|
||||||
|
using ErsatzTV.FFmpeg.Capabilities;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.FFmpegProfiles;
|
||||||
|
|
||||||
|
public class
|
||||||
|
GetSupportedHardwareAccelerationKindsHandler : IRequestHandler<GetSupportedHardwareAccelerationKinds,
|
||||||
|
List<HardwareAccelerationKind>>
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<TvContext> _dbContextFactory;
|
||||||
|
private readonly IHardwareCapabilitiesFactory _hardwareCapabilitiesFactory;
|
||||||
|
|
||||||
|
public GetSupportedHardwareAccelerationKindsHandler(
|
||||||
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
IHardwareCapabilitiesFactory hardwareCapabilitiesFactory)
|
||||||
|
{
|
||||||
|
_dbContextFactory = dbContextFactory;
|
||||||
|
_hardwareCapabilitiesFactory = hardwareCapabilitiesFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<HardwareAccelerationKind>> Handle(
|
||||||
|
GetSupportedHardwareAccelerationKinds request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
Validation<BaseError, string> validation = await Validate(dbContext);
|
||||||
|
|
||||||
|
return await validation.Match(
|
||||||
|
GetHardwareAccelerationKinds,
|
||||||
|
_ => Task.FromResult(new List<HardwareAccelerationKind> { HardwareAccelerationKind.None }));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<HardwareAccelerationKind>> GetHardwareAccelerationKinds(string ffmpegPath)
|
||||||
|
{
|
||||||
|
var result = new List<HardwareAccelerationKind> { HardwareAccelerationKind.None };
|
||||||
|
|
||||||
|
IFFmpegCapabilities ffmpegCapabilities = await _hardwareCapabilitiesFactory.GetFFmpegCapabilities(ffmpegPath);
|
||||||
|
|
||||||
|
if (ffmpegCapabilities.HasHardwareAcceleration(HardwareAccelerationMode.Nvenc))
|
||||||
|
{
|
||||||
|
result.Add(HardwareAccelerationKind.Nvenc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ffmpegCapabilities.HasHardwareAcceleration(HardwareAccelerationMode.Qsv))
|
||||||
|
{
|
||||||
|
result.Add(HardwareAccelerationKind.Qsv);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ffmpegCapabilities.HasHardwareAcceleration(HardwareAccelerationMode.Vaapi))
|
||||||
|
{
|
||||||
|
result.Add(HardwareAccelerationKind.Vaapi);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ffmpegCapabilities.HasHardwareAcceleration(HardwareAccelerationMode.VideoToolbox))
|
||||||
|
{
|
||||||
|
result.Add(HardwareAccelerationKind.VideoToolbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ffmpegCapabilities.HasHardwareAcceleration(HardwareAccelerationMode.Amf))
|
||||||
|
{
|
||||||
|
result.Add(HardwareAccelerationKind.Amf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<Validation<BaseError, string>> Validate(TvContext dbContext) =>
|
||||||
|
await FFmpegPathMustExist(dbContext);
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, string>> FFmpegPathMustExist(TvContext dbContext) =>
|
||||||
|
dbContext.ConfigElements.GetValue<string>(ConfigElementKey.FFmpegPath)
|
||||||
|
.FilterT(File.Exists)
|
||||||
|
.Map(maybePath => maybePath.ToValidation<BaseError>("FFmpeg path does not exist on filesystem"));
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ using ErsatzTV.Core.Interfaces.FFmpeg;
|
|||||||
using ErsatzTV.Core.Interfaces.Metadata;
|
using ErsatzTV.Core.Interfaces.Metadata;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace ErsatzTV.Application.Streaming;
|
namespace ErsatzTV.Application.Streaming;
|
||||||
@@ -18,6 +19,7 @@ namespace ErsatzTV.Application.Streaming;
|
|||||||
public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Either<BaseError, Unit>>
|
public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Either<BaseError, Unit>>
|
||||||
{
|
{
|
||||||
private readonly IConfigElementRepository _configElementRepository;
|
private readonly IConfigElementRepository _configElementRepository;
|
||||||
|
private readonly IHostApplicationLifetime _hostApplicationLifetime;
|
||||||
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
|
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
|
||||||
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
|
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
@@ -38,6 +40,7 @@ public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Eit
|
|||||||
ILogger<HlsSessionWorker> sessionWorkerLogger,
|
ILogger<HlsSessionWorker> sessionWorkerLogger,
|
||||||
IFFmpegSegmenterService ffmpegSegmenterService,
|
IFFmpegSegmenterService ffmpegSegmenterService,
|
||||||
IConfigElementRepository configElementRepository,
|
IConfigElementRepository configElementRepository,
|
||||||
|
IHostApplicationLifetime hostApplicationLifetime,
|
||||||
ChannelWriter<IBackgroundServiceRequest> workerChannel)
|
ChannelWriter<IBackgroundServiceRequest> workerChannel)
|
||||||
{
|
{
|
||||||
_hlsPlaylistFilter = hlsPlaylistFilter;
|
_hlsPlaylistFilter = hlsPlaylistFilter;
|
||||||
@@ -49,6 +52,7 @@ public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Eit
|
|||||||
_sessionWorkerLogger = sessionWorkerLogger;
|
_sessionWorkerLogger = sessionWorkerLogger;
|
||||||
_ffmpegSegmenterService = ffmpegSegmenterService;
|
_ffmpegSegmenterService = ffmpegSegmenterService;
|
||||||
_configElementRepository = configElementRepository;
|
_configElementRepository = configElementRepository;
|
||||||
|
_hostApplicationLifetime = hostApplicationLifetime;
|
||||||
_workerChannel = workerChannel;
|
_workerChannel = workerChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +85,7 @@ public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Eit
|
|||||||
_ffmpegSegmenterService.SessionWorkers.AddOrUpdate(request.ChannelNumber, _ => worker, (_, _) => worker);
|
_ffmpegSegmenterService.SessionWorkers.AddOrUpdate(request.ChannelNumber, _ => worker, (_, _) => worker);
|
||||||
|
|
||||||
// fire and forget worker
|
// fire and forget worker
|
||||||
_ = worker.Run(request.ChannelNumber, idleTimeout, cancellationToken)
|
_ = worker.Run(request.ChannelNumber, idleTimeout, _hostApplicationLifetime.ApplicationStopping)
|
||||||
.ContinueWith(
|
.ContinueWith(
|
||||||
_ =>
|
_ =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using ErsatzTV.Core.FFmpeg;
|
|||||||
using ErsatzTV.Core.Health;
|
using ErsatzTV.Core.Health;
|
||||||
using ErsatzTV.Core.Interfaces.Repositories;
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
using ErsatzTV.FFmpeg.Capabilities;
|
using ErsatzTV.FFmpeg.Capabilities;
|
||||||
|
using ErsatzTV.FFmpeg.Capabilities.Qsv;
|
||||||
using ErsatzTV.FFmpeg.Runtime;
|
using ErsatzTV.FFmpeg.Runtime;
|
||||||
using ErsatzTV.Infrastructure.Data;
|
using ErsatzTV.Infrastructure.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -74,6 +75,7 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
string nvidiaCapabilities = null;
|
string nvidiaCapabilities = null;
|
||||||
|
string qsvCapabilities = null;
|
||||||
string vaapiCapabilities = null;
|
string vaapiCapabilities = null;
|
||||||
Option<ConfigElement> maybeFFmpegPath =
|
Option<ConfigElement> maybeFFmpegPath =
|
||||||
await _configElementRepository.GetConfigElement(ConfigElementKey.FFmpegPath);
|
await _configElementRepository.GetConfigElement(ConfigElementKey.FFmpegPath);
|
||||||
@@ -87,6 +89,20 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
|
|||||||
{
|
{
|
||||||
nvidiaCapabilities = await _hardwareCapabilitiesFactory.GetNvidiaOutput(ffmpegPath.Value);
|
nvidiaCapabilities = await _hardwareCapabilitiesFactory.GetNvidiaOutput(ffmpegPath.Value);
|
||||||
|
|
||||||
|
if (!_memoryCache.TryGetValue("ffmpeg.render_devices", out List<string> vaapiDevices))
|
||||||
|
{
|
||||||
|
vaapiDevices = new List<string> { "/dev/dri/renderD128" };
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string qsvDevice in vaapiDevices)
|
||||||
|
{
|
||||||
|
QsvOutput output = await _hardwareCapabilitiesFactory.GetQsvOutput(ffmpegPath.Value, qsvDevice);
|
||||||
|
qsvCapabilities += $"Checking device {qsvDevice}{Environment.NewLine}";
|
||||||
|
qsvCapabilities += $"Exit Code: {output.ExitCode}{Environment.NewLine}{Environment.NewLine}";
|
||||||
|
qsvCapabilities += output.Output;
|
||||||
|
qsvCapabilities += Environment.NewLine + Environment.NewLine;
|
||||||
|
}
|
||||||
|
|
||||||
if (_runtimeInfo.IsOSPlatform(OSPlatform.Linux))
|
if (_runtimeInfo.IsOSPlatform(OSPlatform.Linux))
|
||||||
{
|
{
|
||||||
var allDrivers = new List<VaapiDriver>
|
var allDrivers = new List<VaapiDriver>
|
||||||
@@ -94,11 +110,6 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
|
|||||||
|
|
||||||
foreach (VaapiDriver activeDriver in allDrivers)
|
foreach (VaapiDriver activeDriver in allDrivers)
|
||||||
{
|
{
|
||||||
if (!_memoryCache.TryGetValue("ffmpeg.render_devices", out List<string> vaapiDevices))
|
|
||||||
{
|
|
||||||
vaapiDevices = new List<string> { "/dev/dri/renderD128" };
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (string vaapiDevice in vaapiDevices)
|
foreach (string vaapiDevice in vaapiDevices)
|
||||||
{
|
{
|
||||||
foreach (string output in await _hardwareCapabilitiesFactory.GetVaapiOutput(
|
foreach (string output in await _hardwareCapabilitiesFactory.GetVaapiOutput(
|
||||||
@@ -123,6 +134,7 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
|
|||||||
activeFFmpegProfiles,
|
activeFFmpegProfiles,
|
||||||
channels,
|
channels,
|
||||||
nvidiaCapabilities,
|
nvidiaCapabilities,
|
||||||
|
qsvCapabilities,
|
||||||
vaapiCapabilities);
|
vaapiCapabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ public record TroubleshootingInfo(
|
|||||||
IEnumerable<FFmpegProfile> FFmpegProfiles,
|
IEnumerable<FFmpegProfile> FFmpegProfiles,
|
||||||
IEnumerable<Channel> Channels,
|
IEnumerable<Channel> Channels,
|
||||||
string NvidiaCapabilities,
|
string NvidiaCapabilities,
|
||||||
|
string QsvCapabilities,
|
||||||
string VaapiCapabilities);
|
string VaapiCapabilities);
|
||||||
|
|||||||
@@ -480,11 +480,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
|
|||||||
|
|
||||||
var videoInputFile = new VideoInputFile(videoPath, new List<VideoStream> { ffmpegVideoStream });
|
var videoInputFile = new VideoInputFile(videoPath, new List<VideoStream> { ffmpegVideoStream });
|
||||||
|
|
||||||
|
// TODO: ignore accel if this already failed once
|
||||||
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, FillerKind.None);
|
HardwareAccelerationMode hwAccel = GetHardwareAccelerationMode(playbackSettings, FillerKind.None);
|
||||||
|
_logger.LogDebug("HW accel mode: {HwAccel}", hwAccel);
|
||||||
|
|
||||||
var ffmpegState = new FFmpegState(
|
var ffmpegState = new FFmpegState(
|
||||||
false,
|
false,
|
||||||
hwAccel,
|
HardwareAccelerationMode.None, // no hw accel decode since errors loop
|
||||||
hwAccel,
|
hwAccel,
|
||||||
VaapiDriverName(hwAccel, vaapiDriver),
|
VaapiDriverName(hwAccel, vaapiDriver),
|
||||||
VaapiDeviceName(hwAccel, vaapiDevice),
|
VaapiDeviceName(hwAccel, vaapiDevice),
|
||||||
|
|||||||
@@ -175,8 +175,7 @@ public static class FFmpegPlaybackSettingsCalculator
|
|||||||
bool hlsRealtime) =>
|
bool hlsRealtime) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
// HardwareAcceleration = ffmpegProfile.HardwareAcceleration,
|
HardwareAcceleration = ffmpegProfile.HardwareAcceleration,
|
||||||
HardwareAcceleration = HardwareAccelerationKind.None,
|
|
||||||
FormatFlags = CommonFormatFlags,
|
FormatFlags = CommonFormatFlags,
|
||||||
VideoFormat = ffmpegProfile.VideoFormat,
|
VideoFormat = ffmpegProfile.VideoFormat,
|
||||||
VideoBitrate = ffmpegProfile.VideoBitrate,
|
VideoBitrate = ffmpegProfile.VideoBitrate,
|
||||||
|
|||||||
@@ -494,6 +494,7 @@ public class PipelineBuilderBaseTests
|
|||||||
{
|
{
|
||||||
public DefaultFFmpegCapabilities()
|
public DefaultFFmpegCapabilities()
|
||||||
: base(
|
: base(
|
||||||
|
new System.Collections.Generic.HashSet<string>(),
|
||||||
new System.Collections.Generic.HashSet<string>(),
|
new System.Collections.Generic.HashSet<string>(),
|
||||||
new System.Collections.Generic.HashSet<string>(),
|
new System.Collections.Generic.HashSet<string>(),
|
||||||
new System.Collections.Generic.HashSet<string>())
|
new System.Collections.Generic.HashSet<string>())
|
||||||
|
|||||||
@@ -5,20 +5,38 @@ namespace ErsatzTV.FFmpeg.Capabilities;
|
|||||||
|
|
||||||
public class FFmpegCapabilities : IFFmpegCapabilities
|
public class FFmpegCapabilities : IFFmpegCapabilities
|
||||||
{
|
{
|
||||||
|
private readonly IReadOnlySet<string> _ffmpegHardwareAccelerations;
|
||||||
private readonly IReadOnlySet<string> _ffmpegDecoders;
|
private readonly IReadOnlySet<string> _ffmpegDecoders;
|
||||||
private readonly IReadOnlySet<string> _ffmpegEncoders;
|
private readonly IReadOnlySet<string> _ffmpegEncoders;
|
||||||
private readonly IReadOnlySet<string> _ffmpegFilters;
|
private readonly IReadOnlySet<string> _ffmpegFilters;
|
||||||
|
|
||||||
public FFmpegCapabilities(
|
public FFmpegCapabilities(
|
||||||
|
IReadOnlySet<string> ffmpegHardwareAccelerations,
|
||||||
IReadOnlySet<string> ffmpegDecoders,
|
IReadOnlySet<string> ffmpegDecoders,
|
||||||
IReadOnlySet<string> ffmpegFilters,
|
IReadOnlySet<string> ffmpegFilters,
|
||||||
IReadOnlySet<string> ffmpegEncoders)
|
IReadOnlySet<string> ffmpegEncoders)
|
||||||
{
|
{
|
||||||
|
_ffmpegHardwareAccelerations = ffmpegHardwareAccelerations;
|
||||||
_ffmpegDecoders = ffmpegDecoders;
|
_ffmpegDecoders = ffmpegDecoders;
|
||||||
_ffmpegFilters = ffmpegFilters;
|
_ffmpegFilters = ffmpegFilters;
|
||||||
_ffmpegEncoders = ffmpegEncoders;
|
_ffmpegEncoders = ffmpegEncoders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasHardwareAcceleration(HardwareAccelerationMode hardwareAccelerationMode)
|
||||||
|
{
|
||||||
|
string accelToCheck = hardwareAccelerationMode switch
|
||||||
|
{
|
||||||
|
HardwareAccelerationMode.Amf => "amf",
|
||||||
|
HardwareAccelerationMode.Nvenc => "cuda",
|
||||||
|
HardwareAccelerationMode.Qsv => "qsv",
|
||||||
|
HardwareAccelerationMode.Vaapi => "vaapi",
|
||||||
|
HardwareAccelerationMode.VideoToolbox => "videotoolbox",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
|
||||||
|
return !string.IsNullOrWhiteSpace(accelToCheck) && _ffmpegHardwareAccelerations.Contains(accelToCheck);
|
||||||
|
}
|
||||||
|
|
||||||
public bool HasDecoder(string decoder) => _ffmpegDecoders.Contains(decoder);
|
public bool HasDecoder(string decoder) => _ffmpegDecoders.Contains(decoder);
|
||||||
|
|
||||||
public bool HasEncoder(string encoder) => _ffmpegEncoders.Contains(encoder);
|
public bool HasEncoder(string encoder) => _ffmpegEncoders.Contains(encoder);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using CliWrap;
|
using CliWrap;
|
||||||
using CliWrap.Buffered;
|
using CliWrap.Buffered;
|
||||||
|
using ErsatzTV.FFmpeg.Capabilities.Qsv;
|
||||||
using ErsatzTV.FFmpeg.Capabilities.Vaapi;
|
using ErsatzTV.FFmpeg.Capabilities.Vaapi;
|
||||||
|
using ErsatzTV.FFmpeg.GlobalOption.HardwareAcceleration;
|
||||||
|
using ErsatzTV.FFmpeg.Runtime;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -15,24 +19,35 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
|
|||||||
private const string ArchitectureCacheKey = "ffmpeg.hardware.nvidia.architecture";
|
private const string ArchitectureCacheKey = "ffmpeg.hardware.nvidia.architecture";
|
||||||
private const string ModelCacheKey = "ffmpeg.hardware.nvidia.model";
|
private const string ModelCacheKey = "ffmpeg.hardware.nvidia.model";
|
||||||
private const string VaapiCacheKeyFormat = "ffmpeg.hardware.vaapi.{0}.{1}";
|
private const string VaapiCacheKeyFormat = "ffmpeg.hardware.vaapi.{0}.{1}";
|
||||||
|
private const string QsvCacheKeyFormat = "ffmpeg.hardware.qsv.{0}";
|
||||||
private const string FFmpegCapabilitiesCacheKeyFormat = "ffmpeg.{0}";
|
private const string FFmpegCapabilitiesCacheKeyFormat = "ffmpeg.{0}";
|
||||||
private readonly ILogger<HardwareCapabilitiesFactory> _logger;
|
private readonly ILogger<HardwareCapabilitiesFactory> _logger;
|
||||||
|
|
||||||
private readonly IMemoryCache _memoryCache;
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
private readonly IRuntimeInfo _runtimeInfo;
|
||||||
|
|
||||||
public HardwareCapabilitiesFactory(IMemoryCache memoryCache, ILogger<HardwareCapabilitiesFactory> logger)
|
public HardwareCapabilitiesFactory(
|
||||||
|
IMemoryCache memoryCache,
|
||||||
|
IRuntimeInfo runtimeInfo,
|
||||||
|
ILogger<HardwareCapabilitiesFactory> logger)
|
||||||
{
|
{
|
||||||
_memoryCache = memoryCache;
|
_memoryCache = memoryCache;
|
||||||
|
_runtimeInfo = runtimeInfo;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IFFmpegCapabilities> GetFFmpegCapabilities(string ffmpegPath)
|
public async Task<IFFmpegCapabilities> GetFFmpegCapabilities(string ffmpegPath)
|
||||||
{
|
{
|
||||||
IReadOnlySet<string> ffmpegDecoders = await GetFFmpegCapabilities(ffmpegPath, "decoders");
|
// TODO: validate videotoolbox somehow
|
||||||
IReadOnlySet<string> ffmpegFilters = await GetFFmpegCapabilities(ffmpegPath, "filters");
|
// TODO: validate amf somehow
|
||||||
IReadOnlySet<string> ffmpegEncoders = await GetFFmpegCapabilities(ffmpegPath, "encoders");
|
|
||||||
|
|
||||||
return new FFmpegCapabilities(ffmpegDecoders, ffmpegFilters, ffmpegEncoders);
|
IReadOnlySet<string> ffmpegHardwareAccelerations =
|
||||||
|
await GetFFmpegCapabilities(ffmpegPath, "hwaccels", ParseFFmpegAccelLine);
|
||||||
|
IReadOnlySet<string> ffmpegDecoders = await GetFFmpegCapabilities(ffmpegPath, "decoders", ParseFFmpegLine);
|
||||||
|
IReadOnlySet<string> ffmpegFilters = await GetFFmpegCapabilities(ffmpegPath, "filters", ParseFFmpegLine);
|
||||||
|
IReadOnlySet<string> ffmpegEncoders = await GetFFmpegCapabilities(ffmpegPath, "encoders", ParseFFmpegLine);
|
||||||
|
|
||||||
|
return new FFmpegCapabilities(ffmpegHardwareAccelerations, ffmpegDecoders, ffmpegFilters, ffmpegEncoders);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IHardwareCapabilities> GetHardwareCapabilities(
|
public async Task<IHardwareCapabilities> GetHardwareCapabilities(
|
||||||
@@ -40,14 +55,31 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
|
|||||||
string ffmpegPath,
|
string ffmpegPath,
|
||||||
HardwareAccelerationMode hardwareAccelerationMode,
|
HardwareAccelerationMode hardwareAccelerationMode,
|
||||||
Option<string> vaapiDriver,
|
Option<string> vaapiDriver,
|
||||||
Option<string> vaapiDevice) =>
|
Option<string> vaapiDevice)
|
||||||
hardwareAccelerationMode switch
|
{
|
||||||
|
if (hardwareAccelerationMode is HardwareAccelerationMode.None)
|
||||||
|
{
|
||||||
|
return new NoHardwareCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ffmpegCapabilities.HasHardwareAcceleration(hardwareAccelerationMode))
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
"FFmpeg does not support {HardwareAcceleration} acceleration; will use software mode",
|
||||||
|
hardwareAccelerationMode);
|
||||||
|
|
||||||
|
return new NoHardwareCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
return hardwareAccelerationMode switch
|
||||||
{
|
{
|
||||||
HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath, ffmpegCapabilities),
|
HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath, ffmpegCapabilities),
|
||||||
|
HardwareAccelerationMode.Qsv => await GetQsvCapabilities(ffmpegPath, vaapiDevice),
|
||||||
HardwareAccelerationMode.Vaapi => await GetVaapiCapabilities(vaapiDriver, vaapiDevice),
|
HardwareAccelerationMode.Vaapi => await GetVaapiCapabilities(vaapiDriver, vaapiDevice),
|
||||||
HardwareAccelerationMode.Amf => new AmfHardwareCapabilities(),
|
HardwareAccelerationMode.Amf => new AmfHardwareCapabilities(),
|
||||||
_ => new DefaultHardwareCapabilities()
|
_ => new DefaultHardwareCapabilities()
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> GetNvidiaOutput(string ffmpegPath)
|
public async Task<string> GetNvidiaOutput(string ffmpegPath)
|
||||||
{
|
{
|
||||||
@@ -71,6 +103,33 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<QsvOutput> GetQsvOutput(string ffmpegPath, Option<string> qsvDevice)
|
||||||
|
{
|
||||||
|
var option = new QsvHardwareAccelerationOption(qsvDevice);
|
||||||
|
var arguments = option.GlobalOptions.ToList();
|
||||||
|
|
||||||
|
arguments.AddRange(
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
"-f", "lavfi",
|
||||||
|
"-i", "nullsrc",
|
||||||
|
"-t", "00:00:01",
|
||||||
|
"-c:v", "h264_qsv",
|
||||||
|
"-f", "null", "-"
|
||||||
|
});
|
||||||
|
|
||||||
|
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 new QsvOutput(result.ExitCode, output);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Option<string>> GetVaapiOutput(Option<string> vaapiDriver, string vaapiDevice)
|
public async Task<Option<string>> GetVaapiOutput(Option<string> vaapiDriver, string vaapiDevice)
|
||||||
{
|
{
|
||||||
@@ -99,13 +158,16 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
|
|||||||
return result.StandardOutput;
|
return result.StandardOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IReadOnlySet<string>> GetFFmpegCapabilities(string ffmpegPath, string capabilities)
|
private async Task<IReadOnlySet<string>> GetFFmpegCapabilities(
|
||||||
|
string ffmpegPath,
|
||||||
|
string capabilities,
|
||||||
|
Func<string, Option<string>> parseLine)
|
||||||
{
|
{
|
||||||
var cacheKey = string.Format(CultureInfo.InvariantCulture, FFmpegCapabilitiesCacheKeyFormat, capabilities);
|
var cacheKey = string.Format(CultureInfo.InvariantCulture, FFmpegCapabilitiesCacheKeyFormat, capabilities);
|
||||||
if (_memoryCache.TryGetValue(cacheKey, out IReadOnlySet<string>? cachedDecoders) &&
|
if (_memoryCache.TryGetValue(cacheKey, out IReadOnlySet<string>? cachedCapabilities) &&
|
||||||
cachedDecoders is not null)
|
cachedCapabilities is not null)
|
||||||
{
|
{
|
||||||
return cachedDecoders;
|
return cachedCapabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] arguments = { "-hide_banner", $"-{capabilities}" };
|
string[] arguments = { "-hide_banner", $"-{capabilities}" };
|
||||||
@@ -120,10 +182,17 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
|
|||||||
: result.StandardOutput;
|
: result.StandardOutput;
|
||||||
|
|
||||||
return output.Split("\n").Map(s => s.Trim())
|
return output.Split("\n").Map(s => s.Trim())
|
||||||
.Bind(l => ParseFFmpegLine(l))
|
.Bind(l => parseLine(l))
|
||||||
.ToImmutableHashSet();
|
.ToImmutableHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Option<string> ParseFFmpegAccelLine(string input)
|
||||||
|
{
|
||||||
|
const string PATTERN = @"^([\w]+)$";
|
||||||
|
Match match = Regex.Match(input, PATTERN);
|
||||||
|
return match.Success ? match.Groups[1].Value : Option<string>.None;
|
||||||
|
}
|
||||||
|
|
||||||
private static Option<string> ParseFFmpegLine(string input)
|
private static Option<string> ParseFFmpegLine(string input)
|
||||||
{
|
{
|
||||||
const string PATTERN = @"^\s*?[A-Z\.]+\s+(\w+).*";
|
const string PATTERN = @"^\s*?[A-Z\.]+\s+(\w+).*";
|
||||||
@@ -195,6 +264,71 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
|
|||||||
return new NoHardwareCapabilities();
|
return new NoHardwareCapabilities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<IHardwareCapabilities> GetQsvCapabilities(string ffmpegPath, Option<string> qsvDevice)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_runtimeInfo.IsOSPlatform(OSPlatform.Linux) && qsvDevice.IsNone)
|
||||||
|
{
|
||||||
|
// this shouldn't really happen
|
||||||
|
_logger.LogError("Cannot detect QSV capabilities without device {Device}", qsvDevice);
|
||||||
|
return new NoHardwareCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
string device = qsvDevice.IfNone(string.Empty);
|
||||||
|
var cacheKey = string.Format(CultureInfo.InvariantCulture, QsvCacheKeyFormat, device);
|
||||||
|
|
||||||
|
if (_memoryCache.TryGetValue(cacheKey, out List<VaapiProfileEntrypoint>? profileEntrypoints) &&
|
||||||
|
profileEntrypoints is not null)
|
||||||
|
{
|
||||||
|
return new VaapiHardwareCapabilities(profileEntrypoints, _logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
QsvOutput output = await GetQsvOutput(ffmpegPath, qsvDevice);
|
||||||
|
if (output.ExitCode != 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("QSV test failed; some hardware accelerated features will be unavailable");
|
||||||
|
return new NoHardwareCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_runtimeInfo.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
Option<string> vaapiOutput = await GetVaapiOutput(Option<string>.None, device);
|
||||||
|
if (vaapiOutput.IsNone)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Unable to determine QSV capabilities; please install vainfo");
|
||||||
|
return new DefaultHardwareCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string o in vaapiOutput)
|
||||||
|
{
|
||||||
|
profileEntrypoints = VaapiCapabilityParser.ParseFull(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profileEntrypoints?.Any() ?? false)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Detected {Count} VAAPI profile entrypoints for using QSV device {Device}",
|
||||||
|
profileEntrypoints.Count,
|
||||||
|
device);
|
||||||
|
|
||||||
|
_memoryCache.Set(cacheKey, profileEntrypoints);
|
||||||
|
return new VaapiHardwareCapabilities(profileEntrypoints, _logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not sure how to check capabilities on windows
|
||||||
|
return new DefaultHardwareCapabilities();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(
|
||||||
|
ex,
|
||||||
|
"Error detecting QSV capabilities; some hardware accelerated features will be unavailable");
|
||||||
|
return new NoHardwareCapabilities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<IHardwareCapabilities> GetNvidiaCapabilities(
|
private async Task<IHardwareCapabilities> GetNvidiaCapabilities(
|
||||||
string ffmpegPath,
|
string ffmpegPath,
|
||||||
IFFmpegCapabilities ffmpegCapabilities)
|
IFFmpegCapabilities ffmpegCapabilities)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace ErsatzTV.FFmpeg.Capabilities;
|
|||||||
|
|
||||||
public interface IFFmpegCapabilities
|
public interface IFFmpegCapabilities
|
||||||
{
|
{
|
||||||
|
bool HasHardwareAcceleration(HardwareAccelerationMode hardwareAccelerationMode);
|
||||||
bool HasDecoder(string decoder);
|
bool HasDecoder(string decoder);
|
||||||
bool HasEncoder(string encoder);
|
bool HasEncoder(string encoder);
|
||||||
bool HasFilter(string filter);
|
bool HasFilter(string filter);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using ErsatzTV.FFmpeg.Capabilities.Qsv;
|
||||||
|
|
||||||
namespace ErsatzTV.FFmpeg.Capabilities;
|
namespace ErsatzTV.FFmpeg.Capabilities;
|
||||||
|
|
||||||
public interface IHardwareCapabilitiesFactory
|
public interface IHardwareCapabilitiesFactory
|
||||||
@@ -13,5 +15,7 @@ public interface IHardwareCapabilitiesFactory
|
|||||||
|
|
||||||
Task<string> GetNvidiaOutput(string ffmpegPath);
|
Task<string> GetNvidiaOutput(string ffmpegPath);
|
||||||
|
|
||||||
|
Task<QsvOutput> GetQsvOutput(string ffmpegPath, Option<string> qsvDevice);
|
||||||
|
|
||||||
Task<Option<string>> GetVaapiOutput(Option<string> vaapiDriver, string vaapiDevice);
|
Task<Option<string>> GetVaapiOutput(Option<string> vaapiDriver, string vaapiDevice);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.FFmpeg.Capabilities.Qsv;
|
||||||
|
|
||||||
|
public record QsvOutput(int ExitCode, string Output);
|
||||||
@@ -7,18 +7,21 @@ public class OutputFormatHls : IPipelineStep
|
|||||||
private readonly FrameState _desiredState;
|
private readonly FrameState _desiredState;
|
||||||
private readonly Option<string> _mediaFrameRate;
|
private readonly Option<string> _mediaFrameRate;
|
||||||
private readonly string _playlistPath;
|
private readonly string _playlistPath;
|
||||||
|
private readonly bool _oneSecondGop;
|
||||||
private readonly string _segmentTemplate;
|
private readonly string _segmentTemplate;
|
||||||
|
|
||||||
public OutputFormatHls(
|
public OutputFormatHls(
|
||||||
FrameState desiredState,
|
FrameState desiredState,
|
||||||
Option<string> mediaFrameRate,
|
Option<string> mediaFrameRate,
|
||||||
string segmentTemplate,
|
string segmentTemplate,
|
||||||
string playlistPath)
|
string playlistPath,
|
||||||
|
bool oneSecondGop = false)
|
||||||
{
|
{
|
||||||
_desiredState = desiredState;
|
_desiredState = desiredState;
|
||||||
_mediaFrameRate = mediaFrameRate;
|
_mediaFrameRate = mediaFrameRate;
|
||||||
_segmentTemplate = segmentTemplate;
|
_segmentTemplate = segmentTemplate;
|
||||||
_playlistPath = playlistPath;
|
_playlistPath = playlistPath;
|
||||||
|
_oneSecondGop = oneSecondGop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
|
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
|
||||||
@@ -33,9 +36,11 @@ public class OutputFormatHls : IPipelineStep
|
|||||||
const int SEGMENT_SECONDS = 4;
|
const int SEGMENT_SECONDS = 4;
|
||||||
int frameRate = _desiredState.FrameRate.IfNone(GetFrameRateFromMedia);
|
int frameRate = _desiredState.FrameRate.IfNone(GetFrameRateFromMedia);
|
||||||
|
|
||||||
|
int gop = _oneSecondGop ? frameRate : frameRate * SEGMENT_SECONDS;
|
||||||
|
|
||||||
return new List<string>
|
return new List<string>
|
||||||
{
|
{
|
||||||
"-g", $"{frameRate * SEGMENT_SECONDS}",
|
"-g", $"{gop}",
|
||||||
"-keyint_min", $"{frameRate * SEGMENT_SECONDS}",
|
"-keyint_min", $"{frameRate * SEGMENT_SECONDS}",
|
||||||
"-force_key_frames", $"expr:gte(t,n_forced*{SEGMENT_SECONDS})",
|
"-force_key_frames", $"expr:gte(t,n_forced*{SEGMENT_SECONDS})",
|
||||||
"-f", "hls",
|
"-f", "hls",
|
||||||
|
|||||||
@@ -274,7 +274,8 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
|
|||||||
desiredState,
|
desiredState,
|
||||||
videoStream.FrameRate,
|
videoStream.FrameRate,
|
||||||
segmentTemplate,
|
segmentTemplate,
|
||||||
playlistPath));
|
playlistPath,
|
||||||
|
ffmpegState.EncoderHardwareAccelerationMode is HardwareAccelerationMode.Qsv));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
|
|||||||
reportsFolder,
|
reportsFolder,
|
||||||
fontsFolder,
|
fontsFolder,
|
||||||
_logger),
|
_logger),
|
||||||
HardwareAccelerationMode.Qsv => new QsvPipelineBuilder(
|
HardwareAccelerationMode.Qsv when capabilities is not NoHardwareCapabilities => new QsvPipelineBuilder(
|
||||||
ffmpegCapabilities,
|
ffmpegCapabilities,
|
||||||
capabilities,
|
capabilities,
|
||||||
hardwareAccelerationMode,
|
hardwareAccelerationMode,
|
||||||
@@ -76,7 +76,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
|
|||||||
reportsFolder,
|
reportsFolder,
|
||||||
fontsFolder,
|
fontsFolder,
|
||||||
_logger),
|
_logger),
|
||||||
HardwareAccelerationMode.VideoToolbox => new VideoToolboxPipelineBuilder(
|
HardwareAccelerationMode.VideoToolbox when capabilities is not NoHardwareCapabilities => new VideoToolboxPipelineBuilder(
|
||||||
ffmpegCapabilities,
|
ffmpegCapabilities,
|
||||||
capabilities,
|
capabilities,
|
||||||
hardwareAccelerationMode,
|
hardwareAccelerationMode,
|
||||||
@@ -87,7 +87,7 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory
|
|||||||
reportsFolder,
|
reportsFolder,
|
||||||
fontsFolder,
|
fontsFolder,
|
||||||
_logger),
|
_logger),
|
||||||
HardwareAccelerationMode.Amf => new AmfPipelineBuilder(
|
HardwareAccelerationMode.Amf when capabilities is not NoHardwareCapabilities => new AmfPipelineBuilder(
|
||||||
ffmpegCapabilities,
|
ffmpegCapabilities,
|
||||||
capabilities,
|
capabilities,
|
||||||
hardwareAccelerationMode,
|
hardwareAccelerationMode,
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ public class TranscodingTests
|
|||||||
//new FakeNvidiaCapabilitiesFactory(),
|
//new FakeNvidiaCapabilitiesFactory(),
|
||||||
new HardwareCapabilitiesFactory(
|
new HardwareCapabilitiesFactory(
|
||||||
MemoryCache,
|
MemoryCache,
|
||||||
|
new RuntimeInfo(),
|
||||||
LoggerFactory.CreateLogger<HardwareCapabilitiesFactory>()),
|
LoggerFactory.CreateLogger<HardwareCapabilitiesFactory>()),
|
||||||
LoggerFactory.CreateLogger<PipelineBuilderFactory>()),
|
LoggerFactory.CreateLogger<PipelineBuilderFactory>()),
|
||||||
Substitute.For<IConfigElementRepository>(),
|
Substitute.For<IConfigElementRepository>(),
|
||||||
@@ -861,6 +862,7 @@ public class TranscodingTests
|
|||||||
//new FakeNvidiaCapabilitiesFactory(),
|
//new FakeNvidiaCapabilitiesFactory(),
|
||||||
new HardwareCapabilitiesFactory(
|
new HardwareCapabilitiesFactory(
|
||||||
MemoryCache,
|
MemoryCache,
|
||||||
|
new RuntimeInfo(),
|
||||||
LoggerFactory.CreateLogger<HardwareCapabilitiesFactory>()),
|
LoggerFactory.CreateLogger<HardwareCapabilitiesFactory>()),
|
||||||
LoggerFactory.CreateLogger<PipelineBuilderFactory>()),
|
LoggerFactory.CreateLogger<PipelineBuilderFactory>()),
|
||||||
Substitute.For<IConfigElementRepository>(),
|
Substitute.For<IConfigElementRepository>(),
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
</MudElement>
|
</MudElement>
|
||||||
<MudElement HtmlTag="div" Class="mt-3">
|
<MudElement HtmlTag="div" Class="mt-3">
|
||||||
<MudSelect Label="Hardware Acceleration" @bind-Value="_model.HardwareAcceleration" For="@(() => _model.HardwareAcceleration)">
|
<MudSelect Label="Hardware Acceleration" @bind-Value="_model.HardwareAcceleration" For="@(() => _model.HardwareAcceleration)">
|
||||||
@foreach (HardwareAccelerationKind hwAccel in Enum.GetValues<HardwareAccelerationKind>())
|
@foreach (HardwareAccelerationKind hwAccel in _hardwareAccelerationKinds)
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@hwAccel">@hwAccel</MudSelectItem>
|
<MudSelectItem Value="@hwAccel">@hwAccel</MudSelectItem>
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudElement>
|
</MudElement>
|
||||||
}
|
}
|
||||||
@if (_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi || _model.HardwareAcceleration == HardwareAccelerationKind.Qsv)
|
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv)
|
||||||
{
|
{
|
||||||
<MudElement HtmlTag="div" Class="mt-3">
|
<MudElement HtmlTag="div" Class="mt-3">
|
||||||
<MudSelect Disabled="@(_model.HardwareAcceleration != HardwareAccelerationKind.Vaapi && _model.HardwareAcceleration != HardwareAccelerationKind.Qsv)"
|
<MudSelect Disabled="@(_model.HardwareAcceleration != HardwareAccelerationKind.Vaapi && _model.HardwareAcceleration != HardwareAccelerationKind.Qsv)"
|
||||||
@@ -167,6 +167,7 @@
|
|||||||
private ValidationMessageStore _messageStore;
|
private ValidationMessageStore _messageStore;
|
||||||
|
|
||||||
private List<ResolutionViewModel> _resolutions = new();
|
private List<ResolutionViewModel> _resolutions = new();
|
||||||
|
private List<HardwareAccelerationKind> _hardwareAccelerationKinds = new();
|
||||||
private List<string> _vaapiDevices = new();
|
private List<string> _vaapiDevices = new();
|
||||||
private PersistingComponentStateSubscription _persistingSubscription;
|
private PersistingComponentStateSubscription _persistingSubscription;
|
||||||
|
|
||||||
@@ -195,6 +196,15 @@
|
|||||||
{
|
{
|
||||||
_resolutions = restoredResolutions;
|
_resolutions = restoredResolutions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ApplicationState.TryTakeFromJson("_hardwareAccelerationKinds", out List<HardwareAccelerationKind> restoredHardwareAccelerationKinds))
|
||||||
|
{
|
||||||
|
_hardwareAccelerationKinds = await _mediator.Send(new GetSupportedHardwareAccelerationKinds(), _cts.Token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_hardwareAccelerationKinds = restoredHardwareAccelerationKinds;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsEdit)
|
if (IsEdit)
|
||||||
{
|
{
|
||||||
@@ -221,6 +231,11 @@
|
|||||||
_model = new FFmpegProfileEditViewModel(await _mediator.Send(new NewFFmpegProfile(), _cts.Token));
|
_model = new FFmpegProfileEditViewModel(await _mediator.Send(new NewFFmpegProfile(), _cts.Token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_hardwareAccelerationKinds.Contains(_model.HardwareAcceleration))
|
||||||
|
{
|
||||||
|
_model.HardwareAcceleration = HardwareAccelerationKind.None;
|
||||||
|
}
|
||||||
|
|
||||||
_editContext = new EditContext(_model);
|
_editContext = new EditContext(_model);
|
||||||
_messageStore = new ValidationMessageStore(_editContext);
|
_messageStore = new ValidationMessageStore(_editContext);
|
||||||
|
|
||||||
@@ -236,6 +251,7 @@
|
|||||||
{
|
{
|
||||||
ApplicationState.PersistAsJson("_model", _model);
|
ApplicationState.PersistAsJson("_model", _model);
|
||||||
ApplicationState.PersistAsJson("_resolutions", _resolutions);
|
ApplicationState.PersistAsJson("_resolutions", _resolutions);
|
||||||
|
ApplicationState.PersistAsJson("_hardwareAccelerationKinds", _hardwareAccelerationKinds);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,16 @@
|
|||||||
Copy
|
Copy
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudExpansionPanel>
|
</MudExpansionPanel>
|
||||||
|
<MudExpansionPanel Text="QSV Capabilities" Class="mb-6">
|
||||||
|
<div class="overflow-y-scroll" style="max-height: 500px">
|
||||||
|
<pre>
|
||||||
|
<code @ref="_qsvView">@_qsvCapabilities</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => CopyToClipboard(_qsvView)">
|
||||||
|
Copy
|
||||||
|
</MudButton>
|
||||||
|
</MudExpansionPanel>
|
||||||
<MudExpansionPanel Text="VAAPI Capabilities">
|
<MudExpansionPanel Text="VAAPI Capabilities">
|
||||||
<div class="overflow-y-scroll" style="max-height: 500px">
|
<div class="overflow-y-scroll" style="max-height: 500px">
|
||||||
<pre>
|
<pre>
|
||||||
@@ -46,9 +56,11 @@
|
|||||||
private readonly CancellationTokenSource _cts = new();
|
private readonly CancellationTokenSource _cts = new();
|
||||||
private string _troubleshootingInfo;
|
private string _troubleshootingInfo;
|
||||||
private string _nvidiaCapabilities;
|
private string _nvidiaCapabilities;
|
||||||
|
private string _qsvCapabilities;
|
||||||
private string _vaapiCapabilities;
|
private string _vaapiCapabilities;
|
||||||
private ElementReference _troubleshootingView;
|
private ElementReference _troubleshootingView;
|
||||||
private ElementReference _nvidiaView;
|
private ElementReference _nvidiaView;
|
||||||
|
private ElementReference _qsvView;
|
||||||
private ElementReference _vaapiView;
|
private ElementReference _vaapiView;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -73,6 +85,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
_nvidiaCapabilities = info.NvidiaCapabilities;
|
_nvidiaCapabilities = info.NvidiaCapabilities;
|
||||||
|
_qsvCapabilities = info.QsvCapabilities;
|
||||||
_vaapiCapabilities = info.VaapiCapabilities;
|
_vaapiCapabilities = info.VaapiCapabilities;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
Reference in New Issue
Block a user