瀏覽代碼

add support for cuda tonemap and overlay

nyanmisaka 3 年之前
父節點
當前提交
3beda02d92

+ 296 - 89
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -40,6 +40,8 @@ namespace MediaBrowser.Controller.MediaEncoding
             "ConstrainedHigh"
             "ConstrainedHigh"
         };
         };
 
 
+        private static readonly Version minVersionForCudaOverlay = new Version(4, 4);
+
         public EncodingHelper(
         public EncodingHelper(
             IMediaEncoder mediaEncoder,
             IMediaEncoder mediaEncoder,
             ISubtitleEncoder subtitleEncoder)
             ISubtitleEncoder subtitleEncoder)
@@ -109,17 +111,41 @@ namespace MediaBrowser.Controller.MediaEncoding
         private bool IsCudaSupported()
         private bool IsCudaSupported()
         {
         {
             return _mediaEncoder.SupportsHwaccel("cuda")
             return _mediaEncoder.SupportsHwaccel("cuda")
-                   && _mediaEncoder.SupportsFilter("scale_cuda", null)
-                   && _mediaEncoder.SupportsFilter("yadif_cuda", null);
+                   && _mediaEncoder.SupportsFilter("scale_cuda")
+                   && _mediaEncoder.SupportsFilter("yadif_cuda")
+                   && _mediaEncoder.SupportsFilter("hwupload_cuda");
         }
         }
 
 
