add support for cuda tonemap and overlay

This commit is contained in:
nyanmisaka 2021-07-25 00:52:16 +08:00
parent aaab6a3518
commit 3beda02d92
6 changed files with 477 additions and 103 deletions

View File

@ -40,6 +40,8 @@ namespace MediaBrowser.Controller.MediaEncoding
"ConstrainedHigh"
};
private static readonly Version minVersionForCudaOverlay = new Version(4, 4);
public EncodingHelper(
IMediaEncoder mediaEncoder,
ISubtitleEncoder subtitleEncoder)
@ -109,17 +111,41 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsCudaSupported()
{
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;
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")
&& 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)
@ -135,23 +161,25 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
// 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)
&& _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
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.
return IsColorDepth10(state)
return string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
&& IsColorDepth10(state)
&& string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
&& _mediaEncoder.SupportsHwaccel("vaapi")
&& _mediaEncoder.SupportsFilter("tonemap_vaapi")
&& _mediaEncoder.SupportsHwaccel("qsv")
&& options.EnableVppTonemapping
&& string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
&& options.EnableVppTonemapping;
}
// Native VPP tonemapping may come to QSV in the future.
@ -489,11 +517,14 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the input argument.
/// </summary>
public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
public string GetInputArgument(EncodingJobInfo state, EncodingOptions options)
{
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 isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", 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 isNvdecDecoder = videoDecoder.Contains("cuda", 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 (state.IsVideoRequest
&& _mediaEncoder.SupportsHwaccel("vaapi")
&& string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
&& string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
if (isVaapiDecoder)
{
if (isTonemappingSupported && !isVppTonemappingSupported)
if (isOpenclTonemappingSupported && !isVppTonemappingSupported)
{
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_output_format vaapi ")
.Append("-filter_hw_device ocl ");
@ -530,14 +559,14 @@ namespace MediaBrowser.Controller.MediaEncoding
{
arg.Append("-hwaccel_output_format vaapi ")
.Append("-vaapi_device ")
.Append(encodingOptions.VaapiDevice)
.Append(options.VaapiDevice)
.Append(' ');
}
}
else if (!isVaapiDecoder && isVaapiEncoder)
{
arg.Append("-vaapi_device ")
.Append(encodingOptions.VaapiDevice)
.Append(options.VaapiDevice)
.Append(' ');
}
@ -545,7 +574,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
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;
@ -581,9 +610,8 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (isVaapiDecoder && isVppTonemappingSupported)
{
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 ");
}
@ -592,7 +620,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
if (state.IsVideoRequest
&& string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
&& string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
&& isNvdecDecoder)
{
// Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562
@ -600,22 +628,31 @@ namespace MediaBrowser.Controller.MediaEncoding
}
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 (isTonemappingSupported)
if (!isCudaTonemappingSupported && isOpenclTonemappingSupported)
{
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
&& string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
&& string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)
&& (isD3d11vaDecoder || isSwDecoder))
{
if (isOpenclTonemappingSupported)
{
arg.Append("-init_hw_device opencl=ocl:")
.Append(options.OpenclDevice)
.Append(" -filter_hw_device ocl ");
}
}
if (state.IsVideoRequest
&& string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
{
arg.Append("-hwaccel videotoolbox ");
}
@ -1991,14 +2028,18 @@ namespace MediaBrowser.Controller.MediaEncoding
var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
var isNvdecDecoder = videoDecoder.Contains("cuda", 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 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.
// 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);
}
@ -2024,13 +2065,22 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(videoSizeParam)
&& !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
// For QSV, feed it into hardware encoder now
// upload graphical subtitle to QSV
if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
{
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 ?
@ -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)
// 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
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
@ -2056,9 +2106,9 @@ namespace MediaBrowser.Controller.MediaEncoding
[sub]: SW scaling subtitle to FixedOutputSize
[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
@ -2071,9 +2121,9 @@ namespace MediaBrowser.Controller.MediaEncoding
[sub]: SW scaling subtitle to FixedOutputSize
[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)
|| string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
@ -2090,16 +2140,25 @@ namespace MediaBrowser.Controller.MediaEncoding
}
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)
{
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}]{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\"";
? " -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(
@ -2196,11 +2255,11 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase);
var isQsvH264Encoder = videoEncoder.Contains("h264_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 isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))
var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported))
|| (isTonemappingSupportedOnQsv && isVppTonemappingSupported);
var outputPixFmt = "format=nv12";
@ -2251,15 +2310,23 @@ namespace MediaBrowser.Controller.MediaEncoding
var outputWidth = width.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 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;
if (isCudaFormatConversionSupported)
{
outputPixFmt = "format=nv12";
if (isTonemappingSupported && isTonemappingSupportedOnNvenc)
outputPixFmt = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder)
? "format=yuv420p"
: "format=nv12";
if ((isOpenclTonemappingSupported || isCudaTonemappingSupported)
&& isTonemappingSupportedOnNvenc)
{
outputPixFmt = "format=p010";
}
@ -2525,16 +2592,21 @@ namespace MediaBrowser.Controller.MediaEncoding
var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
var isCuvidH264Decoder = videoDecoder.Contains("h264_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 isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1;
var isLinux = OperatingSystem.IsLinux();
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 isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
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 hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@ -2546,19 +2618,25 @@ namespace MediaBrowser.Controller.MediaEncoding
var isScalingInAdvance = false;
var isCudaDeintInAdvance = false;
var isHwuploadCudaRequired = false;
var isNoTonemapFilterApplied = true;
var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
// 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.
// AMD Polaris and Vega or higher are recommended.
// 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}";
if (options.TonemappingParam != 0)
@ -2630,7 +2708,11 @@ namespace MediaBrowser.Controller.MediaEncoding
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,
// use tonemap_opencl filter for tone mapping,
@ -2638,6 +2720,14 @@ namespace MediaBrowser.Controller.MediaEncoding
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(
string.Format(
CultureInfo.InvariantCulture,
@ -2649,7 +2739,11 @@ namespace MediaBrowser.Controller.MediaEncoding
options.TonemappingParam,
options.TonemappingRange));
if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder)
if (isNvdecDecoder
|| isCuvidHevcDecoder
|| isCuvidVp9Decoder
|| isSwDecoder
|| isD3d11vaDecoder)
{
filters.Add("hwdownload");
filters.Add("format=nv12");
@ -2665,12 +2759,18 @@ namespace MediaBrowser.Controller.MediaEncoding
// Reverse the data route from opencl to vaapi.
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.
if ((isVaapiH264Encoder || isVaapiHevcEncoder)
&& !(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)))
&& !(isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported)))
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
@ -2778,6 +2878,61 @@ namespace MediaBrowser.Controller.MediaEncoding
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.
// Full hardware based video post processing, faster than OpenCL but lacks fine tuning options.
if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv)
@ -2787,10 +2942,10 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Another case is when using Nvenc decoder.
if (isNvdecDecoder && !isTonemappingSupported)
if (isNvdecDecoder && !isOpenclTonemappingSupported && !isCudaTonemappingSupported)
{
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
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
@ -2799,7 +2954,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isCudaFormatConversionSupported)
{
if (isLibX264Encoder || isLibX265Encoder || hasSubs)
if (isLibX264Encoder
|| isLibX265Encoder
|| hasTextSubs
|| (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))
{
if (isNvencEncoder)
{
@ -2826,7 +2984,11 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Assert 8-bit hardware decodable
else if (!isColorDepth10 && (isLibX264Encoder || isLibX265Encoder || hasSubs))
else if (!isColorDepth10
&& (isLibX264Encoder
|| isLibX265Encoder
|| hasTextSubs
|| (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)))
{
if (isNvencEncoder)
{
@ -2847,7 +3009,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// Convert hw context from ocl to va.
// For tonemapping and text subs burn-in.
if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported)
if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported)
{
filters.Add("scale_vaapi");
}
@ -2893,6 +3055,17 @@ namespace MediaBrowser.Controller.MediaEncoding
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;
if (filters.Count > 0)
{
@ -2905,6 +3078,36 @@ namespace MediaBrowser.Controller.MediaEncoding
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>
/// Gets the number of threads.
/// </summary>
@ -3370,10 +3573,15 @@ namespace MediaBrowser.Controller.MediaEncoding
// Hybrid VPP tonemapping with VAAPI
if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
&& IsVppTonemappingSupported(state, encodingOptions))
{
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))
{
@ -3895,6 +4103,11 @@ namespace MediaBrowser.Controller.MediaEncoding
if (videoStream != null)
{
if (videoStream.BitDepth.HasValue)
{
return videoStream.BitDepth.Value == 10;
}
if (!string.IsNullOrEmpty(videoStream.PixelFormat))
{
result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase);
@ -3914,12 +4127,6 @@ namespace MediaBrowser.Controller.MediaEncoding
return true;
}
}
result = (videoStream.BitDepth ?? 8) == 10;
if (result)
{
return true;
}
}
return result;

View File

@ -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
}
}

