diff options
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
| -rw-r--r-- | MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 646 |
1 files changed, 351 insertions, 295 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 7a1067dcc..a459ce8b6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -64,6 +64,10 @@ 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 _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 static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled); @@ -102,11 +106,13 @@ namespace MediaBrowser.Controller.MediaEncoding "m4v", }; + private static readonly string[] _legacyTonemapModes = new[] { "max", "rgb" }; + private static readonly string[] _advancedTonemapModes = new[] { "lum", "itp" }; + // Set max transcoding channels for encoders that can't handle more than a set amount of channels // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreCase) { - { "wmav2", 2 }, { "libmp3lame", 2 }, { "libfdk_aac", 6 }, { "ac3", 6 }, @@ -231,6 +237,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"); } @@ -248,6 +255,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() @@ -258,6 +267,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() @@ -265,7 +276,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() @@ -275,6 +288,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 IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) @@ -282,14 +297,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) @@ -399,27 +412,6 @@ namespace MediaBrowser.Controller.MediaEncoding return GetMjpegEncoder(state, encodingOptions); } - if (string.Equals(codec, "vp8", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) - { - return "libvpx"; - } - - if (string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)) - { - return "libvpx-vp9"; - } - - if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase)) - { - return "wmv2"; - } - - if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase)) - { - return "libtheora"; - } - if (_validationRegex.IsMatch(codec)) { return codec.ToLowerInvariant(); @@ -739,11 +731,6 @@ namespace MediaBrowser.Controller.MediaEncoding return "libvorbis"; } - if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase)) - { - return "wmav2"; - } - if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) { return "libopus"; @@ -1179,9 +1166,6 @@ namespace MediaBrowser.Controller.MediaEncoding args.Append(vidDecoder); } - // hw transpose filters should be added manually. - args.Append(" -noautorotate"); - return args.ToString().Trim(); } @@ -1340,7 +1324,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Apply aac_adtstoasc bitstream filter when media source is in mpegts. if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) - && (string.Equals(mediaSourceContainer, "mpegts", StringComparison.OrdinalIgnoreCase) + && (string.Equals(mediaSourceContainer, "ts", StringComparison.OrdinalIgnoreCase) || string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase) || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase))) { @@ -1379,20 +1363,6 @@ namespace MediaBrowser.Controller.MediaEncoding // Currently use the same buffer size for all encoders int bufsize = bitrate * 2; - if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoCodec, "libvpx-vp9", StringComparison.OrdinalIgnoreCase)) - { - // When crf is used with vpx, b:v becomes a max rate - // https://trac.ffmpeg.org/wiki/Encode/VP8 - // https://trac.ffmpeg.org/wiki/Encode/VP9 - return FormattableString.Invariant($" -maxrate:v {bitrate} -bufsize:v {bufsize} -b:v {bitrate}"); - } - - if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) - { - return FormattableString.Invariant($" -b:v {bitrate}"); - } - if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase)) { return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}"); @@ -1519,7 +1489,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}", @@ -1541,7 +1510,6 @@ namespace MediaBrowser.Controller.MediaEncoding alphaParam, sub2videoParam, fontParam, - // fallbackFontParam, setPtsParam); } @@ -1815,12 +1783,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) @@ -1928,93 +1890,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // vp8 - { - // Values 0-3, 0 being highest quality but slower - var profileScore = 0; - - var qmin = "0"; - var qmax = "50"; - var crf = "10"; - - if (isVc1) - { - profileScore++; - } - - // Max of 2 - profileScore = Math.Min(profileScore, 2); - - // http://www.webmproject.org/docs/encoder-parameters/ - param += string.Format( - CultureInfo.InvariantCulture, - " -speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}", - profileScore.ToString(CultureInfo.InvariantCulture), - crf, - qmin, - qmax); - } - else if (string.Equals(videoEncoder, "libvpx-vp9", StringComparison.OrdinalIgnoreCase)) // vp9 - { - // When `-deadline` is set to `good` or `best`, `-cpu-used` ranges from 0-5. - // When `-deadline` is set to `realtime`, `-cpu-used` ranges from 0-15. - // Resources: - // * https://trac.ffmpeg.org/wiki/Encode/VP9 - // * https://superuser.com/questions/1586934 - // * https://developers.google.com/media/vp9 - param += encodingOptions.EncoderPreset switch - { - "veryslow" => " -deadline best -cpu-used 0", - "slower" => " -deadline best -cpu-used 2", - "slow" => " -deadline best -cpu-used 3", - "medium" => " -deadline good -cpu-used 0", - "fast" => " -deadline good -cpu-used 1", - "faster" => " -deadline good -cpu-used 2", - "veryfast" => " -deadline good -cpu-used 3", - "superfast" => " -deadline good -cpu-used 4", - "ultrafast" => " -deadline good -cpu-used 5", - _ => " -deadline good -cpu-used 1" - }; - - // TODO: until VP9 gets its own CRF setting, base CRF on H.265. - int h265Crf = encodingOptions.H265Crf; - int defaultVp9Crf = 31; - if (h265Crf >= 0 && h265Crf <= 51) - { - // This conversion factor is chosen to match the default CRF for H.265 to the - // recommended 1080p CRF from Google. The factor also maps the logarithmic CRF - // scale of x265 [0, 51] to that of VP9 [0, 63] relatively well. - - // Resources: - // * https://developers.google.com/media/vp9/settings/vod - const float H265ToVp9CrfConversionFactor = 1.12F; - - var vp9Crf = Convert.ToInt32(h265Crf * H265ToVp9CrfConversionFactor); - - // Encoder allows for CRF values in the range [0, 63]. - vp9Crf = Math.Clamp(vp9Crf, 0, 63); - - param += FormattableString.Invariant($" -crf {vp9Crf}"); - } - else - { - param += FormattableString.Invariant($" -crf {defaultVp9Crf}"); - } - - param += " -row-mt 1 -profile 1"; - } - else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase)) - { - param += " -mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; - } - else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) // asf/wmv - { - param += " -qmin 2"; - } - else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase)) - { - param += " -mbd 2"; - } param += GetVideoBitrateParam(state, videoEncoder); @@ -2194,7 +2069,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)) @@ -2202,8 +2077,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) @@ -3080,8 +2954,10 @@ namespace MediaBrowser.Controller.MediaEncoding } public static string GetHwScaleFilter( + string hwScalePrefix, string hwScaleSuffix, string videoFormat, + bool swapOutputWandH, int? videoWidth, int? videoHeight, int? requestedWidth, @@ -3103,8 +2979,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; @@ -3114,7 +2993,8 @@ namespace MediaBrowser.Controller.MediaEncoding { return string.Format( CultureInfo.InvariantCulture, - "scale_{0}{1}{2}", + "{0}_{1}{2}{3}", + hwScalePrefix ?? "scale", hwScaleSuffix, arg1, arg2); @@ -3143,7 +3023,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return string.Format( CultureInfo.InvariantCulture, - @"scale=-1:{1}:fast_bilinear,scale,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}", + @"scale,scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}", outWidth.Value, outHeight.Value); } @@ -3414,13 +3294,15 @@ namespace MediaBrowser.Controller.MediaEncoding { args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}"; - if (string.Equals(options.TonemappingMode, "max", StringComparison.OrdinalIgnoreCase) - || string.Equals(options.TonemappingMode, "rgb", StringComparison.OrdinalIgnoreCase)) + var useLegacyTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode + && _legacyTonemapModes.Contains(options.TonemappingMode, StringComparison.OrdinalIgnoreCase); + + var useAdvancedTonemapModes = _mediaEncoder.EncoderVersion >= _minFFmpegAdvancedTonemapMode + && _advancedTonemapModes.Contains(options.TonemappingMode, StringComparison.OrdinalIgnoreCase); + + if (useLegacyTonemapModes || useAdvancedTonemapModes) { - if (_mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode) - { - args += ":tonemap_mode={5}"; - } + args += ":tonemap_mode={5}"; } if (options.TonemappingParam != 0) @@ -3492,15 +3374,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)) @@ -3517,6 +3391,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 + }; + } + /// <summary> /// Gets the parameter of software filter chain. /// </summary> @@ -3546,11 +3432,17 @@ 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; 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<string>(); @@ -3565,7 +3457,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"; @@ -3578,11 +3470,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) { @@ -3614,7 +3508,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"); } @@ -3688,6 +3582,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<string>(); @@ -3704,10 +3605,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) @@ -3726,8 +3627,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); } @@ -3777,7 +3684,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"); } @@ -3787,7 +3694,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"); @@ -3802,7 +3709,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"); } @@ -3878,6 +3785,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<string>(); @@ -3894,19 +3809,19 @@ 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. // sw => hw if (doOclTonemap) { - mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=16"); + mainFilters.Add("hwupload=derive_device=d3d11va:extra_hw_frames=24"); mainFilters.Add("format=d3d11"); - mainFilters.Add("hwmap=derive_device=opencl"); + mainFilters.Add("hwmap=derive_device=opencl:mode=read"); } } @@ -3914,12 +3829,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); } @@ -3964,7 +3885,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"); } @@ -3977,7 +3898,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"); } @@ -3987,7 +3908,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"); @@ -3996,7 +3917,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"); } } @@ -4004,7 +3925,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"); } @@ -4100,6 +4021,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<string>(); @@ -4116,10 +4044,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. @@ -4131,8 +4059,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) { @@ -4151,14 +4086,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 @@ -4202,7 +4137,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"); } @@ -4216,7 +4151,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"); } @@ -4226,7 +4161,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"); @@ -4237,9 +4172,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, @@ -4252,7 +4187,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"); } @@ -4297,6 +4232,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<string>(); @@ -4313,10 +4255,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. @@ -4328,24 +4270,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); } @@ -4373,7 +4330,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 @@ -4420,7 +4377,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) @@ -4440,7 +4397,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"); } @@ -4449,7 +4406,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"); @@ -4460,9 +4417,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, @@ -4475,7 +4432,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"); } @@ -4586,6 +4543,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<string>(); @@ -4602,10 +4566,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. @@ -4625,8 +4589,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)) @@ -4648,7 +4618,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 @@ -4662,7 +4632,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"); } @@ -4713,7 +4683,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"); } @@ -4722,7 +4692,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"); @@ -4731,9 +4701,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, @@ -4746,7 +4716,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"); @@ -4791,6 +4761,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<string>(); @@ -4815,7 +4792,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"); } @@ -4823,11 +4800,37 @@ 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"); - 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,16 +4842,30 @@ 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); + mainFilters.Add("format=vulkan"); } if (doVkTonemap && !hasSubs) @@ -4891,7 +4908,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"); } @@ -4900,7 +4917,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"); @@ -4972,6 +4989,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<string>(); @@ -4989,7 +5011,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); @@ -5013,7 +5035,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)) @@ -5109,7 +5131,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"); @@ -5140,13 +5162,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")) { @@ -5163,6 +5187,9 @@ namespace MediaBrowser.Controller.MediaEncoding string vidDecoder, string vidEncoder) { + var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); + var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase); + var inW = state.VideoStream?.Width; var inH = state.VideoStream?.Height; var reqW = state.BaseRequest.Width; @@ -5171,13 +5198,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 usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface); + + 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. @@ -5196,7 +5229,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; @@ -5205,12 +5238,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<string>(); @@ -5221,6 +5248,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"; @@ -5249,7 +5282,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"); } @@ -5258,30 +5291,44 @@ 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"); subFilters.Add(subTextSubtitlesFilter); } - subFilters.Add("hwupload=derive_device=videotoolbox"); + subFilters.Add("hwupload"); overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0"); } + if (usingHwSurface) + { + if (!isVtEncoder) + { + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + } + + return (mainFilters, subFilters, overlayFilters); + } + + // For old jellyfin-ffmpeg that has broken hwsurface, add a hwupload var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) || subFilters.Any(f => !string.IsNullOrEmpty(f)) || overlayFilters.Any(f => !string.IsNullOrEmpty(f)); - - // This is a workaround for ffmpeg's hwupload implementation - // For VideoToolbox encoders, a hwupload without a valid filter actually consuming its frame - // will cause the encoder to produce incorrect frames. if (needFiltering) { // INPUT videotoolbox/memory surface(vram/uma) // this will pass-through automatically if in/out format matches. + mainFilters.Insert(0, "hwupload"); mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld"); - mainFilters.Insert(0, "hwupload=derive_device=videotoolbox"); + + if (!isVtEncoder) + { + mainFilters.Add("hwdownload"); + mainFilters.Add("format=nv12"); + } } return (mainFilters, subFilters, overlayFilters); @@ -5358,6 +5405,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<string>(); @@ -5374,7 +5428,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"; @@ -5382,7 +5436,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. @@ -5397,21 +5451,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); } } @@ -5482,7 +5544,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"); } @@ -5492,7 +5554,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"); @@ -5509,7 +5571,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"); } @@ -5924,6 +5986,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) @@ -5948,13 +6015,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); } } @@ -5962,7 +6029,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); } } } @@ -5975,12 +6042,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); } } @@ -5989,7 +6056,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); } } @@ -5999,7 +6066,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); } @@ -6008,7 +6075,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 @@ -6016,7 +6083,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; @@ -6289,36 +6356,34 @@ namespace MediaBrowser.Controller.MediaEncoding || string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase); - // VideoToolbox's Hardware surface in ffmpeg is not only slower than hwupload, but also breaks HDR in many cases. - // For example: https://trac.ffmpeg.org/ticket/10884 - // Disable it for now. - const bool UseHwSurface = false; + // The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1 at the moment. + bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupported(); if (is8bitSwFormatsVt) { - if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) - || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) - { - return GetHwaccelType(state, options, "h264", bitDepth, UseHwSurface); - } - if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) { - return GetHwaccelType(state, options, "vp8", bitDepth, UseHwSurface); + return GetHwaccelType(state, options, "vp8", bitDepth, useHwSurface); } } if (is8_10bitSwFormatsVt) { + if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + { + return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface); + } + if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase) || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) { - return GetHwaccelType(state, options, "hevc", bitDepth, UseHwSurface); + return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface); } if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) { - return GetHwaccelType(state, options, "vp9", bitDepth, UseHwSurface); + return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface); } } @@ -6431,24 +6496,15 @@ namespace MediaBrowser.Controller.MediaEncoding #nullable enable public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec) { - // VP8 and VP9 encoders must have their thread counts set. - bool mustSetThreadCount = string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase) - || string.Equals(outputVideoCodec, "libvpx-vp9", StringComparison.OrdinalIgnoreCase); - var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount; if (threads <= 0) { // Automatically set thread count - return mustSetThreadCount ? Math.Max(Environment.ProcessorCount - 1, 1) : 0; - } - - if (threads >= Environment.ProcessorCount) - { - return Environment.ProcessorCount; + return 0; } - return threads; + return Math.Min(threads, Environment.ProcessorCount); } #nullable disable |
