From be01aeecd9d211243c6d67935c593aeb93219260 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Tue, 20 Jun 2023 03:49:26 +0800 Subject: Add AV1 hardware and software encoding Signed-off-by: nyanmisaka --- .../MediaEncoding/EncodingHelper.cs | 187 ++++++++++++++++++--- 1 file changed, 160 insertions(+), 27 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b155d674d..d5049ca6b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -46,6 +46,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0); private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0); private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3); + private readonly Version _minFFmpegSvtAv1Params = new Version(5, 1); private static readonly string[] _videoProfilesH264 = new[] { @@ -65,6 +66,13 @@ namespace MediaBrowser.Controller.MediaEncoding "Main10" }; + private static readonly string[] _videoProfilesAv1 = new[] + { + "Main", + "High", + "Professional", + }; + private static readonly HashSet _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase) { "mp4", @@ -113,12 +121,15 @@ namespace MediaBrowser.Controller.MediaEncoding } public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) - => GetH264OrH265Encoder("libx264", "h264", state, encodingOptions); + => GetH26xOrAv1Encoder("libx264", "h264", state, encodingOptions); public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) - => GetH264OrH265Encoder("libx265", "hevc", state, encodingOptions); + => GetH26xOrAv1Encoder("libx265", "hevc", state, encodingOptions); + + public string GetAv1Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) + => GetH26xOrAv1Encoder("libsvtav1", "av1", state, encodingOptions); - private string GetH264OrH265Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions) + private string GetH26xOrAv1Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions) { // Only use alternative encoders for video files. // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully @@ -266,6 +277,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { + if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase)) + { + return GetAv1Encoder(state, encodingOptions); + } + if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) { @@ -565,6 +581,11 @@ namespace MediaBrowser.Controller.MediaEncoding return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); } + if (string.Equals("av1", videoCodec, StringComparison.OrdinalIgnoreCase)) + { + return Array.FindIndex(_videoProfilesAv1, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); + } + return -1; } @@ -1204,6 +1225,11 @@ namespace MediaBrowser.Controller.MediaEncoding return FormattableString.Invariant($" -b:v {bitrate}"); } + if (string.Equals(videoCodec, "libsvtav1", StringComparison.OrdinalIgnoreCase)) + { + return FormattableString.Invariant($" -b:v {bitrate} -bufsize {bufsize}"); + } + if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) { @@ -1211,14 +1237,16 @@ namespace MediaBrowser.Controller.MediaEncoding } if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "av1_amf", StringComparison.OrdinalIgnoreCase)) { // Override the too high default qmin 18 in transcoding preset return FormattableString.Invariant($" -rc cbr -qmin 0 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); } if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) { // VBR in i965 driver may result in pixelated output. if (_mediaEncoder.IsVaapiDeviceInteli965) @@ -1236,14 +1264,23 @@ namespace MediaBrowser.Controller.MediaEncoding { if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel)) { - if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(state.ActualOutputVideoCodec, "av1", StringComparison.OrdinalIgnoreCase)) + { + // Transcode to level 5.3 (15) and lower for maximum compatibility. + // https://en.wikipedia.org/wiki/AV1#Levels + if (requestLevel < 0 || requestLevel >= 15) + { + return "15"; + } + } + else if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)) { // Transcode to level 5.0 and lower for maximum compatibility. // Level 5.0 is suitable for up to 4k 30fps hevc encoding, otherwise let the encoder to handle it. // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding_tiers_and_levels // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880. - if (requestLevel >= 150) + if (requestLevel < 0 || requestLevel >= 150) { return "150"; } @@ -1253,7 +1290,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Transcode to level 5.1 and lower for maximum compatibility. // h264 4k 30fps requires at least level 5.1 otherwise it will break on safari fmp4. // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels - if (requestLevel >= 51) + if (requestLevel < 0 || requestLevel >= 51) { return "51"; } @@ -1391,14 +1428,18 @@ namespace MediaBrowser.Controller.MediaEncoding || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + || string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "libsvtav1", StringComparison.OrdinalIgnoreCase)) { args += gopArg; } else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) { args += keyFrameArg; @@ -1534,18 +1575,60 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -crf " + defaultCrf; } } + else if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)) + { + // Default to use the recommended preset 10. + // Omit presets < 5, which are too slow for on the fly encoding. + // https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Ffmpeg.md + param += encodingOptions.EncoderPreset switch + { + "veryslow" => " -preset 5", + "slower" => " -preset 6", + "slow" => " -preset 7", + "medium" => " -preset 8", + "fast" => " -preset 9", + "faster" => " -preset 10", + "veryfast" => " -preset 11", + "superfast" => " -preset 12", + "ultrafast" => " -preset 13", + _ => " -preset 10" + }; + } + else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) + { + // -compression_level is not reliable on AMD. + if (_mediaEncoder.IsVaapiDeviceInteliHD) + { + param += encodingOptions.EncoderPreset switch + { + "veryslow" => " -compression_level 1", + "slower" => " -compression_level 2", + "slow" => " -compression_level 3", + "medium" => " -compression_level 4", + "fast" => " -compression_level 5", + "faster" => " -compression_level 6", + "veryfast" => " -compression_level 7", + "superfast" => " -compression_level 7", + "ultrafast" => " -compression_level 7", + _ => string.Empty + }; + } + } else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv) - || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_qsv) + || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) // hevc (hevc_qsv) + || string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase)) // av1 (av1_qsv) { - string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; + string[] valid_presets = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; - if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparison.OrdinalIgnoreCase)) + if (valid_presets.Contains(encodingOptions.EncoderPreset, StringComparison.OrdinalIgnoreCase)) { param += " -preset " + encodingOptions.EncoderPreset; } else { - param += " -preset 7"; + param += " -preset veryfast"; } // Only h264_qsv has look_ahead option @@ -1555,7 +1638,8 @@ namespace MediaBrowser.Controller.MediaEncoding } } else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) - || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) // hevc (hevc_nvenc) + || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)) // av1 (av1_nvenc) { switch (encodingOptions.EncoderPreset) { @@ -1595,7 +1679,8 @@ namespace MediaBrowser.Controller.MediaEncoding } } else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf) - || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_amf) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) // hevc (hevc_amf) + || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) // av1 (av1_amf) { switch (encodingOptions.EncoderPreset) { @@ -1622,9 +1707,15 @@ namespace MediaBrowser.Controller.MediaEncoding break; } + if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) + { + param += " -header_insertion_mode gop"; + } + if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { - param += " -header_insertion_mode gop -gops_per_idr 1"; + param += " -gops_per_idr 1"; } } else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // vp8 @@ -1755,6 +1846,14 @@ namespace MediaBrowser.Controller.MediaEncoding profile = "high"; } + // We only need Main profile of AV1 encoders. + if (videoEncoder.Contains("av1", StringComparison.OrdinalIgnoreCase) + && (profile.Contains("high", StringComparison.OrdinalIgnoreCase) + || profile.Contains("professional", StringComparison.OrdinalIgnoreCase))) + { + profile = "main"; + } + // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case, // which is compatible (and ugly). if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) @@ -1822,19 +1921,41 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -level " + (hevcLevel / 3); } } + else if (string.Equals(videoEncoder, "av1_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase)) + { + // libsvtav1 and av1_qsv use -level 60 instead of -level 16 + // https://aomedia.org/av1/specification/annex-a/ + if (int.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out int av1Level)) + { + var x = 2 + (av1Level >> 2); + var y = av1Level & 3; + var res = (x * 10) + y; + param += " -level " + res; + } + } else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_amf", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; } else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "av1_nvenc", StringComparison.OrdinalIgnoreCase)) { // level option may cause NVENC to fail. // NVENC cannot adjust the given level, just throw an error. + } + else if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "av1_vaapi", StringComparison.OrdinalIgnoreCase)) + { // level option may cause corrupted frames on AMD VAAPI. + if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965) + { + param += " -level " + level; + } } else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { @@ -1856,6 +1977,12 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -x265-params:0 no-info=1"; } + if (string.Equals(videoEncoder, "libsvtav1", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.EncoderVersion >= _minFFmpegSvtAv1Params) + { + param += " -svtav1-params:0 rc=1:tune=0:film-grain=0:enable-overlays=1:enable-tf=0"; + } + return param; } @@ -5810,19 +5937,25 @@ namespace MediaBrowser.Controller.MediaEncoding private void ShiftVideoCodecsIfNeeded(List videoCodecs, EncodingOptions encodingOptions) { - // Shift hevc/h265 to the end of list if hevc encoding is not allowed. - if (encodingOptions.AllowHevcEncoding) + // No need to shift if there is only one supported video codec. + if (videoCodecs.Count < 2) { return; } - // No need to shift if there is only one supported video codec. - if (videoCodecs.Count < 2) + // Shift codecs to the end of list if it's not allowed. + var shiftVideoCodecs = new List(); + if (!encodingOptions.AllowHevcEncoding) { - return; + shiftVideoCodecs.Add("hevc"); + shiftVideoCodecs.Add("h265"); + } + + if (!encodingOptions.AllowAv1Encoding) + { + shiftVideoCodecs.Add("av1"); } - var shiftVideoCodecs = new[] { "hevc", "h265" }; if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparison.OrdinalIgnoreCase))) { return; -- cgit v1.2.3 From 27d0d8a7f206c9bb92dd1fda10d7d7cfcb0162a3 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Mon, 19 Jun 2023 23:01:15 +0800 Subject: Refine SwDec and QSV encoding Signed-off-by: nyanmisaka --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index d5049ca6b..cb40579f9 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3772,7 +3772,7 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swDeintFilter); } - var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p"; + var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12"); var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); @@ -3973,7 +3973,7 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swDeintFilter); } - var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p"; + var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12"); var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); // sw scale mainFilters.Add(swScaleFilter); -- cgit v1.2.3 From 20a4509991e7ba81414312f8a860917fd29b6702 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Thu, 15 Jun 2023 13:28:01 +0200 Subject: Migrate VideoRange and VideoRangeType to Enum --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 3 +- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 13 ++- Jellyfin.Data/Enums/VideoRange.cs | 22 +++++ Jellyfin.Data/Enums/VideoRangeType.cs | 37 +++++++++ .../MediaEncoding/EncodingHelper.cs | 20 ++--- .../MediaEncoding/EncodingJobInfo.cs | 10 +-- MediaBrowser.Model/Dlna/ConditionProcessor.cs | 93 +++++++++++++++++++++- MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs | 3 +- MediaBrowser.Model/Dlna/DeviceProfile.cs | 3 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 9 ++- MediaBrowser.Model/Dlna/StreamInfo.cs | 12 +-- MediaBrowser.Model/Entities/MediaStream.cs | 21 ++--- 12 files changed, 201 insertions(+), 45 deletions(-) create mode 100644 Jellyfin.Data/Enums/VideoRange.cs create mode 100644 Jellyfin.Data/Enums/VideoRangeType.cs (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 09cf7303e..898c673ea 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -12,6 +12,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using Jellyfin.MediaEncoding.Hls.Playlist; using MediaBrowser.Common.Configuration; @@ -1809,7 +1810,7 @@ public class DynamicHlsController : BaseJellyfinApiController || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) { if (EncodingHelper.IsCopyCodec(codec) - && (string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase) + && (state.VideoStream.VideoRangeType == VideoRangeType.DOVI || string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase) || string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase) || string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase))) diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 4486954c6..ce73b5754 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; @@ -211,8 +212,7 @@ public class DynamicHlsHelper // Provide SDR HEVC entrance for backward compatibility. if (encodingOptions.AllowHevcEncoding && EncodingHelper.IsCopyCodec(state.OutputVideoCodec) - && !string.IsNullOrEmpty(state.VideoStream.VideoRange) - && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) + && state.VideoStream.VideoRange == VideoRange.HDR && string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) { var requestedVideoProfiles = state.GetRequestedProfiles("hevc"); @@ -255,8 +255,7 @@ public class DynamicHlsHelper if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.VideoStream.Level.HasValue && state.VideoStream.Level > 150 - && !string.IsNullOrEmpty(state.VideoStream.VideoRange) - && string.Equals(state.VideoStream.VideoRange, "SDR", StringComparison.OrdinalIgnoreCase) + && state.VideoStream.VideoRange == VideoRange.SDR && string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) { var playlistCodecsField = new StringBuilder(); @@ -340,17 +339,17 @@ public class DynamicHlsHelper /// StreamState of the current stream. private void AppendPlaylistVideoRangeField(StringBuilder builder, StreamState state) { - if (state.VideoStream is not null && !string.IsNullOrEmpty(state.VideoStream.VideoRange)) + if (state.VideoStream is not null && state.VideoStream.VideoRange != VideoRange.Unknown) { var videoRange = state.VideoStream.VideoRange; if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { - if (string.Equals(videoRange, "SDR", StringComparison.OrdinalIgnoreCase)) + if (videoRange == VideoRange.SDR) { builder.Append(",VIDEO-RANGE=SDR"); } - if (string.Equals(videoRange, "HDR", StringComparison.OrdinalIgnoreCase)) + if (videoRange == VideoRange.HDR) { builder.Append(",VIDEO-RANGE=PQ"); } diff --git a/Jellyfin.Data/Enums/VideoRange.cs b/Jellyfin.Data/Enums/VideoRange.cs new file mode 100644 index 000000000..5072e5ba3 --- /dev/null +++ b/Jellyfin.Data/Enums/VideoRange.cs @@ -0,0 +1,22 @@ +namespace Jellyfin.Data.Enums; + +/// +/// An enum representing video ranges. +/// +public enum VideoRange +{ + /// + /// Unknown video range. + /// + Unknown, + + /// + /// SDR video range. + /// + SDR, + + /// + /// HDR video range. + /// + HDR +} diff --git a/Jellyfin.Data/Enums/VideoRangeType.cs b/Jellyfin.Data/Enums/VideoRangeType.cs new file mode 100644 index 000000000..7ac7bc20a --- /dev/null +++ b/Jellyfin.Data/Enums/VideoRangeType.cs @@ -0,0 +1,37 @@ +namespace Jellyfin.Data.Enums; + +/// +/// An enum representing types of video ranges. +/// +public enum VideoRangeType +{ + /// + /// Unknown video range type. + /// + Unknown, + + /// + /// SDR video range type (8bit). + /// + SDR, + + /// + /// HDR10 video range type (10bit). + /// + HDR10, + + /// + /// HLG video range type (10bit). + /// + HLG, + + /// + /// Dolby Vision video range type (12bit). + /// + DOVI, + + /// + /// HDR10+ video range type (10bit to 16bit). + /// + HDR10Plus +} diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 39d53768e..027053b13 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -209,8 +209,8 @@ namespace MediaBrowser.Controller.MediaEncoding } if (string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) - && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) - && string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase)) + && state.VideoStream.VideoRange == VideoRange.HDR + && state.VideoStream.VideoRangeType == VideoRangeType.DOVI) { // Only native SW decoder and HW accelerator can parse dovi rpu. var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty; @@ -221,9 +221,9 @@ namespace MediaBrowser.Controller.MediaEncoding return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder; } - return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) - && (string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.VideoStream.VideoRangeType, "HLG", StringComparison.OrdinalIgnoreCase)); + return state.VideoStream.VideoRange == VideoRange.HDR + && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10 + || state.VideoStream.VideoRangeType == VideoRangeType.HLG); } private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) @@ -235,7 +235,7 @@ namespace MediaBrowser.Controller.MediaEncoding // libplacebo has partial Dolby Vision to SDR tonemapping support. return options.EnableTonemapping - && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) + && state.VideoStream.VideoRange == VideoRange.HDR && GetVideoColorBitDepth(state) == 10; } @@ -250,8 +250,8 @@ namespace MediaBrowser.Controller.MediaEncoding // Native VPP tonemapping may come to QSV in the future. - return string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) - && string.Equals(state.VideoStream.VideoRangeType, "HDR10", StringComparison.OrdinalIgnoreCase); + return state.VideoStream.VideoRange == VideoRange.HDR + && state.VideoStream.VideoRangeType == VideoRangeType.HDR10; } /// @@ -1945,12 +1945,12 @@ namespace MediaBrowser.Controller.MediaEncoding var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec); if (requestedRangeTypes.Length > 0) { - if (string.IsNullOrEmpty(videoStream.VideoRangeType)) + if (videoStream.VideoRangeType == VideoRangeType.Unknown) { return false; } - if (!requestedRangeTypes.Contains(videoStream.VideoRangeType, StringComparison.OrdinalIgnoreCase)) + if (!requestedRangeTypes.Contains(videoStream.VideoRangeType.ToString(), StringComparison.OrdinalIgnoreCase)) { return false; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index a6b541660..17813559a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; @@ -367,22 +368,21 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Gets the target video range type. /// - public string TargetVideoRangeType + public VideoRangeType TargetVideoRangeType { get { if (BaseRequest.Static || EncodingHelper.IsCopyCodec(OutputVideoCodec)) { - return VideoStream?.VideoRangeType; + return VideoStream?.VideoRangeType ?? VideoRangeType.Unknown; } - var requestedRangeType = GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault(); - if (!string.IsNullOrEmpty(requestedRangeType)) + if (Enum.TryParse(GetRequestedRangeTypes(ActualOutputVideoCodec).FirstOrDefault() ?? "Unknown", true, out VideoRangeType requestedRangeType)) { return requestedRangeType; } - return null; + return VideoRangeType.Unknown; } } diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs index f5e1a3c49..af0787990 100644 --- a/MediaBrowser.Model/Dlna/ConditionProcessor.cs +++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs @@ -1,14 +1,38 @@ -#pragma warning disable CS1591 - using System; using System.Globalization; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna { + /// + /// The condition processor. + /// public static class ConditionProcessor { + /// + /// Checks if a video condition is satisfied. + /// + /// The . + /// The width. + /// The height. + /// The bit depth. + /// The bitrate. + /// The video profile. + /// The . + /// The video level. + /// The framerate. + /// The packet length. + /// The . + /// A value indicating whether tthe video is anamorphic. + /// A value indicating whether tthe video is interlaced. + /// The reference frames. + /// The number of video streams. + /// The number of audio streams. + /// The video codec tag. + /// A value indicating whether the video is AVC. + /// True if the condition is satisfied. public static bool IsVideoConditionSatisfied( ProfileCondition condition, int? width, @@ -16,7 +40,7 @@ namespace MediaBrowser.Model.Dlna int? videoBitDepth, int? videoBitrate, string? videoProfile, - string? videoRangeType, + VideoRangeType? videoRangeType, double? videoLevel, float? videoFramerate, int? packetLength, @@ -70,6 +94,13 @@ namespace MediaBrowser.Model.Dlna } } + /// + /// Checks if a image condition is satisfied. + /// + /// The . + /// The width. + /// The height. + /// True if the condition is satisfied. public static bool IsImageConditionSatisfied(ProfileCondition condition, int? width, int? height) { switch (condition.Property) @@ -83,6 +114,15 @@ namespace MediaBrowser.Model.Dlna } } + /// + /// Checks if an audio condition is satisfied. + /// + /// The . + /// The channel count. + /// The bitrate. + /// The sample rate. + /// The bit depth. + /// True if the condition is satisfied. public static bool IsAudioConditionSatisfied(ProfileCondition condition, int? audioChannels, int? audioBitrate, int? audioSampleRate, int? audioBitDepth) { switch (condition.Property) @@ -100,6 +140,17 @@ namespace MediaBrowser.Model.Dlna } } + /// + /// Checks if an audio condition is satisfied for a video. + /// + /// The . + /// The channel count. + /// The bitrate. + /// The sample rate. + /// The bit depth. + /// The profile. + /// A value indicating whether the audio is a secondary track. + /// True if the condition is satisfied. public static bool IsVideoAudioConditionSatisfied( ProfileCondition condition, int? audioChannels, @@ -281,5 +332,41 @@ namespace MediaBrowser.Model.Dlna throw new InvalidOperationException("Unexpected ProfileConditionType: " + condition.Condition); } } + + private static bool IsConditionSatisfied(ProfileCondition condition, VideoRangeType? currentValue) + { + if (!currentValue.HasValue || currentValue.Equals(VideoRangeType.Unknown)) + { + // If the value is unknown, it satisfies if not marked as required + return !condition.IsRequired; + } + + var conditionType = condition.Condition; + if (conditionType == ProfileConditionType.EqualsAny) + { + foreach (var singleConditionString in condition.Value.AsSpan().Split('|')) + { + if (Enum.TryParse(singleConditionString, true, out VideoRangeType conditionValue) + && conditionValue.Equals(currentValue)) + { + return true; + } + } + + return false; + } + + if (Enum.TryParse(condition.Value, true, out VideoRangeType expected)) + { + return conditionType switch + { + ProfileConditionType.Equals => currentValue.Value == expected, + ProfileConditionType.NotEquals => currentValue.Value != expected, + _ => throw new InvalidOperationException("Unexpected ProfileConditionType: " + condition.Condition) + }; + } + + return false; + } } } diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index 1d5d0b1de..f29022b54 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Jellyfin.Data.Enums; using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Model.Dlna @@ -128,7 +129,7 @@ namespace MediaBrowser.Model.Dlna bool isDirectStream, long? runtimeTicks, string videoProfile, - string videoRangeType, + VideoRangeType videoRangeType, double? videoLevel, float? videoFramerate, int? packetLength, diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs index 79ae95170..b7c23669d 100644 --- a/MediaBrowser.Model/Dlna/DeviceProfile.cs +++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs @@ -2,6 +2,7 @@ using System; using System.ComponentModel; using System.Xml.Serialization; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Model.MediaInfo; @@ -445,7 +446,7 @@ namespace MediaBrowser.Model.Dlna int? bitDepth, int? videoBitrate, string videoProfile, - string videoRangeType, + VideoRangeType videoRangeType, double? videoLevel, float? videoFramerate, int? packetLength, diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 0a955e917..2dbd14da4 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; @@ -889,7 +890,7 @@ namespace MediaBrowser.Model.Dlna int? videoBitrate = videoStream?.BitRate; double? videoLevel = videoStream?.Level; string? videoProfile = videoStream?.Profile; - string? videoRangeType = videoStream?.VideoRangeType; + VideoRangeType? videoRangeType = videoStream?.VideoRangeType; float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isInterlaced = videoStream?.IsInterlaced; @@ -1144,7 +1145,7 @@ namespace MediaBrowser.Model.Dlna int? videoBitrate = videoStream?.BitRate; double? videoLevel = videoStream?.Level; string? videoProfile = videoStream?.Profile; - string? videoRangeType = videoStream?.VideoRangeType; + VideoRangeType? videoRangeType = videoStream?.VideoRangeType; float videoFramerate = videoStream is null ? 0 : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate ?? 0; bool? isAnamorphic = videoStream?.IsAnamorphic; bool? isInterlaced = videoStream?.IsInterlaced; @@ -1932,6 +1933,10 @@ namespace MediaBrowser.Model.Dlna { item.SetOption(qualifier, "rangetype", string.Join(',', values)); } + else if (condition.Condition == ProfileConditionType.NotEquals) + { + item.SetOption(qualifier, "rangetype", string.Join(',', Enum.GetNames(typeof(VideoRangeType)).Except(values))); + } else if (condition.Condition == ProfileConditionType.EqualsAny) { var currentValue = item.GetOption(qualifier, "rangetype"); diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index a78a28e13..00543616d 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -281,23 +282,24 @@ namespace MediaBrowser.Model.Dlna /// /// Gets the target video range type that will be in the output stream. /// - public string TargetVideoRangeType + public VideoRangeType TargetVideoRangeType { get { if (IsDirectStream) { - return TargetVideoStream?.VideoRangeType; + return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown; } var targetVideoCodecs = TargetVideoCodec; var videoCodec = targetVideoCodecs.Length == 0 ? null : targetVideoCodecs[0]; - if (!string.IsNullOrEmpty(videoCodec)) + if (!string.IsNullOrEmpty(videoCodec) + && Enum.TryParse(GetOption(videoCodec, "rangetype"), true, out VideoRangeType videoRangeType)) { - return GetOption(videoCodec, "rangetype"); + return videoRangeType; } - return TargetVideoStream?.VideoRangeType; + return TargetVideoStream?.VideoRangeType ?? VideoRangeType.Unknown; } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 47341f4e1..34642b83a 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Extensions; @@ -148,7 +149,7 @@ namespace MediaBrowser.Model.Entities /// Gets the video range. /// /// The video range. - public string VideoRange + public VideoRange VideoRange { get { @@ -162,7 +163,7 @@ namespace MediaBrowser.Model.Entities /// Gets the video range type. /// /// The video range type. - public string VideoRangeType + public VideoRangeType VideoRangeType { get { @@ -306,9 +307,9 @@ namespace MediaBrowser.Model.Entities attributes.Add(Codec.ToUpperInvariant()); } - if (!string.IsNullOrEmpty(VideoRange)) + if (VideoRange != VideoRange.Unknown) { - attributes.Add(VideoRange.ToUpperInvariant()); + attributes.Add(VideoRange.ToString()); } if (!string.IsNullOrEmpty(Title)) @@ -677,23 +678,23 @@ namespace MediaBrowser.Model.Entities return true; } - public (string VideoRange, string VideoRangeType) GetVideoColorRange() + public (VideoRange VideoRange, VideoRangeType VideoRangeType) GetVideoColorRange() { if (Type != MediaStreamType.Video) { - return (null, null); + return (VideoRange.Unknown, VideoRangeType.Unknown); } var colorTransfer = ColorTransfer; if (string.Equals(colorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)) { - return ("HDR", "HDR10"); + return (VideoRange.HDR, VideoRangeType.HDR10); } if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) { - return ("HDR", "HLG"); + return (VideoRange.HDR, VideoRangeType.HLG); } var codecTag = CodecTag; @@ -711,10 +712,10 @@ namespace MediaBrowser.Model.Entities || string.Equals(codecTag, "dvhe", StringComparison.OrdinalIgnoreCase) || string.Equals(codecTag, "dav1", StringComparison.OrdinalIgnoreCase)) { - return ("HDR", "DOVI"); + return (VideoRange.HDR, VideoRangeType.DOVI); } - return ("SDR", "SDR"); + return (VideoRange.SDR, VideoRangeType.SDR); } } } -- cgit v1.2.3