View File

@ -55,9 +55,21 @@ namespace MediaBrowser.Controller.MediaEncoding
/// Whether given filter is supported.
/// </summary>
/// <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>
/// <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>
/// Extracts the audio image.

View File

@ -89,6 +89,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
"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
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
var version = GetFFmpegVersion(versionOutput);
var version = GetFFmpegVersionInternal(versionOutput);
_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> 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>
/// 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
@ -208,7 +254,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// </summary>
/// <param name="output">The output from "ffmpeg -version".</param>
/// <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
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
@ -297,9 +343,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
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;
}
@ -317,11 +363,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (output.Contains("Filter " + filter, StringComparison.Ordinal))
{
if (string.IsNullOrEmpty(option))
{
return true;
}
return output.Contains(option, StringComparison.Ordinal);
}
@ -362,6 +403,49 @@ namespace MediaBrowser.MediaEncoding.Encoder
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)
{
using (var process = new Process()

View File

@ -66,7 +66,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
private List<string> _encoders = new List<string>();
private List<string> _decoders = 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 _ffprobePath;
private int threads;
@ -130,7 +133,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableDecoders(validator.GetDecoders());
SetAvailableEncoders(validator.GetEncoders());
SetAvailableFilters(validator.GetFilters());
SetAvailableFiltersWithOption(validator.GetFiltersWithOption());
SetAvailableHwaccels(validator.GetHwaccels());
SetMediaEncoderVersion(validator);
threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
}
@ -278,6 +285,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
_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)
{
return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
@ -293,17 +315,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
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)
{
var validator = new EncoderValidator(_logger, _ffmpegPath);
return validator.CheckFilter(filter, option);
if (_filtersWithOption.TryGetValue((int)option, out var val))
{
return val;
}
return false;
}
public Version GetMediaEncoderVersion()
{
return _ffmpegVersion;
}
public bool CanEncodeToAudioCodec(string codec)
{
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))

View File

@ -739,6 +739,23 @@ namespace MediaBrowser.MediaEncoding.Probing
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) ||
// string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) ||
// string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase);