diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder')
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 32 | ||||
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 42 |
2 files changed, 72 insertions, 2 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 30bb21dcb..a865b0e4c 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -27,6 +27,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "msmpeg4", "dca", "ac3", + "ac4", "aac", "mp3", "flac", @@ -94,6 +95,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "h264_v4l2m2m", "h264_videotoolbox", "hevc_videotoolbox", + "mjpeg_videotoolbox", "h264_rkmpp", "hevc_rkmpp" }; @@ -499,6 +501,11 @@ 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 -"); + } + private IEnumerable<string> GetCodecs(Codec codec) { string codecstr = codec == Codec.Encoder ? "encoders" : "decoders"; @@ -604,6 +611,31 @@ 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(); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index d2aaba906..5cfead502 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -74,6 +74,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>(); private bool _isPkeyPauseSupported = false; + private bool _isLowPriorityHwDecodeSupported = false; private bool _isVaapiDeviceAmd = false; private bool _isVaapiDeviceInteliHD = false; @@ -194,6 +195,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _threads = EncodingHelper.GetNumberOfThreads(null, options, null); _isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding"); + _isLowPriorityHwDecodeSupported = validator.CheckSupportedHwaccelFlag("low_priority"); // Check the Vaapi device vendor if (OperatingSystem.IsLinux() @@ -813,12 +815,28 @@ namespace MediaBrowser.MediaEncoding.Encoder int? threads, int? qualityScale, ProcessPriorityClass? priority, + bool enableKeyFrameOnlyExtraction, EncodingHelper encodingHelper, CancellationToken cancellationToken) { var options = allowHwAccel ? _configurationManager.GetEncodingOptions() : new EncodingOptions(); threads ??= _threads; + if (allowHwAccel && enableKeyFrameOnlyExtraction) + { + var supportsKeyFrameOnly = (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdecDecoder) + || (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && OperatingSystem.IsWindows()) + || (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwDecoder) + || string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase); + if (!supportsKeyFrameOnly) + { + // Disable hardware acceleration when the hardware decoder does not support keyframe only mode. + allowHwAccel = false; + options = new EncodingOptions(); + } + } + // A new EncodingOptions instance must be used as to not disable HW acceleration for all of Jellyfin. // Additionally, we must set a few fields without defaults to prevent null pointer exceptions. if (!allowHwAccel) @@ -868,6 +886,17 @@ namespace MediaBrowser.MediaEncoding.Encoder inputArg = "-threads " + threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled } + if (options.HardwareAccelerationType.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase) && _isLowPriorityHwDecodeSupported) + { + // VideoToolbox supports low priority decoding, which is useful for trickplay + inputArg = "-hwaccel_flags +low_priority " + inputArg; + } + + if (enableKeyFrameOnlyExtraction) + { + inputArg = "-skip_frame nokey " + inputArg; + } + var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, vidEncoder).Trim(); if (string.IsNullOrWhiteSpace(filterParam)) { @@ -900,6 +929,14 @@ namespace MediaBrowser.MediaEncoding.Encoder encoderQuality = (100 - ((qualityScale - 1) * (100 / 30))) / 118; } + if (vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase)) + { + // videotoolbox's mjpeg encoder uses jpeg quality scaled to QP2LAMBDA (118) instead of ffmpeg defined qscale + // ffmpeg qscale is a value from 1-31, with 1 being best quality and 31 being worst + // jpeg quality is a value from 0-100, with 0 being worst quality and 100 being best + encoderQuality = 118 - ((qualityScale - 1) * (118 / 30)); + } + // Output arguments var targetDirectory = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); Directory.CreateDirectory(targetDirectory); @@ -908,12 +945,13 @@ namespace MediaBrowser.MediaEncoding.Encoder // Final command arguments var args = string.Format( CultureInfo.InvariantCulture, - "-loglevel error {0} -an -sn {1} -threads {2} -c:v {3} {4}-f {5} \"{6}\"", + "-loglevel error {0} -an -sn {1} -threads {2} -c:v {3} {4}{5}-f {6} \"{7}\"", inputArg, filterParam, outputThreads.GetValueOrDefault(_threads), vidEncoder, qualityScale.HasValue ? "-qscale:v " + encoderQuality.Value.ToString(CultureInfo.InvariantCulture) + " " : string.Empty, + vidEncoder.Contains("videotoolbox", StringComparison.InvariantCultureIgnoreCase) ? "-allow_sw 1 " : string.Empty, // allow_sw fallback for some intel macs "image2", outputPath); @@ -1212,7 +1250,7 @@ namespace MediaBrowser.MediaEncoding.Encoder var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds; // Add file path stanza to concat configuration - sw.WriteLine("file '{0}'", path); + sw.WriteLine("file '{0}'", path.Replace("'", @"'\''", StringComparison.Ordinal)); // Add duration stanza to concat configuration sw.WriteLine("duration {0}", duration); |
