diff options
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs')
| -rw-r--r-- | MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 83 |
1 files changed, 62 insertions, 21 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index e084bda27..2eb647e26 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -72,10 +72,12 @@ namespace MediaBrowser.MediaEncoding.Encoder private List<string> _decoders = new List<string>(); private List<string> _hwaccels = new List<string>(); private List<string> _filters = new List<string>(); - private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>(); + private IDictionary<FilterOptionType, bool> _filtersWithOption = new Dictionary<FilterOptionType, bool>(); + private IDictionary<BitStreamFilterOptionType, bool> _bitStreamFiltersWithOption = new Dictionary<BitStreamFilterOptionType, bool>(); private bool _isPkeyPauseSupported = false; private bool _isLowPriorityHwDecodeSupported = false; + private bool _proberSupportsFirstVideoFrame = false; private bool _isVaapiDeviceAmd = false; private bool _isVaapiDeviceInteliHD = false; @@ -83,6 +85,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private bool _isVaapiDeviceSupportVulkanDrmModifier = false; private bool _isVaapiDeviceSupportVulkanDrmInterop = false; + private bool _isVideoToolboxAv1DecodeAvailable = false; + private static string[] _vulkanImageDrmFmtModifierExts = { "VK_EXT_image_drm_format_modifier", @@ -122,7 +126,13 @@ namespace MediaBrowser.MediaEncoding.Encoder _jsonSerializerOptions = new JsonSerializerOptions(JsonDefaults.Options); _jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter()); - var semaphoreCount = 2 * Environment.ProcessorCount; + // Although the type is not nullable, this might still be null during unit tests + var semaphoreCount = serverConfig.Configuration?.ParallelImageEncodingLimit ?? 0; + if (semaphoreCount < 1) + { + semaphoreCount = Environment.ProcessorCount; + } + _thumbnailResourcePool = new(semaphoreCount); } @@ -153,6 +163,8 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public bool IsVaapiDeviceSupportVulkanDrmInterop => _isVaapiDeviceSupportVulkanDrmInterop; + public bool IsVideoToolboxAv1DecodeAvailable => _isVideoToolboxAv1DecodeAvailable; + [GeneratedRegex(@"[^\/\\]+?(\.[^\/\\\n.]+)?$")] private static partial Regex FfprobePathRegex(); @@ -212,6 +224,7 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableEncoders(validator.GetEncoders()); SetAvailableFilters(validator.GetFilters()); SetAvailableFiltersWithOption(validator.GetFiltersWithOption()); + SetAvailableBitStreamFiltersWithOption(validator.GetBitStreamFiltersWithOption()); SetAvailableHwaccels(validator.GetHwaccels()); SetMediaEncoderVersion(validator); @@ -219,6 +232,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding", _ffmpegVersion); _isLowPriorityHwDecodeSupported = validator.CheckSupportedHwaccelFlag("low_priority"); + _proberSupportsFirstVideoFrame = validator.CheckSupportedProberOption("only_first_vframe", _ffprobePath); // Check the Vaapi device vendor if (OperatingSystem.IsLinux() @@ -255,6 +269,12 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger.LogInformation("VAAPI device {RenderNodePath} supports Vulkan DRM interop", options.VaapiDevice); } } + + // Check if VideoToolbox supports AV1 decode + if (OperatingSystem.IsMacOS() && SupportsHwaccel("videotoolbox")) + { + _isVideoToolboxAv1DecodeAvailable = validator.CheckIsVideoToolboxAv1DecodeAvailable(); + } } _logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty); @@ -321,11 +341,16 @@ namespace MediaBrowser.MediaEncoding.Encoder _filters = list.ToList(); } - public void SetAvailableFiltersWithOption(IDictionary<int, bool> dict) + public void SetAvailableFiltersWithOption(IDictionary<FilterOptionType, bool> dict) { _filtersWithOption = dict; } + public void SetAvailableBitStreamFiltersWithOption(IDictionary<BitStreamFilterOptionType, bool> dict) + { + _bitStreamFiltersWithOption = dict; + } + public void SetMediaEncoderVersion(EncoderValidator validator) { _ffmpegVersion = validator.GetFFmpegVersion(); @@ -358,12 +383,12 @@ namespace MediaBrowser.MediaEncoding.Encoder /// <inheritdoc /> public bool SupportsFilterWithOption(FilterOptionType option) { - if (_filtersWithOption.TryGetValue((int)option, out var val)) - { - return val; - } + return _filtersWithOption.TryGetValue(option, out var val) && val; + } - return false; + public bool SupportsBitStreamFilterWithOption(BitStreamFilterOptionType option) + { + return _bitStreamFiltersWithOption.TryGetValue(option, out var val) && val; } public bool CanEncodeToAudioCodec(string codec) @@ -485,6 +510,12 @@ namespace MediaBrowser.MediaEncoding.Encoder var args = extractChapters ? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format" : "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format"; + + if (_proberSupportsFirstVideoFrame) + { + args += " -show_frames -only_first_vframe"; + } + args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim(); var process = new Process @@ -506,7 +537,7 @@ namespace MediaBrowser.MediaEncoding.Encoder EnableRaisingEvents = true }; - _logger.LogInformation("Starting {ProcessFileName} with args {ProcessArgs}", _ffprobePath, args); + _logger.LogDebug("Starting {ProcessFileName} with args {ProcessArgs}", _ffprobePath, args); var memoryStream = new MemoryStream(); await using (memoryStream.ConfigureAwait(false)) @@ -606,7 +637,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } catch (Exception ex) { - _logger.LogError(ex, "I-frame image extraction failed, will attempt standard way. Input: {Arguments}", inputArgument); + _logger.LogWarning(ex, "I-frame image extraction failed, will attempt standard way. Input: {Arguments}", inputArgument); } } @@ -684,13 +715,11 @@ namespace MediaBrowser.MediaEncoding.Encoder filters.Add(scaler); - // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. - // mpegts need larger batch size otherwise the corrupted thumbnail will be created. Larger batch size will lower the processing speed. + // Use ffmpeg to sample N frames and pick the best thumbnail. Have a fall back just in case. var enableThumbnail = !useTradeoff && useIFrame && !string.Equals("wtv", container, StringComparison.OrdinalIgnoreCase); if (enableThumbnail) { - var useLargerBatchSize = string.Equals("mpegts", container, StringComparison.OrdinalIgnoreCase); - filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24")); + filters.Add("thumbnail=n=24"); } // Use SW tonemap on HDR video stream only when the zscale or tonemapx filter is available. @@ -703,25 +732,37 @@ namespace MediaBrowser.MediaEncoding.Encoder { var peak = videoStream.VideoRangeType == VideoRangeType.DOVI ? "400" : "100"; enableHdrExtraction = true; - filters.Add($"tonemapx=tonemap=bt2390:desat=0:peak={peak}:t=bt709:m=bt709:p=bt709:format=yuv420p"); + filters.Add($"tonemapx=tonemap=bt2390:desat=0:peak={peak}:t=bt709:m=bt709:p=bt709:format=yuv420p:range=full"); } else if (SupportsFilter("zscale") && videoStream.VideoRangeType != VideoRangeType.DOVI) { enableHdrExtraction = true; - filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p"); + filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709:out_range=full,format=yuv420p"); } } var vf = string.Join(',', filters); var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; - var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2}{5} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads, isAudio ? string.Empty : GetImageResolutionParameter()); + var args = string.Format( + CultureInfo.InvariantCulture, + "-i {0}{1} -threads {2} -v quiet -vframes 1 -vf {3}{4}{5} -f image2 \"{6}\"", + inputPath, + mapArg, + _threads, + vf, + isAudio ? string.Empty : GetImageResolutionParameter(), + EncodingHelper.GetVideoSyncOption("-1", EncoderVersion), // auto decide fps mode + tempExtractPath); if (offset.HasValue) { args = string.Format(CultureInfo.InvariantCulture, "-ss {0} ", GetTimeParameter(offset.Value)) + args; } - if (useIFrame && useTradeoff) + // The mpegts demuxer cannot seek to keyframes, so we have to let the + // decoder discard non-keyframes, which may contain corrupted images. + var seekMpegTs = offset.HasValue && string.Equals("mpegts", container, StringComparison.OrdinalIgnoreCase); + if (useIFrame && (useTradeoff || seekMpegTs)) { args = "-skip_frame nokey " + args; } @@ -1101,14 +1142,14 @@ namespace MediaBrowser.MediaEncoding.Encoder private void StopProcesses() { - List<ProcessWrapper> proceses; + List<ProcessWrapper> processes; lock (_runningProcessesLock) { - proceses = _runningProcesses.ToList(); + processes = _runningProcesses.ToList(); _runningProcesses.Clear(); } - foreach (var process in proceses) + foreach (var process in processes) { if (!process.HasExited) { |
