From deb36eeedaba2f1421b92d290d85d45bfe48d1f5 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sat, 30 Mar 2024 06:09:44 +0800 Subject: [PATCH] Add HWA video transpose support (+-90/180) Signed-off-by: nyanmisaka --- .../MediaEncoding/EncodingHelper.cs | 360 +++++++++++++----- .../MediaEncoding/FilterOptionType.cs | 7 +- .../Encoder/EncoderValidator.cs | 9 +- 3 files changed, 276 insertions(+), 100 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 85963e66c3..5143d5f747 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -62,6 +62,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1); private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0); private readonly Version _minFFmpegReadrateOption = new Version(5, 0); + private readonly Version _minFFmpegDisplayRotationOption = new Version(6, 0); private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled); @@ -229,6 +230,7 @@ namespace MediaBrowser.Controller.MediaEncoding && _mediaEncoder.SupportsFilter("tonemap_vaapi") && _mediaEncoder.SupportsFilter("procamp_vaapi") && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync) + && _mediaEncoder.SupportsFilter("transpose_vaapi") && _mediaEncoder.SupportsFilter("hwupload_vaapi"); } @@ -246,6 +248,8 @@ namespace MediaBrowser.Controller.MediaEncoding && _mediaEncoder.SupportsFilter("scale_opencl") && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390) && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync); + + // Let transpose_opencl optional for the time being. } private bool IsCudaFullSupported() @@ -256,6 +260,8 @@ namespace MediaBrowser.Controller.MediaEncoding && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName) && _mediaEncoder.SupportsFilter("overlay_cuda") && _mediaEncoder.SupportsFilter("hwupload_cuda"); + + // Let transpose_cuda optional for the time being. } private bool IsVulkanFullSupported() @@ -263,7 +269,9 @@ namespace MediaBrowser.Controller.MediaEncoding return _mediaEncoder.SupportsHwaccel("vulkan") && _mediaEncoder.SupportsFilter("libplacebo") && _mediaEncoder.SupportsFilter("scale_vulkan") - && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync); + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync) + && _mediaEncoder.SupportsFilter("transpose_vulkan") + && _mediaEncoder.SupportsFilter("flip_vulkan"); } private bool IsVideoToolboxFullSupported() @@ -273,6 +281,8 @@ namespace MediaBrowser.Controller.MediaEncoding && _mediaEncoder.SupportsFilter("overlay_videotoolbox") && _mediaEncoder.SupportsFilter("tonemap_videotoolbox") && _mediaEncoder.SupportsFilter("scale_vt"); + + // Let transpose_vt optional for the time being. } private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) @@ -1156,9 +1166,6 @@ namespace MediaBrowser.Controller.MediaEncoding args.Append(vidDecoder); } - // hw transpose filters should be added manually. - args.Append(" -noautorotate"); - return args.ToString().Trim(); } @@ -3016,8 +3023,10 @@ namespace MediaBrowser.Controller.MediaEncoding } public static string GetHwScaleFilter( + string hwScalePrefix, string hwScaleSuffix, string videoFormat, + bool swapOutputWandH, int? videoWidth, int? videoHeight, int? requestedWidth, @@ -3039,8 +3048,11 @@ namespace MediaBrowser.Controller.MediaEncoding || !videoHeight.HasValue || outHeight.Value != videoHeight.Value; - var arg1 = isSizeFixed ? ("=w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty; - var arg2 = isFormatFixed ? ("format=" + videoFormat) : string.Empty; + var swpOutW = swapOutputWandH ? outHeight.Value : outWidth.Value; + var swpOutH = swapOutputWandH ? outWidth.Value : outHeight.Value; + + var arg1 = isSizeFixed ? $"=w={swpOutW}:h={swpOutH}" : string.Empty; + var arg2 = isFormatFixed ? $"format={videoFormat}" : string.Empty; if (isFormatFixed) { arg2 = (isSizeFixed ? ':' : '=') + arg2; @@ -3050,7 +3062,8 @@ namespace MediaBrowser.Controller.MediaEncoding { return string.Format( CultureInfo.InvariantCulture, - "scale_{0}{1}{2}", + "{0}_{1}{2}{3}", + hwScalePrefix ?? "scale", hwScaleSuffix, arg1, arg2); @@ -3446,6 +3459,18 @@ namespace MediaBrowser.Controller.MediaEncoding tonemapArg); } + public string GetVideoTransposeDirection(EncodingJobInfo state) + { + return (state.VideoStream?.Rotation ?? 0) switch + { + 90 => "cclock", + 180 => "reversal", + -90 => "clock", + -180 => "reversal", + _ => string.Empty + }; + } + /// /// Gets the parameter of software filter chain. /// @@ -3479,6 +3504,11 @@ namespace MediaBrowser.Controller.MediaEncoding var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + var rotation = state.VideoStream?.Rotation ?? 0; + var swapWAndH = Math.Abs(rotation) == 90; + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + /* Make main filters for video stream */ var mainFilters = new List(); @@ -3493,7 +3523,7 @@ namespace MediaBrowser.Controller.MediaEncoding } var outFormat = isSwDecoder ? "yuv420p" : "nv12"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); if (isVaapiEncoder) { outFormat = "nv12"; @@ -3522,7 +3552,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } @@ -3596,6 +3626,13 @@ namespace MediaBrowser.Controller.MediaEncoding && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + var rotation = state.VideoStream?.Rotation ?? 0; + var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state); + var doCuTranspose = !string.IsNullOrEmpty(tranposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda"); + var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose)); + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + /* Make main filters for video stream */ var mainFilters = new List(); @@ -3612,10 +3649,10 @@ namespace MediaBrowser.Controller.MediaEncoding } var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); - mainFilters.Add("format=" + outFormat); + mainFilters.Add($"format={outFormat}"); // sw => hw if (doCuTonemap) @@ -3634,8 +3671,14 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(deintFilter); } + // hw transpose + if (doCuTranspose) + { + mainFilters.Add($"transpose_cuda=dir={tranposeDir}"); + } + var outFormat = doCuTonemap ? string.Empty : "yuv420p"; - var hwScaleFilter = GetHwScaleFilter("cuda", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); // hw scale mainFilters.Add(hwScaleFilter); } @@ -3685,7 +3728,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); subFilters.Add("format=yuva420p"); } @@ -3695,7 +3738,7 @@ namespace MediaBrowser.Controller.MediaEncoding var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); + var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=yuva420p"); @@ -3710,7 +3753,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } @@ -3786,6 +3829,14 @@ namespace MediaBrowser.Controller.MediaEncoding && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + var rotation = state.VideoStream?.Rotation ?? 0; + var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state); + var doOclTranspose = !string.IsNullOrEmpty(tranposeDir) + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal); + var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose)); + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + /* Make main filters for video stream */ var mainFilters = new List(); @@ -3802,10 +3853,10 @@ namespace MediaBrowser.Controller.MediaEncoding } var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); - mainFilters.Add("format=" + outFormat); + mainFilters.Add($"format={outFormat}"); // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. @@ -3814,7 +3865,7 @@ namespace MediaBrowser.Controller.MediaEncoding { mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=16"); mainFilters.Add("format=d3d11"); - mainFilters.Add("hwmap=derive_device=opencl"); + mainFilters.Add("hwmap=derive_device=opencl:mode=read"); } } @@ -3822,12 +3873,18 @@ namespace MediaBrowser.Controller.MediaEncoding { // INPUT d3d11 surface(vram) // map from d3d11va to opencl via d3d11-opencl interop. - mainFilters.Add("hwmap=derive_device=opencl"); + mainFilters.Add("hwmap=derive_device=opencl:mode=read"); // hw deint <= TODO: finsh the 'yadif_opencl' filter + // hw transpose + if (doOclTranspose) + { + mainFilters.Add($"transpose_opencl=dir={tranposeDir}"); + } + var outFormat = doOclTonemap ? string.Empty : "nv12"; - var hwScaleFilter = GetHwScaleFilter("opencl", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var hwScaleFilter = GetHwScaleFilter("scale", "opencl", outFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); // hw scale mainFilters.Add(hwScaleFilter); } @@ -3872,7 +3929,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // OUTPUT d3d11(nv12) surface(vram) // reverse-mapping via d3d11-opencl interop. - mainFilters.Add("hwmap=derive_device=d3d11va:reverse=1"); + mainFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1"); mainFilters.Add("format=d3d11"); } @@ -3885,7 +3942,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); subFilters.Add("format=yuva420p"); } @@ -3895,7 +3952,7 @@ namespace MediaBrowser.Controller.MediaEncoding var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); + var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=yuva420p"); @@ -3904,7 +3961,7 @@ namespace MediaBrowser.Controller.MediaEncoding subFilters.Add("hwupload=derive_device=opencl"); overlayFilters.Add("overlay_opencl=eof_action=pass:repeatlast=0"); - overlayFilters.Add("hwmap=derive_device=d3d11va:reverse=1"); + overlayFilters.Add("hwmap=derive_device=d3d11va:mode=write:reverse=1"); overlayFilters.Add("format=d3d11"); } } @@ -3912,7 +3969,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } @@ -4008,6 +4065,13 @@ namespace MediaBrowser.Controller.MediaEncoding && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + var rotation = state.VideoStream?.Rotation ?? 0; + var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state); + var doVppTranspose = !string.IsNullOrEmpty(tranposeDir); + var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTranspose)); + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + /* Make main filters for video stream */ var mainFilters = new List(); @@ -4024,10 +4088,10 @@ namespace MediaBrowser.Controller.MediaEncoding } var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12"); - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); - mainFilters.Add("format=" + outFormat); + mainFilters.Add($"format={outFormat}"); // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. @@ -4039,8 +4103,15 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (isD3d11vaDecoder || isQsvDecoder) { - var outFormat = doOclTonemap ? string.Empty : "nv12"; - var hwScaleFilter = GetHwScaleFilter("qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var outFormat = doOclTonemap ? (doVppTranspose ? "p010" : string.Empty) : "nv12"; + var swapOutputWandH = doVppTranspose && swapWAndH; + var hwScalePrefix = doVppTranspose ? "vpp" : "scale"; + var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); + + if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose) + { + hwScaleFilter += $":transpose={tranposeDir}"; + } if (isD3d11vaDecoder) { @@ -4059,14 +4130,14 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(deintFilter); } - // hw scale + // hw transpose & scale mainFilters.Add(hwScaleFilter); } if (doOclTonemap && isHwDecoder) { // map from qsv to opencl via qsv(d3d11)-opencl interop. - mainFilters.Add("hwmap=derive_device=opencl"); + mainFilters.Add("hwmap=derive_device=opencl:mode=read"); } // hw tonemap @@ -4110,7 +4181,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // OUTPUT qsv(nv12) surface(vram) // reverse-mapping via qsv(d3d11)-opencl interop. - mainFilters.Add("hwmap=derive_device=qsv:reverse=1"); + mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1"); mainFilters.Add("format=qsv"); } @@ -4124,7 +4195,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (hasGraphicalSubs) { // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, 1080); subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } @@ -4134,7 +4205,7 @@ namespace MediaBrowser.Controller.MediaEncoding var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate); + var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4145,9 +4216,9 @@ namespace MediaBrowser.Controller.MediaEncoding // default to 64 otherwise it will fail on certain iGPU. subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64"); - var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); var overlaySize = (overlayW.HasValue && overlayH.HasValue) - ? (":w=" + overlayW.Value + ":h=" + overlayH.Value) + ? $":w={overlayW.Value}:h={overlayH.Value}" : string.Empty; var overlayQsvFilter = string.Format( CultureInfo.InvariantCulture, @@ -4160,7 +4231,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } @@ -4205,6 +4276,13 @@ namespace MediaBrowser.Controller.MediaEncoding && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + var rotation = state.VideoStream?.Rotation ?? 0; + var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state); + var doVppTranspose = !string.IsNullOrEmpty(tranposeDir); + var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTranspose)); + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + /* Make main filters for video stream */ var mainFilters = new List(); @@ -4221,10 +4299,10 @@ namespace MediaBrowser.Controller.MediaEncoding } var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12"); - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); - mainFilters.Add("format=" + outFormat); + mainFilters.Add($"format={outFormat}"); // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. @@ -4236,24 +4314,39 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (isVaapiDecoder || isQsvDecoder) { + var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv"; + // INPUT vaapi/qsv surface(vram) // hw deint if (doDeintH2645) { - var deintFilter = GetHwDeinterlaceFilter(state, options, isVaapiDecoder ? "vaapi" : "qsv"); + var deintFilter = GetHwDeinterlaceFilter(state, options, hwFilterSuffix); mainFilters.Add(deintFilter); } - var outFormat = doTonemap ? string.Empty : "nv12"; - var hwScaleFilter = GetHwScaleFilter(isVaapiDecoder ? "vaapi" : "qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + // hw transpose(vaapi vpp) + if (isVaapiDecoder && doVppTranspose) + { + mainFilters.Add($"transpose_vaapi=dir={tranposeDir}"); + } - // allocate extra pool sizes for vaapi vpp + var outFormat = doOclTonemap ? ((isQsvDecoder && doVppTranspose) ? "p010" : string.Empty) : "nv12"; + var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH; + var hwScalePrefix = (isQsvDecoder && doVppTranspose) ? "vpp" : "scale"; + var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); + + if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose) + { + hwScaleFilter += $":transpose={tranposeDir}"; + } + + // allocate extra pool sizes for vaapi vpp scale if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder) { hwScaleFilter += ":extra_hw_frames=24"; } - // hw scale + // hw transpose(qsv vpp) & scale mainFilters.Add(hwScaleFilter); } @@ -4279,7 +4372,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (doOclTonemap && isHwDecoder) { // map from qsv to opencl via qsv(vaapi)-opencl interop. - mainFilters.Add("hwmap=derive_device=opencl"); + mainFilters.Add("hwmap=derive_device=opencl:mode=read"); } // ocl tonemap @@ -4326,7 +4419,7 @@ namespace MediaBrowser.Controller.MediaEncoding // OUTPUT qsv(nv12) surface(vram) // reverse-mapping via qsv(vaapi)-opencl interop. // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv. - mainFilters.Add("hwmap=derive_device=qsv:reverse=1:extra_hw_frames=16"); + mainFilters.Add("hwmap=derive_device=qsv:mode=write:reverse=1:extra_hw_frames=16"); mainFilters.Add("format=qsv"); } else if (isVaapiDecoder) @@ -4346,7 +4439,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (hasGraphicalSubs) { // overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, 1080); subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } @@ -4355,7 +4448,7 @@ namespace MediaBrowser.Controller.MediaEncoding var framerate = state.VideoStream?.RealFrameRate; var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate); + var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4366,9 +4459,9 @@ namespace MediaBrowser.Controller.MediaEncoding // default to 64 otherwise it will fail on certain iGPU. subFilters.Add("hwupload=derive_device=qsv:extra_hw_frames=64"); - var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); var overlaySize = (overlayW.HasValue && overlayH.HasValue) - ? (":w=" + overlayW.Value + ":h=" + overlayH.Value) + ? $":w={overlayW.Value}:h={overlayH.Value}" : string.Empty; var overlayQsvFilter = string.Format( CultureInfo.InvariantCulture, @@ -4381,7 +4474,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } @@ -4491,6 +4584,13 @@ namespace MediaBrowser.Controller.MediaEncoding && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + var rotation = state.VideoStream?.Rotation ?? 0; + var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state); + var doVaVppTranspose = !string.IsNullOrEmpty(tranposeDir); + var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose)); + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + /* Make main filters for video stream */ var mainFilters = new List(); @@ -4507,10 +4607,10 @@ namespace MediaBrowser.Controller.MediaEncoding } var outFormat = doOclTonemap ? "yuv420p10le" : "nv12"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); - mainFilters.Add("format=" + outFormat); + mainFilters.Add($"format={outFormat}"); // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. @@ -4530,8 +4630,14 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(deintFilter); } + // hw transpose + if (doVaVppTranspose) + { + mainFilters.Add($"transpose_vaapi=dir={tranposeDir}"); + } + var outFormat = doTonemap ? string.Empty : "nv12"; - var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); // allocate extra pool sizes for vaapi vpp if (!string.IsNullOrEmpty(hwScaleFilter)) @@ -4553,7 +4659,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (doOclTonemap && isVaapiDecoder) { // map from vaapi to opencl via vaapi-opencl interop(Intel only). - mainFilters.Add("hwmap=derive_device=opencl"); + mainFilters.Add("hwmap=derive_device=opencl:mode=read"); } // ocl tonemap @@ -4567,7 +4673,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // OUTPUT vaapi(nv12) surface(vram) // reverse-mapping via vaapi-opencl interop. - mainFilters.Add("hwmap=derive_device=vaapi:reverse=1"); + mainFilters.Add("hwmap=derive_device=vaapi:mode=write:reverse=1"); mainFilters.Add("format=vaapi"); } @@ -4618,7 +4724,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (hasGraphicalSubs) { // overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, 1080); subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } @@ -4627,7 +4733,7 @@ namespace MediaBrowser.Controller.MediaEncoding var framerate = state.VideoStream?.RealFrameRate; var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate); + var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, 1080, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4636,9 +4742,9 @@ namespace MediaBrowser.Controller.MediaEncoding subFilters.Add("hwupload=derive_device=vaapi"); - var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var (overlayW, overlayH) = GetFixedOutputSize(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); var overlaySize = (overlayW.HasValue && overlayH.HasValue) - ? (":w=" + overlayW.Value + ":h=" + overlayH.Value) + ? $":w={overlayW.Value}:h={overlayH.Value}" : string.Empty; var overlayVaapiFilter = string.Format( CultureInfo.InvariantCulture, @@ -4651,7 +4757,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); @@ -4696,6 +4802,13 @@ namespace MediaBrowser.Controller.MediaEncoding && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + var rotation = state.VideoStream?.Rotation ?? 0; + var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state); + var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(tranposeDir); + var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose)); + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + /* Make main filters for video stream */ var mainFilters = new List(); @@ -4720,7 +4833,7 @@ namespace MediaBrowser.Controller.MediaEncoding else { // sw scale - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); mainFilters.Add(swScaleFilter); mainFilters.Add("format=nv12"); } @@ -4728,7 +4841,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isVaapiDecoder) { // INPUT vaapi surface(vram) - if (doVkTonemap || hasSubs) + if (doVkTranspose || doVkTonemap || hasSubs) { // map from vaapi to vulkan/drm via interop (Polaris/gfx8+). mainFilters.Add("hwmap=derive_device=vulkan"); @@ -4744,15 +4857,28 @@ namespace MediaBrowser.Controller.MediaEncoding } // hw scale - var hwScaleFilter = GetHwScaleFilter("vaapi", "nv12", inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW, reqMaxH); mainFilters.Add(hwScaleFilter); } } + // vk transpose + if (doVkTranspose) + { + if (string.Equals(tranposeDir, "reversal", StringComparison.OrdinalIgnoreCase)) + { + mainFilters.Add("flip_vulkan"); + } + else + { + mainFilters.Add($"transpose_vulkan=dir={tranposeDir}"); + } + } + // vk libplacebo if (doVkTonemap || hasSubs) { - var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); mainFilters.Add(libplaceboFilter); } @@ -4796,7 +4922,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } @@ -4805,7 +4931,7 @@ namespace MediaBrowser.Controller.MediaEncoding var framerate = state.VideoStream?.RealFrameRate; var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); + var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -4877,6 +5003,11 @@ namespace MediaBrowser.Controller.MediaEncoding var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream; + var rotation = state.VideoStream?.Rotation ?? 0; + var swapWAndH = Math.Abs(rotation) == 90 && isSwDecoder; + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + /* Make main filters for video stream */ var mainFilters = new List(); @@ -4894,7 +5025,7 @@ namespace MediaBrowser.Controller.MediaEncoding } outFormat = doOclTonemap ? "yuv420p10le" : "nv12"; - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); mainFilters.Add("format=" + outFormat); @@ -4918,7 +5049,7 @@ namespace MediaBrowser.Controller.MediaEncoding } outFormat = doOclTonemap ? string.Empty : "nv12"; - var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, reqMaxH); // allocate extra pool sizes for vaapi vpp if (!string.IsNullOrEmpty(hwScaleFilter)) @@ -5014,7 +5145,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); @@ -5068,6 +5199,14 @@ namespace MediaBrowser.Controller.MediaEncoding string vidDecoder, string vidEncoder) { + var isVtEncoder = vidEncoder.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; @@ -5076,14 +5215,19 @@ namespace MediaBrowser.Controller.MediaEncoding var reqMaxH = state.BaseRequest.MaxHeight; var threeDFormat = state.MediaSource.Video3DFormat; - var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); - 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 doVtTonemap = IsVideoToolboxTonemapAvailable(state, options); var doMetalTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options); + var rotation = state.VideoStream?.Rotation ?? 0; + var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state); + var doVtTranspose = !string.IsNullOrEmpty(tranposeDir) && _mediaEncoder.SupportsFilter("transpose_vt"); + var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose; + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + var scaleFormat = string.Empty; // Use P010 for Metal tone mapping, otherwise force an 8bit output. if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)) @@ -5101,7 +5245,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - var hwScaleFilter = GetHwScaleFilter("vt", scaleFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; @@ -5110,12 +5254,6 @@ namespace MediaBrowser.Controller.MediaEncoding && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); - if (!isVtEncoder) - { - // should not happen. - return (null, null, null); - } - /* Make main filters for video stream */ var mainFilters = new List(); @@ -5131,6 +5269,12 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(deintFilter); } + // hw transpose + if (doVtTranspose) + { + mainFilters.Add($"transpose_vt=dir={tranposeDir}"); + } + if (doVtTonemap) { const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709"; @@ -5159,7 +5303,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } @@ -5168,7 +5312,7 @@ namespace MediaBrowser.Controller.MediaEncoding var framerate = state.VideoStream?.RealFrameRate; var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); + var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -5253,6 +5397,13 @@ namespace MediaBrowser.Controller.MediaEncoding && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)); + var rotation = state.VideoStream?.Rotation ?? 0; + var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state); + var doRkVppTranspose = !string.IsNullOrEmpty(tranposeDir); + var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose)); + var swpInW = swapWAndH ? inH : inW; + var swpInH = swapWAndH ? inW : inH; + /* Make main filters for video stream */ var mainFilters = new List(); @@ -5269,7 +5420,7 @@ namespace MediaBrowser.Controller.MediaEncoding } var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12"); - var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); + var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); if (!string.IsNullOrEmpty(swScaleFilter)) { swScaleFilter += ":flags=fast_bilinear"; @@ -5277,7 +5428,7 @@ namespace MediaBrowser.Controller.MediaEncoding // sw scale mainFilters.Add(swScaleFilter); - mainFilters.Add("format=" + outFormat); + mainFilters.Add($"format={outFormat}"); // keep video at memory except ocl tonemap, // since the overhead caused by hwupload >>> using sw filter. @@ -5292,21 +5443,29 @@ namespace MediaBrowser.Controller.MediaEncoding // INPUT rkmpp/drm surface(gem/dma-heap) var isFullAfbcPipeline = isDrmInDrmOut && !doOclTonemap; + var swapOutputWandH = doRkVppTranspose && swapWAndH; var outFormat = doOclTonemap ? "p010" : "nv12"; - var hwScaleFilter = GetHwScaleFilter("rkrga", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH); - var hwScaleFilter2 = GetHwScaleFilter("rkrga", string.Empty, inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var hwScalePrefix = doRkVppTranspose ? "vpp" : "scale"; + var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); + var hwScaleFilter2 = GetHwScaleFilter(hwScalePrefix, "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); if (!hasSubs + || doRkVppTranspose || !isFullAfbcPipeline || !string.IsNullOrEmpty(hwScaleFilter2)) { + if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose) + { + hwScaleFilter += $":transpose={tranposeDir}"; + } + // try enabling AFBC to save DDR bandwidth if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline) { hwScaleFilter += ":afbc=1"; } - // hw scale + // hw transpose & scale mainFilters.Add(hwScaleFilter); } } @@ -5377,7 +5536,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); subFilters.Add("format=bgra"); } @@ -5387,7 +5546,7 @@ namespace MediaBrowser.Controller.MediaEncoding var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10; // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload - var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); + var alphaSrcFilter = GetAlphaSrcFilter(state, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, subFramerate); var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true); subFilters.Add(alphaSrcFilter); subFilters.Add("format=bgra"); @@ -5404,7 +5563,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (hasGraphicalSubs) { - var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH); + var subPreProcFilters = GetGraphicalSubPreProcessFilters(swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH); subFilters.Add(subPreProcFilters); overlayFilters.Add("overlay=eof_action=pass:repeatlast=0"); } @@ -5806,6 +5965,11 @@ namespace MediaBrowser.Controller.MediaEncoding // Disable the extra internal copy in nvdec. We already handle it in filter chain. var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput; + // Strip the display rotation side data from the transposed fmp4 output stream. + var stripRotationData = (state.VideoStream?.Rotation ?? 0) != 0 + && ffmpegVersion >= _minFFmpegDisplayRotationOption; + var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty; + if (bitDepth == 10 && isCodecAvailable) { if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase) @@ -5830,13 +5994,13 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isVaapiSupported && isCodecAvailable) { - return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotationDataArgs : string.Empty) + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); } if (isD3d11Supported && isCodecAvailable) { - return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stripRotationDataArgs : string.Empty) + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" + (isAv1 ? " -c:v av1" : string.Empty); } } @@ -5844,7 +6008,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isQsvSupported && isCodecAvailable) { - return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv" : string.Empty); + return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv -noautorotate" + stripRotationDataArgs : string.Empty); } } } @@ -5857,12 +6021,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (options.EnableEnhancedNvdecDecoder) { // set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support. - return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty) + return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRotationDataArgs : string.Empty) + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty); } // cuvid decoder doesn't have threading issue. - return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty); + return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda -noautorotate" + stripRotationDataArgs : string.Empty); } } @@ -5871,7 +6035,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isD3d11Supported && isCodecAvailable) { - return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11 -noautorotate" + stripRotationDataArgs : string.Empty) + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); } } @@ -5881,7 +6045,7 @@ namespace MediaBrowser.Controller.MediaEncoding && isVaapiSupported && isCodecAvailable) { - return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi -noautorotate" + stripRotationDataArgs : string.Empty) + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty); } @@ -5890,7 +6054,7 @@ namespace MediaBrowser.Controller.MediaEncoding && isVideotoolboxSupported && isCodecAvailable) { - return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty); + return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty) + "-noautorotate" + stripRotationDataArgs; } // Rockchip rkmpp @@ -5898,7 +6062,7 @@ namespace MediaBrowser.Controller.MediaEncoding && isRkmppSupported && isCodecAvailable) { - return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime" : string.Empty); + return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime -noautorotate" + stripRotationDataArgs : string.Empty); } return null; diff --git a/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs index b1d319d211..a2b6e1d739 100644 --- a/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs +++ b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs @@ -33,6 +33,11 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// The overlay_vulkan_framesync. /// - OverlayVulkanFrameSync = 5 + OverlayVulkanFrameSync = 5, + + /// + /// The transpose_opencl_reversal. + /// + TransposeOpenclReversal = 6 } } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index ae0284e3ab..7226e0bf11 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -110,25 +110,31 @@ namespace MediaBrowser.MediaEncoding.Encoder "yadif_cuda", "tonemap_cuda", "overlay_cuda", + "transpose_cuda", "hwupload_cuda", // opencl "scale_opencl", "tonemap_opencl", "overlay_opencl", + "transpose_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", "scale_vt", + "transpose_vt", "overlay_videotoolbox", "tonemap_videotoolbox", // rkrga @@ -144,7 +150,8 @@ namespace MediaBrowser.MediaEncoding.Encoder { 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" } } + { 5, new string[] { "overlay_vulkan", "Action to take when encountering EOF from secondary input" } }, + { 6, new string[] { "transpose_opencl", "rotate by half-turn" } } }; // These are the library versions that corresponds to our minimum ffmpeg version 4.4 according to the version table below