aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/MediaEncoding
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding')
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs686
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs10
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs4
4 files changed, 462 insertions, 242 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 70018bb36..14417a5e4 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -23,7 +23,7 @@ using Microsoft.Extensions.Configuration;
namespace MediaBrowser.Controller.MediaEncoding
{
- public class EncodingHelper
+ public partial class EncodingHelper
{
private const string QsvAlias = "qs";
private const string VaapiAlias = "va";
@@ -37,7 +37,8 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config;
- private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
+ private readonly IConfigurationManager _configurationManager;
+
// 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);
@@ -45,6 +46,9 @@ namespace MediaBrowser.Controller.MediaEncoding
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 readonly Version _minFFmpegSvtAv1Params = new Version(5, 1);
+ private readonly Version _minFFmpegVaapiH26xEncA53CcSei = new Version(6, 0);
private static readonly string[] _videoProfilesH264 = new[]
{
@@ -64,6 +68,13 @@ namespace MediaBrowser.Controller.MediaEncoding
"Main10"
};
+ private static readonly string[] _videoProfilesAv1 = new[]
+ {
+ "Main",
+ "High",
+ "Professional",
+ };
+
private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
{
"mp4",
@@ -103,21 +114,29 @@ namespace MediaBrowser.Controller.MediaEncoding
IApplicationPaths appPaths,
IMediaEncoder mediaEncoder,
ISubtitleEncoder subtitleEncoder,
- IConfiguration config)
+ IConfiguration config,
+ IConfigurationManager configurationManager)
{
_appPaths = appPaths;
_mediaEncoder = mediaEncoder;
_subtitleEncoder = subtitleEncoder;
_config = config;
+ _configurationManager = configurationManager;
}
+ [GeneratedRegex(@"\s+")]
+ private static partial Regex WhiteSpaceRegex();
+
public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
- => GetH264OrH265Encoder("libx264", "h264", state, encodingOptions);
+ => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions);
public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
- => GetH264OrH265Encoder("libx265", "hevc", state, encodingOptions);
+ => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions);
- private string GetH264OrH265Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions)
+ public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions)
+ => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions);
+
+ private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions)
{
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
@@ -162,7 +181,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")
@@ -207,8 +227,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
if (string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
- && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
- && string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase))
+ && state.VideoStream.VideoRange == VideoRange.HDR
+ && state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
{
// Only native SW decoder and HW accelerator can parse dovi rpu.
var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
@@ -219,9 +239,9 @@ namespace MediaBrowser.Controller.MediaEncoding
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder;
}
- return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
- && (string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase)
- || string.Equals(state.VideoStream.VideoRangeType, "HLG", StringComparison.OrdinalIgnoreCase));
+ return state.VideoStream.VideoRange == VideoRange.HDR
+ && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
+ || state.VideoStream.VideoRangeType == VideoRangeType.HLG);
}
private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
@@ -233,7 +253,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// libplacebo has partial Dolby Vision to SDR tonemapping support.
return options.EnableTonemapping
- && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
+ && state.VideoStream.VideoRange == VideoRange.HDR
&& GetVideoColorBitDepth(state) == 10;
}
@@ -248,8 +268,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// Native VPP tonemapping may come to QSV in the future.
- return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
- && string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase);
+ return state.VideoStream.VideoRange == VideoRange.HDR
+ && state.VideoStream.VideoRangeType == VideoRangeType.HDR10;
}
/// <summary>
@@ -264,6 +284,11 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(codec))
{
+ if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetAv1Encoder(state, encodingOptions);
+ }
+
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
{
@@ -563,6 +588,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
}
+ if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
+ }
+
return -1;
}
@@ -712,28 +742,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())
@@ -754,9 +799,12 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
{
+ // 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;
@@ -824,21 +872,17 @@ 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 (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
@@ -851,19 +895,30 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (_mediaEncoder.IsVaapiDeviceAmd)
{
+ // Disable AMD EFC feature since it's still unstable in upstream Mesa.
+ Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc");
+
if (IsVulkanFullSupported()
- && _mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
- && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
+ && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop)
{
+ 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.
- args.Append(GetVulkanDeviceArgs(0, null, VaapiAlias, VulkanAlias));
filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
}
- else if (doOclTonemap)
+ else
{
- // ROCm/ROCr OpenCL runtime
- args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
- filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
+ 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 if (doOclTonemap)
@@ -1007,7 +1062,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
{
- var tmpConcatPath = Path.Join(options.TranscodingTempPath, state.MediaSource.Id + ".concat");
+ var tmpConcatPath = Path.Join(_configurationManager.GetTranscodePath(), state.MediaSource.Id + ".concat");
_mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath);
arg.Append(" -f concat -safe 0 -i ")
.Append(tmpConcatPath);
@@ -1162,6 +1217,12 @@ namespace MediaBrowser.Controller.MediaEncoding
int bitrate = state.OutputVideoBitrate.Value;
+ // Bit rate under 1000k is not allowed in h264_qsv
+ if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ bitrate = Math.Max(bitrate, 1000);
+ }
+
// Currently use the same buffer size for all encoders
int bufsize = bitrate * 2;
@@ -1179,6 +1240,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return FormattableString.Invariant($" -b:v {bitrate}");
}
+ if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
+ {
+ return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}");
+ }
+
if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
{
@@ -1186,14 +1252,16 @@ namespace MediaBrowser.Controller.MediaEncoding
}
if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase))
{
// Override the too high default qmin 18 in transcoding preset
return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
{
// VBR in i965 driver may result in pixelated output.
if (_mediaEncoder.IsVaapiDeviceInteli965)
@@ -1211,14 +1279,23 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
{
- if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase))
+ {
+ // Transcode to level 5.3 (15) and lower for maximum compatibility.
+ // https://en.wikipedia.org/wiki/AV1#Levels
+ if (requestLevel < 0 || requestLevel >= 15)
+ {
+ return "15";
+ }
+ }
+ else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
{
// Transcode to level 5.0 and lower for maximum compatibility.
// Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it.
// https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels
// MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880.
- if (requestLevel >= 150)
+ if (requestLevel < 0 || requestLevel >= 150)
{
return "150";
}
@@ -1228,7 +1305,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Transcode to level 5.1 and lower for maximum compatibility.
// h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4.
// https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
- if (requestLevel >= 51)
+ if (requestLevel < 0 || requestLevel >= 51)
{
return "51";
}
@@ -1340,22 +1417,11 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var args = string.Empty;
var gopArg = string.Empty;
- var keyFrameArg = string.Empty;
- if (isEventPlaylist)
- {
- keyFrameArg = string.Format(
- CultureInfo.InvariantCulture,
- " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
- segmentLength);
- }
- else if (startNumber.HasValue)
- {
- keyFrameArg = string.Format(
- CultureInfo.InvariantCulture,
- " -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"",
- startNumber.Value * segmentLength,
- segmentLength);
- }
+
+ var keyFrameArg = string.Format(
+ CultureInfo.InvariantCulture,
+ " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
+ segmentLength);
var framerate = state.VideoStream?.RealFrameRate;
if (framerate.HasValue)
@@ -1377,14 +1443,18 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase))
{
args += gopArg;
}
else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
{
args += keyFrameArg;
@@ -1399,6 +1469,13 @@ namespace MediaBrowser.Controller.MediaEncoding
args += keyFrameArg + gopArg;
}
+ // global_header produced by AMD HEVC VA-API encoder causes non-playable fMP4 on iOS
+ if (string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.IsVaapiDeviceAmd)
+ {
+ args += " -flags:v -global_header";
+ }
+
return args;
}
@@ -1520,18 +1597,60 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -crf " + defaultCrf;
}
}
+ else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
+ {
+ // Default to use the recommended preset 10.
+ // Omit presets < 5, which are too slow for on the fly encoding.
+ // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md
+ param += encodingOptions.EncoderPreset switch
+ {
+ "veryslow" => " -preset 5",
+ "slower" => " -preset 6",
+ "slow" => " -preset 7",
+ "medium" => " -preset 8",
+ "fast" => " -preset 9",
+ "faster" => " -preset 10",
+ "veryfast" => " -preset 11",
+ "superfast" => " -preset 12",
+ "ultrafast" => " -preset 13",
+ _ => " -preset 10"
+ };
+ }
+ else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ // -compression_level is not reliable on AMD.
+ if (_mediaEncoder.IsVaapiDeviceInteliHD)
+ {
+ param += encodingOptions.EncoderPreset switch
+ {
+ "veryslow" => " -compression_level 1",
+ "slower" => " -compression_level 2",
+ "slow" => " -compression_level 3",
+ "medium" => " -compression_level 4",
+ "fast" => " -compression_level 5",
+ "faster" => " -compression_level 6",
+ "veryfast" => " -compression_level 7",
+ "superfast" => " -compression_level 7",
+ "ultrafast" => " -compression_level 7",
+ _ => string.Empty
+ };
+ }
+ }
else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv)
- || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_qsv)
+ || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv)
+ || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv)
{
- string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" };
+ string[] valid_presets = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" };
- if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparison.OrdinalIgnoreCase))
+ if (valid_presets.Contains(encodingOptions.EncoderPreset, StringComparison.OrdinalIgnoreCase))
{
param += " -preset " + encodingOptions.EncoderPreset;
}
else
{
- param += " -preset 7";
+ param += " -preset veryfast";
}
// Only h264_qsv has look_ahead option
@@ -1541,7 +1660,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
- || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
+ || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_nvenc)
+ || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)) // av1 (av1_nvenc)
{
switch (encodingOptions.EncoderPreset)
{
@@ -1549,11 +1669,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;
@@ -1581,13 +1701,14 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf)
- || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_amf)
+ || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf)
+ || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) // av1 (av1_amf)
{
switch (encodingOptions.EncoderPreset)
{
case "veryslow":
- case "slow":
case "slower":
+ case "slow":
param += " -quality quality";
break;
@@ -1608,9 +1729,15 @@ namespace MediaBrowser.Controller.MediaEncoding
break;
}
+ if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -header_insertion_mode gop";
+ }
+
if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
{
- param += " -header_insertion_mode gop -gops_per_idr 1";
+ param += " -gops_per_idr 1";
}
}
else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // vp8
@@ -1719,7 +1846,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault() ?? string.Empty;
- profile = Regex.Replace(profile, @"\s+", string.Empty);
+ profile = WhiteSpaceRegex().Replace(profile, string.Empty);
// We only transcode to HEVC 8-bit for now, force Main Profile.
if (profile.Contains("main10", StringComparison.OrdinalIgnoreCase)
@@ -1741,6 +1868,14 @@ namespace MediaBrowser.Controller.MediaEncoding
profile = "high";
}
+ // We only need Main profile of AV1 encoders.
+ if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase)
+ && (profile.Contains("high", StringComparison.OrdinalIgnoreCase)
+ || profile.Contains("professional", StringComparison.OrdinalIgnoreCase)))
+ {
+ profile = "main";
+ }
+
// h264_vaapi does not support Baseline profile, force Constrained Baseline in this case,
// which is compatible (and ugly).
if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
@@ -1782,7 +1917,9 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(profile))
{
- if (!string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
+ // Currently there's no profile option in av1_nvenc encoder
+ if (!(string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)))
{
param += " -profile:v:0 " + profile;
}
@@ -1808,19 +1945,41 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -level " + (hevcLevel / 3);
}
}
+ else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase))
+ {
+ // libsvtav1 and av1_qsv use -level 60 instead of -level 16
+ // https://aomedia.org/av1/specification/annex-a/
+ if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level))
+ {
+ var x = 2 + (av1Level >> 2);
+ var y = av1Level & 3;
+ var res = (x * 10) + y;
+ param += " -level " + res;
+ }
+ }
else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase))
{
param += " -level " + level;
}
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase))
{
// level option may cause NVENC to fail.
// NVENC cannot adjust the given level, just throw an error.
+ }
+ else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
// level option may cause corrupted frames on AMD VAAPI.
+ if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
+ {
+ param += " -level " + level;
+ }
}
else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
{
@@ -1842,6 +2001,20 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -x265-params:0 no-info=1";
}
+ if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params)
+ {
+ param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0";
+ }
+
+ /* Access unit too large: 8192 < 20880 error */
+ if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) &&
+ _mediaEncoder.EncoderVersion >= _minFFmpegVaapiH26xEncA53CcSei)
+ {
+ param += " -sei -a53_cc";
+ }
+
return param;
}
@@ -1920,12 +2093,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
if (requestedRangeTypes.Length > 0)
{
- if (string.IsNullOrEmpty(videoStream.VideoRangeType))
+ if (videoStream.VideoRangeType == VideoRangeType.Unknown)
{
return false;
}
- if (!requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase))
+ if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreCase))
{
return false;
}
@@ -2148,7 +2321,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
// Don't scale the real bitrate lower than the requested bitrate
- var scaleFactor = Math.Min(outputScaleFactor / inputScaleFactor, 1);
+ var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
if (bitrate <= 500000)
{
@@ -2539,7 +2712,7 @@ namespace MediaBrowser.Controller.MediaEncoding
string args = string.Empty;
// http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
- if (state.VideoStream != null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordinal))
+ if (state.VideoStream is not null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordinal))
{
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
@@ -2929,7 +3102,7 @@ namespace MediaBrowser.Controller.MediaEncoding
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))
{
@@ -2941,7 +3114,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={1}:c={2},tonemap_vaapi=format={0}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
+
return string.Format(
CultureInfo.InvariantCulture,
args,
@@ -2949,37 +3123,28 @@ namespace MediaBrowser.Controller.MediaEncoding
options.VppTonemappingBrightness,
options.VppTonemappingContrast);
}
-
- if (string.Equals(hwTonemapSuffix, "vulkan", StringComparison.OrdinalIgnoreCase))
+ else
{
- args = "libplacebo=format={1}:tonemapping={2}:color_primaries=bt709:color_trc=bt709:colorspace=bt709:peak_detect=0:upscaler=none:downscaler=none";
-
- if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
- {
- args += ":range={6}";
- }
+ args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
- if (string.Equals(options.TonemappingAlgorithm, "bt2390", StringComparison.OrdinalIgnoreCase))
- {
- algorithm = "bt.2390";
- }
- else if (string.Equals(options.TonemappingAlgorithm, "none", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(options.TonemappingMode, "max", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(options.TonemappingMode, "rgb", StringComparison.OrdinalIgnoreCase))
{
- algorithm = "clip";
+ if (_mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode)
+ {
+ args += ":tonemap_mode={5}";
+ }
}
- }
- else
- {
- args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
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}";
}
}
@@ -2991,10 +3156,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>
@@ -3569,7 +3804,7 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add(swDeintFilter);
}
- var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
+ var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
// sw scale
mainFilters.Add(swScaleFilter);
@@ -3596,12 +3831,6 @@ namespace MediaBrowser.Controller.MediaEncoding
// map from d3d11va to qsv.
mainFilters.Add("hwmap=derive_device=qsv");
}
- else
- {
- // Insert a qsv scaler to sync the decoder surface,
- // msdk will passthrough this internally.
- mainFilters.Add("hwmap=derive_device=qsv,scale_qsv");
- }
}
// hw deint
@@ -3770,7 +3999,7 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add(swDeintFilter);
}
- var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
+ var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
// sw scale
mainFilters.Add(swScaleFilter);
@@ -3992,14 +4221,13 @@ namespace MediaBrowser.Controller.MediaEncoding
// prefered vaapi + vulkan filters pipeline
if (_mediaEncoder.IsVaapiDeviceAmd
&& isVaapiVkSupported
- && _mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
- && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
+ && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop)
{
- // AMD radeonsi path(Vega/gfx9+, kernel>=5.15), with extra vulkan tonemap and overlay support.
+ // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support.
return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
- // Intel i965 and Amd radeonsi/r600 path(Polaris/gfx8-), only featuring scale and deinterlace support.
+ // Intel i965 and Amd legacy driver path, only featuring scale and deinterlace support.
return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
@@ -4224,7 +4452,6 @@ 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);
@@ -4253,99 +4480,79 @@ 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)
{
- mainFilters.Add("hwupload=derive_device=vaapi");
- mainFilters.Add("format=vaapi");
- mainFilters.Add("hwmap=derive_device=vulkan");
+ // sw => hw
+ mainFilters.Add("hwupload=derive_device=vulkan");
mainFilters.Add("format=vulkan");
}
+ else
+ {
+ // 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 (Polaris/gfx8+).
+ 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");
- mainFilters.Add("format=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 (Polaris/gfx8+).
+ mainFilters.Add("hwmap=derive_device=vaapi");
mainFilters.Add("format=vaapi");
- }
- var memoryOutput = false;
- var isUploadForVkTonemap = isSwDecoder && doVkTonemap;
- if ((isVaapiDecoder && isSwEncoder) || isUploadForVkTonemap)
- {
- memoryOutput = true;
+ // clear the surf->meta_offset and output nv12
+ mainFilters.Add("scale_vaapi=format=nv12");
- // OUTPUT nv12 surface(memory)
- mainFilters.Add("hwdownload");
- mainFilters.Add("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");
}
@@ -4354,55 +4561,51 @@ 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);
- }
-
- // prefer vaapi hwupload to vulkan hwupload,
- // Mesa RADV does not support a dedicated transfer queue.
- subFilters.Add("hwupload=derive_device=vaapi");
- subFilters.Add("format=vaapi");
- subFilters.Add("hwmap=derive_device=vulkan");
- subFilters.Add("format=vulkan");
+ // 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");
- // TODO: figure out why libplacebo can sync without vaSyncSurface VPP support in radeonsi.
- overlayFilters.Add("libplacebo=format=nv12:apply_filmgrain=0:apply_dolbyvision=0:upscaler=none:downscaler=none:dithering=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 = 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 (Polaris/gfx8+).
+ 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);
}
}
}
@@ -5052,10 +5255,8 @@ namespace MediaBrowser.Controller.MediaEncoding
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)
- + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 2" + (isAv1 ? " -c:v av1" : string.Empty);
}
}
else
@@ -5489,7 +5690,6 @@ namespace MediaBrowser.Controller.MediaEncoding
// Apply -analyzeduration as per the environment variable,
// otherwise ffmpeg will break on certain files due to default value is 0.
- // The default value of -probesize is more than enough, so leave it as is.
var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
if (state.MediaSource.AnalyzeDurationMs > 0)
@@ -5508,6 +5708,14 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier = inputModifier.Trim();
+ // Apply -probesize if configured
+ var ffmpegProbeSize = _config.GetFFmpegProbeSize();
+
+ if (!string.IsNullOrEmpty(ffmpegProbeSize))
+ {
+ inputModifier += $" -probesize {ffmpegProbeSize}";
+ }
+
var userAgentParam = GetUserAgentParam(state);
if (!string.IsNullOrEmpty(userAgentParam))
@@ -5755,19 +5963,25 @@ namespace MediaBrowser.Controller.MediaEncoding
private void ShiftVideoCodecsIfNeeded(List<string> videoCodecs, EncodingOptions encodingOptions)
{
- // Shift hevc/h265 to the end of list if hevc encoding is not allowed.
- if (encodingOptions.AllowHevcEncoding)
+ // No need to shift if there is only one supported video codec.
+ if (videoCodecs.Count < 2)
{
return;
}
- // No need to shift if there is only one supported video codec.
- if (videoCodecs.Count < 2)
+ // Shift codecs to the end of list if it's not allowed.
+ var shiftVideoCodecs = new List<string>();
+ if (!encodingOptions.AllowHevcEncoding)
{
- return;
+ shiftVideoCodecs.Add("hevc");
+ shiftVideoCodecs.Add("h265");
+ }
+
+ if (!encodingOptions.AllowAv1Encoding)
+ {
+ shiftVideoCodecs.Add("av1");
}
- var shiftVideoCodecs = new[] { "hevc", "h265" };
if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
{
return;
@@ -6035,6 +6249,12 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
}
+ if (GetAudioEncoder(state).StartsWith("pcm_", StringComparison.Ordinal))
+ {
+ audioTranscodeParams.Add(string.Concat("-f ", GetAudioEncoder(state).AsSpan(4)));
+ audioTranscodeParams.Add("-ar " + state.BaseRequest.AudioBitRate);
+ }
+
if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
// opus only supports specific sampling rates
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index a6b541660..17813559a 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
@@ -367,22 +368,21 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the target video range type.
/// </summary>
- public string TargetVideoRangeType
+ public VideoRangeType TargetVideoRangeType
{
get
{
if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec))
{
- return VideoStream?.VideoRangeType;
+ return VideoStream?.VideoRangeType ?? VideoRangeType.Unknown;
}
- var requestedRangeType = GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault();
- if (!string.IsNullOrEmpty(requestedRangeType))
+ if (Enum.TryParse(GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault() ?? "Unknown", true, out VideoRangeType requestedRangeType))
{
return requestedRangeType;
}
- return null;
+ return VideoRangeType.Unknown;
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index f830b9f29..4114dea4f 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -64,8 +64,8 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets a value indicating whether the configured Vaapi device supports vulkan drm format modifier.
/// </summary>
- /// <value><c>true</c> if the Vaapi device supports vulkan drm format modifier, <c>false</c> otherwise.</value>
- bool IsVaapiDeviceSupportVulkanFmtModifier { get; }
+ /// <value><c>true</c> if the Vaapi device supports vulkan drm interop, <c>false</c> otherwise.</value>
+ bool IsVaapiDeviceSupportVulkanDrmInterop { get; }
/// <summary>
/// Whether given encoder codec is supported.
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index 3b34af4e9..3d288b9f8 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -20,12 +20,12 @@ namespace MediaBrowser.Controller.MediaEncoding
_logger = logger;
}
- public async Task StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
+ public async Task StartStreamingLog(EncodingJobInfo state, StreamReader reader, Stream target)
{
try
{
using (target)
- using (var reader = new StreamReader(source))
+ using (reader)
{
while (!reader.EndOfStream && reader.BaseStream.CanRead)
{