diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs')
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 178 |
1 files changed, 134 insertions, 44 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index ae0284e3ab..68d6d215b2 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -5,15 +5,17 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Runtime.Versioning; using System.Text.RegularExpressions; +using MediaBrowser.Controller.MediaEncoding; using Microsoft.Extensions.Logging; namespace MediaBrowser.MediaEncoding.Encoder { public partial class EncoderValidator { - private static readonly string[] _requiredDecoders = new[] - { + private static readonly string[] _requiredDecoders = + [ "h264", "hevc", "vp8", @@ -27,6 +29,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "msmpeg4", "dca", "ac3", + "ac4", "aac", "mp3", "flac", @@ -54,21 +57,18 @@ namespace MediaBrowser.MediaEncoding.Encoder "vp8_rkmpp", "vp9_rkmpp", "av1_rkmpp" - }; + ]; - private static readonly string[] _requiredEncoders = new[] - { + private static readonly string[] _requiredEncoders = + [ "libx264", "libx265", "libsvtav1", - "mpeg4", - "msmpeg4", - "libvpx", - "libvpx-vp9", "aac", "aac_at", "libfdk_aac", "ac3", + "alac", "dca", "libmp3lame", "libopus", @@ -81,6 +81,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "av1_amf", "h264_qsv", "hevc_qsv", + "mjpeg_qsv", "av1_qsv", "h264_nvenc", "hevc_nvenc", @@ -88,18 +89,22 @@ namespace MediaBrowser.MediaEncoding.Encoder "h264_vaapi", "hevc_vaapi", "av1_vaapi", + "mjpeg_vaapi", "h264_v4l2m2m", "h264_videotoolbox", "hevc_videotoolbox", + "mjpeg_videotoolbox", "h264_rkmpp", - "hevc_rkmpp" - }; + "hevc_rkmpp", + "mjpeg_rkmpp" + ]; - private static readonly string[] _requiredFilters = new[] - { + private static readonly string[] _requiredFilters = + [ // sw "alphasrc", "zscale", + "tonemapx", // qsv "scale_qsv", "vpp_qsv", @@ -108,43 +113,65 @@ namespace MediaBrowser.MediaEncoding.Encoder // cuda "scale_cuda", "yadif_cuda", + "bwdif_cuda", "tonemap_cuda", "overlay_cuda", + "transpose_cuda", "hwupload_cuda", // opencl "scale_opencl", "tonemap_opencl", "overlay_opencl", + "transpose_opencl", + "yadif_opencl", + "bwdif_opencl", // vaapi "scale_vaapi", "deinterlace_vaapi", "tonemap_vaapi", "procamp_vaapi", "overlay_vaapi", + "transpose_vaapi", "hwupload_vaapi", // vulkan "libplacebo", "scale_vulkan", "overlay_vulkan", + "transpose_vulkan", + "flip_vulkan", // videotoolbox "yadif_videotoolbox", + "bwdif_videotoolbox", "scale_vt", + "transpose_vt", "overlay_videotoolbox", "tonemap_videotoolbox", // rkrga "scale_rkrga", "vpp_rkrga", "overlay_rkrga" + ]; + + private static readonly Dictionary<FilterOptionType, (string, string)> _filterOptionsDict = new Dictionary<FilterOptionType, (string, string)> + { + { FilterOptionType.ScaleCudaFormat, ("scale_cuda", "format") }, + { FilterOptionType.TonemapCudaName, ("tonemap_cuda", "GPU accelerated HDR to SDR tonemapping") }, + { FilterOptionType.TonemapOpenclBt2390, ("tonemap_opencl", "bt2390") }, + { FilterOptionType.OverlayOpenclFrameSync, ("overlay_opencl", "Action to take when encountering EOF from secondary input") }, + { FilterOptionType.OverlayVaapiFrameSync, ("overlay_vaapi", "Action to take when encountering EOF from secondary input") }, + { FilterOptionType.OverlayVulkanFrameSync, ("overlay_vulkan", "Action to take when encountering EOF from secondary input") }, + { FilterOptionType.TransposeOpenclReversal, ("transpose_opencl", "rotate by half-turn") }, + { FilterOptionType.OverlayOpenclAlphaFormat, ("overlay_opencl", "alpha_format") }, + { FilterOptionType.OverlayCudaAlphaFormat, ("overlay_cuda", "alpha_format") } }; - private static readonly Dictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]> + private static readonly Dictionary<BitStreamFilterOptionType, (string, string)> _bsfOptionsDict = new Dictionary<BitStreamFilterOptionType, (string, string)> { - { 0, new string[] { "scale_cuda", "Output format (default \"same\")" } }, - { 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } }, - { 2, new string[] { "tonemap_opencl", "bt2390" } }, - { 3, new string[] { "overlay_opencl", "Action to take when encountering EOF from secondary input" } }, - { 4, new string[] { "overlay_vaapi", "Action to take when encountering EOF from secondary input" } }, - { 5, new string[] { "overlay_vulkan", "Action to take when encountering EOF from secondary input" } } + { BitStreamFilterOptionType.HevcMetadataRemoveDovi, ("hevc_metadata", "remove_dovi") }, + { BitStreamFilterOptionType.HevcMetadataRemoveHdr10Plus, ("hevc_metadata", "remove_hdr10plus") }, + { BitStreamFilterOptionType.Av1MetadataRemoveDovi, ("av1_metadata", "remove_dovi") }, + { BitStreamFilterOptionType.Av1MetadataRemoveHdr10Plus, ("av1_metadata", "remove_hdr10plus") }, + { BitStreamFilterOptionType.DoviRpuStrip, ("dovi_rpu", "strip") } }; // These are the library versions that corresponds to our minimum ffmpeg version 4.4 according to the version table below @@ -165,6 +192,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly string _encoderPath; + private readonly Version _minFFmpegMultiThreadedCli = new Version(7, 0); + public EncoderValidator(ILogger logger, string encoderPath) { _logger = logger; @@ -269,7 +298,11 @@ namespace MediaBrowser.MediaEncoding.Encoder public IEnumerable<string> GetFilters() => GetFFmpegFilters(); - public IDictionary<int, bool> GetFiltersWithOption() => GetFFmpegFiltersWithOption(); + public IDictionary<FilterOptionType, bool> GetFiltersWithOption() => _filterOptionsDict + .ToDictionary(item => item.Key, item => CheckFilterWithOption(item.Value.Item1, item.Value.Item2)); + + public IDictionary<BitStreamFilterOptionType, bool> GetBitStreamFiltersWithOption() => _bsfOptionsDict + .ToDictionary(item => item.Key, item => CheckBitStreamFilterWithOption(item.Value.Item1, item.Value.Item2)); public Version? GetFFmpegVersion() { @@ -423,6 +456,12 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + [SupportedOSPlatform("macos")] + public bool CheckIsVideoToolboxAv1DecodeAvailable() + { + return ApplePlatformHelper.HasAv1HardwareAccel(_logger); + } + private IEnumerable<string> GetHwaccelTypes() { string? output = null; @@ -437,10 +476,10 @@ namespace MediaBrowser.MediaEncoding.Encoder if (string.IsNullOrWhiteSpace(output)) { - return Enumerable.Empty<string>(); + return []; } - var found = output.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList(); + var found = output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries).Skip(1).Distinct().ToList(); _logger.LogInformation("Available hwaccel types: {Types}", found); return found; @@ -474,7 +513,35 @@ namespace MediaBrowser.MediaEncoding.Encoder return false; } - public bool CheckSupportedRuntimeKey(string keyDesc) + public bool CheckBitStreamFilterWithOption(string filter, string option) + { + if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option)) + { + return false; + } + + string output; + try + { + output = GetProcessOutput(_encoderPath, "-h bsf=" + filter, false, null); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error detecting the given bit stream filter"); + return false; + } + + if (output.Contains("Bit stream filter " + filter, StringComparison.Ordinal)) + { + return output.Contains(option, StringComparison.Ordinal); + } + + _logger.LogWarning("Bit stream filter: {Name} with option {Option} is not available", filter, option); + + return false; + } + + public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion) { if (string.IsNullOrEmpty(keyDesc)) { @@ -484,7 +551,9 @@ namespace MediaBrowser.MediaEncoding.Encoder string output; try { - output = GetProcessOutput(_encoderPath, "-hide_banner -f lavfi -i nullsrc=s=1x1:d=500 -f null -", true, "?"); + // With multi-threaded cli support, FFmpeg 7 is less sensitive to keyboard input + var duration = ffmpegVersion >= _minFFmpegMultiThreadedCli ? 10000 : 1000; + output = GetProcessOutput(_encoderPath, $"-hide_banner -f lavfi -i nullsrc=s=1x1:d={duration} -f null -", true, "?"); } catch (Exception ex) { @@ -495,6 +564,16 @@ namespace MediaBrowser.MediaEncoding.Encoder return output.Contains(keyDesc, StringComparison.Ordinal); } + public bool CheckSupportedHwaccelFlag(string flag) + { + return !string.IsNullOrEmpty(flag) && GetProcessExitCode(_encoderPath, $"-loglevel quiet -hwaccel_flags +{flag} -hide_banner -f lavfi -i nullsrc=s=1x1:d=100 -f null -"); + } + + public bool CheckSupportedProberOption(string option, string proberPath) + { + return !string.IsNullOrEmpty(option) && GetProcessExitCode(proberPath, $"-loglevel quiet -f lavfi -i nullsrc=s=1x1:d=1 -{option}"); + } + private IEnumerable<string> GetCodecs(Codec codec) { string codecstr = codec == Codec.Encoder ? "encoders" : "decoders"; @@ -506,12 +585,12 @@ namespace MediaBrowser.MediaEncoding.Encoder catch (Exception ex) { _logger.LogError(ex, "Error detecting available {Codec}", codecstr); - return Enumerable.Empty<string>(); + return []; } if (string.IsNullOrWhiteSpace(output)) { - return Enumerable.Empty<string>(); + return []; } var required = codec == Codec.Encoder ? _requiredEncoders : _requiredDecoders; @@ -536,12 +615,12 @@ namespace MediaBrowser.MediaEncoding.Encoder catch (Exception ex) { _logger.LogError(ex, "Error detecting available filters"); - return Enumerable.Empty<string>(); + return []; } if (string.IsNullOrWhiteSpace(output)) { - return Enumerable.Empty<string>(); + return []; } var found = FilterRegex() @@ -554,20 +633,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return found; } - private Dictionary<int, bool> GetFFmpegFiltersWithOption() - { - Dictionary<int, bool> dict = new Dictionary<int, bool>(); - for (int i = 0; i < _filterOptionsDict.Count; i++) - { - if (_filterOptionsDict.TryGetValue(i, out var val) && val.Length == 2) - { - dict.Add(i, CheckFilterWithOption(val[0], val[1])); - } - } - - return dict; - } - private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey) { var redirectStandardIn = !string.IsNullOrEmpty(testKey); @@ -600,10 +665,35 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + private bool GetProcessExitCode(string path, string arguments) + { + using var process = new Process(); + process.StartInfo = new ProcessStartInfo(path, arguments) + { + CreateNoWindow = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false + }; + _logger.LogDebug("Running {Path} {Arguments}", path, arguments); + + try + { + process.Start(); + process.WaitForExit(); + return process.ExitCode == 0; + } + catch (Exception ex) + { + _logger.LogError("Running {Path} {Arguments} failed with exception {Exception}", path, arguments, ex.Message); + return false; + } + } + [GeneratedRegex("^\\s\\S{6}\\s(?<codec>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)] private static partial Regex CodecRegex(); - [GeneratedRegex("^\\s\\S{3}\\s(?<filter>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)] + [GeneratedRegex("^\\s\\S{2,3}\\s(?<filter>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)] private static partial Regex FilterRegex(); } } |
