aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs206
1 files changed, 152 insertions, 54 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 22f58ad70..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"))
@@ -1484,7 +1497,6 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- // TODO: Perhaps also use original_size=1920x800 ??
return string.Format(
CultureInfo.InvariantCulture,
"subtitles=f='{0}'{1}{2}{3}{4}{5}",
@@ -1506,7 +1518,6 @@ namespace MediaBrowser.Controller.MediaEncoding
alphaParam,
sub2videoParam,
fontParam,
- // fallbackFontParam,
setPtsParam);
}
@@ -1659,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;
@@ -1780,12 +1791,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
param += " -preset veryfast";
}
-
- // Only h264_qsv has look_ahead option
- if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
- {
- param += " -look_ahead 0";
- }
}
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_nvenc)
@@ -2072,7 +2077,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{
- param += " -x264opts:0 subme=0:me_range=4:rc_lookahead=10:me=dia:no_chroma_me:8x8dct=0:partitions=none";
+ param += " -x264opts:0 subme=0:me_range=16:rc_lookahead=10:me=hex:open_gop=0";
}
if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
@@ -2080,8 +2085,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// libx265 only accept level option in -x265-params.
// level option may cause libx265 to fail.
// libx265 cannot adjust the given level, just throw an error.
- // TODO: set fine tuned params.
- param += " -x265-params:0 no-info=1";
+ param += " -x265-params:0 subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:rskip-edge-threshold=2:no-sao=1:no-strong-intra-smoothing=1:no-scenecut=1:no-open-gop=1:no-info=1";
}
if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
@@ -3238,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");
}
@@ -3285,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
{
@@ -3378,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))
@@ -3444,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;
@@ -3481,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)
{
@@ -4021,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;
@@ -4040,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)
{
@@ -4068,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)
@@ -4078,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)
@@ -4095,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)
@@ -4229,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;
@@ -4540,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;
@@ -4812,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
{
@@ -4848,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)
@@ -5144,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"))
{
@@ -5170,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;
@@ -5290,6 +5376,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (usingHwSurface)
{
+ if (!isVtEncoder)
+ {
+ mainFilters.Add("hwdownload");
+ mainFilters.Add("format=nv12");
+ }
+
return (mainFilters, subFilters, overlayFilters);
}
@@ -5303,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);