-        private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
+        private bool IsOpenclTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
         {
         {
             var videoStream = state.VideoStream;
             var videoStream = state.VideoStream;
-            return IsColorDepth10(state)
+            if (videoStream == null)
+            {
+                return false;
+            }
+
+            return (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
+                       || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+                   && IsColorDepth10(state)
                    && _mediaEncoder.SupportsHwaccel("opencl")
                    && _mediaEncoder.SupportsHwaccel("opencl")
-                   && options.EnableTonemapping
-                   && string.Equals(videoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
+                   && _mediaEncoder.SupportsFilter("tonemap_opencl")
+                   && options.EnableTonemapping;
+        }
+
+        private bool IsCudaTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
+        {
+            var videoStream = state.VideoStream;
+            if (videoStream == null)
+            {
+                return false;
+            }
+
+            return (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
+                       || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+                   && IsColorDepth10(state)
+                   && _mediaEncoder.SupportsHwaccel("cuda")
+                   && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
+                   && options.EnableTonemapping;
         }
         }
 
 
         private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
         private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
@@ -135,23 +161,25 @@ namespace MediaBrowser.Controller.MediaEncoding
             if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
             {
             {
                 // Limited to HEVC for now since the filter doesn't accept master data from VP9.
                 // Limited to HEVC for now since the filter doesn't accept master data from VP9.
-                return IsColorDepth10(state)
+                return string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
+                       && IsColorDepth10(state)
                        && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
                        && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
                        && _mediaEncoder.SupportsHwaccel("vaapi")
                        && _mediaEncoder.SupportsHwaccel("vaapi")
-                       && options.EnableVppTonemapping
-                       && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
+                       && _mediaEncoder.SupportsFilter("tonemap_vaapi")
+                       && options.EnableVppTonemapping;
             }
             }
 
 
             // Hybrid VPP tonemapping for QSV with VAAPI
             // Hybrid VPP tonemapping for QSV with VAAPI
             if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
             if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
             {
             {
                 // Limited to HEVC for now since the filter doesn't accept master data from VP9.
                 // Limited to HEVC for now since the filter doesn't accept master data from VP9.
-                return IsColorDepth10(state)
+                return string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
+                       && IsColorDepth10(state)
                        && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
                        && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
                        && _mediaEncoder.SupportsHwaccel("vaapi")
                        && _mediaEncoder.SupportsHwaccel("vaapi")
+                       && _mediaEncoder.SupportsFilter("tonemap_vaapi")
                        && _mediaEncoder.SupportsHwaccel("qsv")
                        && _mediaEncoder.SupportsHwaccel("qsv")
-                       && options.EnableVppTonemapping
-                       && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
+                       && options.EnableVppTonemapping;
             }
             }
 
 
             // Native VPP tonemapping may come to QSV in the future.
             // Native VPP tonemapping may come to QSV in the future.
@@ -489,11 +517,14 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <summary>
         /// <summary>
         /// Gets the input argument.
         /// Gets the input argument.
         /// </summary>
         /// </summary>
-        public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
+        public string GetInputArgument(EncodingJobInfo state, EncodingOptions options)
         {
         {
             var arg = new StringBuilder();
             var arg = new StringBuilder();
-            var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
-            var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
+            var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
+            var outputVideoCodec = GetVideoEncoder(state, options) ?? string.Empty;
+            var isWindows = OperatingSystem.IsWindows();
+            var isLinux = OperatingSystem.IsLinux();
+            var isMacOS = OperatingSystem.IsMacOS();
             var isSwDecoder = string.IsNullOrEmpty(videoDecoder);
             var isSwDecoder = string.IsNullOrEmpty(videoDecoder);
             var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1;
             var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1;
             var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
             var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
@@ -502,26 +533,24 @@ namespace MediaBrowser.Controller.MediaEncoding
             var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
             var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
             var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
             var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
             var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
             var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
-            var isWindows = OperatingSystem.IsWindows();
-            var isLinux = OperatingSystem.IsLinux();
-            var isMacOS = OperatingSystem.IsMacOS();
-            var isTonemappingSupported = IsTonemappingSupported(state, encodingOptions);
-            var isVppTonemappingSupported = IsVppTonemappingSupported(state, encodingOptions);
+            var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase);
+            var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
+            var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
+            var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
 
 
             if (!IsCopyCodec(outputVideoCodec))
             if (!IsCopyCodec(outputVideoCodec))
             {
             {
                 if (state.IsVideoRequest
                 if (state.IsVideoRequest
                     && _mediaEncoder.SupportsHwaccel("vaapi")
                     && _mediaEncoder.SupportsHwaccel("vaapi")
-                    && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+                    && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     if (isVaapiDecoder)
                     if (isVaapiDecoder)
                     {
                     {
-                        if (isTonemappingSupported && !isVppTonemappingSupported)
+                        if (isOpenclTonemappingSupported && !isVppTonemappingSupported)
                         {
                         {
                            arg.Append("-init_hw_device vaapi=va:")
                            arg.Append("-init_hw_device vaapi=va:")
-                                .Append(encodingOptions.VaapiDevice)
-                                .Append(' ')
-                                .Append("-init_hw_device opencl=ocl@va ")
+                                .Append(options.VaapiDevice)
+                                .Append(" -init_hw_device opencl=ocl@va ")
                                 .Append("-hwaccel_device va ")
                                 .Append("-hwaccel_device va ")
                                 .Append("-hwaccel_output_format vaapi ")
                                 .Append("-hwaccel_output_format vaapi ")
                                 .Append("-filter_hw_device ocl ");
                                 .Append("-filter_hw_device ocl ");
@@ -530,14 +559,14 @@ namespace MediaBrowser.Controller.MediaEncoding
                         {
                         {
                             arg.Append("-hwaccel_output_format vaapi ")
                             arg.Append("-hwaccel_output_format vaapi ")
                                 .Append("-vaapi_device ")
                                 .Append("-vaapi_device ")
-                                .Append(encodingOptions.VaapiDevice)
+                                .Append(options.VaapiDevice)
                                 .Append(' ');
                                 .Append(' ');
                         }
                         }
                     }
                     }
                     else if (!isVaapiDecoder && isVaapiEncoder)
                     else if (!isVaapiDecoder && isVaapiEncoder)
                     {
                     {
                         arg.Append("-vaapi_device ")
                         arg.Append("-vaapi_device ")
-                            .Append(encodingOptions.VaapiDevice)
+                            .Append(options.VaapiDevice)
                             .Append(' ');
                             .Append(' ');
                     }
                     }
 
 
@@ -545,7 +574,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 }
                 }
 
 
                 if (state.IsVideoRequest
                 if (state.IsVideoRequest
-                    && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+                    && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
                     var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
 
 
@@ -581,9 +610,8 @@ namespace MediaBrowser.Controller.MediaEncoding
                         else if (isVaapiDecoder && isVppTonemappingSupported)
                         else if (isVaapiDecoder && isVppTonemappingSupported)
                         {
                         {
                             arg.Append("-init_hw_device vaapi=va:")
                             arg.Append("-init_hw_device vaapi=va:")
-                                .Append(encodingOptions.VaapiDevice)
-                                .Append(' ')
-                                .Append("-init_hw_device qsv@va ")
+                                .Append(options.VaapiDevice)
+                                .Append(" -init_hw_device qsv@va ")
                                 .Append("-hwaccel_output_format vaapi ");
                                 .Append("-hwaccel_output_format vaapi ");
                         }
                         }
 
 
@@ -592,7 +620,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 }
                 }
 
 
                 if (state.IsVideoRequest
                 if (state.IsVideoRequest
-                    && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
+                    && string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
                     && isNvdecDecoder)
                     && isNvdecDecoder)
                 {
                 {
                     // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562
                     // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562
@@ -600,22 +628,31 @@ namespace MediaBrowser.Controller.MediaEncoding
                 }
                 }
 
 
                 if (state.IsVideoRequest
                 if (state.IsVideoRequest
-                    && ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
-                         && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder))
-                        || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)
-                            && (isD3d11vaDecoder || isSwDecoder))))
+                    && ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
+                         && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder))))
+                {
+                    if (!isCudaTonemappingSupported && isOpenclTonemappingSupported)
+                    {
+                        arg.Append("-init_hw_device opencl=ocl:")
+                            .Append(options.OpenclDevice)
+                            .Append(" -filter_hw_device ocl ");
+                    }
+                }
+
+                if (state.IsVideoRequest
+                    && string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)
+                    && (isD3d11vaDecoder || isSwDecoder))
                 {
                 {
-                    if (isTonemappingSupported)
+                    if (isOpenclTonemappingSupported)
                     {
                     {
                         arg.Append("-init_hw_device opencl=ocl:")
                         arg.Append("-init_hw_device opencl=ocl:")
-                            .Append(encodingOptions.OpenclDevice)
-                            .Append(' ')
-                            .Append("-filter_hw_device ocl ");
+                            .Append(options.OpenclDevice)
+                            .Append(" -filter_hw_device ocl ");
                     }
                     }
                 }
                 }
 
 
                 if (state.IsVideoRequest
                 if (state.IsVideoRequest
-                    && string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+                    && string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     arg.Append("-hwaccel videotoolbox ");
                     arg.Append("-hwaccel videotoolbox ");
                 }
                 }
