aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs682
1 files changed, 496 insertions, 186 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 26b0bc3de..92b345f12 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
@@ -16,16 +15,12 @@ using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
-using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
public class EncodingHelper
{
- private static readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder;
@@ -40,6 +35,8 @@ namespace MediaBrowser.Controller.MediaEncoding
"ConstrainedHigh"
};
+ private static readonly Version _minVersionForCudaOverlay = new Version(4, 4);
+
public EncodingHelper(
IMediaEncoder mediaEncoder,
ISubtitleEncoder subtitleEncoder)
@@ -109,17 +106,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 options.EnableTonemapping
+ && (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");
+ }
+
+ private bool IsCudaTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
+ {
+ var videoStream = state.VideoStream;
+ if (videoStream == null)
+ {
+ return false;
+ }
+
+ return options.EnableTonemapping
+ && (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);
}
private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
@@ -135,24 +156,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 options.EnableVppTonemapping
+ && 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");
}
// Hybrid VPP tonemapping for QSV with VAAPI
- var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
- if (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.
- return IsColorDepth10(state)
+ return options.EnableVppTonemapping
+ && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
+ && IsColorDepth10(state)
&& string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
&& _mediaEncoder.SupportsHwaccel("vaapi")
- && _mediaEncoder.SupportsHwaccel("qsv")
- && options.EnableVppTonemapping
- && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
+ && _mediaEncoder.SupportsFilter("tonemap_vaapi")
+ && _mediaEncoder.SupportsHwaccel("qsv");
}
// Native VPP tonemapping may come to QSV in the future.
@@ -162,6 +184,9 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the name of the output video codec.
/// </summary>
+ /// <param name="state">Encording state.</param>
+ /// <param name="encodingOptions">Encoding options.</param>
+ /// <returns>Encoder string.</returns>
public string GetVideoEncoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var codec = state.OutputVideoCodec;
@@ -179,11 +204,17 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetH264Encoder(state, encodingOptions);
}
- if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(codec, "vp8", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
{
return "libvpx";
}
+ if (string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libvpx-vp9";
+ }
+
if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
{
return "wmv2";
@@ -316,6 +347,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return container;
}
+ /// <summary>
+ /// Gets decoder from a codec.
+ /// </summary>
+ /// <param name="codec">Codec to use.</param>
+ /// <returns>Decoder string.</returns>
public string GetDecoderFromCodec(string codec)
{
// For these need to find out the ffmpeg names
@@ -345,6 +381,8 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Infers the audio codec based on the url.
/// </summary>
+ /// <param name="container">Container to use.</param>
+ /// <returns>Codec string.</returns>
public string InferAudioCodec(string container)
{
var ext = "." + (container ?? string.Empty);
@@ -408,7 +446,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
{
- return "vpx";
+ // TODO: this may not always mean VP8, as the codec ages
+ return "vp8";
}
if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
@@ -490,12 +529,20 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the input argument.
/// </summary>
- public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions)
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <returns>Input arguments.</returns>
+ 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();
+#pragma warning disable CA1508 // Defaults to string.Empty
var isSwDecoder = string.IsNullOrEmpty(videoDecoder);
+#pragma warning restore CA1508
var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1;
var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
@@ -503,42 +550,40 @@ 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 = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
- var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
- 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("-hwaccel_device va ")
- .Append("-hwaccel_output_format vaapi ")
- .Append("-filter_hw_device ocl ");
+ arg.Append("-init_hw_device vaapi=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 ");
}
else
{
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(' ');
}
@@ -546,7 +591,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;
@@ -582,9 +627,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 ");
}
@@ -593,7 +637,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
@@ -601,22 +645,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 ");
}
@@ -643,6 +696,11 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(" -i \"").Append(subtitlePath).Append('\"');
}
+ if (state.AudioStream != null && state.AudioStream.IsExternal)
+ {
+ arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
+ }
+
return arg.ToString();
}
@@ -726,54 +784,42 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
{
- var bitrate = state.OutputVideoBitrate;
-
- if (bitrate.HasValue)
+ if (state.OutputVideoBitrate == null)
{
- if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
- {
- // When crf is used with vpx, b:v becomes a max rate
- // https://trac.ffmpeg.org/wiki/Encode/VP9
- return string.Format(
- CultureInfo.InvariantCulture,
- " -maxrate:v {0} -bufsize:v {1} -b:v {0}",
- bitrate.Value,
- bitrate.Value * 2);
- }
+ return string.Empty;
+ }
- if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
- {
- return string.Format(
- CultureInfo.InvariantCulture,
- " -b:v {0}",
- bitrate.Value);
- }
+ int bitrate = state.OutputVideoBitrate.Value;
- if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
- {
- // h264
- return string.Format(
- CultureInfo.InvariantCulture,
- " -maxrate {0} -bufsize {1}",
- bitrate.Value,
- bitrate.Value * 2);
- }
+ // Currently use the same buffer size for all encoders
+ int bufsize = bitrate * 2;
- // h264
- return string.Format(
- CultureInfo.InvariantCulture,
- " -b:v {0} -maxrate {0} -bufsize {1}",
- bitrate.Value,
- bitrate.Value * 2);
+ if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "libvpx-vp9", StringComparison.OrdinalIgnoreCase))
+ {
+ // When crf is used with vpx, b:v becomes a max rate
+ // https://trac.ffmpeg.org/wiki/Encode/VP8
+ // https://trac.ffmpeg.org/wiki/Encode/VP9
+ return FormattableString.Invariant($" -maxrate:v {bitrate} -bufsize:v {bufsize} -b:v {bitrate}");
}
- return string.Empty;
+ if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+ {
+ return FormattableString.Invariant($" -b:v {bitrate}");
+ }
+
+ if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
+ {
+ return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
+ }
+
+ return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
{
- if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel))
+ if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double requestLevel))
{
if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
@@ -868,7 +914,7 @@ namespace MediaBrowser.Controller.MediaEncoding
CultureInfo.InvariantCulture,
"subtitles='{0}:si={1}'{2}",
_mediaEncoder.EscapeSubtitleFilterPath(mediaPath),
- state.InternalSubtitleStreamOffset.ToString(_usCulture),
+ state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture),
// fallbackFontParam,
setPtsParam);
}
@@ -966,6 +1012,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the video bitrate to specify on the command line.
/// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="videoEncoder">Video encoder to use.</param>
+ /// <param name="encodingOptions">Encoding options.</param>
+ /// <param name="defaultPreset">Default present to use for encoding.</param>
+ /// <returns>Video bitrate.</returns>
public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset)
{
var param = string.Empty;
@@ -1146,7 +1197,7 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -header_insertion_mode gop -gops_per_idr 1";
}
}
- else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm
+ else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // vp8
{
// Values 0-3, 0 being highest quality but slower
var profileScore = 0;
@@ -1169,11 +1220,60 @@ namespace MediaBrowser.Controller.MediaEncoding
param += string.Format(
CultureInfo.InvariantCulture,
" -speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
- profileScore.ToString(_usCulture),
+ profileScore.ToString(CultureInfo.InvariantCulture),
crf,
qmin,
qmax);
}
+ else if (string.Equals(videoEncoder, "libvpx-vp9", StringComparison.OrdinalIgnoreCase)) // vp9
+ {
+ // When `-deadline` is set to `good` or `best`, `-cpu-used` ranges from 0-5.
+ // When `-deadline` is set to `realtime`, `-cpu-used` ranges from 0-15.
+ // Resources:
+ // * https://trac.ffmpeg.org/wiki/Encode/VP9
+ // * https://superuser.com/questions/1586934
+ // * https://developers.google.com/media/vp9
+ param += encodingOptions.EncoderPreset switch
+ {
+ "veryslow" => " -deadline best -cpu-used 0",
+ "slower" => " -deadline best -cpu-used 2",
+ "slow" => " -deadline best -cpu-used 3",
+ "medium" => " -deadline good -cpu-used 0",
+ "fast" => " -deadline good -cpu-used 1",
+ "faster" => " -deadline good -cpu-used 2",
+ "veryfast" => " -deadline good -cpu-used 3",
+ "superfast" => " -deadline good -cpu-used 4",
+ "ultrafast" => " -deadline good -cpu-used 5",
+ _ => " -deadline good -cpu-used 1"
+ };
+
+ // TODO: until VP9 gets its own CRF setting, base CRF on H.265.
+ int h265Crf = encodingOptions.H265Crf;
+ int defaultVp9Crf = 31;
+ if (h265Crf >= 0 && h265Crf <= 51)
+ {
+ // This conversion factor is chosen to match the default CRF for H.265 to the
+ // recommended 1080p CRF from Google. The factor also maps the logarithmic CRF
+ // scale of x265 [0, 51] to that of VP9 [0, 63] relatively well.
+
+ // Resources:
+ // * https://developers.google.com/media/vp9/settings/vod
+ const float H265ToVp9CrfConversionFactor = 1.12F;
+
+ var vp9Crf = Convert.ToInt32(h265Crf * H265ToVp9CrfConversionFactor);
+
+ // Encoder allows for CRF values in the range [0, 63].
+ vp9Crf = Math.Clamp(vp9Crf, 0, 63);
+
+ param += FormattableString.Invariant($" -crf {vp9Crf}");
+ }
+ else
+ {
+ param += FormattableString.Invariant($" -crf {defaultVp9Crf}");
+ }
+
+ param += " -row-mt 1 -profile 1";
+ }
else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase))
{
param += " -mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
@@ -1192,7 +1292,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var framerate = GetFramerateParam(state);
if (framerate.HasValue)
{
- param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(_usCulture));
+ param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.InvariantCulture));
}
var targetVideoCodec = state.ActualOutputVideoCodec;
@@ -1296,7 +1396,7 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
{
// hevc_qsv use -level 51 instead of -level 153.
- if (double.TryParse(level, NumberStyles.Any, _usCulture, out double hevcLevel))
+ if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double hevcLevel))
{
param += " -level " + (hevcLevel / 3);
}
@@ -1458,7 +1558,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If a specific level was requested, the source must match or be less than
var level = state.GetRequestedLevel(videoStream.Codec);
if (!string.IsNullOrEmpty(level)
- && double.TryParse(level, NumberStyles.Any, _usCulture, out var requestLevel))
+ && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var requestLevel))
{
if (!videoStream.Level.HasValue)
{
@@ -1692,7 +1792,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return 128000;
}
- public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions, bool isHls)
+ public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
{
var channels = state.OutputAudioChannels;
@@ -1706,7 +1806,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.AudioStream.Channels.Value > 5
&& !encodingOptions.DownMixAudioBoost.Equals(1))
{
- filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture));
+ filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
}
var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
@@ -1745,7 +1845,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var request = state.BaseRequest;
- var inputChannels = audioStream?.Channels;
+ var inputChannels = audioStream.Channels;
if (inputChannels <= 0)
{
@@ -1904,10 +2004,24 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream != null)
{
- args += string.Format(
- CultureInfo.InvariantCulture,
- " -map 0:{0}",
- state.AudioStream.Index);
+ if (state.AudioStream.IsExternal)
+ {
+ int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1;
+ int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream);
+
+ args += string.Format(
+ CultureInfo.InvariantCulture,
+ " -map {0}:{1}",
+ externalAudioMapIndex,
+ externalAudioStream);
+ }
+ else
+ {
+ args += string.Format(
+ CultureInfo.InvariantCulture,
+ " -map 0:{0}",
+ state.AudioStream.Index);
+ }
}
else
{
@@ -1967,8 +2081,12 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Gets the graphical subtitle param.
+ /// Gets the graphical subtitle parameter.
/// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="outputVideoCodec">Video codec to use.</param>
+ /// <returns>Graphical subtitle parameter.</returns>
public string GetGraphicalSubtitleParam(
EncodingJobInfo state,
EncodingOptions options,
@@ -1983,7 +2101,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var videoSizeParam = string.Empty;
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
- var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ var isLinux = OperatingSystem.IsLinux();
var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
@@ -1992,14 +2110,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);
}
@@ -2009,8 +2131,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// Adjust the size of graphical subtitles to fit the video stream.
var videoStream = state.VideoStream;
- var inputWidth = videoStream?.Width;
- var inputHeight = videoStream?.Height;
+ var inputWidth = videoStream.Width;
+ var inputHeight = videoStream.Height;
var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
if (width.HasValue && height.HasValue)
@@ -2025,13 +2147,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 ?
@@ -2044,9 +2175,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)
@@ -2057,9 +2188,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
@@ -2072,9 +2203,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))
@@ -2091,16 +2222,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)
{
- 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(
@@ -2197,11 +2337,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";
@@ -2252,15 +2392,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";
}
@@ -2303,8 +2451,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isExynosV4L2)
{
- var widthParam = requestedWidth.Value.ToString(_usCulture);
- var heightParam = requestedHeight.Value.ToString(_usCulture);
+ var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
+ var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
filters.Add(
string.Format(
@@ -2322,8 +2470,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
else if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
{
- var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture);
- var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture);
+ var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
+ var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
if (isExynosV4L2)
{
@@ -2355,7 +2503,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else
{
- var widthParam = requestedWidth.Value.ToString(_usCulture);
+ var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
filters.Add(
string.Format(
@@ -2368,7 +2516,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If a fixed height was requested
else if (requestedHeight.HasValue)
{
- var heightParam = requestedHeight.Value.ToString(_usCulture);
+ var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
if (isExynosV4L2)
{
@@ -2391,7 +2539,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If a max width was requested
else if (requestedMaxWidth.HasValue)
{
- var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture);
+ var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
if (isExynosV4L2)
{
@@ -2414,7 +2562,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If a max height was requested
else if (requestedMaxHeight.HasValue)
{
- var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture);
+ var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
if (isExynosV4L2)
{
@@ -2486,6 +2634,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
}
+ /// <summary>
+ /// Gets the output size parameter.
+ /// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="outputVideoCodec">Video codec to use.</param>
+ /// <returns>The output size parameter.</returns>
public string GetOutputSizeParam(
EncodingJobInfo state,
EncodingOptions options,
@@ -2496,8 +2651,13 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
+ /// Gets the output size parameter.
/// If we're going to put a fixed size on the command line, this will calculate it.
/// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="outputVideoCodec">Video codec to use.</param>
+ /// <returns>The output size parameter.</returns>
public string GetOutputSizeParamInternal(
EncodingJobInfo state,
EncodingOptions options,
@@ -2526,16 +2686,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 = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ 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;
@@ -2547,19 +2712,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)
@@ -2631,7 +2802,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,
@@ -2639,6 +2814,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,
@@ -2650,7 +2833,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");
@@ -2666,12 +2853,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");
@@ -2779,6 +2972,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)
@@ -2788,10 +3036,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)
@@ -2800,7 +3048,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isCudaFormatConversionSupported)
{
- if (isLibX264Encoder || isLibX265Encoder || hasSubs)
+ if (isLibX264Encoder
+ || isLibX265Encoder
+ || hasTextSubs
+ || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))
{
if (isNvencEncoder)
{
@@ -2827,7 +3078,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)
{
@@ -2848,7 +3103,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");
}
@@ -2894,6 +3149,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)
{
@@ -2906,26 +3172,56 @@ 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>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="encodingOptions">Encoding options.</param>
+ /// <param name="outputVideoCodec">Video codec to use.</param>
+ /// <returns>Number of threads.</returns>
#nullable enable
public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec)
{
- if (string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
- {
- // per docs:
- // -threads number of threads to use for encoding, can't be 0 [auto] with VP8
- // (recommended value : number of real cores - 1)
- return Math.Max(Environment.ProcessorCount - 1, 1);
- }
+ // VP8 and VP9 encoders must have their thread counts set.
+ bool mustSetThreadCount = string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(outputVideoCodec, "libvpx-vp9", StringComparison.OrdinalIgnoreCase);
var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
- // Automatic
if (threads <= 0)
{
- return 0;
+ // Automatically set thread count
+ return mustSetThreadCount ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
}
else if (threads >= Environment.ProcessorCount)
{
@@ -3067,7 +3363,7 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier += " " + videoDecoder;
if (!IsCopyCodec(state.OutputVideoCodec)
- && (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
+ && videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase))
{
var videoStream = state.VideoStream;
var inputWidth = videoStream?.Width;
@@ -3076,7 +3372,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
- if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
+ if (videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase)
&& width.HasValue
&& height.HasValue)
{
@@ -3372,8 +3668,13 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
&& 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.Contains("qsv", StringComparison.OrdinalIgnoreCase);
+ 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))
@@ -3552,6 +3853,11 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets a hw decoder name.
/// </summary>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="decoder">Decoder to use.</param>
+ /// <param name="videoCodec">Video codec to use.</param>
+ /// <param name="isColorDepth10">Specifies if color depth 10.</param>
+ /// <returns>Hardware decoder name.</returns>
public string GetHwDecoderName(EncodingOptions options, string decoder, string videoCodec, bool isColorDepth10)
{
var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
@@ -3570,10 +3876,15 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system.
/// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="videoCodec">Video codec to use.</param>
+ /// <param name="isColorDepth10">Specifies if color depth 10.</param>
+ /// <returns>Hardware accelerator type.</returns>
public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10)
{
- var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ var isWindows = OperatingSystem.IsWindows();
+ var isLinux = OperatingSystem.IsLinux();
var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1);
var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va");
var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
@@ -3828,15 +4139,15 @@ namespace MediaBrowser.Controller.MediaEncoding
if (bitrate.HasValue)
{
- args += " -ab " + bitrate.Value.ToString(_usCulture);
+ args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
}
if (state.OutputAudioSampleRate.HasValue)
{
- args += " -ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture);
+ args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
}
- args += GetAudioFilterParam(state, encodingOptions, false);
+ args += GetAudioFilterParam(state, encodingOptions);
return args;
}
@@ -3849,12 +4160,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (bitrate.HasValue)
{
- audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture));
+ audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
}
if (state.OutputAudioChannels.HasValue)
{
- audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(_usCulture));
+ audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
// opus will fail on 44100
@@ -3862,7 +4173,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.OutputAudioSampleRate.HasValue)
{
- audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture));
+ audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
}
}
@@ -3896,6 +4207,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);
@@ -3915,12 +4231,6 @@ namespace MediaBrowser.Controller.MediaEncoding
return true;
}
}
-
- result = (videoStream.BitDepth ?? 8) == 10;
- if (result)
- {
- return true;
- }
}
return result;