diff options
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
| -rw-r--r-- | MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 193 |
1 files changed, 150 insertions, 43 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index db86a49f6..24cd141dc 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -67,6 +67,8 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly Version _minFFmpegWorkingVtHwSurface = new Version(7, 0, 1); private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0); private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1); + private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1); + private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1); private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled); @@ -296,14 +298,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.VideoStream is null || !options.EnableTonemapping || GetVideoColorBitDepth(state) != 10 - || !_mediaEncoder.SupportsFilter("tonemapx") - || !(string.Equals(state.VideoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) || string.Equals(state.VideoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))) + || !_mediaEncoder.SupportsFilter("tonemapx")) { return false; } - return state.VideoStream.VideoRange == VideoRange.HDR - && state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG; + return state.VideoStream.VideoRange == VideoRange.HDR; } private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) @@ -349,7 +349,7 @@ namespace MediaBrowser.Controller.MediaEncoding && GetVideoColorBitDepth(state) == 10; } - private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options) + private bool IsIntelVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options) { if (state.VideoStream is null || !options.EnableVppTonemapping @@ -358,7 +358,14 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - // Native VPP tonemapping may come to QSV in the future. + // prefer 'tonemap_vaapi' over 'vpp_qsv' on Linux for supporting Gen9/KBLx. + // 'vpp_qsv' requires VPL, which is only supported on Gen12/TGLx and newer. + if (OperatingSystem.IsWindows() + && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.EncoderVersion < _minFFmpegQsvVppTonemapOption) + { + return false; + } return state.VideoStream.VideoRange == VideoRange.HDR && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10 @@ -879,17 +886,23 @@ namespace MediaBrowser.Controller.MediaEncoding renderNodePath); } - private string GetQsvDeviceArgs(string alias) + private string GetQsvDeviceArgs(string renderNodePath, string alias) { var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias); if (OperatingSystem.IsLinux()) { // derive qsv from vaapi device - return GetVaapiDeviceArgs(null, "iHD", "i915", null, VaapiAlias) + arg + "@" + VaapiAlias; + return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", null, VaapiAlias) + arg + "@" + VaapiAlias; } if (OperatingSystem.IsWindows()) { + // on Windows, the deviceIndex is an int + if (int.TryParse(renderNodePath, NumberStyles.Integer, CultureInfo.InvariantCulture, out int deviceIndex)) + { + return GetD3d11vaDeviceArgs(deviceIndex, string.Empty, D3d11vaAlias) + arg + "@" + D3d11vaAlias; + } + // derive qsv from d3d11va device return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias; } @@ -1049,7 +1062,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - args.Append(GetQsvDeviceArgs(QsvAlias)); + args.Append(GetQsvDeviceArgs(options.QsvDevice, QsvAlias)); var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias); // child device used by qsv. if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va")) @@ -1657,7 +1670,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv") && IsVaapiSupported(state) && IsOpenclFullSupported() - && !IsVaapiVppTonemapAvailable(state, encodingOptions) + && !IsIntelVppTonemapAvailable(state, encodingOptions) && IsHwTonemapAvailable(state, encodingOptions); enableWaFori915Hang = isIntelDecoder && doOclTonemap; @@ -3229,14 +3242,18 @@ namespace MediaBrowser.Controller.MediaEncoding doubleRateDeint ? "1" : "0"); } - public static string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix) + public string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix) { var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.AverageFrameRate ?? 60) <= 30; if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase)) { + var useBwdif = string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.SupportsFilter("bwdif_cuda"); + return string.Format( CultureInfo.InvariantCulture, - "yadif_cuda={0}:-1:0", + "{0}_cuda={1}:-1:0", + useBwdif ? "bwdif" : "yadif", doubleRateDeint ? "1" : "0"); } @@ -3276,14 +3293,31 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase)) { - args = "procamp_vaapi=b={1}:c={2},tonemap_vaapi=format={0}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32"; + var doVaVppProcamp = false; + var procampParams = string.Empty; + if (options.VppTonemappingBrightness != 0 + && options.VppTonemappingBrightness >= -100 + && options.VppTonemappingBrightness <= 100) + { + procampParams += $"=b={options.VppTonemappingBrightness}"; + doVaVppProcamp = true; + } + + if (options.VppTonemappingContrast > 1 + && options.VppTonemappingContrast <= 10) + { + procampParams += doVaVppProcamp ? ":" : "="; + procampParams += $"c={options.VppTonemappingContrast}"; + doVaVppProcamp = true; + } + + args = "{0}tonemap_vaapi=format={1}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32"; return string.Format( CultureInfo.InvariantCulture, args, - videoFormat ?? "nv12", - options.VppTonemappingBrightness, - options.VppTonemappingContrast); + doVaVppProcamp ? $"procamp_vaapi{procampParams}," : string.Empty, + videoFormat ?? "nv12"); } else { @@ -3369,15 +3403,7 @@ namespace MediaBrowser.Controller.MediaEncoding algorithm = "clip"; } - tonemapArg = ":tonemapping=" + algorithm; - - if (string.Equals(mode, "max", StringComparison.OrdinalIgnoreCase) - || string.Equals(mode, "rgb", StringComparison.OrdinalIgnoreCase)) - { - tonemapArg += ":tonemapping_mode=" + mode; - } - - tonemapArg += ":peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709"; + tonemapArg = ":tonemapping=" + algorithm + ":peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709"; if (string.Equals(range, "tv", StringComparison.OrdinalIgnoreCase) || string.Equals(range, "pc", StringComparison.OrdinalIgnoreCase)) @@ -3435,6 +3461,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); var doDeintH2645 = doDeintH264 || doDeintHevc; var doToneMap = IsSwTonemapAvailable(state, options); + var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI; var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; @@ -3472,11 +3499,13 @@ namespace MediaBrowser.Controller.MediaEncoding // sw scale mainFilters.Add(swScaleFilter); - // sw tonemap <= TODO: finish dovi tone mapping - + // sw tonemap if (doToneMap) { - var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format={outFormat}"; + // tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary + var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat; + + var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format={tonemapFormat}"; if (options.TonemappingParam != 0) { @@ -4012,7 +4041,9 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); var doDeintH2645 = doDeintH264 || doDeintHevc; - var doOclTonemap = IsHwTonemapAvailable(state, options); + var doVppTonemap = IsIntelVppTonemapAvailable(state, options); + var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options); + var doTonemap = doVppTonemap || doOclTonemap; var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; @@ -4031,7 +4062,7 @@ namespace MediaBrowser.Controller.MediaEncoding /* Make main filters for video stream */ var mainFilters = new List<string>(); - mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap)); + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap)); if (isSwDecoder) { @@ -4059,9 +4090,33 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (isD3d11vaDecoder || isQsvDecoder) { + var doVppProcamp = false; + var procampParams = string.Empty; + if (doVppTonemap) + { + if (options.VppTonemappingBrightness != 0 + && options.VppTonemappingBrightness >= -100 + && options.VppTonemappingBrightness <= 100) + { + procampParams += $":brightness={options.VppTonemappingBrightness}"; + doVppProcamp = true; + } + + if (options.VppTonemappingContrast > 1 + && options.VppTonemappingContrast <= 10) + { + procampParams += $":contrast={options.VppTonemappingContrast}"; + doVppProcamp = true; + } + + procampParams += doVppProcamp ? ":procamp=1:async_depth=2" : string.Empty; + } + var outFormat = doOclTonemap ? (doVppTranspose ? "p010" : string.Empty) : "nv12"; + outFormat = (doVppTonemap && doVppProcamp) ? "p010" : outFormat; + var swapOutputWandH = doVppTranspose && swapWAndH; - var hwScalePrefix = doVppTranspose ? "vpp" : "scale"; + var hwScalePrefix = (doVppTranspose || doVppTonemap) ? "vpp" : "scale"; var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose) @@ -4069,6 +4124,11 @@ namespace MediaBrowser.Controller.MediaEncoding hwScaleFilter += $":transpose={tranposeDir}"; } + if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap) + { + hwScaleFilter += doVppProcamp ? procampParams : ":tonemap=1"; + } + if (isD3d11vaDecoder) { if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645) @@ -4086,8 +4146,20 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(deintFilter); } - // hw transpose & scale + // hw transpose & scale & tonemap(w/o procamp) mainFilters.Add(hwScaleFilter); + + // hw tonemap(w/ procamp) + if (doVppTonemap && doVppProcamp) + { + mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2"); + } + + // force bt709 just in case vpp tonemap is not triggered or using MSDK instead of VPL. + if (doVppTonemap) + { + mainFilters.Add(GetOverwriteColorPropertiesParam(state, false)); + } } if (doOclTonemap && isHwDecoder) @@ -4220,7 +4292,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - var doVaVppTonemap = IsVaapiVppTonemapAvailable(state, options); + var doVaVppTonemap = IsIntelVppTonemapAvailable(state, options); var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options); var doTonemap = doVaVppTonemap || doOclTonemap; var doDeintH2645 = doDeintH264 || doDeintHevc; @@ -4531,7 +4603,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - var doVaVppTonemap = isVaapiDecoder && IsVaapiVppTonemapAvailable(state, options); + var doVaVppTonemap = isVaapiDecoder && IsIntelVppTonemapAvailable(state, options); var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options); var doTonemap = doVaVppTonemap || doOclTonemap; var doDeintH2645 = doDeintH264 || doDeintHevc; @@ -4803,8 +4875,34 @@ namespace MediaBrowser.Controller.MediaEncoding if (doVkTranspose || doVkTonemap || hasSubs) { // map from vaapi to vulkan/drm via interop (Polaris/gfx8+). - mainFilters.Add("hwmap=derive_device=vulkan"); - mainFilters.Add("format=vulkan"); + if (_mediaEncoder.EncoderVersion >= _minFFmpegAlteredVaVkInterop) + { + if (doVkTranspose || !_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier) + { + // disable the indirect va-drm-vk mapping since it's no longer reliable. + mainFilters.Add("hwmap=derive_device=drm"); + mainFilters.Add("format=drm_prime"); + mainFilters.Add("hwmap=derive_device=vulkan"); + mainFilters.Add("format=vulkan"); + + // workaround for libplacebo using the imported vulkan frame on gfx8. + if (!_mediaEncoder.IsVaapiDeviceSupportVulkanDrmModifier) + { + mainFilters.Add("scale_vulkan"); + } + } + else if (doVkTonemap || hasSubs) + { + // non ad-hoc libplacebo also accepts drm_prime direct input. + mainFilters.Add("hwmap=derive_device=drm"); + mainFilters.Add("format=drm_prime"); + } + } + else // legacy va-vk mapping that works only in jellyfin-ffmpeg6 + { + mainFilters.Add("hwmap=derive_device=vulkan"); + mainFilters.Add("format=vulkan"); + } } else { @@ -4839,6 +4937,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); mainFilters.Add(libplaceboFilter); + mainFilters.Add("format=vulkan"); } if (doVkTonemap && !hasSubs) @@ -5135,13 +5234,15 @@ namespace MediaBrowser.Controller.MediaEncoding return (null, null, null); } + // ReSharper disable once InconsistentNaming var isMacOS = OperatingSystem.IsMacOS(); var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; + var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported(); // legacy videotoolbox pipeline (disable hw filters) - if (!isVtEncoder + if (!(isVtEncoder || isVtDecoder) || !isVtFullSupported || !_mediaEncoder.SupportsFilter("alphasrc")) { @@ -5161,12 +5262,6 @@ namespace MediaBrowser.Controller.MediaEncoding var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); - if (!isVtEncoder) - { - // should not happen. - return (null, null, null); - } - var inW = state.VideoStream?.Width; var inH = state.VideoStream?.Height; var reqW = state.BaseRequest.Width; @@ -5281,6 +5376,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (usingHwSurface) { + if (!isVtEncoder) + { + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + } + return (mainFilters, subFilters, overlayFilters); } @@ -5294,6 +5395,12 @@ namespace MediaBrowser.Controller.MediaEncoding // this will pass-through automatically if in/out format matches. mainFilters.Insert(0, "hwupload"); mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld"); + + if (!isVtEncoder) + { + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + } } return (mainFilters, subFilters, overlayFilters); |