@@ -1991,14 +2028,18 @@ namespace MediaBrowser.Controller.MediaEncoding
             var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
             var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
             var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
             var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
             var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
             var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
-            var isTonemappingSupported = IsTonemappingSupported(state, options);
-            var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
             var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
             var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
             var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
             var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
+            var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
+            var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
+
+            var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
+            var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay;
+            var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
 
 
             // Tonemapping and burn-in graphical subtitles requires overlay_vaapi.
             // Tonemapping and burn-in graphical subtitles requires overlay_vaapi.
             // But it's still in ffmpeg mailing list. Disable it for now.
             // But it's still in ffmpeg mailing list. Disable it for now.
-            if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported)
+            if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported)
             {
             {
                 return GetOutputSizeParam(state, options, outputVideoCodec);
                 return GetOutputSizeParam(state, options, outputVideoCodec);
             }
             }
@@ -2024,13 +2065,22 @@ namespace MediaBrowser.Controller.MediaEncoding
                 if (!string.IsNullOrEmpty(videoSizeParam)
                 if (!string.IsNullOrEmpty(videoSizeParam)
                     && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
                     && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
                 {
                 {
-                    // For QSV, feed it into hardware encoder now
+                    // upload graphical subtitle to QSV
                     if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
                     if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
                         || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
                         || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
                     {
                     {
                         videoSizeParam += ",hwupload=extra_hw_frames=64";
                         videoSizeParam += ",hwupload=extra_hw_frames=64";
                     }
                     }
                 }
                 }
+
+                if (!string.IsNullOrEmpty(videoSizeParam))
+                {
+                    // upload graphical subtitle to cuda
+                    if (isNvdecDecoder && isNvencEncoder && isCudaOverlaySupported && isCudaFormatConversionSupported)
+                    {
+                        videoSizeParam += ",hwupload_cuda";
+                    }
+                }
             }
             }
 
 
             var mapPrefix = state.SubtitleStream.IsExternal ?
             var mapPrefix = state.SubtitleStream.IsExternal ?
@@ -2043,9 +2093,9 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
             // Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
             // Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
             // Always put the scaler before the overlay for better performance
             // Always put the scaler before the overlay for better performance
-            var retStr = !outputSizeParam.IsEmpty
-                ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""
-                : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
+            var retStr = outputSizeParam.IsEmpty
+                ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""
+                : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
 
 
             // When the input may or may not be hardware VAAPI decodable
             // When the input may or may not be hardware VAAPI decodable
             if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
             if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
@@ -2056,9 +2106,9 @@ namespace MediaBrowser.Controller.MediaEncoding
                     [sub]: SW scaling subtitle to FixedOutputSize
                     [sub]: SW scaling subtitle to FixedOutputSize
                     [base][sub]: SW overlay
                     [base][sub]: SW overlay
                 */
                 */
-                retStr = !outputSizeParam.IsEmpty
-                    ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""
-                    : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
+                retStr = outputSizeParam.IsEmpty
+                    ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""
+                    : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
             }
             }
 
 
             // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
             // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
@@ -2071,9 +2121,9 @@ namespace MediaBrowser.Controller.MediaEncoding
                     [sub]: SW scaling subtitle to FixedOutputSize
                     [sub]: SW scaling subtitle to FixedOutputSize
                     [base][sub]: SW overlay
                     [base][sub]: SW overlay
                 */
                 */
-                retStr = !outputSizeParam.IsEmpty
-                    ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""
-                    : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
+                retStr = outputSizeParam.IsEmpty
+                    ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""
+                    : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
             }
             }
             else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
             else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
                      || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
                      || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
@@ -2090,16 +2140,25 @@ namespace MediaBrowser.Controller.MediaEncoding
                 }
                 }
                 else if (isLinux)
                 else if (isLinux)
                 {
                 {
-                    retStr = !outputSizeParam.IsEmpty
-                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""
-                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
+                    retStr = outputSizeParam.IsEmpty
+                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""
+                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"";
                 }
                 }
             }
             }
             else if (isNvdecDecoder && isNvencEncoder)
             else if (isNvdecDecoder && isNvencEncoder)
             {
             {
-                retStr = !outputSizeParam.IsEmpty
-                    ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""
-                    : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"";
+                if (isCudaOverlaySupported && isCudaFormatConversionSupported)
+                {
+                    retStr = outputSizeParam.IsEmpty
+                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]scale_cuda=format=yuv420p[base];[base][sub]overlay_cuda\""
+                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_cuda\"";
+                }
+                else
+                {
+                    retStr = !outputSizeParam.IsEmpty
+                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""
+                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"";
+                }
             }
             }
 
 
             return string.Format(
             return string.Format(
@@ -2196,11 +2255,11 @@ namespace MediaBrowser.Controller.MediaEncoding
                 var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase);
                 var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase);
                 var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase);
                 var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase);
                 var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
                 var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
-                var isTonemappingSupported = IsTonemappingSupported(state, options);
+                var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
                 var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
                 var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
                 var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
                 var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
                 var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
                 var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
-                var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))
+                var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported))
                     || (isTonemappingSupportedOnQsv && isVppTonemappingSupported);
                     || (isTonemappingSupportedOnQsv && isVppTonemappingSupported);
 
 
                 var outputPixFmt = "format=nv12";
                 var outputPixFmt = "format=nv12";
@@ -2251,15 +2310,23 @@ namespace MediaBrowser.Controller.MediaEncoding
                 var outputWidth = width.Value;
                 var outputWidth = width.Value;
                 var outputHeight = height.Value;
                 var outputHeight = height.Value;
 
 
-                var isTonemappingSupported = IsTonemappingSupported(state, options);
+                var isNvencEncoder = videoEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
+                var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
+                var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
                 var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase);
                 var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase);
-                var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")");
+                var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
+                var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay;
+                var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
+                var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
 
 
                 var outputPixFmt = string.Empty;
                 var outputPixFmt = string.Empty;
                 if (isCudaFormatConversionSupported)
                 if (isCudaFormatConversionSupported)
                 {
                 {
-                    outputPixFmt = "format=nv12";
-                    if (isTonemappingSupported && isTonemappingSupportedOnNvenc)
+                    outputPixFmt = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder)
+                        ? "format=yuv420p"
+                        : "format=nv12";
+                    if ((isOpenclTonemappingSupported || isCudaTonemappingSupported)
+                        && isTonemappingSupportedOnNvenc)
                     {
                     {
                         outputPixFmt = "format=p010";
                         outputPixFmt = "format=p010";
                     }
                     }
@@ -2525,16 +2592,21 @@ namespace MediaBrowser.Controller.MediaEncoding
             var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
             var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
             var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase);
             var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase);
             var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
             var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
+            var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase);
             var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
             var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
             var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1;
             var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1;
             var isLinux = OperatingSystem.IsLinux();
             var isLinux = OperatingSystem.IsLinux();
             var isColorDepth10 = IsColorDepth10(state);
             var isColorDepth10 = IsColorDepth10(state);
-            var isTonemappingSupported = IsTonemappingSupported(state, options);
-            var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
-            var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder);
+
+            var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder);
             var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder);
             var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder);
             var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
             var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
             var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
             var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
+            var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
+            var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
+            var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
+            var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
+            var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay;
 
 
             var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
             var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
             var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
             var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@@ -2546,19 +2618,25 @@ namespace MediaBrowser.Controller.MediaEncoding
             var isScalingInAdvance = false;
             var isScalingInAdvance = false;
             var isCudaDeintInAdvance = false;
             var isCudaDeintInAdvance = false;
             var isHwuploadCudaRequired = false;
             var isHwuploadCudaRequired = false;
+            var isNoTonemapFilterApplied = true;
             var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
             var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
             var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
             var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
 
 
             // Add OpenCL tonemapping filter for NVENC/AMF/VAAPI.
             // Add OpenCL tonemapping filter for NVENC/AMF/VAAPI.
-            if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported))
+            if ((isTonemappingSupportedOnNvenc && !isCudaTonemappingSupported) || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported))
             {
             {
-                // Currently only with the use of NVENC decoder can we get a decent performance.
-                // Currently only the HEVC/H265 format is supported with NVDEC decoder.
                 // NVIDIA Pascal and Turing or higher are recommended.
                 // NVIDIA Pascal and Turing or higher are recommended.
                 // AMD Polaris and Vega or higher are recommended.
                 // AMD Polaris and Vega or higher are recommended.
                 // Intel Kaby Lake or newer is required.
                 // Intel Kaby Lake or newer is required.
-                if (isTonemappingSupported)
+                if (isOpenclTonemappingSupported)
                 {
                 {
+                    isNoTonemapFilterApplied = false;
+                    var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer);
+                    if (!string.IsNullOrEmpty(inputHdrParams))
+                    {
+                        filters.Add(inputHdrParams);
+                    }
+
                     var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
                     var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
 
 
                     if (options.TonemappingParam != 0)
                     if (options.TonemappingParam != 0)
@@ -2630,7 +2708,11 @@ namespace MediaBrowser.Controller.MediaEncoding
                         filters.Add("hwdownload,format=p010");
                         filters.Add("hwdownload,format=p010");
                     }
                     }
 
 
-                    if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder)
+                    if (isNvdecDecoder
+                        || isCuvidHevcDecoder
+                        || isCuvidVp9Decoder
+                        || isSwDecoder
+                        || isD3d11vaDecoder)
                     {
                     {
                         // Upload the HDR10 or HLG data to the OpenCL device,
                         // Upload the HDR10 or HLG data to the OpenCL device,
                         // use tonemap_opencl filter for tone mapping,
                         // use tonemap_opencl filter for tone mapping,
@@ -2638,6 +2720,14 @@ namespace MediaBrowser.Controller.MediaEncoding
                         filters.Add("hwupload");
                         filters.Add("hwupload");
                     }
                     }
 
 
