aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
diff options
context:
space:
mode:
authorDominik <git@secnd.me>2023-06-15 19:38:42 +0200
committerGitHub <noreply@github.com>2023-06-15 19:38:42 +0200
commit17f1e8d19b1fd693893d66d2275ed8ae2476344e (patch)
tree7f48be975faa92042769870957587b3c7864f631 /MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
parente8ae7e5c38e28f13fa8de295e26c930cb46d9b79 (diff)
parent6771b5cabe96b4b3cbd1cd0c998d564f3dd17ed4 (diff)
Merge branch 'master' into segment-deletion
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs1271
1 files changed, 814 insertions, 457 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 0ddb3be42..af540ecb8 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -38,7 +38,14 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config;
private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
- private readonly Version _minKernelVersioni915Hang = new Version(5, 18);
+ // i915 hang was fixed by linux 6.2 (3f882f2)
+ private readonly Version _minKerneli915Hang = new Version(5, 18);
+ private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
+ private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
+
+ private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0);
+ private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
+ private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
private static readonly string[] _videoProfilesH264 = new[]
{
@@ -58,6 +65,41 @@ namespace MediaBrowser.Controller.MediaEncoding
"Main10"
};
+ private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "mp4",
+ "m4a",
+ "m4p",
+ "m4b",
+ "m4r",
+ "m4v",
+ };
+
+ // Set max transcoding channels for encoders that can't handle more than a set amount of channels
+ // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
+ private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreCase)
+ {
+ { "wmav2", 2 },
+ { "libmp3lame", 2 },
+ { "libfdk_aac", 6 },
+ { "aac_at", 6 },
+ { "ac3", 6 },
+ { "eac3", 6 },
+ { "dca", 6 },
+ { "mlp", 6 },
+ { "truehd", 6 },
+ };
+
+ public static readonly string[] LosslessAudioCodecs = new string[]
+ {
+ "alac",
+ "ape",
+ "flac",
+ "mlp",
+ "truehd",
+ "wavpack"
+ };
+
public EncodingHelper(
IApplicationPaths appPaths,
IMediaEncoder mediaEncoder,
@@ -97,14 +139,10 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(hwType)
&& encodingOptions.EnableHardwareEncoding
- && codecMap.ContainsKey(hwType))
+ && codecMap.TryGetValue(hwType, out var preferredEncoder)
+ && _mediaEncoder.SupportsEncoder(preferredEncoder))
{
- var preferredEncoder = codecMap[hwType];
-
- if (_mediaEncoder.SupportsEncoder(preferredEncoder))
- {
- return preferredEncoder;
- }
+ return preferredEncoder;
}
}
@@ -125,7 +163,8 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsVaapiFullSupported()
{
- return _mediaEncoder.SupportsHwaccel("vaapi")
+ return _mediaEncoder.SupportsHwaccel("drm")
+ && _mediaEncoder.SupportsHwaccel("vaapi")
&& _mediaEncoder.SupportsFilter("scale_vaapi")
&& _mediaEncoder.SupportsFilter("deinterlace_vaapi")
&& _mediaEncoder.SupportsFilter("tonemap_vaapi")
@@ -162,7 +201,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- if (state.VideoStream == null
+ if (state.VideoStream is null
|| !options.EnableTonemapping
|| GetVideoColorBitDepth(state) != 10)
{
@@ -189,7 +228,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- if (state.VideoStream == null)
+ if (state.VideoStream is null)
{
return false;
}
@@ -202,7 +241,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- if (state.VideoStream == null
+ if (state.VideoStream is null
|| !options.EnableVppTonemapping
|| GetVideoColorBitDepth(state) != 10)
{
@@ -520,7 +559,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
}
- else if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
{
return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
}
@@ -530,9 +570,12 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetInputPathArgument(EncodingJobInfo state)
{
- var mediaPath = state.MediaPath ?? string.Empty;
-
- return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource);
+ return state.MediaSource.VideoType switch
+ {
+ VideoType.Dvd => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource),
+ VideoType.BluRay => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource),
+ _ => _mediaEncoder.GetInputArgument(state.MediaPath, state.MediaSource)
+ };
}
/// <summary>
@@ -546,6 +589,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
{
+ // Use Apple's aac encoder if available as it provides best audio quality
+ if (_mediaEncoder.SupportsEncoder("aac_at"))
+ {
+ return "aac_at";
+ }
+
// Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
{
@@ -580,6 +629,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return "flac";
}
+ if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
+ {
+ return "dca";
+ }
+
return codec.ToLowerInvariant();
}
@@ -605,6 +659,26 @@ namespace MediaBrowser.Controller.MediaEncoding
deviceIndex);
}
+ private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
+ {
+ alias ??= VulkanAlias;
+ deviceIndex = deviceIndex >= 0
+ ? deviceIndex
+ : 0;
+ var vendorOpts = string.IsNullOrEmpty(deviceName)
+ ? ":" + deviceIndex
+ : ":" + "\"" + deviceName + "\"";
+ var options = string.IsNullOrEmpty(srcDeviceAlias)
+ ? vendorOpts
+ : "@" + srcDeviceAlias;
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ " -init_hw_device vulkan={0}{1}",
+ alias,
+ options);
+ }
+
private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias)
{
alias ??= OpenclAlias;
@@ -640,28 +714,43 @@ namespace MediaBrowser.Controller.MediaEncoding
options);
}
- private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string alias)
+ private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string srcDeviceAlias, string alias)
{
alias ??= VaapiAlias;
renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
- var options = string.IsNullOrEmpty(driver)
- ? renderNodePath
- : ",driver=" + driver + (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver);
+ var driverOpts = string.IsNullOrEmpty(driver)
+ ? ":" + renderNodePath
+ : ":,driver=" + driver + (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver);
+ var options = string.IsNullOrEmpty(srcDeviceAlias)
+ ? driverOpts
+ : "@" + srcDeviceAlias;
return string.Format(
CultureInfo.InvariantCulture,
- " -init_hw_device vaapi={0}:{1}",
+ " -init_hw_device vaapi={0}{1}",
alias,
options);
}
+ private string GetDrmDeviceArgs(string renderNodePath, string alias)
+ {
+ alias ??= DrmAlias;
+ renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ " -init_hw_device drm={0}:{1}",
+ alias,
+ renderNodePath);
+ }
+
private string GetQsvDeviceArgs(string alias)
{
var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
if (OperatingSystem.IsLinux())
{
// derive qsv from vaapi device
- return GetVaapiDeviceArgs(null, "iHD", "i915", VaapiAlias) + arg + "@" + VaapiAlias;
+ return GetVaapiDeviceArgs(null, "iHD", "i915", null, VaapiAlias) + arg + "@" + VaapiAlias;
}
if (OperatingSystem.IsWindows())
@@ -682,9 +771,12 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
{
- if (state.SubtitleStream != null
+ // DVBSUB and DVDSUB use the fixed canvas size 720x576
+ if (state.SubtitleStream is not null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
- && !state.SubtitleStream.IsTextSubtitleStream)
+ && !state.SubtitleStream.IsTextSubtitleStream
+ && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(state.SubtitleStream.Codec, "DVDSUB", StringComparison.OrdinalIgnoreCase))
{
var inW = state.VideoStream?.Width;
var inH = state.VideoStream?.Height;
@@ -752,47 +844,57 @@ namespace MediaBrowser.Controller.MediaEncoding
if (_mediaEncoder.IsVaapiDeviceInteliHD)
{
- args.Append(GetVaapiDeviceArgs(null, "iHD", null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(null, "iHD", null, null, VaapiAlias));
}
else if (_mediaEncoder.IsVaapiDeviceInteli965)
{
// Only override i965 since it has lower priority than iHD in libva lookup.
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965");
Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965");
- args.Append(GetVaapiDeviceArgs(null, "i965", null, VaapiAlias));
- }
- else
- {
- args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(null, "i965", null, null, VaapiAlias));
}
- var filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
+ var filterDevArgs = string.Empty;
+ var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
- if (isHwTonemapAvailable && IsOpenclFullSupported())
+ if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
{
- if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
+ if (doOclTonemap && !isVaapiDecoder)
{
- if (!isVaapiDecoder)
- {
- args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
- filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
- }
+ args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
+ }
+ }
+ else if (_mediaEncoder.IsVaapiDeviceAmd)
+ {
+ if (IsVulkanFullSupported()
+ && _mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
+ && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
+ {
+ args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
+ args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias));
+ args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
+
+ // libplacebo wants an explicitly set vulkan filter device.
+ filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
}
- else if (_mediaEncoder.IsVaapiDeviceAmd)
+ else
{
- if (!IsVulkanFullSupported()
- || !_mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
- || Environment.OSVersion.Version < _minKernelVersionAmdVkFmtModifier)
+ args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, VaapiAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
+
+ if (doOclTonemap)
{
+ // ROCm/ROCr OpenCL runtime
args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
}
- else
- {
- args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
- filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
- }
+ }
+ else if (doOclTonemap)
+ {
+ args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
args.Append(filterDevArgs);
@@ -928,18 +1030,30 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(canvasArgs);
}
- arg.Append(" -i ")
- .Append(GetInputPathArgument(state));
+ if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
+ {
+ var tmpConcatPath = Path.Join(options.TranscodingTempPath, state.MediaSource.Id + ".concat");
+ _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath);
+ arg.Append(" -f concat -safe 0 -i ")
+ .Append(tmpConcatPath);
+ }
+ else
+ {
+ arg.Append(" -i ")
+ .Append(GetInputPathArgument(state));
+ }
// sub2video for external graphical subtitles
- if (state.SubtitleStream != null
+ if (state.SubtitleStream is not null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& !state.SubtitleStream.IsTextSubtitleStream
&& state.SubtitleStream.IsExternal)
{
var subtitlePath = state.SubtitleStream.Path;
+ var subtitleExtension = Path.GetExtension(subtitlePath);
- if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(subtitleExtension, ".sub", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(subtitleExtension, ".sup", StringComparison.OrdinalIgnoreCase))
{
var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
if (File.Exists(idxFile))
@@ -963,7 +1077,7 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
}
- if (state.AudioStream != null && state.AudioStream.IsExternal)
+ if (state.AudioStream is not null && state.AudioStream.IsExternal)
{
// Also seek the external audio stream.
var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
@@ -977,7 +1091,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Disable auto inserted SW scaler for HW decoders in case of changed resolution.
var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
- if (!isSwDecoder)
+ if (!isSwDecoder && _mediaEncoder.EncoderVersion >= new Version(4, 4))
{
arg.Append(" -autoscale 0");
}
@@ -1021,19 +1135,19 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return "-bsf:v h264_mp4toannexb";
}
- else if (IsH265(stream))
+
+ if (IsH265(stream))
{
return "-bsf:v hevc_mp4toannexb";
}
- else if (IsAAC(stream))
+
+ if (IsAAC(stream))
{
// Convert adts header(mpegts) to asc header(mp4).
return "-bsf:a aac_adtstoasc";
}
- else
- {
- return null;
- }
+
+ return null;
}
public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
@@ -1066,7 +1180,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
{
- if (state.OutputVideoBitrate == null)
+ if (state.OutputVideoBitrate is null)
{
return string.Empty;
}
@@ -1111,10 +1225,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
- else
- {
- return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
- }
+
+ return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
@@ -1122,7 +1234,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
{
- if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double requestLevel))
+ if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
{
if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
@@ -1176,24 +1288,6 @@ namespace MediaBrowser.Controller.MediaEncoding
":fontsdir='{0}'",
_mediaEncoder.EscapeSubtitleFilterPath(fontPath));
- // TODO
- // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf");
- // string fallbackFontParam = string.Empty;
-
- // if (!File.Exists(fallbackFontPath))
- // {
- // _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(fallbackFontPath));
- // using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), GetType().Namespace + ".DroidSansFallback.ttf"))
- // {
- // using (var fileStream = new FileStream(fallbackFontPath, FileMode.Create, FileAccess.Write, FileShare.Read))
- // {
- // stream.CopyTo(fileStream);
- // }
- // }
- // }
-
- // fallbackFontParam = string.Format(CultureInfo.InvariantCulture, ":force_style='FontName=Droid Sans Fallback':fontsdir='{0}'", _mediaEncoder.EscapeSubtitleFilterPath(_fileSystem.GetDirectoryName(fallbackFontPath)));
-
if (state.SubtitleStream.IsExternal)
{
var charsetParam = string.Empty;
@@ -1221,7 +1315,6 @@ namespace MediaBrowser.Controller.MediaEncoding
alphaParam,
sub2videoParam,
fontParam,
- // fallbackFontParam,
setPtsParam);
}
@@ -1250,7 +1343,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var maxrate = request.MaxFramerate;
- if (maxrate.HasValue && state.VideoStream != null)
+ if (maxrate.HasValue && state.VideoStream is not null)
{
var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
@@ -1336,13 +1429,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var param = string.Empty;
// Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
- // https://01.org/linuxgraphics/downloads/firmware
+ // https://01.org/group/43/downloads/firmware
// https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
// Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
var intelLowPowerHwEncoding = false;
- // Workaround for linux 5.18+ i915 hang at cost of performance.
+ // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
// https://github.com/intel/media-driver/issues/1456
var enableWaFori915Hang = false;
@@ -1361,18 +1454,25 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
- if (OperatingSystem.IsLinux() && Environment.OSVersion.Version >= _minKernelVersioni915Hang)
+ if (OperatingSystem.IsLinux())
{
- var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
- var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
- || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
- var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
- && IsVaapiSupported(state)
- && IsOpenclFullSupported()
- && !IsVaapiVppTonemapAvailable(state, encodingOptions)
- && IsHwTonemapAvailable(state, encodingOptions);
+ var ver = Environment.OSVersion.Version;
+ var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
+ var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
- enableWaFori915Hang = isIntelDecoder && doOclTonemap;
+ if (!(isUnaffectedKernel || isFixedKernel60))
+ {
+ var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
+ var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
+ || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
+ && IsVaapiSupported(state)
+ && IsOpenclFullSupported()
+ && !IsVaapiVppTonemapAvailable(state, encodingOptions)
+ && IsHwTonemapAvailable(state, encodingOptions);
+
+ enableWaFori915Hang = isIntelDecoder && doOclTonemap;
+ }
}
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
@@ -1463,11 +1563,11 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -preset p7";
break;
- case "slow":
+ case "slower":
param += " -preset p6";
break;
- case "slower":
+ case "slow":
param += " -preset p5";
break;
@@ -1500,8 +1600,8 @@ namespace MediaBrowser.Controller.MediaEncoding
switch (encodingOptions.EncoderPreset)
{
case "veryslow":
- case "slow":
case "slower":
+ case "slow":
param += " -quality quality";
break;
@@ -1717,7 +1817,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, CultureInfo.InvariantCulture, out double hevcLevel))
+ if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
{
param += " -level " + (hevcLevel / 3);
}
@@ -1896,8 +1996,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, CultureInfo.InvariantCulture, out var requestLevel))
+ if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
{
if (!videoStream.Level.HasValue)
{
@@ -1974,9 +2073,9 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- // Video bitrate must fall within requested value
+ // Audio bitrate must fall within requested value
if (request.AudioBitRate.HasValue
- && audioStream.BitDepth.HasValue
+ && audioStream.BitRate.HasValue
&& audioStream.BitRate.Value > request.AudioBitRate.Value)
{
return false;
@@ -1989,7 +2088,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var bitrate = request.VideoBitRate;
- if (videoStream != null)
+ if (videoStream is not null)
{
var isUpscaling = request.Height.HasValue
&& videoStream.Height.HasValue
@@ -2040,14 +2139,20 @@ namespace MediaBrowser.Controller.MediaEncoding
private static double GetVideoBitrateScaleFactor(string codec)
{
+ // hevc & vp9 - 40% more efficient than h.264
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
{
return .6;
}
+ // av1 - 50% more efficient than h.264
+ if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
+ {
+ return .5;
+ }
+
return 1;
}
@@ -2055,7 +2160,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
- var scaleFactor = outputScaleFactor / inputScaleFactor;
+
+ // Don't scale the real bitrate lower than the requested bitrate
+ var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
if (bitrate <= 500000)
{
@@ -2077,56 +2184,96 @@ namespace MediaBrowser.Controller.MediaEncoding
return Convert.ToInt32(scaleFactor * bitrate);
}
- public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream)
+ public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChannels)
{
- return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream);
+ return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
}
- public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream)
+ public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudioChannels)
{
- if (audioStream == null)
+ if (audioStream is null)
{
return null;
}
- if (audioBitRate.HasValue && string.IsNullOrEmpty(audioCodec))
+ var inputChannels = audioStream.Channels ?? 0;
+ var outputChannels = outputAudioChannels ?? 0;
+ var bitrate = audioBitRate ?? int.MaxValue;
+
+ if (string.IsNullOrEmpty(audioCodec)
+ || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
{
- return Math.Min(384000, audioBitRate.Value);
+ return (inputChannels, outputChannels) switch
+ {
+ (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
+ (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
+ (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
+ (_, _) => Math.Min(384000, bitrate)
+ };
}
- if (audioBitRate.HasValue && !string.IsNullOrEmpty(audioCodec))
+ if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
+ return (inputChannels, outputChannels) switch
{
- if ((audioStream.Channels ?? 0) >= 6)
- {
- return Math.Min(640000, audioBitRate.Value);
- }
+ (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
+ (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
+ (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
+ (_, _) => Math.Min(672000, bitrate)
+ };
+ }
- return Math.Min(384000, audioBitRate.Value);
- }
+ // Empty bitrate area is not allow on iOS
+ // Default audio bitrate to 128K per channel if we don't have codec specific defaults
+ // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
+ return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
+ }
- if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))
+ public string GetAudioVbrModeParam(string encoder, int bitratePerChannel)
+ {
+ if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
+ {
+ return " -vbr:a " + bitratePerChannel switch
{
- if ((audioStream.Channels ?? 0) >= 6)
- {
- return Math.Min(3584000, audioBitRate.Value);
- }
+ < 32000 => "1",
+ < 48000 => "2",
+ < 64000 => "3",
+ < 96000 => "4",
+ _ => "5"
+ };
+ }
- return Math.Min(1536000, audioBitRate.Value);
- }
+ if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
+ {
+ return " -qscale:a " + bitratePerChannel switch
+ {
+ < 48000 => "8",
+ < 64000 => "6",
+ < 88000 => "4",
+ < 112000 => "2",
+ _ => "0"
+ };
}
- // Empty bitrate area is not allow on iOS
- // Default audio bitrate to 128K if it is not being requested
- // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
- return 128000;
+ if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
+ {
+ return " -qscale:a " + bitratePerChannel switch
+ {
+ < 40000 => "0",
+ < 56000 => "2",
+ < 80000 => "4",
+ < 112000 => "6",
+ _ => "8"
+ };
+ }
+
+ return null;
}
public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -2135,19 +2282,34 @@ namespace MediaBrowser.Controller.MediaEncoding
var filters = new List<string>();
- // Boost volume to 200% when downsampling from 6ch to 2ch
if (channels.HasValue
- && channels.Value <= 2
- && state.AudioStream != null
+ && channels.Value == 2
+ && state.AudioStream is not null
&& state.AudioStream.Channels.HasValue
- && state.AudioStream.Channels.Value > 5
- && !encodingOptions.DownMixAudioBoost.Equals(1))
+ && state.AudioStream.Channels.Value > 5)
{
- filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
+ switch (encodingOptions.DownMixStereoAlgorithm)
+ {
+ case DownMixStereoAlgorithms.Dave750:
+ filters.Add("volume=4.25");
+ filters.Add("pan=stereo|c0=0.5*c2+0.707*c0+0.707*c4+0.5*c3|c1=0.5*c2+0.707*c1+0.707*c5+0.5*c3");
+ break;
+ case DownMixStereoAlgorithms.NightmodeDialogue:
+ filters.Add("pan=stereo|c0=c2+0.30*c0+0.30*c4|c1=c2+0.30*c1+0.30*c5");
+ break;
+ case DownMixStereoAlgorithms.None:
+ default:
+ if (!encodingOptions.DownMixAudioBoost.Equals(1))
+ {
+ filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
+ }
+
+ break;
+ }
}
var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
- if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && !isCopyingTimestamps)
+ if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && !isCopyingTimestamps)
{
var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
@@ -2175,94 +2337,55 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.Nullable{System.Int32}.</returns>
public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
{
- if (audioStream == null)
+ if (audioStream is null)
{
return null;
}
var request = state.BaseRequest;
- var inputChannels = audioStream.Channels;
+ var codec = outputAudioCodec ?? string.Empty;
- if (inputChannels <= 0)
- {
- inputChannels = null;
- }
+ int? resultChannels = state.GetRequestedAudioChannels(codec);
- var codec = outputAudioCodec ?? string.Empty;
+ var inputChannels = audioStream.Channels;
- int? transcoderChannelLimit;
- if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
- {
- // wmav2 currently only supports two channel output
- transcoderChannelLimit = 2;
- }
- else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
- {
- // libmp3lame currently only supports two channel output
- transcoderChannelLimit = 2;
- }
- else if (codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1)
- {
- // aac is able to handle 8ch(7.1 layout)
- transcoderChannelLimit = 8;
- }
- else
+ if (inputChannels > 0)
{
- // If we don't have any media info then limit it to 6 to prevent encoding errors due to asking for too many channels
- transcoderChannelLimit = 6;
+ resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
}
var isTranscodingAudio = !IsCopyCodec(codec);
- int? resultChannels = state.GetRequestedAudioChannels(codec);
if (isTranscodingAudio)
{
- resultChannels = GetMinValue(request.TranscodingMaxAudioChannels, resultChannels);
- }
+ var audioEncoder = GetAudioEncoder(state);
+ if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
+ {
+ // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many channels.
+ transcoderChannelLimit = 8;
+ }
- if (inputChannels.HasValue)
- {
- resultChannels = resultChannels.HasValue
- ? Math.Min(resultChannels.Value, inputChannels.Value)
- : inputChannels.Value;
- }
+ // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelLimit
+ resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? transcoderChannelLimit;
- if (isTranscodingAudio && transcoderChannelLimit.HasValue)
- {
- resultChannels = resultChannels.HasValue
- ? Math.Min(resultChannels.Value, transcoderChannelLimit.Value)
- : transcoderChannelLimit.Value;
- }
+ if (request.TranscodingMaxAudioChannels < resultChannels)
+ {
+ resultChannels = request.TranscodingMaxAudioChannels;
+ }
- // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
- // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices
- if (isTranscodingAudio
- && state.TranscodingType != TranscodingJobType.Progressive
- && resultChannels.HasValue
- && ((resultChannels.Value > 2 && resultChannels.Value < 6) || resultChannels.Value == 7))
- {
- resultChannels = 2;
+ // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
+ // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices
+ if (state.TranscodingType != TranscodingJobType.Progressive
+ && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
+ {
+ resultChannels = 2;
+ }
}
return resultChannels;
}
- private int? GetMinValue(int? val1, int? val2)
- {
- if (!val1.HasValue)
- {
- return val2;
- }
-
- if (!val2.HasValue)
- {
- return val1;
- }
-
- return Math.Min(val1.Value, val2.Value);
- }
-
/// <summary>
/// Enforces the resolution limit.
/// </summary>
@@ -2328,26 +2451,26 @@ namespace MediaBrowser.Controller.MediaEncoding
// If we don't have known media info
// If input is video, use -sn to drop subtitles
// Otherwise just return empty
- if (state.VideoStream == null && state.AudioStream == null)
+ if (state.VideoStream is null && state.AudioStream is null)
{
return state.IsInputVideo ? "-sn" : string.Empty;
}
// We have media info, but we don't know the stream index
- if (state.VideoStream != null && state.VideoStream.Index == -1)
+ if (state.VideoStream is not null && state.VideoStream.Index == -1)
{
return "-sn";
}
// We have media info, but we don't know the stream index
- if (state.AudioStream != null && state.AudioStream.Index == -1)
+ if (state.AudioStream is not null && state.AudioStream.Index == -1)
{
return state.IsInputVideo ? "-sn" : string.Empty;
}
var args = string.Empty;
- if (state.VideoStream != null)
+ if (state.VideoStream is not null)
{
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
@@ -2362,12 +2485,12 @@ namespace MediaBrowser.Controller.MediaEncoding
args += "-vn";
}
- if (state.AudioStream != null)
+ if (state.AudioStream is not null)
{
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
if (state.AudioStream.IsExternal)
{
- bool hasExternalGraphicsSubs = state.SubtitleStream != null
+ bool hasExternalGraphicsSubs = state.SubtitleStream is not null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& state.SubtitleStream.IsExternal
&& !state.SubtitleStream.IsTextSubtitleStream;
@@ -2393,7 +2516,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var subtitleMethod = state.SubtitleDeliveryMethod;
- if (state.SubtitleStream == null || subtitleMethod == SubtitleDeliveryMethod.Hls)
+ if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
{
args += " -map -0:s";
}
@@ -2420,6 +2543,30 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
+ /// Gets the negative map args by filters.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="videoProcessFilters">The videoProcessFilters.</param>
+ /// <returns>System.String.</returns>
+ public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
+ {
+ string args = string.Empty;
+
+ // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
+ if (state.VideoStream != null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordinal))
+ {
+ int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
+
+ args += string.Format(
+ CultureInfo.InvariantCulture,
+ "-map -0:{0} ",
+ videoStreamIndex);
+ }
+
+ return args;
+ }
+
+ /// <summary>
/// Determines which stream will be used for playback.
/// </summary>
/// <param name="allStream">All stream.</param>
@@ -2435,7 +2582,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
- if (stream != null)
+ if (stream is not null)
{
return stream;
}
@@ -2480,8 +2627,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (outputWidth > maximumWidth || outputHeight > maximumHeight)
{
- var scaleW = (double)maximumWidth / (double)outputWidth;
- var scaleH = (double)maximumHeight / (double)outputHeight;
+ var scaleW = (double)maximumWidth / outputWidth;
+ var scaleH = (double)maximumHeight / outputHeight;
var scale = Math.Min(scaleW, scaleH);
outputWidth = Math.Min(maximumWidth, (int)(outputWidth * scale));
outputHeight = Math.Min(maximumHeight, (int)(outputHeight * scale));
@@ -2628,79 +2775,76 @@ namespace MediaBrowser.Controller.MediaEncoding
widthParam,
heightParam);
}
- else
- {
- return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
- }
+
+ return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
}
// 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)
+
+ if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
{
var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
return string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
- maxWidthParam,
- maxHeightParam,
- scaleVal);
+ CultureInfo.InvariantCulture,
+ "scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
+ maxWidthParam,
+ maxHeightParam,
+ scaleVal);
}
// If a fixed width was requested
- else if (requestedWidth.HasValue)
+ if (requestedWidth.HasValue)
{
if (threedFormat.HasValue)
{
// This method can handle 0 being passed in for the requested height
return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
}
- else
- {
- var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
- return string.Format(
- CultureInfo.InvariantCulture,
- "scale={0}:trunc(ow/a/2)*2",
- widthParam);
- }
+ var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "scale={0}:trunc(ow/a/2)*2",
+ widthParam);
}
// If a fixed height was requested
- else if (requestedHeight.HasValue)
+ if (requestedHeight.HasValue)
{
var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
return string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/{1})*{1}:{0}",
- heightParam,
- scaleVal);
+ CultureInfo.InvariantCulture,
+ "scale=trunc(oh*a/{1})*{1}:{0}",
+ heightParam,
+ scaleVal);
}
// If a max width was requested
- else if (requestedMaxWidth.HasValue)
+ if (requestedMaxWidth.HasValue)
{
var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
return string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
- maxWidthParam,
- scaleVal);
+ CultureInfo.InvariantCulture,
+ "scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
+ maxWidthParam,
+ scaleVal);
}
// If a max height was requested
- else if (requestedMaxHeight.HasValue)
+ if (requestedMaxHeight.HasValue)
{
var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
return string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
- maxHeightParam,
- scaleVal);
+ CultureInfo.InvariantCulture,
+ "scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
+ maxHeightParam,
+ scaleVal);
}
return string.Empty;
@@ -2739,7 +2883,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// default
- if (filter == null)
+ if (filter is null)
{
if (requestedHeight > 0)
{
@@ -2774,22 +2918,32 @@ namespace MediaBrowser.Controller.MediaEncoding
"yadif_cuda={0}:-1:0",
doubleRateDeint ? "1" : "0");
}
- else if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
+
+ if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
{
return string.Format(
CultureInfo.InvariantCulture,
"deinterlace_vaapi=rate={0}",
doubleRateDeint ? "field" : "frame");
}
- else if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
+
+ if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
{
return "deinterlace_qsv=mode=2";
}
+ if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif_videotoolbox={0}:-1:0",
+ doubleRateDeint ? "1" : "0");
+ }
+
return string.Empty;
}
- public static string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat)
+ public string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat)
{
if (string.IsNullOrEmpty(hwTonemapSuffix))
{
@@ -2801,7 +2955,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
{
- args = "tonemap_vaapi=format={0}:p=bt709:t=bt709:m=bt709,procamp_vaapi=b={1}:c={2}:extra_hw_frames=16";
+ args = "procamp_vaapi=b={2}:c={3}," + args + ":extra_hw_frames=32";
+
return string.Format(
CultureInfo.InvariantCulture,
args,
@@ -2809,7 +2964,8 @@ namespace MediaBrowser.Controller.MediaEncoding
options.VppTonemappingBrightness,
options.VppTonemappingContrast);
}
- else if (string.Equals(hwTonemapSuffix, "vulkan", StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(hwTonemapSuffix, "vulkan", StringComparison.OrdinalIgnoreCase))
{
args = "libplacebo=format={1}:tonemapping={2}:color_primaries=bt709:color_trc=bt709:colorspace=bt709:peak_detect=0:upscaler=none:downscaler=none";
@@ -2822,7 +2978,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
algorithm = "bt.2390";
}
-
else if (string.Equals(options.TonemappingAlgorithm, "none", StringComparison.OrdinalIgnoreCase))
{
algorithm = "clip";
@@ -2832,14 +2987,24 @@ namespace MediaBrowser.Controller.MediaEncoding
{
args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
+ if (string.Equals(options.TonemappingMode, "max", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(options.TonemappingMode, "rgb", StringComparison.OrdinalIgnoreCase))
+ {
+ if (_mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode)
+ {
+ args += ":tonemap_mode={5}";
+ }
+ }
+
if (options.TonemappingParam != 0)
{
- args += ":param={5}";
+ args += ":param={6}";
}
- if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(options.TonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(options.TonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
{
- args += ":range={6}";
+ args += ":range={7}";
}
}
@@ -2851,10 +3016,80 @@ namespace MediaBrowser.Controller.MediaEncoding
algorithm,
options.TonemappingPeak,
options.TonemappingDesat,
+ options.TonemappingMode,
options.TonemappingParam,
options.TonemappingRange);
}
+ public string GetLibplaceboFilter(
+ EncodingOptions options,
+ string videoFormat,
+ bool doTonemap,
+ int? videoWidth,
+ int? videoHeight,
+ int? requestedWidth,
+ int? requestedHeight,
+ int? requestedMaxWidth,
+ int? requestedMaxHeight)
+ {
+ var (outWidth, outHeight) = GetFixedOutputSize(
+ videoWidth,
+ videoHeight,
+ requestedWidth,
+ requestedHeight,
+ requestedMaxWidth,
+ requestedMaxHeight);
+
+ var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
+ var isSizeFixed = !videoWidth.HasValue
+ || outWidth.Value != videoWidth.Value
+ || !videoHeight.HasValue
+ || outHeight.Value != videoHeight.Value;
+
+ var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
+ var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
+ var tonemapArg = string.Empty;
+
+ if (doTonemap)
+ {
+ var algorithm = options.TonemappingAlgorithm;
+ var mode = options.TonemappingMode;
+ var range = options.TonemappingRange;
+
+ if (string.Equals(algorithm, "bt2390", StringComparison.OrdinalIgnoreCase))
+ {
+ algorithm = "bt.2390";
+ }
+ else if (string.Equals(algorithm, "none", StringComparison.OrdinalIgnoreCase))
+ {
+ algorithm = "clip";
+ }
+
+ tonemapArg = ":tonemapping=" + algorithm;
+
+ if (string.Equals(mode, "max", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(mode, "rgb", StringComparison.OrdinalIgnoreCase))
+ {
+ tonemapArg += ":tonemapping_mode=" + mode;
+ }
+
+ tonemapArg += ":peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709";
+
+ if (string.Equals(range, "tv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(range, "pc", StringComparison.OrdinalIgnoreCase))
+ {
+ tonemapArg += ":range=" + range;
+ }
+ }
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
+ sizeArg,
+ formatArg,
+ tonemapArg);
+ }
+
/// <summary>
/// Gets the parameter of software filter chain.
/// </summary>
@@ -2884,7 +3119,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -2931,8 +3166,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (hasGraphicalSubs)
{
- // [0:s]scale=expr
- var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ // [0:s]scale=s=1280x720
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -2999,7 +3234,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doCuTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3118,9 +3353,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3189,7 +3422,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3257,7 +3490,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl.
- var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap";
+ var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
mainFilters.Add(hwTransferFilter);
mainFilters.Add("format=nv12");
}
@@ -3320,9 +3553,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3411,7 +3642,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3502,7 +3733,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl.
// qsv hwmap is not fully implemented for the time being.
- mainFilters.Add(isHwmapUsable ? "hwmap" : "hwdownload");
+ mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
mainFilters.Add("format=nv12");
}
@@ -3574,9 +3805,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3614,7 +3843,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doTonemap = doVaVppTonemap || doOclTonemap;
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3662,6 +3891,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter(isVaapiDecoder ? "vaapi" : "qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ // allocate extra pool sizes for vaapi vpp
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
+ {
+ hwScaleFilter += ":extra_hw_frames=24";
+ }
+
// hw scale
mainFilters.Add(hwScaleFilter);
}
@@ -3708,7 +3944,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl/vaapi.
// qsv hwmap is not fully implemented for the time being.
- mainFilters.Add(isHwmapUsable ? "hwmap" : "hwdownload");
+ mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
mainFilters.Add("format=nv12");
}
@@ -3785,9 +4021,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
}
@@ -3891,7 +4125,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doTonemap = doVaVppTonemap || doOclTonemap;
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3939,6 +4173,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ // allocate extra pool sizes for vaapi vpp
+ if (!string.IsNullOrEmpty(hwScaleFilter))
+ {
+ hwScaleFilter += ":extra_hw_frames=24";
+ }
+
// hw scale
mainFilters.Add(hwScaleFilter);
}
@@ -3980,7 +4221,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl/vaapi.
- mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
+ mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
mainFilters.Add("format=nv12");
}
@@ -4046,9 +4287,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4080,14 +4319,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isVaapiEncoder;
- var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -4109,95 +4347,81 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add(swDeintFilter);
}
- var outFormat = doVkTonemap ? "yuv420p10le" : "nv12";
- var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
- // sw scale
- mainFilters.Add(swScaleFilter);
- mainFilters.Add("format=" + outFormat);
-
- // keep video at memory except vk tonemap,
- // since the overhead caused by hwupload >>> using sw filter.
- // sw => hw
- if (doVkTonemap)
+ if (doVkTonemap || hasSubs)
+ {
+ // sw => hw
+ mainFilters.Add("hwupload=derive_device=vulkan");
+ mainFilters.Add("format=vulkan");
+ }
+ else
{
- mainFilters.Add("hwupload=derive_device=vulkan:extra_hw_frames=16");
+ // sw scale
+ var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ mainFilters.Add(swScaleFilter);
+ mainFilters.Add("format=nv12");
}
}
else if (isVaapiDecoder)
{
// INPUT vaapi surface(vram)
- // hw deint
- if (doDeintH2645)
+ if (doVkTonemap || hasSubs)
{
- var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
- mainFilters.Add(deintFilter);
+ // map from vaapi to vulkan/drm via interop (Vega/gfx9+).
+ mainFilters.Add("hwmap=derive_device=vulkan");
+ mainFilters.Add("format=vulkan");
}
-
- var outFormat = doVkTonemap ? string.Empty : (hasSubs && isVaInVaOut ? "bgra" : "nv12");
- var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
-
- // allocate extra pool sizes for overlay_vulkan
- if (!string.IsNullOrEmpty(hwScaleFilter) && isVaInVaOut && hasSubs)
+ else
{
- hwScaleFilter += ":extra_hw_frames=32";
- }
-
- // hw scale
- mainFilters.Add(hwScaleFilter);
- }
+ // hw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
+ mainFilters.Add(deintFilter);
+ }
- if ((isVaapiDecoder && doVkTonemap) || (isVaInVaOut && (doVkTonemap || hasSubs)))
- {
- // map from vaapi to vulkan via vaapi-vulkan interop (Vega/gfx9+).
- mainFilters.Add("hwmap=derive_device=vulkan");
+ // hw scale
+ var hwScaleFilter = GetHwScaleFilter("vaapi", "nv12", inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ mainFilters.Add(hwScaleFilter);
+ }
}
- // vk tonemap
- if (doVkTonemap)
+ // vk libplacebo
+ if (doVkTonemap || hasSubs)
{
- var outFormat = isVaInVaOut && hasSubs ? "bgra" : "nv12";
- var tonemapFilter = GetHwTonemapFilter(options, "vulkan", outFormat);
- mainFilters.Add(tonemapFilter);
+ var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ mainFilters.Add(libplaceboFilter);
}
- if (doVkTonemap && isVaInVaOut && !hasSubs)
+ if (doVkTonemap && !hasSubs)
{
- // OUTPUT vaapi(nv12/bgra) surface(vram)
- // reverse-mapping via vaapi-vulkan interop.
- mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
+ // OUTPUT vaapi(nv12) surface(vram)
+ // map from vulkan/drm to vaapi via interop (Vega/gfx9+).
+ mainFilters.Add("hwmap=derive_device=drm");
+ mainFilters.Add("format=drm_prime");
+ mainFilters.Add("hwmap=derive_device=vaapi");
mainFilters.Add("format=vaapi");
- }
- var memoryOutput = false;
- var isUploadForVkTonemap = isSwDecoder && doVkTonemap;
- if ((isVaapiDecoder && isSwEncoder) || isUploadForVkTonemap)
- {
- memoryOutput = true;
-
- // OUTPUT nv12 surface(memory)
- mainFilters.Add("hwdownload");
- mainFilters.Add("format=nv12");
- }
+ // clear the surf->meta_offset and output nv12
+ mainFilters.Add("scale_vaapi=format=nv12");
- // OUTPUT nv12 surface(memory)
- if (isSwDecoder && isVaapiEncoder)
- {
- memoryOutput = true;
+ // hw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
+ mainFilters.Add(deintFilter);
+ }
}
- if (memoryOutput)
+ if (!hasSubs)
{
- // text subtitles
- if (hasTextSubs)
+ // OUTPUT nv12 surface(memory)
+ if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
{
- var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
- mainFilters.Add(textSubtitlesFilter);
+ mainFilters.Add("hwdownload");
+ mainFilters.Add("format=nv12");
}
- }
- if (memoryOutput && isVaapiEncoder)
- {
- if (!hasGraphicalSubs)
+ if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
{
mainFilters.Add("hwupload_vaapi");
}
@@ -4206,52 +4430,53 @@ namespace MediaBrowser.Controller.MediaEncoding
/* Make sub and overlay filters for subtitle stream */
var subFilters = new List<string>();
var overlayFilters = new List<string>();
- if (isVaInVaOut)
+ if (hasSubs)
{
- if (hasSubs)
+ if (hasGraphicalSubs)
{
- if (hasGraphicalSubs)
- {
- // scale=s=1280x720,format=bgra,hwupload
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
- subFilters.Add("format=bgra");
- }
- else if (hasTextSubs)
- {
- var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
- var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
- subFilters.Add(alphaSrcFilter);
- subFilters.Add("format=bgra");
- subFilters.Add(subTextSubtitlesFilter);
- }
-
- subFilters.Add("hwupload=derive_device=vulkan:extra_hw_frames=16");
+ // scale=s=1280x720,format=bgra,hwupload
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ subFilters.Add("format=bgra");
+ }
+ else if (hasTextSubs)
+ {
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
+ var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
+ subFilters.Add(alphaSrcFilter);
+ subFilters.Add("format=bgra");
+ subFilters.Add(subTextSubtitlesFilter);
+ }
- overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0");
+ subFilters.Add("hwupload=derive_device=vulkan");
+ subFilters.Add("format=vulkan");
- // explicitly sync using libplacebo.
- overlayFilters.Add("libplacebo=format=nv12:upscaler=none:downscaler=none");
+ overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0");
- // OUTPUT vaapi(nv12/bgra) surface(vram)
- // reverse-mapping via vaapi-vulkan interop.
- overlayFilters.Add("hwmap=derive_device=vaapi:reverse=1");
- overlayFilters.Add("format=vaapi");
+ if (isSwEncoder)
+ {
+ // OUTPUT nv12 surface(memory)
+ overlayFilters.Add("scale_vulkan=format=nv12");
+ overlayFilters.Add("hwdownload");
+ overlayFilters.Add("format=nv12");
}
- }
- else if (memoryOutput)
- {
- if (hasGraphicalSubs)
+ else if (isVaapiEncoder)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
- overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
+ // OUTPUT vaapi(nv12) surface(vram)
+ // map from vulkan/drm to vaapi via interop (Vega/gfx9+).
+ overlayFilters.Add("hwmap=derive_device=drm");
+ overlayFilters.Add("format=drm_prime");
+ overlayFilters.Add("hwmap=derive_device=vaapi");
+ overlayFilters.Add("format=vaapi");
- if (isVaapiEncoder)
+ // clear the surf->meta_offset and output nv12
+ overlayFilters.Add("scale_vaapi=format=nv12");
+
+ // hw deint
+ if (doDeintH2645)
{
- overlayFilters.Add("hwupload_vaapi");
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
+ overlayFilters.Add(deintFilter);
}
}
}
@@ -4286,7 +4511,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -4332,6 +4557,13 @@ namespace MediaBrowser.Controller.MediaEncoding
outFormat = doOclTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ // allocate extra pool sizes for vaapi vpp
+ if (!string.IsNullOrEmpty(hwScaleFilter))
+ {
+ hwScaleFilter += ":extra_hw_frames=24";
+ }
+
// hw scale
mainFilters.Add(hwScaleFilter);
}
@@ -4420,9 +4652,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4437,6 +4667,75 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
+ /// Gets the parameter of Apple VideoToolBox filter chain.
+ /// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="vidEncoder">Video encoder to use.</param>
+ /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidEncoder)
+ {
+ if (!string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ return (null, null, null);
+ }
+
+ var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
+
+ if (!options.EnableHardwareEncoding)
+ {
+ return swFilterChain;
+ }
+
+ if (_mediaEncoder.EncoderVersion.CompareTo(new Version("5.0.0")) < 0)
+ {
+ // All features used here requires ffmpeg 5.0 or later, fallback to software filters if using an old ffmpeg
+ return swFilterChain;
+ }
+
+ var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
+ var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
+ var doDeintH2645 = doDeintH264 || doDeintHevc;
+ var inW = state.VideoStream?.Width;
+ var inH = state.VideoStream?.Height;
+ var reqW = state.BaseRequest.Width;
+ var reqH = state.BaseRequest.Height;
+ var reqMaxW = state.BaseRequest.MaxWidth;
+ var reqMaxH = state.BaseRequest.MaxHeight;
+ var threeDFormat = state.MediaSource.Video3DFormat;
+ var newfilters = new List<string>();
+ var noOverlay = swFilterChain.OverlayFilters.Count == 0;
+ var supportsHwDeint = _mediaEncoder.SupportsFilter("yadif_videotoolbox");
+ // fallback to software filters if we are using filters not supported by hardware yet.
+ var useHardwareFilters = noOverlay && (!doDeintH2645 || supportsHwDeint);
+
+ if (!useHardwareFilters)
+ {
+ return swFilterChain;
+ }
+
+ // ffmpeg cannot use videotoolbox to scale
+ var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ newfilters.Add(swScaleFilter);
+
+ // hwupload on videotoolbox encoders can automatically convert AVFrame into its CVPixelBuffer equivalent
+ // videotoolbox will automatically convert the CVPixelBuffer to a pixel format the encoder supports, so we don't have to set a pixel format explicitly here
+ // This will reduce CPU usage significantly on UHD videos with 10 bit colors because we bypassed the ffmpeg pixel format conversion
+ newfilters.Add("hwupload");
+
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
+ newfilters.Add(deintFilter);
+ }
+
+ return (newfilters, swFilterChain.SubFilters, swFilterChain.OverlayFilters);
+ }
+
+ /// <summary>
/// Gets the parameter of video processing filters.
/// </summary>
/// <param name="state">Encoding state.</param>
@@ -4449,12 +4748,12 @@ namespace MediaBrowser.Controller.MediaEncoding
string outputVideoCodec)
{
var videoStream = state.VideoStream;
- if (videoStream == null)
+ if (videoStream is null)
{
return string.Empty;
}
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -4478,6 +4777,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
(mainFilters, subFilters, overlayFilters) = GetAmdVidFilterChain(state, options, outputVideoCodec);
}
+ else if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ (mainFilters, subFilters, overlayFilters) = GetAppleVidFilterChain(state, options, outputVideoCodec);
+ }
else
{
(mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec);
@@ -4504,7 +4807,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (overlayFilters?.Count > 0
&& subFilters?.Count > 0
- && state.SubtitleStream != null)
+ && state.SubtitleStream is not null)
{
// overlay graphical/text subtitles
var subStr = string.Format(
@@ -4591,32 +4894,33 @@ namespace MediaBrowser.Controller.MediaEncoding
public static int GetVideoColorBitDepth(EncodingJobInfo state)
{
var videoStream = state.VideoStream;
- if (videoStream != null)
+ if (videoStream is not null)
{
if (videoStream.BitDepth.HasValue)
{
return videoStream.BitDepth.Value;
}
- else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
{
return 8;
}
- else if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
{
return 10;
}
- else if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
{
return 12;
}
- else
- {
- return 8;
- }
+
+ return 8;
}
return 0;
@@ -4632,13 +4936,13 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var videoStream = state.VideoStream;
var mediaSource = state.MediaSource;
- if (videoStream == null || mediaSource == null)
+ if (videoStream is null || mediaSource is null)
{
return null;
}
// HWA decoders can handle both video files and video folders.
- var videoType = mediaSource.VideoType;
+ var videoType = state.VideoType;
if (videoType != VideoType.VideoFile
&& videoType != VideoType.Iso
&& videoType != VideoType.Dvd
@@ -4779,8 +5083,18 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
+ var ffmpegVersion = _mediaEncoder.EncoderVersion;
+
// Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
- var isAv1 = string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
+ var isAv1 = ffmpegVersion < _minFFmpegImplictHwaccel
+ && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
+
+ // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
+ var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
+
+ // Disable the extra internal copy in nvdec. We already handle it in filter chain.
+ var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
if (bitDepth == 10 && isCodecAvailable)
{
@@ -4806,14 +5120,16 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isVaapiSupported && isCodecAvailable)
{
- return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty)
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
}
if (isD3d11Supported && isCodecAvailable)
{
// set -threads 3 to intel d3d11va decoder explicitly. Lower threads may result in dead lock.
// on newer devices such as Xe, the larger the init_pool_size, the longer the initialization time for opencl to derive from d3d11.
- return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
+ return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty)
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
}
}
else
@@ -4833,13 +5149,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (options.EnableEnhancedNvdecDecoder)
{
// set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
- return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty);
- }
- else
- {
- // cuvid decoder doesn't have threading issue.
- return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty);
+ return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty)
+ + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty);
}
+
+ // cuvid decoder doesn't have threading issue.
+ return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty);
}
}
@@ -4848,7 +5163,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isD3d11Supported && isCodecAvailable)
{
- return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty)
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
}
}
@@ -4857,9 +5173,11 @@ namespace MediaBrowser.Controller.MediaEncoding
&& isVaapiSupported
&& isCodecAvailable)
{
- return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty)
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
}
+ // Apple videotoolbox
if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)
&& isVideotoolboxSupported
&& isCodecAvailable)
@@ -5194,7 +5512,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// Automatically set thread count
return mustSetThreadCount ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
}
- else if (threads >= Environment.ProcessorCount)
+
+ if (threads >= Environment.ProcessorCount)
{
return Environment.ProcessorCount;
}
@@ -5205,7 +5524,7 @@ namespace MediaBrowser.Controller.MediaEncoding
#nullable disable
public void TryStreamCopy(EncodingJobInfo state)
{
- if (state.VideoStream != null && CanStreamCopyVideo(state, state.VideoStream))
+ if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
@@ -5214,13 +5533,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var user = state.User;
// If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
- if (user != null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
+ if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
{
state.OutputVideoCodec = "copy";
}
}
- if (state.AudioStream != null
+ if (state.AudioStream is not null
&& CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
@@ -5230,7 +5549,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var user = state.User;
// If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
- if (user != null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
+ if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
{
state.OutputAudioCodec = "copy";
}
@@ -5427,7 +5746,7 @@ namespace MediaBrowser.Controller.MediaEncoding
state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
- if (state.SubtitleStream != null && !state.SubtitleStream.IsExternal)
+ if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
{
state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal).ToList().IndexOf(state.SubtitleStream);
}
@@ -5445,7 +5764,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var request = state.BaseRequest;
var supportedAudioCodecs = state.SupportedAudioCodecs;
- if (request != null && supportedAudioCodecs != null && supportedAudioCodecs.Length > 0)
+ if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
{
var supportedAudioCodecsList = supportedAudioCodecs.ToList();
@@ -5458,7 +5777,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var supportedVideoCodecs = state.SupportedVideoCodecs;
- if (request != null && supportedVideoCodecs != null && supportedVideoCodecs.Length > 0)
+ if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
{
var supportedVideoCodecsList = supportedVideoCodecs.ToList();
@@ -5478,15 +5797,23 @@ namespace MediaBrowser.Controller.MediaEncoding
return;
}
- var inputChannels = audioStream == null ? 6 : audioStream.Channels ?? 6;
+ var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
+ var shiftAudioCodecs = new List<string>();
if (inputChannels >= 6)
{
- return;
+ // DTS and TrueHD are not supported by HLS
+ // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
+ shiftAudioCodecs.Add("dca");
+ shiftAudioCodecs.Add("truehd");
+ }
+ else
+ {
+ // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
+ // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
+ shiftAudioCodecs.Add("ac3");
+ shiftAudioCodecs.Add("eac3");
}
- // Transcoding to 2ch ac3 almost always causes a playback failure
- // Keep it in the supported codecs list, but shift it to the end of the list so that if transcoding happens, another codec is used
- var shiftAudioCodecs = new[] { "ac3", "eac3" };
if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
{
return;
@@ -5530,7 +5857,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private void NormalizeSubtitleEmbed(EncodingJobInfo state)
{
- if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
+ if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
{
return;
}
@@ -5545,7 +5872,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetSubtitleEmbedArguments(EncodingJobInfo state)
{
- if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
+ if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
{
return string.Empty;
}
@@ -5577,7 +5904,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.BaseRequest.Context == EncodingContext.Streaming)
{
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
- format = " -f mp4 -movflags frag_keyframe+empty_moov";
+ format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
}
var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
@@ -5626,7 +5953,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (IsCopyCodec(videoCodec))
{
- if (state.VideoStream != null
+ if (state.VideoStream is not null
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
@@ -5656,14 +5983,16 @@ namespace MediaBrowser.Controller.MediaEncoding
args += keyFrameArg;
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasCopyTs = false;
// video processing filters.
var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
- args += videoProcessParam;
+ var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
+
+ args = negativeMapArgs + args + videoProcessParam;
hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
@@ -5676,7 +6005,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " -avoid_negative_ts disabled";
- if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
+ if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
{
args += " -start_at_zero";
}
@@ -5703,7 +6032,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
{
// If the video doesn't have an audio stream, return a default.
- if (state.AudioStream == null && state.VideoStream != null)
+ if (state.AudioStream is null && state.VideoStream is not null)
{
return string.Empty;
}
@@ -5718,19 +6047,25 @@ namespace MediaBrowser.Controller.MediaEncoding
return args;
}
- // Add the number of audio channels
var channels = state.OutputAudioChannels;
- if (channels.HasValue)
+ if (channels.HasValue && ((channels.Value != 2 && state.AudioStream.Channels <= 5) || encodingOptions.DownMixStereoAlgorithm == DownMixStereoAlgorithms.None))
{
args += " -ac " + channels.Value;
}
var bitrate = state.OutputAudioBitrate;
-
- if (bitrate.HasValue)
+ if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
{
- args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
+ var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2));
+ if (encodingOptions.EnableAudioVbr && vbrParam is not null)
+ {
+ args += vbrParam;
+ }
+ else
+ {
+ args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
+ }
}
if (state.OutputAudioSampleRate.HasValue)
@@ -5748,18 +6083,33 @@ namespace MediaBrowser.Controller.MediaEncoding
var audioTranscodeParams = new List<string>();
var bitrate = state.OutputAudioBitrate;
+ var channels = state.OutputAudioChannels;
+ var outputCodec = state.OutputAudioCodec;
- if (bitrate.HasValue)
+ if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
{
- audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
+ var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value / (channels ?? 2));
+ if (encodingOptions.EnableAudioVbr && vbrParam is not null)
+ {
+ audioTranscodeParams.Add(vbrParam);
+ }
+ else
+ {
+ audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
+ }
}
- if (state.OutputAudioChannels.HasValue)
+ if (channels.HasValue)
{
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
- if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(outputCodec))
+ {
+ audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
+ }
+
+ if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
// opus only supports specific sampling rates
var sampleRate = state.OutputAudioSampleRate;
@@ -5778,6 +6128,13 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ // Copy the movflags from GetProgressiveVideoFullCommandLine
+ // See #9248 and the associated PR for why this is needed
+ if (_mp4ContainerNames.Contains(state.OutputContainer))
+ {
+ audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
+ }
+
var threads = GetNumberOfThreads(state, encodingOptions, null);
var inputModifier = GetInputModifier(state, encodingOptions, null);