From 85965741f57e709133fbaf5ba59ed45bbd3b5d26 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 8 Nov 2020 01:39:32 +0800 Subject: add initial support for HEVC over FMP4-HLS --- .../MediaEncoding/EncodingHelper.cs | 187 +++++++++++++-------- 1 file changed, 120 insertions(+), 67 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5846a603a..87670e2eb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; @@ -23,7 +24,7 @@ namespace MediaBrowser.Controller.MediaEncoding { public class EncodingHelper { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; @@ -654,16 +655,26 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - public string NormalizeTranscodingLevel(string videoCodec, string level) + public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level) { - // Clients may direct play higher than level 41, but there's no reason to transcode higher - if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel) - && requestLevel > 41 - && (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase))) + if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel)) { - return "41"; + if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)) + { + if (requestLevel >= 150) + { + return "150"; + } + } + else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase)) + { + // Clients may direct play higher than level 41, but there's no reason to transcode higher + if (requestLevel >= 41) + { + return "41"; + } + } } return level; @@ -809,7 +820,8 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -crf " + defaultCrf; } } - else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) // h264 (h264_qsv) + else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) // h264 (h264_qsv) + || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_qsv) { string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; @@ -825,8 +837,9 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -look_ahead 0"; } else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) - || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc) { + // following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead switch (encodingOptions.EncoderPreset) { case "veryslow": @@ -856,8 +869,8 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) // h264 (h264_amf) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_amf) { switch (encodingOptions.EncoderPreset) { @@ -896,6 +909,11 @@ namespace MediaBrowser.Controller.MediaEncoding // Enhance workload when tone mapping with AMF on some APUs param += " -preanalysis true"; } + + if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + { + param += " -header_insertion_mode gop -gops_per_idr 1"; + } } else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm { @@ -945,10 +963,24 @@ namespace MediaBrowser.Controller.MediaEncoding } var targetVideoCodec = state.ActualOutputVideoCodec; + if (string.Equals(targetVideoCodec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(targetVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) + { + targetVideoCodec = "hevc"; + } var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault(); + profile = Regex.Replace(profile, @"\s+", String.Empty); + + // only libx264 support encoding H264 High 10 Profile, otherwise force High Profile + if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) + && profile != null + && profile.IndexOf("high 10", StringComparison.OrdinalIgnoreCase) != -1) + { + profile = "high"; + } - // vaapi does not support Baseline profile, force Constrained Baseline in this case, + // 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) && profile != null @@ -957,6 +989,24 @@ namespace MediaBrowser.Controller.MediaEncoding profile = "constrained_baseline"; } + // libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case + if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + && profile != null + && profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1) + { + profile = "baseline"; + } + + // Currently hevc_amf only support encoding HEVC Main Profile, otherwise force Main Profile + if (!string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) + && profile != null + && profile.IndexOf("main 10", StringComparison.OrdinalIgnoreCase) != -1) + { + profile = "main"; + } + if (!string.IsNullOrEmpty(profile)) { if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) @@ -971,55 +1021,35 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(level)) { - level = NormalizeTranscodingLevel(state.OutputVideoCodec, level); + level = NormalizeTranscodingLevel(state, level); - // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format - // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 + // libx264, QSV, AMF, VAAPI can adjust the given level to match the output if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { - switch (level) + param += " -level " + level; + } + else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) + { + // hevc_qsv use -level 51 instead of -level 153 + if (double.TryParse(level, NumberStyles.Any, _usCulture, out double hevcLevel)) { - case "30": - param += " -level 3.0"; - break; - case "31": - param += " -level 3.1"; - break; - case "32": - param += " -level 3.2"; - break; - case "40": - param += " -level 4.0"; - break; - case "41": - param += " -level 4.1"; - break; - case "42": - param += " -level 4.2"; - break; - case "50": - param += " -level 5.0"; - break; - case "51": - param += " -level 5.1"; - break; - case "52": - param += " -level 5.2"; - break; - default: - param += " -level " + level; - break; + param += " -level " + hevcLevel / 3; } } + else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + { + param += " -level " + level; + } else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) { - // nvenc doesn't decode with param -level set ?! - // TODO: + // level option may cause NVENC to fail. + // NVENC cannot adjust the given level, just throw an error. } - else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) + else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) + || !string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; } @@ -1032,7 +1062,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { - // todo + // 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 + // TODO: set fine tuned params + param += " -x265-params:0 no-info=1"; } if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) @@ -1040,13 +1074,19 @@ namespace MediaBrowser.Controller.MediaEncoding && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) + && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt yuv420p " + param; } if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { var videoStream = state.VideoStream; var isColorDepth10 = IsColorDepth10(state); @@ -1708,7 +1748,8 @@ namespace MediaBrowser.Controller.MediaEncoding } // For QSV, feed it into hardware encoder now - if (isLinux && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))) { videoSizeParam += ",hwupload=extra_hw_frames=64"; } @@ -1729,7 +1770,8 @@ namespace MediaBrowser.Controller.MediaEncoding : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""; // When the input may or may not be hardware VAAPI decodable - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(outputVideoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) { /* [base]: HW scaling video to OutputSize @@ -1741,7 +1783,8 @@ namespace MediaBrowser.Controller.MediaEncoding // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first else if (_mediaEncoder.SupportsHwaccel("vaapi") && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 - && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) + && (string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase) + || string.Equals(outputVideoCodec, "libx265", StringComparison.OrdinalIgnoreCase))) { /* [base]: SW scaling video to OutputSize @@ -1750,7 +1793,8 @@ namespace MediaBrowser.Controller.MediaEncoding */ retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; } - else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) { /* QSV in FFMpeg can now setup hardware overlay for transcodes. @@ -1776,7 +1820,7 @@ namespace MediaBrowser.Controller.MediaEncoding videoSizeParam); } - private (int? width, int? height) GetFixedOutputSize( + public static (int? width, int? height) GetFixedOutputSize( int? videoWidth, int? videoHeight, int? requestedWidth, @@ -1836,7 +1880,9 @@ namespace MediaBrowser.Controller.MediaEncoding requestedMaxHeight); if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) && width.HasValue && height.HasValue) { @@ -1845,7 +1891,8 @@ namespace MediaBrowser.Controller.MediaEncoding // output dimensions. Output dimensions are guaranteed to be even. var outputWidth = width.Value; var outputHeight = height.Value; - var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase); + var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase); var isDeintEnabled = state.DeInterlace("h264", true) || state.DeInterlace("avc", true) || state.DeInterlace("h265", true) @@ -2107,10 +2154,13 @@ namespace MediaBrowser.Controller.MediaEncoding var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; + var isQsvHevcEncoder = outputVideoCodec.IndexOf("hevc_qsv", StringComparison.OrdinalIgnoreCase) != -1; var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; + var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1; var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isColorDepth10 = IsColorDepth10(state); @@ -2185,6 +2235,7 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwdownload"); if (isLibX264Encoder + || isLibX265Encoder || hasGraphicalSubs || (isNvdecHevcDecoder && isDeinterlaceHevc) || (!isNvdecHevcDecoder && isDeinterlaceH264 || isDeinterlaceHevc)) @@ -2195,20 +2246,20 @@ namespace MediaBrowser.Controller.MediaEncoding } // When the input may or may not be hardware VAAPI decodable - if (isVaapiH264Encoder) + if (isVaapiH264Encoder || isVaapiHevcEncoder) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); } // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context - else if (isLinux && hasGraphicalSubs && isQsvH264Encoder) + else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder)) { filters.Add("hwupload=extra_hw_frames=64"); } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first - else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder) + else if (IsVaapiSupported(state) && isVaapiDecoder && (isLibX264Encoder || isLibX265Encoder)) { var codec = videoStream.Codec.ToLowerInvariant(); @@ -2250,7 +2301,9 @@ namespace MediaBrowser.Controller.MediaEncoding // Add software deinterlace filter before scaling filter if ((isDeinterlaceH264 || isDeinterlaceHevc) && !isVaapiH264Encoder + && !isVaapiHevcEncoder && !isQsvH264Encoder + && !isQsvHevcEncoder && !isNvdecH264Decoder) { if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase)) @@ -2289,7 +2342,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642) - if (isVaapiH264Encoder) + if (isVaapiH264Encoder || isVaapiHevcEncoder) { if (hasTextSubs) { -- cgit v1.2.3 From 5048719a64ce914f859e058c1a7b03258255836e Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 8 Nov 2020 09:01:58 +0000 Subject: minor changes per suggestions --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 6 +- Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs | 99 ++++++++++++++-------- .../MediaEncoding/EncodingHelper.cs | 8 +- 3 files changed, 72 insertions(+), 41 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 9941c9f6e..6b1618421 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1412,7 +1412,7 @@ namespace Jellyfin.Api.Controllers return string.Format( CultureInfo.InvariantCulture, - "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -individual_header_trailer 0 -hls_segment_type {8} -start_number {9} -hls_segment_filename \"{10}\" -hls_playlist_type vod -hls_list_size 0 -y \"{11}\"", + "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts 0 -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -individual_header_trailer 0 -hls_segment_type {8} -start_number {9} -hls_segment_filename \"{10}\" -hls_playlist_type vod -hls_list_size 0 -y \"{11}\"", inputModifier, _encodingHelper.GetInputArgument(state, encodingOptions), threads, @@ -1576,7 +1576,9 @@ namespace Jellyfin.Api.Controllers args += " " + gopArg; } else if (string.Equals(codec, "libx264", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)) + || string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) { args += " " + keyFrameArg; } diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs index 3d7a9d9a0..4999fcc62 100644 --- a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs @@ -9,13 +9,38 @@ namespace Jellyfin.Api.Helpers /// public static class HlsCodecStringHelpers { + /// + /// Codec name for MP3. + /// + public const string MP3 = "mp4a.40.34"; + + /// + /// Codec name for AC-3. + /// + public const string AC3 = "mp4a.a5"; + + /// + /// Codec name for E-AC-3. + /// + public const string EAC3 = "mp4a.a6"; + + /// + /// Codec name for FLAC. + /// + public const string FLAC = "fLaC"; + + /// + /// Codec name for ALAC. + /// + public const string ALAC = "alac"; + /// /// Gets a MP3 codec string. /// /// MP3 codec string. public static string GetMP3String() { - return "mp4a.40.34"; + return MP3; } /// @@ -40,6 +65,42 @@ namespace Jellyfin.Api.Helpers return result.ToString(); } + /// + /// Gets an AC-3 codec string. + /// + /// AC-3 codec string. + public static string GetAC3String() + { + return AC3; + } + + /// + /// Gets an E-AC-3 codec string. + /// + /// E-AC-3 codec string. + public static string GetEAC3String() + { + return EAC3; + } + + /// + /// Gets an FLAC codec string. + /// + /// FLAC codec string. + public static string GetFLACString() + { + return FLAC; + } + + /// + /// Gets an ALAC codec string. + /// + /// ALAC codec string. + public static string GetALACString() + { + return ALAC; + } + /// /// Gets a H.264 codec string. /// @@ -104,41 +165,5 @@ namespace Jellyfin.Api.Helpers return result.ToString(); } - - /// - /// Gets an AC-3 codec string. - /// - /// AC-3 codec string. - public static string GetAC3String() - { - return "mp4a.a5"; - } - - /// - /// Gets an E-AC-3 codec string. - /// - /// E-AC-3 codec string. - public static string GetEAC3String() - { - return "mp4a.a6"; - } - - /// - /// Gets an FLAC codec string. - /// - /// FLAC codec string. - public static string GetFLACString() - { - return "fLaC"; - } - - /// - /// Gets an ALAC codec string. - /// - /// ALAC codec string. - public static string GetALACString() - { - return "alac"; - } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 87670e2eb..6e348f1e6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -662,6 +662,10 @@ namespace MediaBrowser.Controller.MediaEncoding 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) { return "150"; @@ -3293,7 +3297,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) { - args += " -copyts -avoid_negative_ts disabled -start_at_zero"; + args += " -copyts -avoid_negative_ts 0 -start_at_zero"; } if (!state.RunTimeTicks.HasValue) @@ -3341,7 +3345,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += " -copyts"; } - args += " -avoid_negative_ts disabled"; + args += " -avoid_negative_ts 0"; if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) { -- cgit v1.2.3 From 0b01acbe9172872f2685b115c219efd98619b4bc Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Wed, 11 Nov 2020 02:03:53 +0000 Subject: Apply suggestions from code review Co-authored-by: BaronGreenback --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 +-- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 17 ++++++------ .../MediaEncoding/EncodingHelper.cs | 30 +++++++++++----------- 3 files changed, 26 insertions(+), 25 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 6b1618421..4cf4c24d7 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1565,7 +1565,7 @@ namespace Jellyfin.Api.Controllers args += " " + _encodingHelper.GetVideoQualityParam(state, codec, encodingOptions, "veryfast"); - // Unable to force key frames using these encoders, set key frames by GOP + // Unable to force key frames using these encoders, set key frames by GOP. if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase) @@ -1587,7 +1587,7 @@ namespace Jellyfin.Api.Controllers args += " " + keyFrameArg + gopArg; } - // Currenly b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now + // Currenly b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now. if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)) { args += " -bf 0"; diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 4999a582e..d6fa6e98d 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -206,7 +206,7 @@ namespace Jellyfin.Api.Helpers if (state.VideoStream != null && state.VideoRequest != null) { - // Provide SDR HEVC entrance for backward compatibility + // Provide SDR HEVC entrance for backward compatibility. if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && !string.IsNullOrEmpty(state.VideoStream.VideoRange) && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) @@ -215,7 +215,7 @@ namespace Jellyfin.Api.Helpers var requestedVideoProfiles = state.GetRequestedProfiles("hevc"); if (requestedVideoProfiles != null && requestedVideoProfiles.Length > 0) { - // Force HEVC Main Profile and disable video stream copy + // Force HEVC Main Profile and disable video stream copy. state.OutputVideoCodec = "hevc"; var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(",", requestedVideoProfiles), "main"); sdrVideoUrl += "&AllowVideoStreamCopy=false"; @@ -232,7 +232,7 @@ namespace Jellyfin.Api.Helpers } } - // Provide Level 5.0 entrance for backward compatibility + // Provide Level 5.0 entrance for backward compatibility. // e.g. Apple A10 chips refuse the master playlist containing SDR HEVC Main Level 5.1 video, // but in fact it is capable of playing videos up to Level 6.1. if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) @@ -245,13 +245,13 @@ namespace Jellyfin.Api.Helpers var playlistCodecsField = new StringBuilder(); AppendPlaylistCodecsField(playlistCodecsField, state); - // Force the video level to 5.0 + // Force the video level to 5.0. var originalLevel = state.VideoStream.Level; state.VideoStream.Level = 150; var newPlaylistCodecsField = new StringBuilder(); AppendPlaylistCodecsField(newPlaylistCodecsField, state); - // Restore the video level + // Restore the video level. state.VideoStream.Level = originalLevel; var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField); builder.Append(newPlaylist); @@ -333,7 +333,7 @@ namespace Jellyfin.Api.Helpers } else { - // Currently we only encode to SDR + // Currently we only encode to SDR. builder.Append(",VIDEO-RANGE=SDR"); } } @@ -693,9 +693,10 @@ namespace Jellyfin.Api.Helpers private string ReplaceProfile(string url, string codec, string oldValue, string newValue) { + string profileStr = codec + "-profile="; return url.Replace( - codec + "-profile=" + oldValue, - codec + "-profile=" + newValue, + profileStr + oldValue, + profileStr + newValue, StringComparison.OrdinalIgnoreCase); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 6e348f1e6..6255e1b61 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -662,10 +662,10 @@ namespace MediaBrowser.Controller.MediaEncoding 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 + // 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, + // MaxLumaSampleRate = 3840*2160*30 = 248832000 < 267386880. if (requestLevel >= 150) { return "150"; @@ -673,7 +673,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase)) { - // Clients may direct play higher than level 41, but there's no reason to transcode higher + // Clients may direct play higher than level 41, but there's no reason to transcode higher. if (requestLevel >= 41) { return "41"; @@ -843,7 +843,7 @@ 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) { - // following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead + // following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead. switch (encodingOptions.EncoderPreset) { case "veryslow": @@ -976,7 +976,7 @@ namespace MediaBrowser.Controller.MediaEncoding var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault(); profile = Regex.Replace(profile, @"\s+", String.Empty); - // only libx264 support encoding H264 High 10 Profile, otherwise force High Profile + // Only libx264 support encoding H264 High 10 Profile, otherwise force High Profile. if (!string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) && profile != null && profile.IndexOf("high 10", StringComparison.OrdinalIgnoreCase) != -1) @@ -985,7 +985,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // h264_vaapi does not support Baseline profile, force Constrained Baseline in this case, - // which is compatible (and ugly) + // which is compatible (and ugly). if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && profile != null && profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1) @@ -993,7 +993,7 @@ namespace MediaBrowser.Controller.MediaEncoding profile = "constrained_baseline"; } - // libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case + // libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case. if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) @@ -1003,7 +1003,7 @@ namespace MediaBrowser.Controller.MediaEncoding profile = "baseline"; } - // Currently hevc_amf only support encoding HEVC Main Profile, otherwise force Main Profile + // Currently hevc_amf only support encoding HEVC Main Profile, otherwise force Main Profile. if (!string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase) && profile != null && profile.IndexOf("main 10", StringComparison.OrdinalIgnoreCase) != -1) @@ -1027,7 +1027,7 @@ namespace MediaBrowser.Controller.MediaEncoding { level = NormalizeTranscodingLevel(state, level); - // libx264, QSV, AMF, VAAPI can adjust the given level to match the output + // libx264, QSV, AMF, VAAPI can adjust the given level to match the output. if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) { @@ -1035,7 +1035,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) { - // hevc_qsv use -level 51 instead of -level 153 + // hevc_qsv use -level 51 instead of -level 153. if (double.TryParse(level, NumberStyles.Any, _usCulture, out double hevcLevel)) { param += " -level " + hevcLevel / 3; @@ -1066,10 +1066,10 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { - // 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 - // TODO: set fine tuned params + // 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. + // TODO: set fine tuned params. param += " -x265-params:0 no-info=1"; } -- cgit v1.2.3 From 57e5b59b93272bbbafeb1b57bdacc862c48f0996 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Wed, 11 Nov 2020 17:08:50 +0800 Subject: adjust bitrate limit for HLS audio codecs --- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 2 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 49 ++++++--- .../MediaEncoding/EncodingHelper.cs | 50 +++++---- .../Probing/ProbeResultNormalizer.cs | 112 ++++++++++++++++++++- MediaBrowser.Model/Dlna/ResolutionNormalizer.cs | 8 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 72 +++++++++++-- MediaBrowser.Model/Dlna/StreamInfo.cs | 2 +- 7 files changed, 246 insertions(+), 49 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index d6fa6e98d..d2710bf40 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -222,7 +222,7 @@ namespace Jellyfin.Api.Helpers EncodingHelper encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration); var sdrOutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec) ?? 0; - var sdrOutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.VideoRequest.AudioBitRate, state.AudioStream) ?? 0; + var sdrOutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0; var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index f4ec29bde..5bd347846 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -183,7 +183,7 @@ namespace Jellyfin.Api.Helpers state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); - state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, state.AudioStream); + state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream); state.OutputAudioCodec = streamingRequest.AudioCodec; @@ -196,20 +196,41 @@ namespace Jellyfin.Api.Helpers encodingHelper.TryStreamCopy(state); - if (state.OutputVideoBitrate.HasValue && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) + if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue) { - var resolution = ResolutionNormalizer.Normalize( - state.VideoStream?.BitRate, - state.VideoStream?.Width, - state.VideoStream?.Height, - state.OutputVideoBitrate.Value, - state.VideoStream?.Codec, - state.OutputVideoCodec, - state.VideoRequest.MaxWidth, - state.VideoRequest.MaxHeight); - - state.VideoRequest.MaxWidth = resolution.MaxWidth; - state.VideoRequest.MaxHeight = resolution.MaxHeight; + var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue + && !state.VideoRequest.Height.HasValue + && !state.VideoRequest.MaxWidth.HasValue + && !state.VideoRequest.MaxHeight.HasValue; + + if (isVideoResolutionNotRequested + && state.VideoRequest.VideoBitRate.HasValue + && state.VideoStream.BitRate.HasValue + && state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value) + { + // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested, + // and the requested video bitrate is higher than source video bitrate. + if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue) + { + state.VideoRequest.MaxWidth = state.VideoStream?.Width; + state.VideoRequest.MaxHeight = state.VideoStream?.Height; + } + } + else + { + var resolution = ResolutionNormalizer.Normalize( + state.VideoStream?.BitRate, + state.VideoStream?.Width, + state.VideoStream?.Height, + state.OutputVideoBitrate.Value, + state.VideoStream?.Codec, + state.OutputVideoCodec, + state.VideoRequest.MaxWidth, + state.VideoRequest.MaxHeight); + + state.VideoRequest.MaxWidth = resolution.MaxWidth; + state.VideoRequest.MaxHeight = resolution.MaxHeight; + } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 6255e1b61..e8b4869ee 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1390,7 +1390,7 @@ namespace MediaBrowser.Controller.MediaEncoding || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)) { - return .5; + return .6; } return 1; @@ -1424,36 +1424,48 @@ namespace MediaBrowser.Controller.MediaEncoding public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream) { - if (audioStream == null) - { - return null; - } - - if (request.AudioBitRate.HasValue) - { - // Don't encode any higher than this - return Math.Min(384000, request.AudioBitRate.Value); - } - - // Empty bitrate area is not allow on iOS - // Default audio bitrate to 128K if it is not being requested - // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options - return 128000; + return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream); } - public int? GetAudioBitrateParam(int? audioBitRate, MediaStream audioStream) + public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream) { if (audioStream == null) { return null; } - if (audioBitRate.HasValue) + if (audioBitRate.HasValue && string.IsNullOrEmpty(audioCodec)) { - // Don't encode any higher than this return Math.Min(384000, audioBitRate.Value); } + if (audioBitRate.HasValue && !string.IsNullOrEmpty(audioCodec)) + { + if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase)) + { + if ((audioStream.Channels ?? 0) >= 6) + { + return Math.Min(640000, audioBitRate.Value); + } + + return Math.Min(384000, audioBitRate.Value); + } + + if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase)) + { + if ((audioStream.Channels ?? 0) >= 6) + { + return Math.Min(3584000, audioBitRate.Value); + } + + return Math.Min(1536000, audioBitRate.Value); + } + } + // Empty bitrate area is not allow on iOS // Default audio bitrate to 128K if it is not being requested // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index cdeefbbbd..1b0d4d1af 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -234,8 +234,8 @@ namespace MediaBrowser.MediaEncoding.Probing var channelsValue = channels.Value; - if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase) || - string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) { if (channelsValue <= 2) { @@ -248,6 +248,34 @@ namespace MediaBrowser.MediaEncoding.Probing } } + if (string.Equals(codec, "ac3", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase)) + { + if (channelsValue <= 2) + { + return 192000; + } + + if (channelsValue >= 5) + { + return 640000; + } + } + + if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase)) + { + if (channelsValue <= 2) + { + return 960000; + } + + if (channelsValue >= 5) + { + return 2880000; + } + } + return null; } @@ -774,6 +802,35 @@ namespace MediaBrowser.MediaEncoding.Probing stream.BitRate = bitrate; } + // Extract bitrate info from tag "BPS" if possible. + if (!stream.BitRate.HasValue + && (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase) + || string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase))) + { + var bps = GetBPSFromTags(streamInfo); + if (bps != null && bps > 0) + { + stream.BitRate = bps; + } + } + + // Get average bitrate info from tag "NUMBER_OF_BYTES" and "DURATION" if possible. + if (!stream.BitRate.HasValue + && (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase) + || string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase))) + { + var durationInSeconds = GetRuntimeSecondsFromTags(streamInfo); + var bytes = GetNumberOfBytesFromTags(streamInfo); + if (durationInSeconds != null && bytes != null) + { + var bps = Convert.ToInt32(bytes * 8 / durationInSeconds); + if (bps > 0) + { + stream.BitRate = bps; + } + } + } + var disposition = streamInfo.Disposition; if (disposition != null) { @@ -963,6 +1020,57 @@ namespace MediaBrowser.MediaEncoding.Probing } } + private int? GetBPSFromTags(MediaStreamInfo streamInfo) + { + if (streamInfo != null && streamInfo.Tags != null) + { + var bps = GetDictionaryValue(streamInfo.Tags, "BPS-eng") ?? GetDictionaryValue(streamInfo.Tags, "BPS"); + if (!string.IsNullOrEmpty(bps)) + { + if (int.TryParse(bps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBps)) + { + return parsedBps; + } + } + } + + return null; + } + + private double? GetRuntimeSecondsFromTags(MediaStreamInfo streamInfo) + { + if (streamInfo != null && streamInfo.Tags != null) + { + var duration = GetDictionaryValue(streamInfo.Tags, "DURATION-eng") ?? GetDictionaryValue(streamInfo.Tags, "DURATION"); + if (!string.IsNullOrEmpty(duration)) + { + if (TimeSpan.TryParse(duration, out var parsedDuration)) + { + return parsedDuration.TotalSeconds; + } + } + } + + return null; + } + + private long? GetNumberOfBytesFromTags(MediaStreamInfo streamInfo) + { + if (streamInfo != null && streamInfo.Tags != null) + { + var numberOfBytes = GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES-eng") ?? GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES"); + if (!string.IsNullOrEmpty(numberOfBytes)) + { + if (long.TryParse(numberOfBytes, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBytes)) + { + return parsedBytes; + } + } + } + + return null; + } + private void SetSize(InternalMediaInfoResult data, MediaInfo info) { if (data.Format != null) diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index 102db3b44..39439f1fa 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -79,11 +79,11 @@ namespace MediaBrowser.Model.Dlna private static double GetVideoBitrateScaleFactor(string codec) { - if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || - string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) || - string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)) { - return .5; + return .6; } return 1; diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 4959a9b92..2e99fe5bf 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -872,11 +872,34 @@ namespace MediaBrowser.Model.Dlna return playlistItem; } - private static int GetDefaultAudioBitrateIfUnknown(MediaStream audioStream) + private static int GetDefaultAudioBitrate(string audioCodec, int? audioChannels) { - if ((audioStream.Channels ?? 0) >= 6) + if (!string.IsNullOrEmpty(audioCodec)) { - return 384000; + // Default to a higher bitrate for stream copy + if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase)) + { + if ((audioChannels ?? 0) < 2) + { + return 128000; + } + + return (audioChannels ?? 0) >= 6 ? 640000 : 384000; + } + + if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase)) + { + if ((audioChannels ?? 0) < 2) + { + return 768000; + } + + return (audioChannels ?? 0) >= 6 ? 3584000 : 1536000; + } } return 192000; @@ -897,14 +920,27 @@ namespace MediaBrowser.Model.Dlna } else { - if (targetAudioChannels.HasValue && audioStream.Channels.HasValue && targetAudioChannels.Value < audioStream.Channels.Value) + if (targetAudioChannels.HasValue + && audioStream.Channels.HasValue + && audioStream.Channels.Value > targetAudioChannels.Value) { - // Reduce the bitrate if we're downmixing - defaultBitrate = targetAudioChannels.Value < 2 ? 128000 : 192000; + // Reduce the bitrate if we're downmixing. + defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, targetAudioChannels); + } + else if (targetAudioChannels.HasValue + && audioStream.Channels.HasValue + && audioStream.Channels.Value <= targetAudioChannels.Value + && !string.IsNullOrEmpty(audioStream.Codec) + && targetAudioCodecs != null + && targetAudioCodecs.Length > 0 + && !Array.Exists(targetAudioCodecs, elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase))) + { + // Shift the bitrate if we're transcoding to a different audio codec. + defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, audioStream.Channels.Value); } else { - defaultBitrate = audioStream.BitRate ?? GetDefaultAudioBitrateIfUnknown(audioStream); + defaultBitrate = audioStream.BitRate ?? GetDefaultAudioBitrate(targetAudioCodec, targetAudioChannels); } // Seeing webm encoding failures when source has 1 audio channel and 22k bitrate. @@ -938,8 +974,28 @@ namespace MediaBrowser.Model.Dlna { return 448000; } + else if (totalBitrate <= 4000000) + { + return 640000; + } + else if (totalBitrate <= 5000000) + { + return 768000; + } + else if (totalBitrate <= 10000000) + { + return 1536000; + } + else if (totalBitrate <= 15000000) + { + return 2304000; + } + else if (totalBitrate <= 20000000) + { + return 3584000; + } - return 640000; + return 7168000; } private (PlayMethod?, List) GetVideoDirectPlayProfile( diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 9399d21f1..7b72a1303 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -787,7 +787,7 @@ namespace MediaBrowser.Model.Dlna public int? GetTargetAudioChannels(string codec) { - var defaultValue = GlobalMaxAudioChannels; + var defaultValue = GlobalMaxAudioChannels ?? TranscodingMaxAudioChannels; var value = GetOption(codec, "audiochannels"); if (string.IsNullOrEmpty(value)) -- cgit v1.2.3 From 11c74cb65cd936f0fece57357beb7a27741edc24 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Wed, 11 Nov 2020 19:04:58 +0800 Subject: fix for no audio stream video --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 5 +++++ MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 5 +++++ MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs | 10 ++++++++++ 3 files changed, 20 insertions(+) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 4cf4c24d7..0581928a0 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1429,6 +1429,11 @@ namespace Jellyfin.Api.Controllers private string GetAudioArguments(StreamState state, EncodingOptions encodingOptions) { + if (state.AudioStream == null) + { + return string.Empty; + } + var audioCodec = _encodingHelper.GetAudioEncoder(state); if (!state.IsOutputVideo) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e8b4869ee..caf3ef169 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1518,6 +1518,11 @@ namespace MediaBrowser.Controller.MediaEncoding /// System.Nullable{System.Int32}. public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec) { + if (audioStream == null) + { + return null; + } + var request = state.BaseRequest; var inputChannels = audioStream?.Channels; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 6e9362cd1..09e7889b9 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -593,6 +593,11 @@ namespace MediaBrowser.Controller.MediaEncoding { get { + if (VideoStream == null) + { + return null; + } + if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) { return VideoStream?.Codec; @@ -606,6 +611,11 @@ namespace MediaBrowser.Controller.MediaEncoding { get { + if (AudioStream == null) + { + return null; + } + if (EncodingHelper.IsCopyCodec(OutputAudioCodec)) { return AudioStream?.Codec; -- cgit v1.2.3 From 5bd0c2b69d0f4fccd09866a67c741742710372dc Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 12 Nov 2020 11:02:56 +0800 Subject: add an option to disable hevc encoding --- Jellyfin.Api/Helpers/StreamingHelpers.cs | 4 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 3 +- .../MediaEncoding/EncodingHelper.cs | 43 +++++++++++++++++++++- .../Configuration/EncodingOptions.cs | 3 ++ 4 files changed, 50 insertions(+), 3 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 5bd347846..be85d5eb8 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -165,7 +165,9 @@ namespace Jellyfin.Api.Helpers state.DirectStreamProvider = liveStreamInfo.Item2; } - encodingHelper.AttachMediaSourceInfo(state, mediaSource, url); + var encodingOptions = serverConfigurationManager.GetEncodingOptions(); + + encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url); string? containerInternal = Path.GetExtension(state.RequestedUrl); diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 0db1fabff..f93c95fb5 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -770,8 +770,9 @@ namespace Jellyfin.Api.Helpers new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken }, cancellationTokenSource.Token) .ConfigureAwait(false); + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); - _encodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl); + _encodingHelper.AttachMediaSourceInfo(state, encodingOptions, liveStreamResponse.MediaSource, state.RequestedUrl); if (state.VideoRequest != null) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index caf3ef169..1074f876c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2631,6 +2631,7 @@ namespace MediaBrowser.Controller.MediaEncoding public void AttachMediaSourceInfo( EncodingJobInfo state, + EncodingOptions encodingOptions, MediaSourceInfo mediaSource, string requestedUrl) { @@ -2761,11 +2762,23 @@ namespace MediaBrowser.Controller.MediaEncoding request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToAudioCodec(i)) ?? state.SupportedAudioCodecs.FirstOrDefault(); } + + var supportedVideoCodecs = state.SupportedVideoCodecs; + if (request != null && supportedVideoCodecs != null && supportedVideoCodecs.Length > 0) + { + var supportedVideoCodecsList = supportedVideoCodecs.ToList(); + + ShiftVideoCodecsIfNeeded(supportedVideoCodecsList, encodingOptions); + + state.SupportedVideoCodecs = supportedVideoCodecsList.ToArray(); + + request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); + } } private void ShiftAudioCodecsIfNeeded(List audioCodecs, MediaStream audioStream) { - // Nothing to do here + // No need to shift if there is only one supported audio codec. if (audioCodecs.Count < 2) { return; @@ -2793,6 +2806,34 @@ 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) + { + return; + } + + // No need to shift if there is only one supported video codec. + if (videoCodecs.Count < 2) + { + return; + } + + var shiftVideoCodecs = new[] { "hevc", "h265" }; + if (videoCodecs.All(i => shiftVideoCodecs.Contains(i, StringComparer.OrdinalIgnoreCase))) + { + return; + } + + while (shiftVideoCodecs.Contains(videoCodecs[0], StringComparer.OrdinalIgnoreCase)) + { + var removed = shiftVideoCodecs[0]; + videoCodecs.RemoveAt(0); + videoCodecs.Add(removed); + } + } + private void NormalizeSubtitleEmbed(EncodingJobInfo state) { if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed) diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 2cd637c5b..2d4688972 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -63,6 +63,8 @@ namespace MediaBrowser.Model.Configuration public bool EnableHardwareEncoding { get; set; } + public bool AllowHevcEncoding { get; set; } + public bool EnableSubtitleExtraction { get; set; } public string[] HardwareDecodingCodecs { get; set; } @@ -94,6 +96,7 @@ namespace MediaBrowser.Model.Configuration EnableDecodingColorDepth10Hevc = true; EnableDecodingColorDepth10Vp9 = true; EnableHardwareEncoding = true; + AllowHevcEncoding = true; EnableSubtitleExtraction = true; HardwareDecodingCodecs = new string[] { "h264", "vc1" }; } -- cgit v1.2.3 From d91a099c9e04cb39d1b8e387dba0b44dde64342d Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 12 Nov 2020 23:10:57 +0800 Subject: allow transcoding 8ch(7.1 layout) in aac --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 1074f876c..1ae896df0 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1545,6 +1545,11 @@ namespace MediaBrowser.Controller.MediaEncoding // libmp3lame currently only supports two channel output transcoderChannelLimit = 2; } + else if (codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1) + { + // aac is able to handle 8ch(7.1 layout) + transcoderChannelLimit = 8; + } else { // If we don't have any media info then limit it to 6 to prevent encoding errors due to asking for too many channels -- cgit v1.2.3 From 536b0548730466d7b1e51178146c004af8801ae8 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sat, 14 Nov 2020 04:06:24 +0800 Subject: add experimental flag for flac --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 1ae896df0..0bc5308b6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // 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 - // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. + // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such as this. if (state.VideoType == VideoType.VideoFile) { var hwType = encodingOptions.HardwareAccelerationType; @@ -441,6 +441,12 @@ namespace MediaBrowser.Controller.MediaEncoding return "libopus"; } + if (string.Equals(codec, "flac", StringComparison.OrdinalIgnoreCase)) + { + // flac is experimental in mp4 muxer + return "flac -strict -2"; + } + return codec.ToLowerInvariant(); } -- cgit v1.2.3 From 32bb73acbb071a8329c485738176711dac4e1bcb Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sat, 14 Nov 2020 14:22:15 +0800 Subject: add aac_adtstoasc bitstream filter for mpegts to mp4 conversion --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 29 +++++++++++++++++++--- Jellyfin.Api/Controllers/VideoHlsController.cs | 28 +++++++++++++++++++-- .../MediaEncoding/EncodingHelper.cs | 16 ++++++++++-- 3 files changed, 66 insertions(+), 7 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index d4ae124b9..10c2d3fec 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1444,7 +1444,19 @@ namespace Jellyfin.Api.Controllers { if (EncodingHelper.IsCopyCodec(audioCodec)) { - return "-acodec copy -strict -2"; + var segmentFormat = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); + var bitStreamArgs = string.Empty; + + // Apply aac_adtstoasc bitstream filter when media source is in mpegts. + if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) + && (string.Equals(state.MediaSource.Container, "mpegts", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) + { + bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); + bitStreamArgs = !string.IsNullOrEmpty(bitStreamArgs) ? " " + bitStreamArgs : string.Empty; + } + + return "-acodec copy -strict -2" + bitStreamArgs; } var audioTranscodeParams = new List(); @@ -1473,13 +1485,24 @@ namespace Jellyfin.Api.Controllers if (EncodingHelper.IsCopyCodec(audioCodec)) { var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); + var segmentFormat = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); + var bitStreamArgs = string.Empty; + + // Apply aac_adtstoasc bitstream filter when media source is in mpegts. + if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) + && (string.Equals(state.MediaSource.Container, "mpegts", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) + { + bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); + bitStreamArgs = !string.IsNullOrEmpty(bitStreamArgs) ? " " + bitStreamArgs : string.Empty; + } if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec)) { - return "-codec:a:0 copy -strict -2 -copypriorss:a:0 0"; + return "-codec:a:0 copy -strict -2 -copypriorss:a:0 0" + bitStreamArgs; } - return "-codec:a:0 copy -strict -2"; + return "-codec:a:0 copy -strict -2" + bitStreamArgs; } var args = "-codec:a:0 " + audioCodec; diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 7b50b47b1..21c042f4b 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -439,7 +439,19 @@ namespace Jellyfin.Api.Controllers { if (EncodingHelper.IsCopyCodec(audioCodec)) { - return "-acodec copy -strict -2"; + var segmentFormat = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); + var bitStreamArgs = string.Empty; + + // Apply aac_adtstoasc bitstream filter when media source is in mpegts. + if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) + && (string.Equals(state.MediaSource.Container, "mpegts", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) + { + bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); + bitStreamArgs = !string.IsNullOrEmpty(bitStreamArgs) ? " " + bitStreamArgs : string.Empty; + } + + return "-acodec copy -strict -2" + bitStreamArgs; } var audioTranscodeParams = new List(); @@ -467,7 +479,19 @@ namespace Jellyfin.Api.Controllers if (EncodingHelper.IsCopyCodec(audioCodec)) { - return "-codec:a:0 copy -strict -2"; + var segmentFormat = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); + var bitStreamArgs = string.Empty; + + // Apply aac_adtstoasc bitstream filter when media source is in mpegts. + if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) + && (string.Equals(state.MediaSource.Container, "mpegts", StringComparison.OrdinalIgnoreCase) + || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) + { + bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); + bitStreamArgs = !string.IsNullOrEmpty(bitStreamArgs) ? " " + bitStreamArgs : string.Empty; + } + + return "-acodec copy -strict -2" + bitStreamArgs; } var args = "-codec:a:0 " + audioCodec; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 0bc5308b6..a8252d473 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -596,10 +596,17 @@ namespace MediaBrowser.Controller.MediaEncoding || codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1; } - // 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 + public bool IsAAC(MediaStream stream) + { + var codec = stream.Codec ?? string.Empty; + + return codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1; + } + public string GetBitStreamArgs(MediaStream stream) { + // 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)) { return "-bsf:v h264_mp4toannexb"; @@ -608,6 +615,11 @@ namespace MediaBrowser.Controller.MediaEncoding { return "-bsf:v hevc_mp4toannexb"; } + else if (IsAAC(stream)) + { + // convert adts header(mpegts) to asc header(mp4) + return "-bsf:a aac_adtstoasc"; + } else { return null; -- cgit v1.2.3 From 06670351ae6be1d3c47a25fe14b2f253cc6ab3e7 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Sat, 14 Nov 2020 10:19:41 +0000 Subject: Apply suggestions from code review Co-authored-by: BaronGreenback --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 ++-- Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 643c6073c..625275171 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1453,7 +1453,7 @@ namespace Jellyfin.Api.Controllers || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) { bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); - bitStreamArgs = !string.IsNullOrEmpty(bitStreamArgs) ? " " + bitStreamArgs : string.Empty; + bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs; } return "-acodec copy -strict -2" + bitStreamArgs; @@ -1494,7 +1494,7 @@ namespace Jellyfin.Api.Controllers || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) { bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); - bitStreamArgs = !string.IsNullOrEmpty(bitStreamArgs) ? " " + bitStreamArgs : string.Empty; + bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs; } if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec)) diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 4ae61fbd3..93bb3a903 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -448,7 +448,7 @@ namespace Jellyfin.Api.Controllers || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) { bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); - bitStreamArgs = !string.IsNullOrEmpty(bitStreamArgs) ? " " + bitStreamArgs : string.Empty; + bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs;; } return "-acodec copy -strict -2" + bitStreamArgs; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a8252d473..b181a6ee6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -605,7 +605,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetBitStreamArgs(MediaStream stream) { - // TODO This is auto inserted into the mpegts mux so it might not be needed + // 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)) { @@ -617,7 +617,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (IsAAC(stream)) { - // convert adts header(mpegts) to asc header(mp4) + // Convert adts header(mpegts) to asc header(mp4). return "-bsf:a aac_adtstoasc"; } else -- cgit v1.2.3 From d7cdaeea7dfc7dd556765a0fb224d8428bc38c0b Mon Sep 17 00:00:00 2001 From: Fernando Fernández Date: Tue, 17 Nov 2020 14:26:05 +0100 Subject: Use all system cores when threads are set to auto or bigger than the amount of system's cores --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5846a603a..3a9f57c55 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2344,7 +2344,7 @@ namespace MediaBrowser.Controller.MediaEncoding // Automatic if (threads <= 0 || threads >= Environment.ProcessorCount) { - return 0; + return Environment.ProcessorCount; } return threads; -- cgit v1.2.3 From 38c3b6fcd37d71d09c6afe26ad26c762850f9971 Mon Sep 17 00:00:00 2001 From: Fernando Fernández Date: Wed, 18 Nov 2020 10:01:03 +0100 Subject: Fix build and thread detection logic --- Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs | 2 +- .../LiveTv/EmbyTV/EncodedRecorder.cs | 1 + Jellyfin.Api/Controllers/DynamicHlsController.cs | 2 +- Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 13 ++++++++----- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 16 ++++++++-------- 6 files changed, 20 insertions(+), 16 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index fcc2d1eeb..0dc045ee6 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1635,7 +1635,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http)) { - return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer); + return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config); } return new DirectRecorder(_logger, _httpClientFactory, _streamHelper); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 4726211d2..e6ee9819e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -8,6 +8,7 @@ using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 6e59da798..bba5e972e 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1329,7 +1329,7 @@ namespace Jellyfin.Api.Controllers { var videoCodec = _encodingHelper.GetVideoEncoder(state, encodingOptions); - var threads = _encodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec); + var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec); // GetNumberOfThreads is static if (state.BaseRequest.BreakOnNonKeyFrames) { diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 389dc8a08..065fa786e 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -359,7 +359,7 @@ namespace Jellyfin.Api.Controllers private string GetCommandLineArguments(string outputPath, StreamState state) { var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); - var threads = _encodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec); + var threads = EncodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec); // GetNumberOfThreads is static var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions); var format = !string.IsNullOrWhiteSpace(state.Request.SegmentContainer) ? "." + state.Request.SegmentContainer : ".ts"; var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 3a9f57c55..60c44cfcf 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2329,20 +2329,23 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Gets the number of threads. /// - public int GetNumberOfThreads(EncodingJobInfo state, EncodingOptions encodingOptions, string outputVideoCodec) + public static int GetNumberOfThreads(EncodingJobInfo state, EncodingOptions encodingOptions, string outputVideoCodec) { - if (string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) + if (outputVideoCodec != null && string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) { // per docs: // -threads number of threads to use for encoding, can't be 0 [auto] with VP8 // (recommended value : number of real cores - 1) return Math.Max(Environment.ProcessorCount - 1, 1); } - - var threads = state.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount; + var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount; // Automatic - if (threads <= 0 || threads >= Environment.ProcessorCount) + if (threads <= 0) + { + return 0; + } + else if (threads >= Environment.ProcessorCount) { return Environment.ProcessorCount; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 2b4712b2d..5f60c09ae 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -524,26 +524,26 @@ namespace MediaBrowser.MediaEncoding.Encoder // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar. // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar - var vf = String.Empty; + var vf = string.Empty; if (threedFormat.HasValue) { switch (threedFormat.Value) { case Video3DFormat.HalfSideBySide: - vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; + vf = "-vf crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not. break; case Video3DFormat.FullSideBySide: - vf = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; + vf = "-vf crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; // fsbs crop width in half,set the display aspect,crop out any black bars we may have made break; case Video3DFormat.HalfTopAndBottom: - vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; + vf = "-vf crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made break; case Video3DFormat.FullTopAndBottom: - vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; + vf = "-vf crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1"; // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made break; default: @@ -557,8 +557,8 @@ namespace MediaBrowser.MediaEncoding.Encoder // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case. var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty; - var args = useIFrame ? string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {5} -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail, threads) : - string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads); + var args = useIFrame ? string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {5} -v quiet -vframes 1 {2}{4} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail, threads) : + string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads); var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1); var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1); @@ -695,7 +695,7 @@ namespace MediaBrowser.MediaEncoding.Encoder Directory.CreateDirectory(targetDirectory); var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg"); - var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf, threads); + var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, threads); var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1); var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1); -- cgit v1.2.3 From 9a323f6df08927b5db06ba634f430523a236265f Mon Sep 17 00:00:00 2001 From: Greenback Date: Wed, 18 Nov 2020 13:46:14 +0000 Subject: More spelling corrections. --- Jellyfin.Data/Entities/Libraries/CollectionItem.cs | 4 ++-- Jellyfin.Data/Entities/Libraries/ItemMetadata.cs | 2 +- .../Users/DefaultAuthenticationProvider.cs | 2 +- MediaBrowser.Common/Net/INetworkManager.cs | 4 ++-- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- MediaBrowser.Controller/Entities/Folder.cs | 2 +- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 6 +++--- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- MediaBrowser.Model/Dlna/ResolutionNormalizer.cs | 2 +- MediaBrowser.Providers/Manager/ProviderUtils.cs | 4 ++-- MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs | 10 +++++----- MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs | 2 +- MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs | 10 +++++----- RSSDP/DisposableManagedObjectBase.cs | 6 +++--- RSSDP/HttpParserBase.cs | 2 +- RSSDP/ISsdpDeviceLocator.cs | 4 ++-- RSSDP/SsdpDevicePublisher.cs | 4 ++-- RSSDP/SsdpRootDevice.cs | 4 ++-- 18 files changed, 36 insertions(+), 36 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs index 4467c9bbd..f9539964d 100644 --- a/Jellyfin.Data/Entities/Libraries/CollectionItem.cs +++ b/Jellyfin.Data/Entities/Libraries/CollectionItem.cs @@ -73,7 +73,7 @@ namespace Jellyfin.Data.Entities.Libraries /// Gets or sets the next item in the collection. /// /// - /// TODO check if this properly updated dependant and has the proper principal relationship. + /// TODO check if this properly updated Dependant and has the proper principal relationship. /// public virtual CollectionItem Next { get; set; } @@ -81,7 +81,7 @@ namespace Jellyfin.Data.Entities.Libraries /// Gets or sets the previous item in the collection. /// /// - /// TODO check if this properly updated dependant and has the proper principal relationship. + /// TODO check if this properly updated Dependant and has the proper principal relationship. /// public virtual CollectionItem Previous { get; set; } diff --git a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs index 1d2dc0f66..d74330c05 100644 --- a/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs +++ b/Jellyfin.Data/Entities/Libraries/ItemMetadata.cs @@ -141,7 +141,7 @@ namespace Jellyfin.Data.Entities.Libraries public virtual ICollection PersonRoles { get; protected set; } /// - /// Gets or sets a collection containing the generes for this item. + /// Gets or sets a collection containing the genres for this item. /// public virtual ICollection Genres { get; protected set; } diff --git a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index f79e433a6..662b4bf65 100644 --- a/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -53,7 +53,7 @@ namespace Jellyfin.Server.Implementations.Users bool success = false; - // As long as jellyfin supports passwordless users, we need this little block here to accommodate + // As long as jellyfin supports password-less users, we need this little block here to accommodate if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password)) { return Task.FromResult(new ProviderAuthenticationResult diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index a0330afef..12966a474 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Common.Net /// /// Investigates an caches a list of interface addresses, excluding local link and LAN excluded addresses. /// - /// The list of ipaddresses. + /// The list of ip addresses. IPAddress[] GetLocalIpAddresses(); /// @@ -73,7 +73,7 @@ namespace MediaBrowser.Common.Net /// Returns true if address is in the LAN list in the config file. /// /// The address to check. - /// If true, check against addresses in the LAN settings which have [] arroud and return true if it matches the address give in address. + /// If true, check against addresses in the LAN settings which have [] around and return true if it matches the address give in address. /// If true, returns false if address is in the 127.x.x.x or 169.128.x.x range. /// falseif the address isn't in the LAN list, true if the address has been defined as a LAN address. bool IsAddressInSubnets(IPAddress address, bool excludeInterfaces, bool excludeRFC); diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 1d44a5511..8d7773004 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -2611,7 +2611,7 @@ namespace MediaBrowser.Controller.Entities { if (!AllowsMultipleImages(type)) { - throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots"); + throw new ArgumentException("The change index operation is only applicable to backdrops and screen shots"); } var info1 = GetImageInfo(type, index1); diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index a76c8a376..1e3825c6e 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.Entities /// /// Loads our children. Validation will occur externally. - /// We want this sychronous. + /// We want this synchronous. /// protected virtual List LoadChildren() { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5846a603a..a4a5cecdc 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // 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 - // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. + // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such as this. if (state.VideoType == VideoType.VideoFile) { var hwType = encodingOptions.HardwareAccelerationType; @@ -247,7 +247,7 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } - // Seeing reported failures here, not sure yet if this is related to specfying input format + // Seeing reported failures here, not sure yet if this is related to specifying input format if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase)) { return null; @@ -2752,7 +2752,7 @@ namespace MediaBrowser.Controller.MediaEncoding var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile; // 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 - // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. + // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such as this. if (videoType != VideoType.VideoFile) { return null; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 5a3a9185d..e060253ee 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -542,7 +542,7 @@ namespace MediaBrowser.MediaEncoding.Encoder break; case Video3DFormat.FullTopAndBottom: vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600 + // ftab crop height in half, set the display aspect,crop out any black bars we may have made the scale width to 600 break; default: break; diff --git a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs index a4305c810..2f8614386 100644 --- a/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs +++ b/MediaBrowser.Model/Dlna/ResolutionNormalizer.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Model.Dlna int? maxWidth, int? maxHeight) { - // If the bitrate isn't changing, then don't downlscale the resolution + // If the bitrate isn't changing, then don't downscale the resolution if (inputBitrate.HasValue && outputBitrate >= inputBitrate.Value) { if (maxWidth.HasValue || maxHeight.HasValue) diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 70a5a6ac1..5621d2b86 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Providers.Manager { if (replaceData || string.IsNullOrEmpty(target.Name)) { - // Safeguard against incoming data having an emtpy name + // Safeguard against incoming data having an empty name if (!string.IsNullOrWhiteSpace(source.Name)) { target.Name = source.Name; @@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Manager if (replaceData || string.IsNullOrEmpty(target.OriginalTitle)) { - // Safeguard against incoming data having an emtpy name + // Safeguard against incoming data having an empty name if (!string.IsNullOrWhiteSpace(source.OriginalTitle)) { target.OriginalTitle = source.OriginalTitle; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 6af52b591..332479ff8 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var result = await GetRootObject(imdbId, cancellationToken).ConfigureAwait(false); - // Only take the name and rating if the user's language is set to english, since Omdb has no localization + // Only take the name and rating if the user's language is set to English, since Omdb has no localization if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase) || _configurationManager.Configuration.EnableNewOmdbSupport) { item.Name = result.Title; @@ -151,7 +151,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb return false; } - // Only take the name and rating if the user's language is set to english, since Omdb has no localization + // Only take the name and rating if the user's language is set to English, since Omdb has no localization if (string.Equals(language, "en", StringComparison.OrdinalIgnoreCase) || _configurationManager.Configuration.EnableNewOmdbSupport) { item.Name = result.Title; @@ -385,7 +385,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var isConfiguredForEnglish = IsConfiguredForEnglish(item) || _configurationManager.Configuration.EnableNewOmdbSupport; // Grab series genres because IMDb data is better than TVDB. Leave movies alone - // But only do it if english is the preferred language because this data will not be localized + // But only do it if English is the preferred language because this data will not be localized if (isConfiguredForEnglish && !string.IsNullOrWhiteSpace(result.Genre)) { item.Genres = Array.Empty(); @@ -401,7 +401,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb if (isConfiguredForEnglish) { - // Omdb is currently english only, so for other languages skip this and let secondary providers fill it in + // Omdb is currently English only, so for other languages skip this and let secondary providers fill it in item.Overview = result.Plot; } @@ -455,7 +455,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb { var lang = item.GetPreferredMetadataLanguage(); - // The data isn't localized and so can only be used for english users + // The data isn't localized and so can only be used for English users return string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase); } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index b754a0795..0e8a5baab 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -98,7 +98,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb if (preferredLanguage.Length == 5) // like en-US { - // Currenty, TMDB supports 2-letter language codes only + // Currently, TMDB supports 2-letter language codes only // They are planning to change this in the future, thus we're // supplying both codes if we're having a 5-letter code. languages.Add(preferredLanguage.Substring(0, 2)); diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index 9cc0344c1..bce4cf009 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -134,7 +134,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - // int.TryParse is local aware, so it can be probamatic, force us culture + // int.TryParse is local aware, so it can be problematic, force us culture if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) { item.AirsBeforeEpisodeNumber = rval; @@ -150,7 +150,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - // int.TryParse is local aware, so it can be probamatic, force us culture + // int.TryParse is local aware, so it can be problematic, force us culture if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) { item.AirsAfterSeasonNumber = rval; @@ -166,7 +166,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - // int.TryParse is local aware, so it can be probamatic, force us culture + // int.TryParse is local aware, so it can be problematic, force us culture if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) { item.AirsBeforeSeasonNumber = rval; @@ -182,7 +182,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - // int.TryParse is local aware, so it can be probamatic, force us culture + // int.TryParse is local aware, so it can be problematic, force us culture if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) { item.AirsBeforeSeasonNumber = rval; @@ -198,7 +198,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - // int.TryParse is local aware, so it can be probamatic, force us culture + // int.TryParse is local aware, so it can be problematic, force us culture if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) { item.AirsBeforeEpisodeNumber = rval; diff --git a/RSSDP/DisposableManagedObjectBase.cs b/RSSDP/DisposableManagedObjectBase.cs index 745ec359c..7d6a471f9 100644 --- a/RSSDP/DisposableManagedObjectBase.cs +++ b/RSSDP/DisposableManagedObjectBase.cs @@ -5,7 +5,7 @@ using System.Text; namespace Rssdp.Infrastructure { /// - /// Correclty implements the interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an property. + /// Correctly implements the interface and pattern for an object containing only managed resources, and adds a few common niceties not on the interface such as an property. /// public abstract class DisposableManagedObjectBase : IDisposable { @@ -61,10 +61,10 @@ namespace Rssdp.Infrastructure /// Disposes this object instance and all internally managed resources. /// /// - /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes. + /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behavior of derived classes. /// /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfer with the dispose process.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "We do exactly as asked, but CA doesn't seem to like us also setting the IsDisposed property. Too bad, it's a good idea and shouldn't cause an exception or anything likely to interfere with the dispose process.")] public void Dispose() { IsDisposed = true; diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs index 11202940e..c56249523 100644 --- a/RSSDP/HttpParserBase.cs +++ b/RSSDP/HttpParserBase.cs @@ -105,7 +105,7 @@ namespace Rssdp.Infrastructure var headerName = line.Substring(0, headerKeySeparatorIndex).Trim(); var headerValue = line.Substring(headerKeySeparatorIndex + 1).Trim(); - // Not sure how to determine where request headers and and content headers begin, + // Not sure how to determine where request headers and content headers begin, // at least not without a known set of headers (general headers first the content headers) // which seems like a bad way of doing it. So we'll assume if it's a known content header put it there // else use request headers. diff --git a/RSSDP/ISsdpDeviceLocator.cs b/RSSDP/ISsdpDeviceLocator.cs index 413055643..4df166cd2 100644 --- a/RSSDP/ISsdpDeviceLocator.cs +++ b/RSSDP/ISsdpDeviceLocator.cs @@ -52,7 +52,7 @@ namespace Rssdp.Infrastructure } /// - /// Aynchronously performs a search for all devices using the default search timeout, and returns an awaitable task that can be used to retrieve the results. + /// Asynchronously performs a search for all devices using the default search timeout, and returns an awaitable task that can be used to retrieve the results. /// /// A task whose result is an of instances, representing all found devices. System.Threading.Tasks.Task> SearchAsync(); @@ -83,7 +83,7 @@ namespace Rssdp.Infrastructure /// /// The amount of time to wait for network responses to the search request. Longer values will likely return more devices, but increase search time. A value between 1 and 5 is recommended by the UPnP 1.1 specification. Specify TimeSpan.Zero to return only devices already in the cache. /// - /// By design RSSDP does not support 'publishing services' as it is intended for use with non-standard UPnP devices that don't publish UPnP style services. However, it is still possible to use RSSDP to search for devices implemetning these services if you know the service type. + /// By design RSSDP does not support 'publishing services' as it is intended for use with non-standard UPnP devices that don't publish UPnP style services. However, it is still possible to use RSSDP to search for devices implementing these services if you know the service type. /// /// A task whose result is an of instances, representing all found devices. System.Threading.Tasks.Task> SearchAsync(string searchTarget, TimeSpan searchWaitTime); diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index 1a8577d8d..90925b9e0 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -102,7 +102,7 @@ namespace Rssdp.Infrastructure /// The instance to add. /// Thrown if the argument is null. /// Thrown if the contains property values that are not acceptable to the UPnP 1.0 specification. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capture task to local variable supresses compiler warning, but task is not really needed.")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "t", Justification = "Capture task to local variable suppresses compiler warning, but task is not really needed.")] public void AddDevice(SsdpRootDevice device) { if (device == null) @@ -180,7 +180,7 @@ namespace Rssdp.Infrastructure /// /// /// Enabling this option will cause devices to show up in Microsoft Windows Explorer's network screens (if discovery is enabled etc.). Windows Explorer appears to search only for pnp:rootdeivce and not upnp:rootdevice. - /// If false, the system will only use upnp:rootdevice for notifiation broadcasts and and search responses, which is correct according to the UPnP/SSDP spec. + /// If false, the system will only use upnp:rootdevice for notification broadcasts and and search responses, which is correct according to the UPnP/SSDP spec. /// public bool SupportPnpRootDevice { diff --git a/RSSDP/SsdpRootDevice.cs b/RSSDP/SsdpRootDevice.cs index 8937ec331..4084b31ca 100644 --- a/RSSDP/SsdpRootDevice.cs +++ b/RSSDP/SsdpRootDevice.cs @@ -25,7 +25,7 @@ namespace Rssdp /// Specifies how long clients can cache this device's details for. Optional but defaults to which means no-caching. Recommended value is half an hour. /// /// - /// Specifiy to indicate no caching allowed. + /// Specify to indicate no caching allowed. /// Also used to specify how often to rebroadcast alive notifications. /// The UPnP/SSDP specifications indicate this should not be less than 1800 seconds (half an hour), but this is not enforced by this library. /// @@ -50,7 +50,7 @@ namespace Rssdp public IPAddress SubnetMask { get; set; } /// - /// The base URL to use for all relative url's provided in other propertise (and those of child devices). Optional. + /// The base URL to use for all relative url's provided in other properties (and those of child devices). Optional. /// /// /// Defines the base URL. Used to construct fully-qualified URLs. All relative URLs that appear elsewhere in the description are combined with this base URL. If URLBase is empty or not given, the base URL is the URL from which the device description was retrieved (which is the preferred implementation; use of URLBase is no longer recommended). Specified by UPnP vendor. Single URL. -- cgit v1.2.3 From 51dab0958d11e142f582da9c46a8679335b99dda Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 19 Nov 2020 22:46:02 +0800 Subject: changes per suggestions --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 91 ++------- Jellyfin.Api/Controllers/VideoHlsController.cs | 90 ++------ Jellyfin.Api/Helpers/HlsHelpers.cs | 20 +- .../MediaEncoding/EncodingHelper.cs | 227 +++++++++++++++------ 4 files changed, 193 insertions(+), 235 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 28357091b..90c2a6b58 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -43,7 +43,7 @@ namespace Jellyfin.Api.Controllers public class DynamicHlsController : BaseJellyfinApiController { private const string DefaultEncoderPreset = "veryfast"; - private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Hls; + private const TranscodingJobType _transcodingJobType = TranscodingJobType.Hls; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; @@ -1446,58 +1446,38 @@ namespace Jellyfin.Api.Controllers { if (EncodingHelper.IsCopyCodec(audioCodec)) { - var segmentFormat = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); - var bitStreamArgs = string.Empty; - - // Apply aac_adtstoasc bitstream filter when media source is in mpegts. - if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) - && (string.Equals(state.MediaSource.Container, "mpegts", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) - { - bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); - bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs; - } + var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); return "-acodec copy -strict -2" + bitStreamArgs; } - var audioTranscodeParams = new List(); + var audioTranscodeParams = string.Empty; - audioTranscodeParams.Add("-acodec " + audioCodec); + audioTranscodeParams += "-acodec " + audioCodec; if (state.OutputAudioBitrate.HasValue) { - audioTranscodeParams.Add("-ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture)); + audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture); } if (state.OutputAudioChannels.HasValue) { - audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); + audioTranscodeParams += " -ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture); } if (state.OutputAudioSampleRate.HasValue) { - audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)); + audioTranscodeParams += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); } - audioTranscodeParams.Add("-vn"); - return string.Join(' ', audioTranscodeParams); + audioTranscodeParams += " -vn"; + return audioTranscodeParams; } if (EncodingHelper.IsCopyCodec(audioCodec)) { var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); - var segmentFormat = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); - var bitStreamArgs = string.Empty; - - // Apply aac_adtstoasc bitstream filter when media source is in mpegts. - if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) - && (string.Equals(state.MediaSource.Container, "mpegts", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) - { - bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); - bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs; - } + var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); if (EncodingHelper.IsCopyCodec(videoCodec) && state.EnableBreakOnNonKeyFrames(videoCodec)) { @@ -1528,7 +1508,7 @@ namespace Jellyfin.Api.Controllers args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); } - args += " " + _encodingHelper.GetAudioFilterParam(state, _encodingOptions, true); + args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions, true); return args; } @@ -1574,7 +1554,7 @@ namespace Jellyfin.Api.Controllers { if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - string bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.VideoStream); + string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream); if (!string.IsNullOrEmpty(bitStreamArgs)) { args += " " + bitStreamArgs; @@ -1587,51 +1567,10 @@ namespace Jellyfin.Api.Controllers } else { - var gopArg = string.Empty; - var keyFrameArg = string.Format( - CultureInfo.InvariantCulture, - " -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"", - startNumber * state.SegmentLength, - state.SegmentLength); - - var framerate = state.VideoStream?.RealFrameRate; - if (framerate.HasValue) - { - // This is to make sure keyframe interval is limited to our segment, - // as forcing keyframes is not enough. - // Example: we encoded half of desired length, then codec detected - // scene cut and inserted a keyframe; next forced keyframe would - // be created outside of segment, which breaks seeking. - // -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe. - gopArg = string.Format( - CultureInfo.InvariantCulture, - " -g {0} -keyint_min {0} -sc_threshold 0", - Math.Ceiling(state.SegmentLength * framerate.Value)); - } - - args += " " + _encodingHelper.GetVideoQualityParam(state, codec, _encodingOptions, DefaultEncoderPreset); + args += _encodingHelper.GetVideoQualityParam(state, codec, _encodingOptions, DefaultEncoderPreset); - // Unable to force key frames using these encoders, set key frames by GOP. - if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || 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)) - { - 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)) - { - args += " " + keyFrameArg; - } - else - { - args += " " + keyFrameArg + gopArg; - } + // Set the key frame params for video encoding to match the hls segment time. + args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, false, startNumber); // Currenly b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now. if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)) diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index b03402c1b..bb8026543 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -367,7 +367,7 @@ namespace Jellyfin.Api.Controllers var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath); var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension); - var outputExtension = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer); + var outputExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer); var outputTsArg = outputPrefix + "%d" + outputExtension; var segmentFormat = outputExtension.TrimStart('.'); @@ -441,57 +441,37 @@ namespace Jellyfin.Api.Controllers { if (EncodingHelper.IsCopyCodec(audioCodec)) { - var segmentFormat = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); - var bitStreamArgs = string.Empty; - - // Apply aac_adtstoasc bitstream filter when media source is in mpegts. - if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) - && (string.Equals(state.MediaSource.Container, "mpegts", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) - { - bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); - bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs; - } + var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); return "-acodec copy -strict -2" + bitStreamArgs; } - var audioTranscodeParams = new List(); + var audioTranscodeParams = string.Empty; - audioTranscodeParams.Add("-acodec " + audioCodec); + audioTranscodeParams += "-acodec " + audioCodec; if (state.OutputAudioBitrate.HasValue) { - audioTranscodeParams.Add("-ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture)); + audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture); } if (state.OutputAudioChannels.HasValue) { - audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); + audioTranscodeParams += " -ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture); } if (state.OutputAudioSampleRate.HasValue) { - audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)); + audioTranscodeParams += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); } - audioTranscodeParams.Add("-vn"); - return string.Join(' ', audioTranscodeParams); + audioTranscodeParams += " -vn"; + return audioTranscodeParams; } if (EncodingHelper.IsCopyCodec(audioCodec)) { - var segmentFormat = HlsHelpers.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); - var bitStreamArgs = string.Empty; - - // Apply aac_adtstoasc bitstream filter when media source is in mpegts. - if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) - && (string.Equals(state.MediaSource.Container, "mpegts", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.MediaSource.Container, "hls", StringComparison.OrdinalIgnoreCase))) - { - bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.AudioStream); - bitStreamArgs = !string.IsNullOrEmpty(bitStreamArgs) ? " " + bitStreamArgs : string.Empty; - } + var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container); return "-acodec copy -strict -2" + bitStreamArgs; } @@ -517,7 +497,7 @@ namespace Jellyfin.Api.Controllers args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); } - args += " " + _encodingHelper.GetAudioFilterParam(state, _encodingOptions, true); + args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions, true); return args; } @@ -563,7 +543,7 @@ namespace Jellyfin.Api.Controllers // If h264_mp4toannexb is ever added, do not use it for live tv. if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - string bitStreamArgs = _encodingHelper.GetBitStreamArgs(state.VideoStream); + string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream); if (!string.IsNullOrEmpty(bitStreamArgs)) { args += " " + bitStreamArgs; @@ -574,50 +554,10 @@ namespace Jellyfin.Api.Controllers } else { - var gopArg = string.Empty; - var keyFrameArg = string.Format( - CultureInfo.InvariantCulture, - " -force_key_frames \"expr:gte(t,n_forced*{0})\"", - state.SegmentLength.ToString(CultureInfo.InvariantCulture)); - - var framerate = state.VideoStream?.RealFrameRate; - if (framerate.HasValue) - { - // This is to make sure keyframe interval is limited to our segment, - // as forcing keyframes is not enough. - // Example: we encoded half of desired length, then codec detected - // scene cut and inserted a keyframe; next forced keyframe would - // be created outside of segment, which breaks seeking. - // -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe. - gopArg = string.Format( - CultureInfo.InvariantCulture, - " -g {0} -keyint_min {0} -sc_threshold 0", - Math.Ceiling(state.SegmentLength * framerate.Value)); - } - - args += " " + _encodingHelper.GetVideoQualityParam(state, codec, _encodingOptions, DefaultEncoderPreset); + args += _encodingHelper.GetVideoQualityParam(state, codec, _encodingOptions, DefaultEncoderPreset); - // Unable to force key frames using these encoders, set key frames by GOP. - if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || 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)) - { - 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)) - { - args += " " + keyFrameArg; - } - else - { - args += " " + keyFrameArg + gopArg; - } + // Set the key frame params for video encoding to match the hls segment time. + args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, true, null); // Currenly b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now. if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase)) diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs index 1c874e4bd..18e23fb5c 100644 --- a/Jellyfin.Api/Helpers/HlsHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsHelpers.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; @@ -76,21 +77,6 @@ namespace Jellyfin.Api.Helpers } } - /// - /// Gets the extension of segment container. - /// - /// The name of the segment container. - /// The string text of extension. - public static string GetSegmentFileExtension(string? segmentContainer) - { - if (!string.IsNullOrWhiteSpace(segmentContainer)) - { - return "." + segmentContainer; - } - - return ".ts"; - } - /// /// Gets the #EXT-X-MAP string. /// @@ -103,7 +89,7 @@ namespace Jellyfin.Api.Helpers var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath); var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension); - var outputExtension = GetSegmentFileExtension(state.Request.SegmentContainer); + var outputExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer); // on Linux/Unix // #EXT-X-MAP:URI="prefix-1.mp4" @@ -137,7 +123,7 @@ namespace Jellyfin.Api.Helpers var text = reader.ReadToEnd(); - var segmentFormat = GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); + var segmentFormat = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.'); if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase)) { var fmp4InitFileName = GetFmp4InitFileName(path, state, true); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b181a6ee6..a2e3318f3 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -580,7 +580,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// The stream. /// true if the specified stream is H264; otherwise, false. - public bool IsH264(MediaStream stream) + public static bool IsH264(MediaStream stream) { var codec = stream.Codec ?? string.Empty; @@ -588,7 +588,7 @@ namespace MediaBrowser.Controller.MediaEncoding || codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; } - public bool IsH265(MediaStream stream) + public static bool IsH265(MediaStream stream) { var codec = stream.Codec ?? string.Empty; @@ -596,14 +596,14 @@ namespace MediaBrowser.Controller.MediaEncoding || codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1; } - public bool IsAAC(MediaStream stream) + public static bool IsAAC(MediaStream stream) { var codec = stream.Codec ?? string.Empty; return codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1; } - public string GetBitStreamArgs(MediaStream stream) + public static string GetBitStreamArgs(MediaStream stream) { // 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 @@ -626,6 +626,33 @@ namespace MediaBrowser.Controller.MediaEncoding } } + public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer) + { + var bitStreamArgs = string.Empty; + var segmentFormat = GetSegmentFileExtension(segmentContainer).TrimStart('.'); + + // Apply aac_adtstoasc bitstream filter when media source is in mpegts. + if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase) + && (string.Equals(mediaSourceContainer, "mpegts", StringComparison.OrdinalIgnoreCase) + || string.Equals(mediaSourceContainer, "hls", StringComparison.OrdinalIgnoreCase))) + { + bitStreamArgs = GetBitStreamArgs(state.AudioStream); + bitStreamArgs = string.IsNullOrEmpty(bitStreamArgs) ? string.Empty : " " + bitStreamArgs; + } + + return bitStreamArgs; + } + + public static string GetSegmentFileExtension(string segmentContainer) + { + if (!string.IsNullOrWhiteSpace(segmentContainer)) + { + return "." + segmentContainer; + } + + return ".ts"; + } + public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec) { var bitrate = state.OutputVideoBitrate; @@ -799,6 +826,72 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } + public string GetHlsVideoKeyFrameArguments( + EncodingJobInfo state, + string codec, + int segmentLength, + bool isEventPlaylist, + int? startNumber) + { + var args = string.Empty; + var gopArg = string.Empty; + var keyFrameArg = string.Empty; + if (isEventPlaylist) + { + keyFrameArg = string.Format( + CultureInfo.InvariantCulture, + " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"", + segmentLength); + } + else if (startNumber.HasValue) + { + keyFrameArg = string.Format( + CultureInfo.InvariantCulture, + " -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"", + startNumber.Value * segmentLength, + segmentLength); + } + + var framerate = state.VideoStream?.RealFrameRate; + if (framerate.HasValue) + { + // This is to make sure keyframe interval is limited to our segment, + // as forcing keyframes is not enough. + // Example: we encoded half of desired length, then codec detected + // scene cut and inserted a keyframe; next forced keyframe would + // be created outside of segment, which breaks seeking. + // -sc_threshold 0 is used to prevent the hardware encoder from post processing to break the set keyframe. + gopArg = string.Format( + CultureInfo.InvariantCulture, + " -g:v:0 {0} -keyint_min:v:0 {0} -sc_threshold:v:0 0", + Math.Ceiling(segmentLength * framerate.Value)); + } + + // Unable to force key frames using these encoders, set key frames by GOP. + if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || 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)) + { + 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)) + { + args += " " + keyFrameArg; + } + else + { + args += " " + keyFrameArg + gopArg; + } + + return args; + } + /// /// Gets the video bitrate to specify on the command line. /// @@ -806,6 +899,47 @@ namespace MediaBrowser.Controller.MediaEncoding { var param = string.Empty; + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + { + param += " -pix_fmt yuv420p"; + } + + if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + { + var videoStream = state.VideoStream; + var isColorDepth10 = IsColorDepth10(state); + + if (isColorDepth10 + && _mediaEncoder.SupportsHwaccel("opencl") + && encodingOptions.EnableTonemapping + && !string.IsNullOrEmpty(videoStream.VideoRange) + && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) + { + param += " -pix_fmt nv12"; + } + else + { + param += " -pix_fmt yuv420p"; + } + } + + if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) + { + param += " -pix_fmt nv21"; + } + var isVc1 = state.VideoStream != null && string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase); @@ -814,11 +948,11 @@ namespace MediaBrowser.Controller.MediaEncoding { if (!string.IsNullOrEmpty(encodingOptions.EncoderPreset)) { - param += "-preset " + encodingOptions.EncoderPreset; + param += " -preset " + encodingOptions.EncoderPreset; } else { - param += "-preset " + defaultPreset; + param += " -preset " + defaultPreset; } int encodeCrf = encodingOptions.H264Crf; @@ -849,11 +983,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparer.OrdinalIgnoreCase)) { - param += "-preset " + encodingOptions.EncoderPreset; + param += " -preset " + encodingOptions.EncoderPreset; } else { - param += "-preset 7"; + param += " -preset 7"; } param += " -look_ahead 0"; @@ -866,16 +1000,16 @@ namespace MediaBrowser.Controller.MediaEncoding { case "veryslow": - param += "-preset slow"; // lossless is only supported on maxwell and newer(2014+) + param += " -preset slow"; // lossless is only supported on maxwell and newer(2014+) break; case "slow": case "slower": - param += "-preset slow"; + param += " -preset slow"; break; case "medium": - param += "-preset medium"; + param += " -preset medium"; break; case "fast": @@ -883,11 +1017,11 @@ namespace MediaBrowser.Controller.MediaEncoding case "veryfast": case "superfast": case "ultrafast": - param += "-preset fast"; + param += " -preset fast"; break; default: - param += "-preset default"; + param += " -preset default"; break; } } @@ -899,11 +1033,11 @@ namespace MediaBrowser.Controller.MediaEncoding case "veryslow": case "slow": case "slower": - param += "-quality quality"; + param += " -quality quality"; break; case "medium": - param += "-quality balanced"; + param += " -quality balanced"; break; case "fast": @@ -911,11 +1045,11 @@ namespace MediaBrowser.Controller.MediaEncoding case "veryfast": case "superfast": case "ultrafast": - param += "-quality speed"; + param += " -quality speed"; break; default: - param += "-quality speed"; + param += " -quality speed"; break; } @@ -957,7 +1091,7 @@ namespace MediaBrowser.Controller.MediaEncoding profileScore = Math.Min(profileScore, 2); // http://www.webmproject.org/docs/encoder-parameters/ - param += string.Format(CultureInfo.InvariantCulture, "-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}", + param += string.Format(CultureInfo.InvariantCulture, " -speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}", profileScore.ToString(_usCulture), crf, qmin, @@ -965,15 +1099,15 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase)) { - param += "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; + param += " -mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; } else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) // asf/wmv { - param += "-qmin 2"; + param += " -qmin 2"; } else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase)) { - param += "-mbd 2"; + param += " -mbd 2"; } param += GetVideoBitrateParam(state, videoEncoder); @@ -1035,7 +1169,7 @@ namespace MediaBrowser.Controller.MediaEncoding && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { // not supported by h264_omx - param += " -profile:v " + profile; + param += " -profile:v:0 " + profile; } } @@ -1091,47 +1225,6 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -x265-params:0 no-info=1"; } - if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) - && !string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) - { - param = "-pix_fmt yuv420p " + param; - } - - if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) - { - var videoStream = state.VideoStream; - var isColorDepth10 = IsColorDepth10(state); - - if (isColorDepth10 - && _mediaEncoder.SupportsHwaccel("opencl") - && encodingOptions.EnableTonemapping - && !string.IsNullOrEmpty(videoStream.VideoRange) - && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) - { - param = "-pix_fmt nv12 " + param; - } - else - { - param = "-pix_fmt yuv420p " + param; - } - } - - if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) - { - param = "-pix_fmt nv21 " + param; - } - return param; } @@ -1521,7 +1614,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (filters.Count > 0) { - return "-af \"" + string.Join(",", filters) + "\""; + return " -af \"" + string.Join(",", filters) + "\""; } return string.Empty; @@ -3378,7 +3471,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) { - args += " -copyts -avoid_negative_ts 0 -start_at_zero"; + args += " -copyts -avoid_negative_ts disabled -start_at_zero"; } if (!state.RunTimeTicks.HasValue) @@ -3426,7 +3519,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += " -copyts"; } - args += " -avoid_negative_ts 0"; + args += " -avoid_negative_ts disabled"; if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) { @@ -3490,7 +3583,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += " -ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture); } - args += " " + GetAudioFilterParam(state, encodingOptions, false); + args += GetAudioFilterParam(state, encodingOptions, false); return args; } -- cgit v1.2.3 From 5ff08338d5a475d2975ecc6d4fe5222456368bd2 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Thu, 19 Nov 2020 15:02:36 +0000 Subject: Apply suggestions from code review Co-authored-by: Claus Vium --- Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 6 +++--- .../Probing/ProbeResultNormalizer.cs | 23 ++++++++-------------- MediaBrowser.Model/Dlna/StreamBuilder.cs | 12 +++++------ 4 files changed, 18 insertions(+), 25 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index bb8026543..b151f85e2 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -578,7 +578,7 @@ namespace Jellyfin.Api.Controllers args += _encodingHelper.GetOutputSizeParam(state, _encodingOptions, codec); } - if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)) + if (state.SubtitleStream == null || !state.SubtitleStream.IsExternal || state.SubtitleStream.IsTextSubtitleStream) { args += " -start_at_zero"; } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a2e3318f3..6e36adc16 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1194,7 +1194,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; } @@ -1205,7 +1205,7 @@ namespace MediaBrowser.Controller.MediaEncoding // NVENC cannot adjust the given level, just throw an error. } else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) - || !string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) + || !string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { param += " -level " + level; } @@ -1931,7 +1931,7 @@ namespace MediaBrowser.Controller.MediaEncoding retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; } else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) + || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) { /* QSV in FFMpeg can now setup hardware overlay for transcodes. diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 387594ce6..3d3d1eb48 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -1025,12 +1025,10 @@ namespace MediaBrowser.MediaEncoding.Probing if (streamInfo != null && streamInfo.Tags != null) { var bps = GetDictionaryValue(streamInfo.Tags, "BPS-eng") ?? GetDictionaryValue(streamInfo.Tags, "BPS"); - if (!string.IsNullOrEmpty(bps)) + if (!string.IsNullOrEmpty(bps) + && int.TryParse(bps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBps)) { - if (int.TryParse(bps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBps)) - { - return parsedBps; - } + return parsedBps; } } @@ -1042,12 +1040,9 @@ namespace MediaBrowser.MediaEncoding.Probing if (streamInfo != null && streamInfo.Tags != null) { var duration = GetDictionaryValue(streamInfo.Tags, "DURATION-eng") ?? GetDictionaryValue(streamInfo.Tags, "DURATION"); - if (!string.IsNullOrEmpty(duration)) + if (!string.IsNullOrEmpty(duration) && TimeSpan.TryParse(duration, out var parsedDuration)) { - if (TimeSpan.TryParse(duration, out var parsedDuration)) - { - return parsedDuration.TotalSeconds; - } + return parsedDuration.TotalSeconds; } } @@ -1059,12 +1054,10 @@ namespace MediaBrowser.MediaEncoding.Probing if (streamInfo != null && streamInfo.Tags != null) { var numberOfBytes = GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES-eng") ?? GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES"); - if (!string.IsNullOrEmpty(numberOfBytes)) + if (!string.IsNullOrEmpty(numberOfBytes) + && long.TryParse(numberOfBytes, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBytes)) { - if (long.TryParse(numberOfBytes, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBytes)) - { - return parsedBytes; - } + return parsedBytes; } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 76b137c49..2e5137005 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -928,12 +928,12 @@ namespace MediaBrowser.Model.Dlna defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, targetAudioChannels); } else if (targetAudioChannels.HasValue - && audioStream.Channels.HasValue - && audioStream.Channels.Value <= targetAudioChannels.Value - && !string.IsNullOrEmpty(audioStream.Codec) - && targetAudioCodecs != null - && targetAudioCodecs.Length > 0 - && !Array.Exists(targetAudioCodecs, elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase))) + && audioStream.Channels.HasValue + && audioStream.Channels.Value <= targetAudioChannels.Value + && !string.IsNullOrEmpty(audioStream.Codec) + && targetAudioCodecs != null + && targetAudioCodecs.Length > 0 + && !Array.Exists(targetAudioCodecs, elem => string.Equals(audioStream.Codec, elem, StringComparison.OrdinalIgnoreCase))) { // Shift the bitrate if we're transcoding to a different audio codec. defaultBitrate = GetDefaultAudioBitrate(targetAudioCodec, audioStream.Channels.Value); -- cgit v1.2.3 From 2deda0437d32016a0c73158840c9c04bc729b261 Mon Sep 17 00:00:00 2001 From: Fernando Fernández Date: Wed, 18 Nov 2020 10:54:21 +0100 Subject: Review suggestions --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 2 +- Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index bba5e972e..1c2e4474c 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1329,7 +1329,7 @@ namespace Jellyfin.Api.Controllers { var videoCodec = _encodingHelper.GetVideoEncoder(state, encodingOptions); - var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec); // GetNumberOfThreads is static + var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec); // GetNumberOfThreads is static. if (state.BaseRequest.BreakOnNonKeyFrames) { diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 065fa786e..df9d35b13 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -359,7 +359,7 @@ namespace Jellyfin.Api.Controllers private string GetCommandLineArguments(string outputPath, StreamState state) { var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions); - var threads = EncodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec); // GetNumberOfThreads is static + var threads = EncodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec); // GetNumberOfThreads is static. var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions); var format = !string.IsNullOrWhiteSpace(state.Request.SegmentContainer) ? "." + state.Request.SegmentContainer : ".ts"; var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 60c44cfcf..0fb8a9238 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2329,15 +2329,17 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Gets the number of threads. /// - public static int GetNumberOfThreads(EncodingJobInfo state, EncodingOptions encodingOptions, string outputVideoCodec) +#nullable enable + public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec) { - if (outputVideoCodec != null && string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)) { // per docs: // -threads number of threads to use for encoding, can't be 0 [auto] with VP8 // (recommended value : number of real cores - 1) return Math.Max(Environment.ProcessorCount - 1, 1); } + var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount; // Automatic @@ -2352,7 +2354,7 @@ namespace MediaBrowser.Controller.MediaEncoding return threads; } - +#nullable disable public void TryStreamCopy(EncodingJobInfo state) { if (state.VideoStream != null && CanStreamCopyVideo(state, state.VideoStream)) -- cgit v1.2.3 From 44ff7a48436123e7290c576e2ca97c400e9f0ffa Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 20 Nov 2020 08:06:28 +0100 Subject: Apply suggestions from code review --- Jellyfin.Api/Controllers/VideoHlsController.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 32 +++++++++++----------- 2 files changed, 17 insertions(+), 17 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index f1d6a5d45..2ac16de6b 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -394,7 +394,7 @@ namespace Jellyfin.Api.Controllers } else { - _logger.LogError("Invalid HLS segment container: " + segmentFormat); + _logger.LogError("Invalid HLS segment container: {SegmentFormat}", segmentFormat); } var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128 diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 388c5089c..9a6f1231f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -878,9 +878,9 @@ namespace MediaBrowser.Controller.MediaEncoding 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, "libx265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)) { args += " " + keyFrameArg; } @@ -977,7 +977,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } 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[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; @@ -993,7 +993,7 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -look_ahead 0"; } 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) { // following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead. switch (encodingOptions.EncoderPreset) @@ -1026,7 +1026,7 @@ 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) { switch (encodingOptions.EncoderPreset) { @@ -1147,10 +1147,10 @@ namespace MediaBrowser.Controller.MediaEncoding // libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case. if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) - && profile != null - && profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1) + || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + && profile != null + && profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1) { profile = "baseline"; } @@ -1199,7 +1199,7 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -level " + level; } else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) { // level option may cause NVENC to fail. // NVENC cannot adjust the given level, just throw an error. @@ -1920,8 +1920,8 @@ namespace MediaBrowser.Controller.MediaEncoding // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first else if (_mediaEncoder.SupportsHwaccel("vaapi") && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1 - && (string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase) - || string.Equals(outputVideoCodec, "libx265", StringComparison.OrdinalIgnoreCase))) + && (string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase) + || string.Equals(outputVideoCodec, "libx265", StringComparison.OrdinalIgnoreCase))) { /* [base]: SW scaling video to OutputSize @@ -2017,9 +2017,9 @@ namespace MediaBrowser.Controller.MediaEncoding requestedMaxHeight); if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) - || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) + || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) && width.HasValue && height.HasValue) { -- cgit v1.2.3