+                    // Fallback to hable if bt2390 is chosen but not supported in tonemap_opencl.
+                    var isBt2390SupportedInOpenclTonemap = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390);
+                    if (string.Equals(options.TonemappingAlgorithm, "bt2390", StringComparison.OrdinalIgnoreCase)
+                        && !isBt2390SupportedInOpenclTonemap)
+                    {
+                        options.TonemappingAlgorithm = "hable";
+                    }
+
                     filters.Add(
                     filters.Add(
                         string.Format(
                         string.Format(
                             CultureInfo.InvariantCulture,
                             CultureInfo.InvariantCulture,
@@ -2649,7 +2739,11 @@ namespace MediaBrowser.Controller.MediaEncoding
                             options.TonemappingParam,
                             options.TonemappingParam,
                             options.TonemappingRange));
                             options.TonemappingRange));
 
 
-                    if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder)
+                    if (isNvdecDecoder
+                        || isCuvidHevcDecoder
+                        || isCuvidVp9Decoder
+                        || isSwDecoder
+                        || isD3d11vaDecoder)
                     {
                     {
                         filters.Add("hwdownload");
                         filters.Add("hwdownload");
                         filters.Add("format=nv12");
                         filters.Add("format=nv12");
@@ -2665,12 +2759,18 @@ namespace MediaBrowser.Controller.MediaEncoding
                         // Reverse the data route from opencl to vaapi.
                         // Reverse the data route from opencl to vaapi.
                         filters.Add("hwmap=derive_device=vaapi:reverse=1");
                         filters.Add("hwmap=derive_device=vaapi:reverse=1");
                     }
                     }
+
+                    var outputSdrParams = GetOutputSdrParams(options.TonemappingRange);
+                    if (!string.IsNullOrEmpty(outputSdrParams))
+                    {
+                        filters.Add(outputSdrParams);
+                    }
                 }
                 }
             }
             }
 
 
             // When the input may or may not be hardware VAAPI decodable.
             // When the input may or may not be hardware VAAPI decodable.
             if ((isVaapiH264Encoder || isVaapiHevcEncoder)
             if ((isVaapiH264Encoder || isVaapiHevcEncoder)
-                && !(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)))
+                && !(isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported)))
             {
             {
                 filters.Add("format=nv12|vaapi");
                 filters.Add("format=nv12|vaapi");
                 filters.Add("hwupload");
                 filters.Add("hwupload");
@@ -2778,6 +2878,61 @@ namespace MediaBrowser.Controller.MediaEncoding
                         request.MaxHeight));
                         request.MaxHeight));
             }
             }
 
 
+            // Add Cuda tonemapping filter.
+            if (isNvdecDecoder && isCudaTonemappingSupported)
+            {
+                isNoTonemapFilterApplied = false;
+                var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer);
+                if (!string.IsNullOrEmpty(inputHdrParams))
+                {
+                    filters.Add(inputHdrParams);
+                }
+
+                var parameters = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder)
+                    ? "tonemap_cuda=format=yuv420p:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}"
+                    : "tonemap_cuda=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}";
+
+                if (options.TonemappingParam != 0)
+                {
+                    parameters += ":param={3}";
+                }
+
+                if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
+                {
+                    parameters += ":range={4}";
+                }
+
+                filters.Add(
+                    string.Format(
+                        CultureInfo.InvariantCulture,
+                        parameters,
+                        options.TonemappingAlgorithm,
+                        options.TonemappingPeak,
+                        options.TonemappingDesat,
+                        options.TonemappingParam,
+                        options.TonemappingRange));
+
+                if (isLibX264Encoder
+                    || isLibX265Encoder
+                    || hasTextSubs
+                    || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))
+                {
+                    if (isNvencEncoder)
+                    {
+                        isHwuploadCudaRequired = true;
+                    }
+
+                    filters.Add("hwdownload");
+                    filters.Add("format=nv12");
+                }
+
+                var outputSdrParams = GetOutputSdrParams(options.TonemappingRange);
+                if (!string.IsNullOrEmpty(outputSdrParams))
+                {
+                    filters.Add(outputSdrParams);
+                }
+            }
+
             // Add VPP tonemapping filter for VAAPI.
             // Add VPP tonemapping filter for VAAPI.
             // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options.
             // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options.
             if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv)
             if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv)
@@ -2787,10 +2942,10 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
             }
 
 
             // Another case is when using Nvenc decoder.
             // Another case is when using Nvenc decoder.
