aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
diff options
context:
space:
mode:
authorMarc Brooks <IDisposable@gmail.com>2025-02-03 19:48:59 -0600
committerGitHub <noreply@github.com>2025-02-03 19:48:59 -0600
commite8cbcde02ebd930a5eeb6c95e0875a9e30acb3e8 (patch)
tree2ecd43f232012c8f037f4cd6fee4168e46d01aa3 /MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
parent6dc61a430ba3a8480399309f277e5debfd6403ba (diff)
parentd376b5fbc7cf3ae7440a606a9e885d70605956bd (diff)
Merge branch 'master' into sort-nfo-data
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs638
1 files changed, 440 insertions, 198 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 88aa888a1..a9e419df4 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -60,7 +60,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
- private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0);
+ private readonly Version _minFFmpegImplicitHwaccel = 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);
@@ -71,6 +71,9 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly Version _minFFmpegAdvancedTonemapMode = new Version(7, 0, 1);
private readonly Version _minFFmpegAlteredVaVkInterop = new Version(7, 0, 1);
private readonly Version _minFFmpegQsvVppTonemapOption = new Version(7, 0, 1);
+ private readonly Version _minFFmpegQsvVppOutRangeOption = new Version(7, 0, 1);
+ private readonly Version _minFFmpegVaapiDeviceVendorId = new Version(7, 0, 1);
+ private readonly Version _minFFmpegQsvVppScaleModeOption = new Version(6, 0);
private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled);
@@ -207,6 +210,14 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var hwType = encodingOptions.HardwareAccelerationType;
+ // Only Intel has VA-API MJPEG encoder
+ if (hwType == HardwareAccelerationType.vaapi
+ && !(_mediaEncoder.IsVaapiDeviceInteliHD
+ || _mediaEncoder.IsVaapiDeviceInteli965))
+ {
+ return _defaultMjpegEncoder;
+ }
+
if (hwType != HardwareAccelerationType.none
&& encodingOptions.EnableHardwareEncoding
&& _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder)
@@ -298,8 +309,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
if (state.VideoStream is null
- || !options.EnableTonemapping
- || GetVideoColorBitDepth(state) != 10
+ || GetVideoColorBitDepth(state) < 10
|| !_mediaEncoder.SupportsFilter("tonemapx"))
{
return false;
@@ -312,7 +322,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.VideoStream is null
|| !options.EnableTonemapping
- || GetVideoColorBitDepth(state) != 10)
+ || GetVideoColorBitDepth(state) < 10)
{
return false;
}
@@ -354,7 +364,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.VideoStream is null
|| !options.EnableVppTonemapping
- || GetVideoColorBitDepth(state) != 10)
+ || GetVideoColorBitDepth(state) < 10)
{
return false;
}
@@ -377,7 +387,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.VideoStream is null
|| !options.EnableVideoToolboxTonemapping
- || GetVideoColorBitDepth(state) != 10)
+ || GetVideoColorBitDepth(state) < 10)
{
return false;
}
@@ -388,6 +398,25 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.HDR10Plus or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG;
}
+ private bool IsVideoStreamHevcRext(EncodingJobInfo state)
+ {
+ var videoStream = state.VideoStream;
+ if (videoStream is null)
+ {
+ return false;
+ }
+
+ return string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ && (string.Equals(videoStream.Profile, "Rext", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase));
+ }
+
/// <summary>
/// Gets the name of the output video codec.
/// </summary>
@@ -599,49 +628,21 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>Codec string.</returns>
public string InferAudioCodec(string container)
{
- var ext = "." + (container ?? string.Empty);
-
- if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
- {
- return "mp3";
- }
-
- if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
+ if (string.IsNullOrWhiteSpace(container))
{
+ // this may not work, but if the client is that broken we cannot do anything better
return "aac";
}
- if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
- {
- return "wma";
- }
-
- if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
-
- if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
+ var inferredCodec = container.ToLowerInvariant();
- if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
+ return inferredCodec switch
{
- return "vorbis";
- }
-
- if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
-
- if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
-
- return "copy";
+ "ogg" or "oga" or "ogv" or "webm" or "webma" => "opus",
+ "m4a" or "m4b" or "mp4" or "mov" or "mkv" or "mka" => "aac",
+ "ts" or "avi" or "flv" or "f4v" or "swf" => "mp3",
+ _ => inferredCodec
+ };
}
/// <summary>
@@ -852,13 +853,15 @@ namespace MediaBrowser.Controller.MediaEncoding
options);
}
- private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string srcDeviceAlias, string alias)
+ private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string vendorId, string srcDeviceAlias, string alias)
{
alias ??= VaapiAlias;
+ var haveVendorId = !string.IsNullOrEmpty(vendorId)
+ && _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
- // 'renderNodePath' has higher priority than 'kernelDriver'
+ // Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
var driverOpts = string.IsNullOrEmpty(renderNodePath)
- ? (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver)
+ ? (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",kernel_driver={kernelDriver}"))
: renderNodePath;
// 'driver' behaves similarly to env LIBVA_DRIVER_NAME
@@ -893,7 +896,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (OperatingSystem.IsLinux())
{
// derive qsv from vaapi device
- return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", null, VaapiAlias) + arg + "@" + VaapiAlias;
+ return GetVaapiDeviceArgs(renderNodePath, "iHD", "i915", "0x8086", null, VaapiAlias) + arg + "@" + VaapiAlias;
}
if (OperatingSystem.IsWindows())
@@ -922,7 +925,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// DVBSUB uses the fixed canvas size 720x576
if (state.SubtitleStream is not null
- && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ && ShouldEncodeSubtitle(state)
&& !state.SubtitleStream.IsTextSubtitleStream
&& !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
{
@@ -988,14 +991,14 @@ namespace MediaBrowser.Controller.MediaEncoding
if (_mediaEncoder.IsVaapiDeviceInteliHD)
{
- args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, 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(options.VaapiDevice, "i965", null, null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, null, VaapiAlias));
}
var filterDevArgs = string.Empty;
@@ -1019,7 +1022,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
{
args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
- args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(null, null, null, null, DrmAlias, VaapiAlias));
args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
// libplacebo wants an explicitly set vulkan filter device.
@@ -1027,7 +1030,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else
{
- args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, null, VaapiAlias));
filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
if (doOclTonemap)
@@ -1221,7 +1224,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// sub2video for external graphical subtitles
if (state.SubtitleStream is not null
- && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ && ShouldEncodeSubtitle(state)
&& !state.SubtitleStream.IsTextSubtitleStream
&& state.SubtitleStream.IsExternal)
{
@@ -1421,7 +1424,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var encoderPreset = preset ?? defaultPreset;
if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
{
- param += " -preset " + encoderPreset.ToString().ToLowerInvariant();
+ var presetString = encoderPreset switch
+ {
+ EncoderPreset.auto => EncoderPreset.veryfast.ToString().ToLowerInvariant(),
+ _ => encoderPreset.ToString().ToLowerInvariant()
+ };
+
+ param += " -preset " + presetString;
int encodeCrf = encodingOptions.H264Crf;
if (isLibX265)
@@ -2051,7 +2060,13 @@ namespace MediaBrowser.Controller.MediaEncoding
// libx265 only accept level option in -x265-params.
// level option may cause libx265 to fail.
// libx265 cannot adjust the given level, just throw an error.
- param += " -x265-params:0 subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:rskip-edge-threshold=2:no-sao=1:no-strong-intra-smoothing=1:no-scenecut=1:no-open-gop=1:no-info=1";
+ param += " -x265-params:0 no-scenecut=1:no-open-gop=1:no-info=1";
+
+ if (encodingOptions.EncoderPreset < EncoderPreset.ultrafast)
+ {
+ // The following params are slower than the ultrafast preset, don't use when ultrafast is selected.
+ param += ":subme=3:merange=25:rc-lookahead=10:me=star:ctu=32:max-tu-size=32:min-cu-size=16:rskip=2:rskip-edge-threshold=2:no-sao=1:no-strong-intra-smoothing=1";
+ }
}
if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)
@@ -2186,7 +2201,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var videoFrameRate = videoStream.ReferenceFrameRate;
- if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
+ // Add a little tolerance to the framerate check because some videos might record a framerate
+ // that is slightly greater than the intended framerate, but the device can still play it correctly.
+ // 0.05 fps tolerance should be safe enough.
+ if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value + 0.05f)
{
return false;
}
@@ -2381,7 +2399,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return 1;
}
- private static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
+ public static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
{
var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
@@ -2405,6 +2423,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
scaleFactor = Math.Max(scaleFactor, 2);
}
+ else if (bitrate >= 30000000)
+ {
+ // Don't scale beyond 30Mbps, it is hardly visually noticeable for most codecs with our prefer speed encoding
+ // and will cause extremely high bitrate to be used for av1->h264 transcoding that will overload clients and encoders
+ scaleFactor = 1;
+ }
return Convert.ToInt32(scaleFactor * bitrate);
}
@@ -2535,7 +2559,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
- if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && !isCopyingTimestamps)
+ if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(state) && !isCopyingTimestamps)
{
var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
@@ -2652,6 +2676,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options, string segmentContainer)
{
var time = state.BaseRequest.StartTimeTicks ?? 0;
+ var maxTime = state.RunTimeTicks ?? 0;
var seekParam = string.Empty;
if (time > 0)
@@ -2662,6 +2687,14 @@ namespace MediaBrowser.Controller.MediaEncoding
// This will help subtitle syncing.
var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCodec(state.OutputVideoCodec);
var seekTick = isHlsRemuxing ? time + 5000000L : time;
+
+ // Seeking beyond EOF makes no sense in transcoding. Clamp the seekTick value to
+ // [0, RuntimeTicks - 0.5s], so that the muxer gets packets and avoid error codes.
+ if (maxTime > 0)
+ {
+ seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 5000000L, 0));
+ }
+
seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekTick));
if (state.IsVideoRequest)
@@ -2736,7 +2769,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream.IsExternal)
{
bool hasExternalGraphicsSubs = state.SubtitleStream is not null
- && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ && ShouldEncodeSubtitle(state)
&& state.SubtitleStream.IsExternal
&& !state.SubtitleStream.IsTextSubtitleStream;
int externalAudioMapIndex = hasExternalGraphicsSubs ? 2 : 1;
@@ -3272,7 +3305,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty;
}
- public string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat)
+ private string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat, bool forceFullRange)
{
if (string.IsNullOrEmpty(hwTonemapSuffix))
{
@@ -3282,7 +3315,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var args = string.Empty;
var algorithm = options.TonemappingAlgorithm.ToString().ToLowerInvariant();
var mode = options.TonemappingMode.ToString().ToLowerInvariant();
- var range = options.TonemappingRange;
+ var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
var rangeString = range.ToString().ToLowerInvariant();
if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
@@ -3293,24 +3326,25 @@ namespace MediaBrowser.Controller.MediaEncoding
&& options.VppTonemappingBrightness >= -100
&& options.VppTonemappingBrightness <= 100)
{
- procampParams += $"=b={options.VppTonemappingBrightness}";
+ procampParams += "procamp_vaapi=b={0}";
doVaVppProcamp = true;
}
if (options.VppTonemappingContrast > 1
&& options.VppTonemappingContrast <= 10)
{
- procampParams += doVaVppProcamp ? ":" : "=";
- procampParams += $"c={options.VppTonemappingContrast}";
+ procampParams += doVaVppProcamp ? ":c={1}" : "procamp_vaapi=c={1}";
doVaVppProcamp = true;
}
- args = "{0}tonemap_vaapi=format={1}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
+ args = procampParams + "{2}tonemap_vaapi=format={3}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
return string.Format(
CultureInfo.InvariantCulture,
args,
- doVaVppProcamp ? $"procamp_vaapi{procampParams}," : string.Empty,
+ options.VppTonemappingBrightness,
+ options.VppTonemappingContrast,
+ doVaVppProcamp ? "," : string.Empty,
videoFormat ?? "nv12");
}
else
@@ -3352,7 +3386,7 @@ namespace MediaBrowser.Controller.MediaEncoding
rangeString);
}
- public string GetLibplaceboFilter(
+ private string GetLibplaceboFilter(
EncodingOptions options,
string videoFormat,
bool doTonemap,
@@ -3361,7 +3395,8 @@ namespace MediaBrowser.Controller.MediaEncoding
int? requestedWidth,
int? requestedHeight,
int? requestedMaxWidth,
- int? requestedMaxHeight)
+ int? requestedMaxHeight,
+ bool forceFullRange)
{
var (outWidth, outHeight) = GetFixedOutputSize(
videoWidth,
@@ -3386,7 +3421,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var algorithm = options.TonemappingAlgorithm;
var algorithmString = "clip";
var mode = options.TonemappingMode;
- var range = options.TonemappingRange;
+ var range = forceFullRange ? TonemappingRange.pc : options.TonemappingRange;
if (algorithm == TonemappingAlgorithm.bt2390)
{
@@ -3397,7 +3432,7 @@ namespace MediaBrowser.Controller.MediaEncoding
algorithmString = algorithm.ToString().ToLowerInvariant();
}
- tonemapArg = ":tonemapping=" + algorithm + ":peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709";
+ tonemapArg = $":tonemapping={algorithmString}:peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709";
if (range == TonemappingRange.tv || range == TonemappingRange.pc)
{
@@ -3456,7 +3491,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doToneMap = IsSwTonemapAvailable(state, options);
var requireDoviReshaping = doToneMap && state.VideoStream.VideoRangeType == VideoRangeType.DOVI;
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -3497,20 +3532,29 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// tonemapx requires yuv420p10 input for dovi reshaping, let ffmpeg convert the frame when necessary
var tonemapFormat = requireDoviReshaping ? "yuv420p" : outFormat;
-
- var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format={tonemapFormat}";
+ var tonemapArgString = "tonemapx=tonemap={0}:desat={1}:peak={2}:t=bt709:m=bt709:p=bt709:format={3}";
if (options.TonemappingParam != 0)
{
- tonemapArgs += $":param={options.TonemappingParam}";
+ tonemapArgString += ":param={4}";
}
var range = options.TonemappingRange;
if (range == TonemappingRange.tv || range == TonemappingRange.pc)
{
- tonemapArgs += $":range={options.TonemappingRange}";
+ tonemapArgString += ":range={5}";
}
+ var tonemapArgs = string.Format(
+ CultureInfo.InvariantCulture,
+ tonemapArgString,
+ options.TonemappingAlgorithm,
+ options.TonemappingDesat,
+ options.TonemappingPeak,
+ tonemapFormat,
+ options.TonemappingParam,
+ options.TonemappingRange);
+
mainFilters.Add(tonemapArgs);
}
else
@@ -3569,7 +3613,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetSwVidFilterChain(state, options, vidEncoder);
}
- // prefered nvdec/cuvid + cuda filters + nvenc pipeline
+ // preferred nvdec/cuvid + cuda filters + nvenc pipeline
return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
@@ -3591,6 +3635,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isNvencEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isCuInCuOut = isNvDecoder && isNvencEncoder;
var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.ReferenceFrameRate ?? 60) <= 30;
@@ -3599,7 +3644,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doCuTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3609,8 +3654,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
- var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
- var doCuTranspose = !string.IsNullOrEmpty(tranposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
+ var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
+ var doCuTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_cuda");
var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isNvDecoder && doCuTranspose));
var swpInW = swapWAndH ? inH : inW;
var swpInH = swapWAndH ? inW : inH;
@@ -3656,10 +3701,11 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw transpose
if (doCuTranspose)
{
- mainFilters.Add($"transpose_cuda=dir={tranposeDir}");
+ mainFilters.Add($"transpose_cuda=dir={transposeDir}");
}
- var outFormat = doCuTonemap ? string.Empty : "yuv420p";
+ var isRext = IsVideoStreamHevcRext(state);
+ var outFormat = doCuTonemap ? (isRext ? "p010" : string.Empty) : "yuv420p";
var hwScaleFilter = GetHwScaleFilter("scale", "cuda", outFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
// hw scale
mainFilters.Add(hwScaleFilter);
@@ -3668,7 +3714,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw tonemap
if (doCuTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p");
+ var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -3775,7 +3821,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetSwVidFilterChain(state, options, vidEncoder);
}
- // prefered d3d11va + opencl filters + amf pipeline
+ // preferred d3d11va + opencl filters + amf pipeline
return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
@@ -3797,6 +3843,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isAmfEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
@@ -3804,7 +3851,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3814,8 +3861,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
- var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
- var doOclTranspose = !string.IsNullOrEmpty(tranposeDir)
+ var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
+ var doOclTranspose = !string.IsNullOrEmpty(transposeDir)
&& _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TransposeOpenclReversal);
var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isD3d11vaDecoder && doOclTranspose));
var swpInW = swapWAndH ? inH : inW;
@@ -3859,12 +3906,12 @@ namespace MediaBrowser.Controller.MediaEncoding
// map from d3d11va to opencl via d3d11-opencl interop.
mainFilters.Add("hwmap=derive_device=opencl:mode=read");
- // hw deint <= TODO: finsh the 'yadif_opencl' filter
+ // hw deint <= TODO: finish the 'yadif_opencl' filter
// hw transpose
if (doOclTranspose)
{
- mainFilters.Add($"transpose_opencl=dir={tranposeDir}");
+ mainFilters.Add($"transpose_opencl=dir={transposeDir}");
}
var outFormat = doOclTonemap ? string.Empty : "nv12";
@@ -3876,7 +3923,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -4000,13 +4047,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetSwVidFilterChain(state, options, vidEncoder);
}
- // prefered qsv(vaapi) + opencl filters pipeline
+ // preferred qsv(vaapi) + opencl filters pipeline
if (isIntelVaapiOclSupported)
{
return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
- // prefered qsv(d3d11) + opencl filters pipeline
+ // preferred qsv(d3d11) + opencl filters pipeline
if (isIntelDx11OclSupported)
{
return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
@@ -4035,6 +4082,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isQsvEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
@@ -4044,7 +4092,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doOclTonemap = !doVppTonemap && IsHwTonemapAvailable(state, options);
var doTonemap = doVppTonemap || doOclTonemap;
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -4054,8 +4102,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
- var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
- var doVppTranspose = !string.IsNullOrEmpty(tranposeDir);
+ var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
+ var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isD3d11vaDecoder || isQsvDecoder) && doVppTranspose));
var swpInW = swapWAndH ? inH : inW;
var swpInH = swapWAndH ? inW : inH;
@@ -4077,6 +4125,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
// sw scale
mainFilters.Add(swScaleFilter);
mainFilters.Add($"format={outFormat}");
@@ -4091,43 +4145,69 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (isD3d11vaDecoder || isQsvDecoder)
{
+ var isRext = IsVideoStreamHevcRext(state);
+ var twoPassVppTonemap = false;
+ var doVppFullRangeOut = isMjpegEncoder
+ && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
+ var doVppScaleModeHq = isMjpegEncoder
+ && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
var doVppProcamp = false;
var procampParams = string.Empty;
+ var procampParamsString = string.Empty;
if (doVppTonemap)
{
+ if (isRext)
+ {
+ // VPP tonemap requires p010 input
+ twoPassVppTonemap = true;
+ }
+
if (options.VppTonemappingBrightness != 0
&& options.VppTonemappingBrightness >= -100
&& options.VppTonemappingBrightness <= 100)
{
- procampParams += $":brightness={options.VppTonemappingBrightness}";
- doVppProcamp = true;
+ procampParamsString += ":brightness={0}";
+ twoPassVppTonemap = doVppProcamp = true;
}
if (options.VppTonemappingContrast > 1
&& options.VppTonemappingContrast <= 10)
{
- procampParams += $":contrast={options.VppTonemappingContrast}";
- doVppProcamp = true;
+ procampParamsString += ":contrast={1}";
+ twoPassVppTonemap = doVppProcamp = true;
}
- procampParams += doVppProcamp ? ":procamp=1:async_depth=2" : string.Empty;
+ if (doVppProcamp)
+ {
+ procampParamsString += ":procamp=1:async_depth=2";
+ procampParams = string.Format(
+ CultureInfo.InvariantCulture,
+ procampParamsString,
+ options.VppTonemappingBrightness,
+ options.VppTonemappingContrast);
+ }
}
- var outFormat = doOclTonemap ? (doVppTranspose ? "p010" : string.Empty) : "nv12";
- outFormat = (doVppTonemap && doVppProcamp) ? "p010" : outFormat;
+ var outFormat = doOclTonemap ? ((doVppTranspose || isRext) ? "p010" : string.Empty) : "nv12";
+ outFormat = twoPassVppTonemap ? "p010" : outFormat;
var swapOutputWandH = doVppTranspose && swapWAndH;
- var hwScalePrefix = (doVppTranspose || doVppTonemap) ? "vpp" : "scale";
- var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var hwScaleFilter = GetHwScaleFilter("vpp", "qsv", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
{
- hwScaleFilter += $":transpose={tranposeDir}";
+ hwScaleFilter += $":transpose={transposeDir}";
+ }
+
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
+ {
+ hwScaleFilter += (doVppFullRangeOut && !doOclTonemap) ? ":out_range=pc" : string.Empty;
+ hwScaleFilter += doVppScaleModeHq ? ":scale_mode=hq" : string.Empty;
}
if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTonemap)
{
- hwScaleFilter += doVppProcamp ? procampParams : ":tonemap=1";
+ hwScaleFilter += doVppProcamp ? procampParams : (twoPassVppTonemap ? string.Empty : ":tonemap=1");
}
if (isD3d11vaDecoder)
@@ -4151,7 +4231,7 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add(hwScaleFilter);
// hw tonemap(w/ procamp)
- if (doVppTonemap && doVppProcamp)
+ if (doVppTonemap && twoPassVppTonemap)
{
mainFilters.Add("vpp_qsv=tonemap=1:format=nv12:async_depth=2");
}
@@ -4172,7 +4252,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -4289,6 +4369,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isHwDecoder = isVaapiDecoder || isQsvDecoder;
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isQsvEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
@@ -4298,7 +4379,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doTonemap = doVaVppTonemap || doOclTonemap;
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -4308,8 +4389,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
- var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
- var doVppTranspose = !string.IsNullOrEmpty(tranposeDir);
+ var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
+ var doVppTranspose = !string.IsNullOrEmpty(transposeDir);
var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || ((isVaapiDecoder || isQsvDecoder) && doVppTranspose));
var swpInW = swapWAndH ? inH : inW;
var swpInH = swapWAndH ? inW : inH;
@@ -4331,6 +4412,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
// sw scale
mainFilters.Add(swScaleFilter);
mainFilters.Add($"format={outFormat}");
@@ -4346,6 +4433,11 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (isVaapiDecoder || isQsvDecoder)
{
var hwFilterSuffix = isVaapiDecoder ? "vaapi" : "qsv";
+ var isRext = IsVideoStreamHevcRext(state);
+ var doVppFullRangeOut = isMjpegEncoder
+ && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppOutRangeOption;
+ var doVppScaleModeHq = isMjpegEncoder
+ && _mediaEncoder.EncoderVersion >= _minFFmpegQsvVppScaleModeOption;
// INPUT vaapi/qsv surface(vram)
// hw deint
@@ -4358,17 +4450,23 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw transpose(vaapi vpp)
if (isVaapiDecoder && doVppTranspose)
{
- mainFilters.Add($"transpose_vaapi=dir={tranposeDir}");
+ mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
}
- var outFormat = doOclTonemap ? ((isQsvDecoder && doVppTranspose) ? "p010" : string.Empty) : "nv12";
+ var outFormat = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv12";
var swapOutputWandH = isQsvDecoder && doVppTranspose && swapWAndH;
- var hwScalePrefix = (isQsvDecoder && doVppTranspose) ? "vpp" : "scale";
+ var hwScalePrefix = isQsvDecoder ? "vpp" : "scale";
var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, hwFilterSuffix, outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
{
- hwScaleFilter += $":transpose={tranposeDir}";
+ hwScaleFilter += $":transpose={transposeDir}";
+ }
+
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
+ {
+ hwScaleFilter += ((isQsvDecoder && !doVppFullRangeOut) || doOclTonemap) ? string.Empty : ":out_range=pc";
+ hwScaleFilter += isQsvDecoder ? (doVppScaleModeHq ? ":scale_mode=hq" : string.Empty) : ":mode=hq";
}
// allocate extra pool sizes for vaapi vpp scale
@@ -4391,7 +4489,7 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add("format=vaapi");
}
- var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
if (isQsvDecoder)
@@ -4411,7 +4509,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// ocl tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -4563,14 +4661,14 @@ namespace MediaBrowser.Controller.MediaEncoding
return swFilterChain;
}
- // prefered vaapi + opencl filters pipeline
+ // preferred vaapi + opencl filters pipeline
if (_mediaEncoder.IsVaapiDeviceInteliHD)
{
// Intel iHD path, with extra vpp tonemap and overlay support.
return GetIntelVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
}
- // prefered vaapi + vulkan filters pipeline
+ // preferred vaapi + vulkan filters pipeline
if (_mediaEncoder.IsVaapiDeviceAmd
&& isVaapiVkSupported
&& _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop
@@ -4602,6 +4700,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isVaapiEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
@@ -4611,7 +4710,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doTonemap = doVaVppTonemap || doOclTonemap;
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -4621,8 +4720,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
- var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
- var doVaVppTranspose = !string.IsNullOrEmpty(tranposeDir);
+ var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
+ var doVaVppTranspose = !string.IsNullOrEmpty(transposeDir);
var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVaVppTranspose));
var swpInW = swapWAndH ? inH : inW;
var swpInH = swapWAndH ? inW : inH;
@@ -4644,6 +4743,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
// sw scale
mainFilters.Add(swScaleFilter);
mainFilters.Add($"format={outFormat}");
@@ -4658,6 +4763,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (isVaapiDecoder)
{
+ var isRext = IsVideoStreamHevcRext(state);
+
// INPUT vaapi surface(vram)
// hw deint
if (doDeintH2645)
@@ -4669,12 +4776,18 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw transpose
if (doVaVppTranspose)
{
- mainFilters.Add($"transpose_vaapi=dir={tranposeDir}");
+ mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
}
- var outFormat = doTonemap ? string.Empty : "nv12";
+ var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
+ {
+ hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
+ hwScaleFilter += ":mode=hq";
+ }
+
// allocate extra pool sizes for vaapi vpp
if (!string.IsNullOrEmpty(hwScaleFilter))
{
@@ -4688,7 +4801,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// vaapi vpp tonemap
if (doVaVppTonemap && isVaapiDecoder)
{
- var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -4701,7 +4814,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// ocl tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -4825,13 +4938,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isVaapiEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
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 is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -4839,8 +4953,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
var rotation = state.VideoStream?.Rotation ?? 0;
- var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
- var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(tranposeDir);
+ var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
+ var doVkTranspose = isVaapiDecoder && !string.IsNullOrEmpty(transposeDir);
var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isVaapiDecoder && doVkTranspose));
var swpInW = swapWAndH ? inH : inW;
var swpInH = swapWAndH ? inW : inH;
@@ -4920,6 +5034,12 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw scale
var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", "nv12", false, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder && !doVkTonemap)
+ {
+ hwScaleFilter += ":out_range=pc:mode=hq";
+ }
+
mainFilters.Add(hwScaleFilter);
}
}
@@ -4927,20 +5047,20 @@ namespace MediaBrowser.Controller.MediaEncoding
// vk transpose
if (doVkTranspose)
{
- if (string.Equals(tranposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(transposeDir, "reversal", StringComparison.OrdinalIgnoreCase))
{
mainFilters.Add("flip_vulkan");
}
else
{
- mainFilters.Add($"transpose_vulkan=dir={tranposeDir}");
+ mainFilters.Add($"transpose_vulkan=dir={transposeDir}");
}
}
// vk libplacebo
if (doVkTonemap || hasSubs)
{
- var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH, isMjpegEncoder);
mainFilters.Add(libplaceboFilter);
mainFilters.Add("format=vulkan");
}
@@ -5055,6 +5175,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isVaapiEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
@@ -5064,7 +5185,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -5091,6 +5212,12 @@ namespace MediaBrowser.Controller.MediaEncoding
outFormat = doOclTonemap ? "yuv420p10le" : "nv12";
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
// sw scale
mainFilters.Add(swScaleFilter);
mainFilters.Add("format=" + outFormat);
@@ -5116,6 +5243,12 @@ namespace MediaBrowser.Controller.MediaEncoding
outFormat = doOclTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter("scale", "vaapi", outFormat, false, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
+ {
+ hwScaleFilter += doOclTonemap ? string.Empty : ":out_range=pc";
+ hwScaleFilter += ":mode=hq";
+ }
+
// allocate extra pool sizes for vaapi vpp
if (!string.IsNullOrEmpty(hwScaleFilter))
{
@@ -5144,7 +5277,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// ocl tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -5270,6 +5403,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
var isVtDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var inW = state.VideoStream?.Width;
var inH = state.VideoStream?.Height;
@@ -5287,8 +5421,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var usingHwSurface = isVtDecoder && (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface);
var rotation = state.VideoStream?.Rotation ?? 0;
- var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
- var doVtTranspose = !string.IsNullOrEmpty(tranposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
+ var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
+ var doVtTranspose = !string.IsNullOrEmpty(transposeDir) && _mediaEncoder.SupportsFilter("transpose_vt");
var swapWAndH = Math.Abs(rotation) == 90 && doVtTranspose;
var swpInW = swapWAndH ? inH : inW;
var swpInH = swapWAndH ? inW : inH;
@@ -5312,7 +5446,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var hwScaleFilter = GetHwScaleFilter("scale", "vt", scaleFormat, false, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -5332,7 +5466,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw transpose
if (doVtTranspose)
{
- mainFilters.Add($"transpose_vt=dir={tranposeDir}");
+ mainFilters.Add($"transpose_vt=dir={transposeDir}");
}
if (doVtTonemap)
@@ -5351,7 +5485,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Metal tonemap
if (doMetalTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "videotoolbox", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -5447,7 +5581,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetSwVidFilterChain(state, options, vidEncoder);
}
- // prefered rkmpp + rkrga + opencl filters pipeline
+ // preferred rkmpp + rkrga + opencl filters pipeline
if (isRkmppOclSupported)
{
return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
@@ -5474,6 +5608,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = !isRkmppDecoder;
var isSwEncoder = !isRkmppEncoder;
+ var isMjpegEncoder = vidEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase);
var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
var isEncoderSupportAfbc = isRkmppEncoder
&& (vidEncoder.Contains("h264", StringComparison.OrdinalIgnoreCase)
@@ -5484,7 +5619,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 != null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -5494,8 +5629,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var subH = state.SubtitleStream?.Height;
var rotation = state.VideoStream?.Rotation ?? 0;
- var tranposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
- var doRkVppTranspose = !string.IsNullOrEmpty(tranposeDir);
+ var transposeDir = rotation == 0 ? string.Empty : GetVideoTransposeDirection(state);
+ var doRkVppTranspose = !string.IsNullOrEmpty(transposeDir);
var swapWAndH = Math.Abs(rotation) == 90 && (isSwDecoder || (isRkmppDecoder && doRkVppTranspose));
var swpInW = swapWAndH ? inH : inW;
var swpInH = swapWAndH ? inW : inH;
@@ -5517,6 +5652,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, swpInW, swpInH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isMjpegEncoder && !doOclTonemap)
+ {
+ // sw decoder + hw mjpeg encoder
+ swScaleFilter = string.IsNullOrEmpty(swScaleFilter) ? "scale=out_range=pc" : $"{swScaleFilter}:out_range=pc";
+ }
+
if (!string.IsNullOrEmpty(swScaleFilter))
{
swScaleFilter += ":flags=fast_bilinear";
@@ -5540,19 +5681,31 @@ namespace MediaBrowser.Controller.MediaEncoding
var isFullAfbcPipeline = isEncoderSupportAfbc && isDrmInDrmOut && !doOclTonemap;
var swapOutputWandH = doRkVppTranspose && swapWAndH;
- var outFormat = doOclTonemap ? "p010" : "nv12";
- var hwScalePrefix = doRkVppTranspose ? "vpp" : "scale";
- var hwScaleFilter = GetHwScaleFilter(hwScalePrefix, "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
- var hwScaleFilter2 = GetHwScaleFilter(hwScalePrefix, "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var outFormat = doOclTonemap ? "p010" : (isMjpegEncoder ? "bgra" : "nv12"); // RGA only support full range in rgb fmts
+ var hwScaleFilter = GetHwScaleFilter("vpp", "rkrga", outFormat, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
+ var doScaling = GetHwScaleFilter("vpp", "rkrga", string.Empty, swapOutputWandH, swpInW, swpInH, reqW, reqH, reqMaxW, reqMaxH);
if (!hasSubs
|| doRkVppTranspose
|| !isFullAfbcPipeline
- || !string.IsNullOrEmpty(hwScaleFilter2))
+ || !string.IsNullOrEmpty(doScaling))
{
+ // RGA3 hardware only support (1/8 ~ 8) scaling in each blit operation,
+ // but in Trickplay there's a case: (3840/320 == 12), enable 2pass for it
+ if (!string.IsNullOrEmpty(doScaling)
+ && !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f))
+ {
+ // Vendor provided BSP kernel has an RGA driver bug that causes the output to be corrupted for P010 format.
+ // Use NV15 instead of P010 to avoid the issue.
+ // SDR inputs are using BGRA formats already which is not affected.
+ var intermediateFormat = string.Equals(outFormat, "p010", StringComparison.OrdinalIgnoreCase) ? "nv15" : outFormat;
+ var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={intermediateFormat}:force_divisible_by=4:afbc=1";
+ mainFilters.Add(hwScaleFilterFirstPass);
+ }
+
if (!string.IsNullOrEmpty(hwScaleFilter) && doRkVppTranspose)
{
- hwScaleFilter += $":transpose={tranposeDir}";
+ hwScaleFilter += $":transpose={transposeDir}";
}
// try enabling AFBC to save DDR bandwidth
@@ -5569,13 +5722,13 @@ namespace MediaBrowser.Controller.MediaEncoding
if (doOclTonemap && isRkmppDecoder)
{
// map from rkmpp/drm to opencl via drm-opencl interop.
- mainFilters.Add("hwmap=derive_device=opencl:mode=read");
+ mainFilters.Add("hwmap=derive_device=opencl");
}
// ocl tonemap
if (doOclTonemap)
{
- var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12", isMjpegEncoder);
mainFilters.Add(tonemapFilter);
}
@@ -5612,7 +5765,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// OUTPUT drm(nv12) surface(gem/dma-heap)
// reverse-mapping via drm-opencl interop.
- mainFilters.Add("hwmap=derive_device=rkmpp:mode=write:reverse=1");
+ mainFilters.Add("hwmap=derive_device=rkmpp:reverse=1");
mainFilters.Add("format=drm_prime");
}
}
@@ -5686,7 +5839,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Empty;
}
- var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -5938,19 +6091,6 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- var whichCodec = videoStream.Codec;
- if (string.Equals(whichCodec, "avc", StringComparison.OrdinalIgnoreCase))
- {
- whichCodec = "h264";
- }
- else if (string.Equals(whichCodec, "h265", StringComparison.OrdinalIgnoreCase))
- {
- whichCodec = "hevc";
- }
-
- // Avoid a second attempt if no hardware acceleration is being used
- options.HardwareDecodingCodecs = options.HardwareDecodingCodecs.Where(c => !string.Equals(c, whichCodec, StringComparison.OrdinalIgnoreCase)).ToArray();
-
// leave blank so ffmpeg will decide
return null;
}
@@ -5974,7 +6114,11 @@ namespace MediaBrowser.Controller.MediaEncoding
var decoderName = decoderPrefix + '_' + decoderSuffix;
var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
- if (bitDepth == 10 && isCodecAvailable)
+
+ // VideoToolbox decoders have built-in SW fallback
+ if (bitDepth == 10
+ && isCodecAvailable
+ && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
{
if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
&& options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
@@ -6035,7 +6179,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var ffmpegVersion = _mediaEncoder.EncoderVersion;
// Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
- var isAv1 = ffmpegVersion < _minFFmpegImplictHwaccel
+ var isAv1 = ffmpegVersion < _minFFmpegImplicitHwaccel
&& string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
// Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
@@ -6050,17 +6194,40 @@ namespace MediaBrowser.Controller.MediaEncoding
&& ffmpegVersion >= _minFFmpegDisplayRotationOption;
var stripRotationDataArgs = stripRotationData ? " -display_rotation 0" : string.Empty;
- if (bitDepth == 10 && isCodecAvailable)
+ // VideoToolbox decoders have built-in SW fallback
+ if (isCodecAvailable
+ && (options.HardwareAccelerationType != HardwareAccelerationType.videotoolbox))
{
if (string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
- && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase)
- && !options.EnableDecodingColorDepth10Hevc)
+ && options.HardwareDecodingCodecs.Contains("hevc", StringComparison.OrdinalIgnoreCase))
{
- return null;
+ if (IsVideoStreamHevcRext(state))
+ {
+ if (bitDepth <= 10 && !options.EnableDecodingColorDepth10HevcRext)
+ {
+ return null;
+ }
+
+ if (bitDepth == 12 && !options.EnableDecodingColorDepth12HevcRext)
+ {
+ return null;
+ }
+
+ if (hardwareAccelerationType == HardwareAccelerationType.vaapi
+ && !_mediaEncoder.IsVaapiDeviceInteliHD)
+ {
+ return null;
+ }
+ }
+ else if (bitDepth == 10 && !options.EnableDecodingColorDepth10Hevc)
+ {
+ return null;
+ }
}
if (string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
&& options.HardwareDecodingCodecs.Contains("vp9", StringComparison.OrdinalIgnoreCase)
+ && bitDepth == 10
&& !options.EnableDecodingColorDepth10Vp9)
{
return null;
@@ -6172,6 +6339,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10_12bitSwFormatsQsv = is8_10bitSwFormatsQsv
+ || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
if (is8bitSwFormatsQsv)
@@ -6200,12 +6375,6 @@ namespace MediaBrowser.Controller.MediaEncoding
if (is8_10bitSwFormatsQsv)
{
- if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
- {
- return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "qsv", "hevc", bitDepth);
- }
-
if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
{
return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "qsv", "vp9", bitDepth);
@@ -6217,6 +6386,15 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ if (is8_10_12bitSwFormatsQsv)
+ {
+ if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "qsv", "hevc", bitDepth);
+ }
+ }
+
return null;
}
@@ -6232,6 +6410,11 @@ namespace MediaBrowser.Controller.MediaEncoding
var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10_12bitSwFormatsNvdec = is8_10bitSwFormatsNvdec
+ || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
if (is8bitSwFormatsNvdec)
@@ -6265,12 +6448,6 @@ namespace MediaBrowser.Controller.MediaEncoding
if (is8_10bitSwFormatsNvdec)
{
- if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
- || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
- {
- return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "cuvid", "hevc", bitDepth);
- }
-
if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "cuvid", "vp9", bitDepth);
@@ -6282,6 +6459,15 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ if (is8_10_12bitSwFormatsNvdec)
+ {
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "cuvid", "hevc", bitDepth);
+ }
+ }
+
return null;
}
@@ -6356,6 +6542,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10_12bitSwFormatsVaapi = is8_10bitSwFormatsVaapi
+ || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
if (is8bitSwFormatsVaapi)
{
@@ -6383,12 +6577,6 @@ namespace MediaBrowser.Controller.MediaEncoding
if (is8_10bitSwFormatsVaapi)
{
- if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
- || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
- {
- return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
- }
-
if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
@@ -6400,6 +6588,15 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ if (is8_10_12bitSwFormatsVaapi)
+ {
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
+ }
+ }
+
return null;
}
@@ -6414,6 +6611,14 @@ namespace MediaBrowser.Controller.MediaEncoding
var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10_12bitSwFormatsVt = is8_10bitSwFormatsVt
+ || string.Equals("yuv422p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
// The related patches make videotoolbox hardware surface working is only available in jellyfin-ffmpeg 7.0.1 at the moment.
bool useHwSurface = (_mediaEncoder.EncoderVersion >= _minFFmpegWorkingVtHwSurface) && IsVideoToolboxFullSupported();
@@ -6434,15 +6639,18 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
}
- if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
- || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
+ return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
}
+ }
- if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ if (is8_10_12bitSwFormatsVt)
+ {
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
+ return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
}
}
@@ -6667,7 +6875,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(state.InputVideoSync))
{
- inputModifier += " -vsync " + state.InputVideoSync;
+ inputModifier += GetVideoSyncOption(state.InputVideoSync, _mediaEncoder.EncoderVersion);
}
if (state.ReadInputAtNativeFramerate && state.InputProtocol != MediaProtocol.Rtsp)
@@ -6865,7 +7073,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
// 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("dts");
shiftAudioCodecs.Add("truehd");
}
else
@@ -7052,7 +7260,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += keyFrameArg;
- var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream && ShouldEncodeSubtitle(state);
var hasCopyTs = false;
@@ -7090,7 +7298,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(state.OutputVideoSync))
{
- args += " -vsync " + state.OutputVideoSync;
+ args += GetVideoSyncOption(state.OutputVideoSync, _mediaEncoder.EncoderVersion);
}
args += GetOutputFFlags(state);
@@ -7257,5 +7465,39 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
}
+
+ private static bool ShouldEncodeSubtitle(EncodingJobInfo state)
+ {
+ return state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ || (state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding && !IsCopyCodec(state.OutputVideoCodec));
+ }
+
+ public static string GetVideoSyncOption(string videoSync, Version encoderVersion)
+ {
+ if (string.IsNullOrEmpty(videoSync))
+ {
+ return string.Empty;
+ }
+
+ if (encoderVersion >= new Version(5, 1))
+ {
+ if (int.TryParse(videoSync, CultureInfo.InvariantCulture, out var vsync))
+ {
+ return vsync switch
+ {
+ -1 => " -fps_mode auto",
+ 0 => " -fps_mode passthrough",
+ 1 => " -fps_mode cfr",
+ 2 => " -fps_mode vfr",
+ _ => string.Empty
+ };
+ }
+
+ return string.Empty;
+ }
+
+ // -vsync is deprecated in FFmpeg 5.1 and will be removed in the future.
+ return $" -vsync {videoSync}";
+ }
}
}