aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs')
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs364
1 files changed, 287 insertions, 77 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 9399679a4..7c3138002 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -13,10 +13,13 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
+using Jellyfin.Data;
using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Extensions;
+using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
@@ -35,7 +38,13 @@ namespace MediaBrowser.Controller.MediaEncoding
/// periods, underscores, commas, and vertical bars, with a length between 0 and 40 characters.
/// This should matches all common valid codecs.
/// </summary>
- public const string ValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
+ public const string ContainerValidationRegex = @"^[a-zA-Z0-9\-\._,|]{0,40}$";
+
+ /// <summary>
+ /// The level validation regex.
+ /// This regular expression matches strings representing a double.
+ /// </summary>
+ public const string LevelValidationRegex = @"-?[0-9]+(?:\.[0-9]+)?";
private const string _defaultMjpegEncoder = "mjpeg";
@@ -53,6 +62,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config;
private readonly IConfigurationManager _configurationManager;
+ private readonly IPathManager _pathManager;
// i915 hang was fixed by linux 6.2 (3f882f2)
private readonly Version _minKerneli915Hang = new Version(5, 18);
@@ -60,7 +70,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);
@@ -74,8 +84,9 @@ namespace MediaBrowser.Controller.MediaEncoding
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 readonly Version _minFFmpegRkmppHevcDecDoviRpu = new Version(7, 1, 1);
- private static readonly Regex _validationRegex = new(ValidationRegex, RegexOptions.Compiled);
+ private static readonly Regex _containerValidationRegex = new(ContainerValidationRegex, RegexOptions.Compiled);
private static readonly string[] _videoProfilesH264 =
[
@@ -151,13 +162,22 @@ namespace MediaBrowser.Controller.MediaEncoding
IMediaEncoder mediaEncoder,
ISubtitleEncoder subtitleEncoder,
IConfiguration config,
- IConfigurationManager configurationManager)
+ IConfigurationManager configurationManager,
+ IPathManager pathManager)
{
_appPaths = appPaths;
_mediaEncoder = mediaEncoder;
_subtitleEncoder = subtitleEncoder;
_config = config;
_configurationManager = configurationManager;
+ _pathManager = pathManager;
+ }
+
+ private enum DynamicHdrMetadataRemovalPlan
+ {
+ None,
+ RemoveDovi,
+ RemoveHdr10Plus,
}
[GeneratedRegex(@"\s+")]
@@ -309,7 +329,6 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
if (state.VideoStream is null
- || !options.EnableTonemapping
|| GetVideoColorBitDepth(state) < 10
|| !_mediaEncoder.SupportsFilter("tonemapx"))
{
@@ -331,8 +350,17 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.VideoStream.VideoRange == VideoRange.HDR
&& state.VideoStream.VideoRangeType == VideoRangeType.DOVI)
{
- // Only native SW decoder and HW accelerator can parse dovi rpu.
+ // Only native SW decoder, HW accelerator and hevc_rkmpp decoder can parse dovi rpu.
var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+
+ var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
+ if (isRkmppDecoder
+ && _mediaEncoder.EncoderVersion >= _minFFmpegRkmppHevcDecDoviRpu
+ && string.Equals(state.VideoStream?.Codec, "hevc", StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
@@ -341,11 +369,8 @@ namespace MediaBrowser.Controller.MediaEncoding
return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
}
- return state.VideoStream.VideoRange == VideoRange.HDR
- && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
- || state.VideoStream.VideoRangeType == VideoRangeType.HLG
- || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10
- || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHLG);
+ // GPU tonemapping supports all HDR RangeTypes
+ return state.VideoStream.VideoRange == VideoRange.HDR;
}
private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
@@ -380,8 +405,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
return state.VideoStream.VideoRange == VideoRange.HDR
- && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
- || state.VideoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10);
+ && IsDoviWithHdr10Bl(state.VideoStream);
}
private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
@@ -396,7 +420,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce correct mapping results with transcoding.
// All other HDR formats working.
return state.VideoStream.VideoRange == VideoRange.HDR
- && state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.HDR10Plus or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG;
+ && (IsDoviWithHdr10Bl(state.VideoStream)
+ || state.VideoStream.VideoRangeType is VideoRangeType.HLG);
}
private bool IsVideoStreamHevcRext(EncodingJobInfo state)
@@ -451,7 +476,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetMjpegEncoder(state, encodingOptions);
}
- if (_validationRegex.IsMatch(codec))
+ if (_containerValidationRegex.IsMatch(codec))
{
return codec.ToLowerInvariant();
}
@@ -492,7 +517,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public static string GetInputFormat(string container)
{
- if (string.IsNullOrEmpty(container) || !_validationRegex.IsMatch(container))
+ if (string.IsNullOrEmpty(container) || !_containerValidationRegex.IsMatch(container))
{
return null;
}
@@ -631,7 +656,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (string.IsNullOrWhiteSpace(container))
{
- // this may not work, but if the client is that broken we can not do anything better
+ // this may not work, but if the client is that broken we cannot do anything better
return "aac";
}
@@ -710,7 +735,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var codec = state.OutputAudioCodec;
- if (!_validationRegex.IsMatch(codec))
+ if (!_containerValidationRegex.IsMatch(codec))
{
codec = "aac";
}
@@ -861,9 +886,9 @@ namespace MediaBrowser.Controller.MediaEncoding
&& _mediaEncoder.EncoderVersion >= _minFFmpegVaapiDeviceVendorId;
// Priority: 'renderNodePath' > 'vendorId' > 'kernelDriver'
- var driverOpts = string.IsNullOrEmpty(renderNodePath)
- ? (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",kernel_driver={kernelDriver}"))
- : renderNodePath;
+ var driverOpts = File.Exists(renderNodePath)
+ ? renderNodePath
+ : (haveVendorId ? $",vendor_id={vendorId}" : (string.IsNullOrEmpty(kernelDriver) ? string.Empty : $",kernel_driver={kernelDriver}"));
// 'driver' behaves similarly to env LIBVA_DRIVER_NAME
driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver;
@@ -1300,6 +1325,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|| codec.Contains("hevc", StringComparison.OrdinalIgnoreCase);
}
+ public static bool IsAv1(MediaStream stream)
+ {
+ var codec = stream.Codec ?? string.Empty;
+
+ return codec.Contains("av1", StringComparison.OrdinalIgnoreCase);
+ }
+
public static bool IsAAC(MediaStream stream)
{
var codec = stream.Codec ?? string.Empty;
@@ -1307,8 +1339,125 @@ namespace MediaBrowser.Controller.MediaEncoding
return codec.Contains("aac", StringComparison.OrdinalIgnoreCase);
}
- public static string GetBitStreamArgs(MediaStream stream)
+ public static bool IsDoviWithHdr10Bl(MediaStream stream)
+ {
+ var rangeType = stream?.VideoRangeType;
+
+ return rangeType is VideoRangeType.DOVIWithHDR10
+ or VideoRangeType.DOVIWithEL
+ or VideoRangeType.DOVIWithHDR10Plus
+ or VideoRangeType.DOVIWithELHDR10Plus
+ or VideoRangeType.DOVIInvalid;
+ }
+
+ public static bool IsDovi(MediaStream stream)
+ {
+ var rangeType = stream?.VideoRangeType;
+
+ return IsDoviWithHdr10Bl(stream)
+ || (rangeType is VideoRangeType.DOVI
+ or VideoRangeType.DOVIWithHLG
+ or VideoRangeType.DOVIWithSDR);
+ }
+
+ public static bool IsHdr10Plus(MediaStream stream)
+ {
+ var rangeType = stream?.VideoRangeType;
+
+ return rangeType is VideoRangeType.HDR10Plus
+ or VideoRangeType.DOVIWithHDR10Plus
+ or VideoRangeType.DOVIWithELHDR10Plus;
+ }
+
+ /// <summary>
+ /// Check if dynamic HDR metadata should be removed during stream copy.
+ /// Please note this check assumes the range check has already been done
+ /// and trivial fallbacks like HDR10+ to HDR10, DOVIWithHDR10 to HDR10 is already checked.
+ /// </summary>
+ private static DynamicHdrMetadataRemovalPlan ShouldRemoveDynamicHdrMetadata(EncodingJobInfo state)
+ {
+ var videoStream = state.VideoStream;
+ if (videoStream.VideoRange is not VideoRange.HDR)
+ {
+ return DynamicHdrMetadataRemovalPlan.None;
+ }
+
+ var requestedRangeTypes = state.GetRequestedRangeTypes(state.VideoStream.Codec);
+ if (requestedRangeTypes.Length == 0)
+ {
+ return DynamicHdrMetadataRemovalPlan.None;
+ }
+
+ var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.OrdinalIgnoreCase);
+ var requestHasDOVI = requestedRangeTypes.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase);
+ var requestHasDOVIwithEL = requestedRangeTypes.Contains(VideoRangeType.DOVIWithEL.ToString(), StringComparison.OrdinalIgnoreCase);
+ var requestHasDOVIwithELHDR10plus = requestedRangeTypes.Contains(VideoRangeType.DOVIWithELHDR10Plus.ToString(), StringComparison.OrdinalIgnoreCase);
+
+ var shouldRemoveHdr10Plus = false;
+ // Case 1: Client supports HDR10, does not support DOVI with EL but EL presets
+ var shouldRemoveDovi = (!requestHasDOVIwithEL && requestHasHDR10) && videoStream.VideoRangeType == VideoRangeType.DOVIWithEL;
+
+ // Case 2: Client supports DOVI, does not support broken DOVI config
+ // Client does not report DOVI support should be allowed to copy bad data for remuxing as HDR10 players would not crash
+ shouldRemoveDovi = shouldRemoveDovi || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVIInvalid);
+
+ // Special case: we have a video with both EL and HDR10+
+ // If the client supports EL but not in the case of coexistence with HDR10+, remove HDR10+ for compatibility reasons.
+ // Otherwise, remove DOVI if the client is not a DOVI player
+ if (videoStream.VideoRangeType == VideoRangeType.DOVIWithELHDR10Plus)
+ {
+ shouldRemoveHdr10Plus = requestHasDOVIwithEL && !requestHasDOVIwithELHDR10plus;
+ shouldRemoveDovi = shouldRemoveDovi || !shouldRemoveHdr10Plus;
+ }
+
+ if (shouldRemoveDovi)
+ {
+ return DynamicHdrMetadataRemovalPlan.RemoveDovi;
+ }
+
+ // If the client is a Dolby Vision Player, remove the HDR10+ metadata to avoid playback issues
+ shouldRemoveHdr10Plus = shouldRemoveHdr10Plus || (requestHasDOVI && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10Plus);
+ return shouldRemoveHdr10Plus ? DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus : DynamicHdrMetadataRemovalPlan.None;
+ }
+
+ private bool CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan plan, MediaStream videoStream)
{
+ return plan switch
+ {
+ DynamicHdrMetadataRemovalPlan.RemoveDovi => _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.DoviRpuStrip)
+ || (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadataRemoveDovi))
+ || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemoveDovi)),
+ DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus => (IsH265(videoStream) && _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadataRemoveHdr10Plus))
+ || (IsAv1(videoStream) && _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemoveHdr10Plus)),
+ _ => true,
+ };
+ }
+
+ public bool IsDoviRemoved(EncodingJobInfo state)
+ {
+ return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalPlan.RemoveDovi
+ && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.RemoveDovi, state.VideoStream);
+ }
+
+ public bool IsHdr10PlusRemoved(EncodingJobInfo state)
+ {
+ return state?.VideoStream is not null && ShouldRemoveDynamicHdrMetadata(state) == DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus
+ && CanEncoderRemoveDynamicHdrMetadata(DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus, state.VideoStream);
+ }
+
+ public string GetBitStreamArgs(EncodingJobInfo state, MediaStreamType streamType)
+ {
+ if (state is null)
+ {
+ return null;
+ }
+
+ var stream = streamType switch
+ {
+ MediaStreamType.Audio => state.AudioStream,
+ MediaStreamType.Video => state.VideoStream,
+ _ => state.VideoStream
+ };
// TODO This is auto inserted into the mpegts mux so it might not be needed.
// https://www.ffmpeg.org/ffmpeg-bitstream-filters.html#h264_005fmp4toannexb
if (IsH264(stream))
@@ -1316,21 +1465,57 @@ namespace MediaBrowser.Controller.MediaEncoding
return "-bsf:v h264_mp4toannexb";
}
+ if (IsAAC(stream))
+ {
+ // Convert adts header(mpegts) to asc header(mp4).
+ return "-bsf:a aac_adtstoasc";
+ }
+
if (IsH265(stream))
{
- return "-bsf:v hevc_mp4toannexb";
+ var filter = "-bsf:v hevc_mp4toannexb";
+
+ // The following checks are not complete because the copy would be rejected
+ // if the encoder cannot remove required metadata.
+ // And if bsf is used, we must already be using copy codec.
+ switch (ShouldRemoveDynamicHdrMetadata(state))
+ {
+ default:
+ case DynamicHdrMetadataRemovalPlan.None:
+ break;
+ case DynamicHdrMetadataRemovalPlan.RemoveDovi:
+ filter += _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.HevcMetadataRemoveDovi)
+ ? ",hevc_metadata=remove_dovi=1"
+ : ",dovi_rpu=strip=1";
+ break;
+ case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
+ filter += ",hevc_metadata=remove_hdr10plus=1";
+ break;
+ }
+
+ return filter;
}
- if (IsAAC(stream))
+ if (IsAv1(stream))
{
- // Convert adts header(mpegts) to asc header(mp4).
- return "-bsf:a aac_adtstoasc";
+ switch (ShouldRemoveDynamicHdrMetadata(state))
+ {
+ default:
+ case DynamicHdrMetadataRemovalPlan.None:
+ return null;
+ case DynamicHdrMetadataRemovalPlan.RemoveDovi:
+ return _mediaEncoder.SupportsBitStreamFilterWithOption(BitStreamFilterOptionType.Av1MetadataRemoveDovi)
+ ? "-bsf:v av1_metadata=remove_dovi=1"
+ : "-bsf:v dovi_rpu=strip=1";
+ case DynamicHdrMetadataRemovalPlan.RemoveHdr10Plus:
+ return "-bsf:v av1_metadata=remove_hdr10plus=1";
+ }
}
return null;
}
- public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
+ public string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
{
var bitStreamArgs = string.Empty;
var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.');
@@ -1341,7 +1526,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(mediaSourceContainer, "aac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase)))
{
- bitStreamArgs = GetBitStreamArgs(state.AudioStream);
+ bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Audio);
bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;
}
@@ -1620,7 +1805,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
- var fontPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id);
+ var fontPath = _pathManager.GetAttachmentFolderPath(state.MediaSource.Id);
var fontParam = string.Format(
CultureInfo.InvariantCulture,
":fontsdir='{0}'",
@@ -2061,7 +2246,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)
@@ -2162,7 +2353,6 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// DOVIWithHDR10 should be compatible with HDR10 supporting players. Same goes with HLG and of course SDR. So allow copy of those formats
-
var requestHasHDR10 = requestedRangeTypes.Contains(VideoRangeType.HDR10.ToString(), StringComparison.OrdinalIgnoreCase);
var requestHasHLG = requestedRangeTypes.Contains(VideoRangeType.HLG.ToString(), StringComparison.OrdinalIgnoreCase);
var requestHasSDR = requestedRangeTypes.Contains(VideoRangeType.SDR.ToString(), StringComparison.OrdinalIgnoreCase);
@@ -2170,9 +2360,17 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreCase)
&& !((requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.DOVIWithHDR10)
|| (requestHasHLG && videoStream.VideoRangeType == VideoRangeType.DOVIWithHLG)
- || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)))
- {
- return false;
+ || (requestHasSDR && videoStream.VideoRangeType == VideoRangeType.DOVIWithSDR)
+ || (requestHasHDR10 && videoStream.VideoRangeType == VideoRangeType.HDR10Plus)))
+ {
+ // Check complicated cases where we need to remove dynamic metadata
+ // Conservatively refuse to copy if the encoder can't remove dynamic metadata,
+ // but a removal is required for compatability reasons.
+ var dynamicHdrMetadataRemovalPlan = ShouldRemoveDynamicHdrMetadata(state);
+ if (!CanEncoderRemoveDynamicHdrMetadata(dynamicHdrMetadataRemovalPlan, videoStream))
+ {
+ return false;
+ }
}
}
@@ -2197,7 +2395,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var videoFrameRate = videoStream.ReferenceFrameRate;
// Add a little tolerance to the framerate check because some videos might record a framerate
- // that is slightly higher than the intended framerate, but the device can still play it correctly.
+ // 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)
{
@@ -2684,10 +2882,10 @@ namespace MediaBrowser.Controller.MediaEncoding
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.
+ // [0, RuntimeTicks - 5.0s], so that the muxer gets packets and avoid error codes.
if (maxTime > 0)
{
- seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 5000000L, 0));
+ seekTick = Math.Clamp(seekTick, 0, Math.Max(maxTime - 50000000L, 0));
}
seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekTick));
@@ -3608,7 +3806,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);
}
@@ -3649,8 +3847,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;
@@ -3696,7 +3894,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw transpose
if (doCuTranspose)
{
- mainFilters.Add($"transpose_cuda=dir={tranposeDir}");
+ mainFilters.Add($"transpose_cuda=dir={transposeDir}");
}
var isRext = IsVideoStreamHevcRext(state);
@@ -3816,7 +4014,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);
}
@@ -3856,8 +4054,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;
@@ -3901,12 +4099,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";
@@ -4042,13 +4240,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);
@@ -4097,8 +4295,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;
@@ -4191,7 +4389,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(hwScaleFilter) && doVppTranspose)
{
- hwScaleFilter += $":transpose={tranposeDir}";
+ hwScaleFilter += $":transpose={transposeDir}";
}
if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
@@ -4384,8 +4582,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;
@@ -4445,7 +4643,7 @@ 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 = doTonemap ? (((isQsvDecoder && doVppTranspose) || isRext) ? "p010" : string.Empty) : "nv12";
@@ -4455,7 +4653,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(hwScaleFilter) && isQsvDecoder && doVppTranspose)
{
- hwScaleFilter += $":transpose={tranposeDir}";
+ hwScaleFilter += $":transpose={transposeDir}";
}
if (!string.IsNullOrEmpty(hwScaleFilter) && isMjpegEncoder)
@@ -4656,14 +4854,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
@@ -4715,8 +4913,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;
@@ -4771,7 +4969,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw transpose
if (doVaVppTranspose)
{
- mainFilters.Add($"transpose_vaapi=dir={tranposeDir}");
+ mainFilters.Add($"transpose_vaapi=dir={transposeDir}");
}
var outFormat = doTonemap ? (isRext ? "p010" : string.Empty) : "nv12";
@@ -4948,8 +5146,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;
@@ -5042,13 +5240,13 @@ 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}");
}
}
@@ -5416,8 +5614,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;
@@ -5461,7 +5659,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// hw transpose
if (doVtTranspose)
{
- mainFilters.Add($"transpose_vt=dir={tranposeDir}");
+ mainFilters.Add($"transpose_vt=dir={transposeDir}");
}
if (doVtTonemap)
@@ -5576,7 +5774,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);
@@ -5614,7 +5812,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && ShouldEncodeSubtitle(state);
+ var hasSubs = state.SubtitleStream is not null && ShouldEncodeSubtitle(state);
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -5624,8 +5822,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;
@@ -5690,13 +5888,17 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(doScaling)
&& !IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f))
{
- var hwScaleFilterFirstPass = $"scale_rkrga=w=iw/7.9:h=ih/7.9:format={outFormat}:afbc=1";
+ // 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
@@ -6170,7 +6372,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.
@@ -6610,6 +6812,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals("yuv420p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuv422p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|| string.Equals("yuv444p12le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var isAv1SupportedSwFormatsVt = is8_10bitSwFormatsVt || string.Equals("yuv420p12le", 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();
@@ -6643,6 +6846,13 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
}
+
+ if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ && isAv1SupportedSwFormatsVt
+ && _mediaEncoder.IsVideoToolboxAv1DecodeAvailable)
+ {
+ return GetHwaccelType(state, options, "av1", bitDepth, useHwSurface);
+ }
}
return null;
@@ -6971,7 +7181,7 @@ namespace MediaBrowser.Controller.MediaEncoding
state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
- if (state.ReadInputAtNativeFramerate
+ if ((state.ReadInputAtNativeFramerate && !state.IsSegmentedLiveStream)
|| (mediaSource.Protocol == MediaProtocol.File
&& string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)))
{
@@ -7064,7 +7274,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
@@ -7225,7 +7435,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
- string bitStreamArgs = GetBitStreamArgs(state.VideoStream);
+ string bitStreamArgs = GetBitStreamArgs(state, MediaStreamType.Video);
if (!string.IsNullOrEmpty(bitStreamArgs))
{
args += " " + bitStreamArgs;