-            if (isNvdecDecoder && !isTonemappingSupported)
+            if (isNvdecDecoder && !isOpenclTonemappingSupported && !isCudaTonemappingSupported)
             {
             {
                 var codec = videoStream.Codec;
                 var codec = videoStream.Codec;
-                var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")");
+                var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
 
 
                 // Assert 10-bit hardware decodable
                 // Assert 10-bit hardware decodable
                 if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
                 if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
@@ -2799,7 +2954,10 @@ namespace MediaBrowser.Controller.MediaEncoding
                 {
                 {
                     if (isCudaFormatConversionSupported)
                     if (isCudaFormatConversionSupported)
                     {
                     {
-                        if (isLibX264Encoder || isLibX265Encoder || hasSubs)
+                        if (isLibX264Encoder
+                            || isLibX265Encoder
+                            || hasTextSubs
+                            || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))
                         {
                         {
                             if (isNvencEncoder)
                             if (isNvencEncoder)
                             {
                             {
@@ -2826,7 +2984,11 @@ namespace MediaBrowser.Controller.MediaEncoding
                 }
                 }
 
 
                 // Assert 8-bit hardware decodable
                 // Assert 8-bit hardware decodable
-                else if (!isColorDepth10 && (isLibX264Encoder || isLibX265Encoder || hasSubs))
+                else if (!isColorDepth10
+                         && (isLibX264Encoder
+                             || isLibX265Encoder
+                             || hasTextSubs
+                             || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)))
                 {
                 {
                     if (isNvencEncoder)
                     if (isNvencEncoder)
                     {
                     {
@@ -2847,7 +3009,7 @@ namespace MediaBrowser.Controller.MediaEncoding
                 {
                 {
                     // Convert hw context from ocl to va.
                     // Convert hw context from ocl to va.
                     // For tonemapping and text subs burn-in.
                     // For tonemapping and text subs burn-in.
-                    if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported)
+                    if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported)
                     {
                     {
                         filters.Add("scale_vaapi");
                         filters.Add("scale_vaapi");
                     }
                     }
@@ -2893,6 +3055,17 @@ namespace MediaBrowser.Controller.MediaEncoding
                 filters.Add("hwupload_cuda");
                 filters.Add("hwupload_cuda");
             }
             }
 
 
+            // If no tonemap filter is applied,
+            // tag the video range as SDR to prevent the encoder from encoding HDR video.
+            if (isNoTonemapFilterApplied)
+            {
+                var outputSdrParams = GetOutputSdrParams(null);
+                if (!string.IsNullOrEmpty(outputSdrParams))
+                {
+                    filters.Add(outputSdrParams);
+                }
+            }
+
             var output = string.Empty;
             var output = string.Empty;
             if (filters.Count > 0)
             if (filters.Count > 0)
             {
             {
@@ -2905,6 +3078,36 @@ namespace MediaBrowser.Controller.MediaEncoding
             return output;
             return output;
         }
         }
 
 
+        public static string GetInputHdrParams(string colorTransfer)
+        {
+            if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+            {
+                // HLG
+                return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
+            }
+            else
+            {
+                // HDR10
+                return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
+            }
+        }
+
+        public static string GetOutputSdrParams(string tonemappingRange)
+        {
+            // SDR
+            if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
+            {
+                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
+            }
+
+            if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
+            {
+                return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
+            }
+
+            return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the number of threads.
         /// Gets the number of threads.
         /// </summary>
         /// </summary>
@@ -3371,8 +3574,13 @@ namespace MediaBrowser.Controller.MediaEncoding
                 if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
                 if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
                     && IsVppTonemappingSupported(state, encodingOptions))
                     && IsVppTonemappingSupported(state, encodingOptions))
                 {
                 {
-                    // Since tonemap_vaapi only support HEVC for now, no need to check the codec again.
-                    return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
+                    var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
+                    var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
+                    if (isQsvEncoder)
+                    {
+                        // Since tonemap_vaapi only support HEVC for now, no need to check the codec again.
+                        return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
+                    }
                 }
                 }
 
 
                 if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
                 if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
@@ -3895,6 +4103,11 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
             if (videoStream != null)
             if (videoStream != null)
             {
             {
+                if (videoStream.BitDepth.HasValue)
+                {
+                    return videoStream.BitDepth.Value == 10;
+                }
+
                 if (!string.IsNullOrEmpty(videoStream.PixelFormat))
                 if (!string.IsNullOrEmpty(videoStream.PixelFormat))
                 {
                 {
                     result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase);
                     result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase);
@@ -3914,12 +4127,6 @@ namespace MediaBrowser.Controller.MediaEncoding
                         return true;
                         return true;
                     }
                     }
                 }
                 }
-
-                result = (videoStream.BitDepth ?? 8) == 10;
-                if (result)
-                {
-                    return true;
-                }
             }
             }
 
 
             return result;
             return result;

+ 23 - 0
MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs

@@ -0,0 +1,23 @@
+namespace MediaBrowser.Controller.MediaEncoding
+{
+    /// <summary>
+    /// Enum FilterOptionType.
+    /// </summary>
+    public enum FilterOptionType
+    {
+        /// <summary>
+        /// The scale_cuda_format.
+        /// </summary>
+        ScaleCudaFormat = 0,
+
+        /// <summary>
+        /// The tonemap_cuda_name.
+        /// </summary>
+        TonemapCudaName = 1,
+
+        /// <summary>
+        /// The tonemap_opencl_bt2390.
+        /// </summary>
+        TonemapOpenclBt2390 = 2
+    }
+}

+ 13 - 1
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -55,9 +55,21 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// Whether given filter is supported.
         /// Whether given filter is supported.
         /// </summary>
         /// </summary>
         /// <param name="filter">The filter.</param>
         /// <param name="filter">The filter.</param>
+        /// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
+        bool SupportsFilter(string filter);
+
+        /// <summary>
+        /// Whether filter is supported with the given option.
+        /// </summary>
         /// <param name="option">The option.</param>
         /// <param name="option">The option.</param>
         /// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
         /// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
-        bool SupportsFilter(string filter, string option);
+        bool SupportsFilterWithOption(FilterOptionType option);
+
+        /// <summary>
+        /// Get the version of media encoder.
+        /// </summary>
+        /// <returns>The version of media encoder.</returns>
+        Version GetMediaEncoderVersion();
 
 
         /// <summary>
         /// <summary>
         /// Extracts the audio image.
         /// Extracts the audio image.

+ 93 - 9
MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs

@@ -89,6 +89,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
             "hevc_videotoolbox"
             "hevc_videotoolbox"
         };
         };
 
 
+        private static readonly string[] _requiredFilters = new[]
+        {
+            "scale_cuda",
+            "yadif_cuda",
+            "hwupload_cuda",
+            "overlay_cuda",
+            "tonemap_cuda",
+            "tonemap_opencl",
+            "tonemap_vaapi",
+        };
+
+        private static readonly IReadOnlyDictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
+        {
+            { 0, new string[] { "scale_cuda", "Output format (default \"same\")" } },
+            { 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } },
+            { 2, new string[] { "tonemap_opencl", "bt2390" } }
+        };
+
         // These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
         // These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
         private static readonly IReadOnlyDictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
         private static readonly IReadOnlyDictionary<string, Version> _ffmpegMinimumLibraryVersions = new Dictionary<string, Version>
         {
         {
@@ -156,7 +174,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
             }
 
 
             // Work out what the version under test is
             // Work out what the version under test is
-            var version = GetFFmpegVersion(versionOutput);
+            var version = GetFFmpegVersionInternal(versionOutput);
 
 
             _logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown");
             _logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown");
 
 
@@ -200,6 +218,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
         public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
         public IEnumerable<string> GetHwaccels() => GetHwaccelTypes();
 
 
+        public IEnumerable<string> GetFilters() => GetFFmpegFilters();
+
+        public IDictionary<int, bool> GetFiltersWithOption() => GetFFmpegFiltersWithOption();
+
+        public Version? GetFFmpegVersion()
+        {
+            string output;
+            try
+            {
+                output = GetProcessOutput(_encoderPath, "-version");
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error validating encoder");
+                return null;
+            }
+
+            if (string.IsNullOrWhiteSpace(output))
+            {
+                _logger.LogError("FFmpeg validation: The process returned no result");
+                return null;
+            }
+
+            _logger.LogDebug("ffmpeg output: {Output}", output);
+
+            return GetFFmpegVersionInternal(output);
+        }
+
         /// <summary>
         /// <summary>
         /// Using the output from "ffmpeg -version" work out the FFmpeg version.
         /// Using the output from "ffmpeg -version" work out the FFmpeg version.
         /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
         /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy
@@ -208,7 +254,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// </summary>
         /// </summary>
         /// <param name="output">The output from "ffmpeg -version".</param>
         /// <param name="output">The output from "ffmpeg -version".</param>
         /// <returns>The FFmpeg version.</returns>
         /// <returns>The FFmpeg version.</returns>
-        internal Version? GetFFmpegVersion(string output)
+        internal Version? GetFFmpegVersionInternal(string output)
         {
         {
             // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
             // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
             var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
             var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
@@ -297,9 +343,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return found;
             return found;
         }
         }
 
 
-        public bool CheckFilter(string filter, string option)
+        public bool CheckFilterWithOption(string filter, string option)
         {
         {
-            if (string.IsNullOrEmpty(filter))
+            if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option))
             {
             {
                 return false;
                 return false;
             }
             }
@@ -317,11 +363,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             if (output.Contains("Filter " + filter, StringComparison.Ordinal))
             if (output.Contains("Filter " + filter, StringComparison.Ordinal))
             {
             {
-                if (string.IsNullOrEmpty(option))
-                {
-                    return true;
-                }
-
                 return output.Contains(option, StringComparison.Ordinal);
                 return output.Contains(option, StringComparison.Ordinal);
             }
             }
 
 
@@ -362,6 +403,49 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return found;
             return found;
         }
         }
 
 
+        private IEnumerable<string> GetFFmpegFilters()
+        {
+            string output;
+            try
+            {
+                output = GetProcessOutput(_encoderPath, "-filters");
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "Error detecting available filters");
+                return Enumerable.Empty<string>();
+            }
+
+            if (string.IsNullOrWhiteSpace(output))
+            {
+                return Enumerable.Empty<string>();
+            }
+
+            var found = Regex
+                .Matches(output, @"^\s\S{3}\s(?<filter>[\w|-]+)\s+.+$", RegexOptions.Multiline)
+                .Cast<Match>()
+                .Select(x => x.Groups["filter"].Value)
+                .Where(x => _requiredFilters.Contains(x));
+
+            _logger.LogInformation("Available filters: {Filters}", found);
+
+            return found;
+        }
+
+        private IDictionary<int, bool> GetFFmpegFiltersWithOption()
+        {
+            IDictionary<int, bool> dict = new Dictionary<int, bool>();
+            for (int i = 0; i < _filterOptionsDict.Count; i++)
+            {
+                if (_filterOptionsDict.TryGetValue(i, out var val) && val.Length == 2)
+                {
+                    dict.Add(i, CheckFilterWithOption(val[0], val[1]));
+                }
+            }
+
+            return dict;
+        }
+
         private string GetProcessOutput(string path, string arguments)
         private string GetProcessOutput(string path, string arguments)
         {
         {
             using (var process = new Process()
             using (var process = new Process()

+ 35 - 4
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -66,7 +66,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
         private List<string> _encoders = new List<string>();
         private List<string> _encoders = new List<string>();
         private List<string> _decoders = new List<string>();
         private List<string> _decoders = new List<string>();
         private List<string> _hwaccels = new List<string>();
         private List<string> _hwaccels = new List<string>();
+        private List<string> _filters = new List<string>();
+        private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
 
 
+        private Version _ffmpegVersion = null;
         private string _ffmpegPath = string.Empty;
         private string _ffmpegPath = string.Empty;
         private string _ffprobePath;
         private string _ffprobePath;
         private int threads;
         private int threads;
@@ -130,7 +133,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
                 SetAvailableDecoders(validator.GetDecoders());
                 SetAvailableDecoders(validator.GetDecoders());
                 SetAvailableEncoders(validator.GetEncoders());
                 SetAvailableEncoders(validator.GetEncoders());
+                SetAvailableFilters(validator.GetFilters());
+                SetAvailableFiltersWithOption(validator.GetFiltersWithOption());
                 SetAvailableHwaccels(validator.GetHwaccels());
                 SetAvailableHwaccels(validator.GetHwaccels());
+                SetMediaEncoderVersion(validator);
+
                 threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
                 threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
             }
             }
 
 
@@ -278,6 +285,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
             _hwaccels = list.ToList();
             _hwaccels = list.ToList();
         }
         }
 
 
+        public void SetAvailableFilters(IEnumerable<string> list)
+        {
+            _filters = list.ToList();
+        }
+
+        public void SetAvailableFiltersWithOption(IDictionary<int, bool> dict)
+        {
+            _filtersWithOption = dict;
+        }
+
+        public void SetMediaEncoderVersion(EncoderValidator validator)
+        {
+            _ffmpegVersion = validator.GetFFmpegVersion();
+        }
+
         public bool SupportsEncoder(string encoder)
         public bool SupportsEncoder(string encoder)
         {
         {
             return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
             return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
@@ -293,17 +315,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
             return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
         }
         }
 
 
-        public bool SupportsFilter(string filter, string option)
+        public bool SupportsFilter(string filter)
         {
         {
-            if (_ffmpegPath != null)
+            return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase);
+        }
+
+        public bool SupportsFilterWithOption(FilterOptionType option)
+        {
+            if (_filtersWithOption.TryGetValue((int)option, out var val))
             {
             {
-                var validator = new EncoderValidator(_logger, _ffmpegPath);
-                return validator.CheckFilter(filter, option);
+                return val;
             }
             }
 
 
             return false;
             return false;
         }
         }
 
 
+        public Version GetMediaEncoderVersion()
+        {
+            return _ffmpegVersion;
+        }
+
         public bool CanEncodeToAudioCodec(string codec)
         public bool CanEncodeToAudioCodec(string codec)
         {
         {
             if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))

+ 17 - 0
MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs

@@ -739,6 +739,23 @@ namespace MediaBrowser.MediaEncoding.Probing
                     stream.BitDepth = streamInfo.BitsPerRawSample;
                     stream.BitDepth = streamInfo.BitsPerRawSample;
                 }
                 }
 
 
+                if (!stream.BitDepth.HasValue)
+                {
+                    if (!string.IsNullOrEmpty(streamInfo.PixelFormat)
+                        && streamInfo.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase))
+                    {
+                        stream.BitDepth = 10;
+                    }
+
+                    if (!string.IsNullOrEmpty(streamInfo.Profile)
+                        && (streamInfo.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
+                            || streamInfo.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)
+                            || streamInfo.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase)))
+                    {
+                        stream.BitDepth = 10;
+                    }
+                }
+
                 // stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
                 // stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) ||
                 //    string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
                 //    string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
                 //    string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);
                 //    string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);