From 2b4bf81575aa5ece241b107fdb29caecd5d65337 Mon Sep 17 00:00:00 2001 From: knackebrot Date: Tue, 12 Jul 2022 15:54:17 +0200 Subject: [PATCH 01/12] Calculate output bitrate from output channel count --- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 2 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 2 +- .../MediaEncoding/EncodingHelper.cs | 53 +++++++++---------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 83cb12d2ac..bc3431e008 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -221,7 +221,7 @@ namespace Jellyfin.Api.Helpers sdrVideoUrl += "&AllowVideoStreamCopy=false"; var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); - var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0; + var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 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 b552df0a45..77f8161d00 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -184,7 +184,7 @@ namespace Jellyfin.Api.Helpers state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); - state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream); + state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels); state.OutputAudioCodec = streamingRequest.AudioCodec; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a7b613486f..7f2273dd52 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2066,50 +2066,47 @@ namespace MediaBrowser.Controller.MediaEncoding return Convert.ToInt32(scaleFactor * bitrate); } - public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream) + public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChannels) { - return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream); + return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels); } - public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream) + public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudioChannels) { if (audioStream == null) { return null; } - if (audioBitRate.HasValue && string.IsNullOrEmpty(audioCodec)) + var inputChannels = audioStream.Channels ?? 0; + var outputChannels = outputAudioChannels ?? 0; + + if (audioBitRate.HasValue && (string.IsNullOrEmpty(audioCodec) + || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))) { - return Math.Min(384000, audioBitRate.Value); + return (inputChannels, outputChannels) switch + { + (>= 6, >= 6 or 0) => Math.Min(640000, audioBitRate.Value), + (> 0, > 0) => Math.Min(outputChannels * 128000, audioBitRate.Value), + (> 0, _) => Math.Min(inputChannels * 128000, audioBitRate.Value), + (_, _) => Math.Min(384000, audioBitRate.Value) + }; } - if (audioBitRate.HasValue && !string.IsNullOrEmpty(audioCodec)) + if (audioBitRate.HasValue && (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))) { - if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase) - || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase) - || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase) - || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase) - || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase) - || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase)) + if ((audioStream.Channels ?? 0) >= 6) { - if ((audioStream.Channels ?? 0) >= 6) - { - return Math.Min(640000, audioBitRate.Value); - } - - return Math.Min(384000, audioBitRate.Value); + return Math.Min(3584000, 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); - } + return Math.Min(1536000, audioBitRate.Value); } // Empty bitrate area is not allow on iOS From 9908dad045de4dcb96933475aa7713cc7549300d Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 24 Feb 2023 14:31:57 +0100 Subject: [PATCH 02/12] Take channels into account when calculating fallback audio bitrate --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 7f2273dd52..cb809117fa 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2101,7 +2101,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (audioBitRate.HasValue && (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))) { - if ((audioStream.Channels ?? 0) >= 6) + if (inputChannels >= 6) { return Math.Min(3584000, audioBitRate.Value); } @@ -2110,9 +2110,9 @@ namespace MediaBrowser.Controller.MediaEncoding } // Empty bitrate area is not allow on iOS - // Default audio bitrate to 128K if it is not being requested + // Default audio bitrate to 128K per channel if it is not being requested // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options - return 128000; + return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 1); } public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions) From eafd785eb614bc11982a42e6fa44317936c41665 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 24 Feb 2023 15:04:52 +0100 Subject: [PATCH 03/12] Fix encoder checks for DTS and TrueHD --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 5 +++++ MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index cb809117fa..a113dffe28 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -548,6 +548,11 @@ namespace MediaBrowser.Controller.MediaEncoding return "flac"; } + if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase)) + { + return "dca"; + } + return codec.ToLowerInvariant(); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 9b4b1db947..89c1958289 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -25,11 +25,12 @@ namespace MediaBrowser.MediaEncoding.Encoder "mpeg2video", "mpeg4", "msmpeg4", - "dts", + "dca", "ac3", "aac", "mp3", "flac", + "truehd", "h264_qsv", "hevc_qsv", "mpeg2_qsv", @@ -58,10 +59,12 @@ namespace MediaBrowser.MediaEncoding.Encoder "aac", "libfdk_aac", "ac3", + "dca", "libmp3lame", "libopus", "libvorbis", "flac", + "truehd", "srt", "h264_amf", "hevc_amf", From 233e079e58665056b5c5284fda58bc93cebab890 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 24 Feb 2023 15:21:22 +0100 Subject: [PATCH 04/12] Add DTS and TrueHD bitrate limits, enforce bitrate limits if no bitrate is requested --- .../MediaEncoding/EncodingHelper.cs | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a113dffe28..5bb9ee98ca 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2085,37 +2085,60 @@ namespace MediaBrowser.Controller.MediaEncoding var inputChannels = audioStream.Channels ?? 0; var outputChannels = outputAudioChannels ?? 0; + var bitrate = audioBitRate.HasValue ? audioBitRate.Value : int.MaxValue; - if (audioBitRate.HasValue && (string.IsNullOrEmpty(audioCodec) + if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase) - || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))) + || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase)) { return (inputChannels, outputChannels) switch { - (>= 6, >= 6 or 0) => Math.Min(640000, audioBitRate.Value), - (> 0, > 0) => Math.Min(outputChannels * 128000, audioBitRate.Value), - (> 0, _) => Math.Min(inputChannels * 128000, audioBitRate.Value), - (_, _) => Math.Min(384000, audioBitRate.Value) + (>= 6, >= 6 or 0) => Math.Min(640000, bitrate), + (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate), + (> 0, _) => Math.Min(inputChannels * 128000, bitrate), + (_, _) => Math.Min(384000, bitrate) }; } - if (audioBitRate.HasValue && (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase) - || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))) + if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase)) { if (inputChannels >= 6) { - return Math.Min(3584000, audioBitRate.Value); + return Math.Min(3584000, bitrate); } - return Math.Min(1536000, audioBitRate.Value); + return Math.Min(1536000, bitrate); + } + + if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase) + || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase)) + { + return (inputChannels, outputChannels) switch + { + (>= 6, >= 6 or 0) => Math.Min(768000, bitrate), + (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate), + (> 0, _) => Math.Min(inputChannels * 136000, bitrate), + (_, _) => Math.Min(672000, bitrate) + }; + } + + if (string.Equals(audioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) + { + return (inputChannels, outputChannels) switch + { + (> 0, > 0) => Math.Min(outputChannels * 768000, bitrate), + (> 0, _) => Math.Min(inputChannels * 768000, bitrate), + (_, _) => Math.Min(768000, bitrate) + }; } // Empty bitrate area is not allow on iOS - // Default audio bitrate to 128K per channel if it is not being requested + // Default audio bitrate to 128K per channel if we don't have codec specific defaults // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 1); } From 060097703bbd9eefb186b4a55d28f9425ee9ccf5 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 27 Feb 2023 00:08:25 +0100 Subject: [PATCH 05/12] Enforce HLS codec restrictions --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 4a38c3739e..e56d8591cb 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -770,6 +770,14 @@ namespace MediaBrowser.Model.Dlna { // Prefer matching video codecs var videoCodecs = ContainerProfile.SplitValue(videoCodec); + + // Enforce HLS video codec restrictions + if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + var supportedHlsVideoCodecs = new List { "h264", "hevc" }; + videoCodecs = videoCodecs.Where(codec => supportedHlsVideoCodecs.Contains(codec)).ToArray(); + } + var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null; if (directVideoCodec != null) { @@ -805,6 +813,22 @@ namespace MediaBrowser.Model.Dlna // Prefer matching audio codecs, could do better here var audioCodecs = ContainerProfile.SplitValue(audioCodec); + + // Enforce HLS audio codec restrictions + if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) + { + var supportedHlsAudioCodecs = new List { "aac", "ac3", "eac3", "mp3" }; + if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase)) + { + // fMP4 supports more codecs than TS + supportedHlsAudioCodecs.Add("alac"); + supportedHlsAudioCodecs.Add("flac"); + supportedHlsAudioCodecs.Add("opus"); + } + + audioCodecs = audioCodecs.Where(codec => supportedHlsAudioCodecs.Contains(codec)).ToArray(); + } + var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)); playlistItem.AudioCodecs = audioCodecs; if (directAudioStream != null) From 31712e5da9cac29d37deba9f7368666a808c62f2 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 27 Feb 2023 16:03:12 +0100 Subject: [PATCH 06/12] Apply review suggestions --- .../MediaEncoding/EncodingHelper.cs | 2 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5bb9ee98ca..6f3e9c6994 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2085,7 +2085,7 @@ namespace MediaBrowser.Controller.MediaEncoding var inputChannels = audioStream.Channels ?? 0; var outputChannels = outputAudioChannels ?? 0; - var bitrate = audioBitRate.HasValue ? audioBitRate.Value : int.MaxValue; + var bitrate = audioBitRate ?? int.MaxValue; if (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index e56d8591cb..682c1155b5 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -23,6 +23,9 @@ namespace MediaBrowser.Model.Dlna private readonly ILogger _logger; private readonly ITranscoderSupport _transcoderSupport; + private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc" }; + private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" }; + private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus" }; public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger) { @@ -774,8 +777,7 @@ namespace MediaBrowser.Model.Dlna // Enforce HLS video codec restrictions if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) { - var supportedHlsVideoCodecs = new List { "h264", "hevc" }; - videoCodecs = videoCodecs.Where(codec => supportedHlsVideoCodecs.Contains(codec)).ToArray(); + videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToArray(); } var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null; @@ -817,16 +819,14 @@ namespace MediaBrowser.Model.Dlna // Enforce HLS audio codec restrictions if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) { - var supportedHlsAudioCodecs = new List { "aac", "ac3", "eac3", "mp3" }; if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase)) { - // fMP4 supports more codecs than TS - supportedHlsAudioCodecs.Add("alac"); - supportedHlsAudioCodecs.Add("flac"); - supportedHlsAudioCodecs.Add("opus"); + audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToArray(); + } + else + { + audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToArray(); } - - audioCodecs = audioCodecs.Where(codec => supportedHlsAudioCodecs.Contains(codec)).ToArray(); } var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)); From 3d735e242af4f3606a2947cf75a99e8284f1615c Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Thu, 2 Mar 2023 20:57:59 +0100 Subject: [PATCH 07/12] Use source audio bitrate if requested codec is lossless --- .../Controllers/DynamicHlsController.cs | 16 +++---- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 24 ++++++++-- Jellyfin.Api/Helpers/StreamingHelpers.cs | 16 +++++-- .../MediaEncoding/EncodingHelper.cs | 46 +++++++++---------- 4 files changed, 60 insertions(+), 42 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index ba5d0e41cf..ffd5f6818f 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -13,6 +13,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Extensions; using Jellyfin.MediaEncoding.Hls.Playlist; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; @@ -1694,7 +1695,7 @@ namespace Jellyfin.Api.Controllers audioTranscodeParams += "-acodec " + audioCodec; - if (state.OutputAudioBitrate.HasValue) + if (state.OutputAudioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture); } @@ -1715,11 +1716,11 @@ namespace Jellyfin.Api.Controllers // dts, flac, opus and truehd are experimental in mp4 muxer var strictArgs = string.Empty; - - if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.ActualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) - || string.Equals(state.ActualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) + var actualOutputAudioCodec = state.ActualOutputAudioCodec; + if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) + || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) { strictArgs = " -strict -2"; } @@ -1748,8 +1749,7 @@ namespace Jellyfin.Api.Controllers } var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) + if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); } diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index bc3431e008..26402c002e 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -220,11 +221,26 @@ namespace Jellyfin.Api.Helpers var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main"); sdrVideoUrl += "&AllowVideoStreamCopy=false"; - var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); - var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0; - var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; + var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); + var sdrOutputAudioBitrate = 0; + if (EncodingHelper.LosslessAudioCodecs.Contains(state.VideoRequest.AudioCodec, StringComparison.OrdinalIgnoreCase)) + { + sdrOutputAudioBitrate = state.AudioStream.BitRate ?? 0; + } + else + { + sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0; + } - AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); + var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; + var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); + + // Provide a workaround for the case issue between flac and fLaC. + flacWaPlaylist = ApplyFlacCaseWorkaround(state, sdrPlaylist.ToString()); + if (!string.IsNullOrEmpty(flacWaPlaylist)) + { + builder.Append(flacWaPlaylist); + } // Restore the video codec state.OutputVideoCodec = "copy"; diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 77f8161d00..963931a24c 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -182,12 +182,18 @@ namespace Jellyfin.Api.Helpers : GetOutputFileExtension(state, mediaSource); } - state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); - - state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels); - - state.OutputAudioCodec = streamingRequest.AudioCodec; + var outputAudioCodec = streamingRequest.AudioCodec; + if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec)) + { + state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0; + } + else + { + state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels) ?? 0; + } + state.OutputAudioCodec = outputAudioCodec; + state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); if (state.VideoRequest != null) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 6f3e9c6994..ebae38a4d9 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -62,6 +62,16 @@ namespace MediaBrowser.Controller.MediaEncoding "Main10" }; + public static readonly string[] LosslessAudioCodecs = new string[] + { + "alac", + "ape", + "flac", + "mlp", + "truehd", + "wavpack" + }; + public EncodingHelper( IApplicationPaths appPaths, IMediaEncoder mediaEncoder, @@ -2104,17 +2114,6 @@ namespace MediaBrowser.Controller.MediaEncoding }; } - if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase) - || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase)) - { - if (inputChannels >= 6) - { - return Math.Min(3584000, bitrate); - } - - return Math.Min(1536000, bitrate); - } - if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase) || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase)) { @@ -2127,20 +2126,10 @@ namespace MediaBrowser.Controller.MediaEncoding }; } - if (string.Equals(audioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) - { - return (inputChannels, outputChannels) switch - { - (> 0, > 0) => Math.Min(outputChannels * 768000, bitrate), - (> 0, _) => Math.Min(inputChannels * 768000, bitrate), - (_, _) => Math.Min(768000, bitrate) - }; - } - // Empty bitrate area is not allow on iOS // Default audio bitrate to 128K per channel if we don't have codec specific defaults // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options - return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 1); + return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2); } public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions) @@ -5562,7 +5551,7 @@ namespace MediaBrowser.Controller.MediaEncoding var bitrate = state.OutputAudioBitrate; - if (bitrate.HasValue) + if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase)) { args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); } @@ -5582,8 +5571,10 @@ namespace MediaBrowser.Controller.MediaEncoding var audioTranscodeParams = new List(); var bitrate = state.OutputAudioBitrate; + var channels = state.OutputAudioChannels; + var outputCodec = state.OutputAudioCodec; - if (bitrate.HasValue) + if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase)) { audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture)); } @@ -5593,7 +5584,12 @@ namespace MediaBrowser.Controller.MediaEncoding audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); } - if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(outputCodec)) + { + audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state)); + } + + if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase)) { // opus only supports specific sampling rates var sampleRate = state.OutputAudioSampleRate; From 2cc0869144646c46db8263b7e05e5f5314844677 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Wed, 15 Mar 2023 10:13:06 +0100 Subject: [PATCH 08/12] Prefer other codecs over DTS and TrueHD on transcode --- .../MediaEncoding/EncodingHelper.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ebae38a4d9..597503f071 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -5299,15 +5299,23 @@ namespace MediaBrowser.Controller.MediaEncoding return; } - var inputChannels = audioStream == null ? 6 : audioStream.Channels ?? 6; + var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6; + var shiftAudioCodecs = new List(); if (inputChannels >= 6) { - return; + // DTS and TrueHD are not supported by HLS + // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used + shiftAudioCodecs.Add("dca"); + shiftAudioCodecs.Add("truehd"); + } + else + { + // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure + // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used + shiftAudioCodecs.Add("ac3"); + shiftAudioCodecs.Add("eac3"); } - // Transcoding to 2ch ac3 almost always causes a playback failure - // Keep it in the supported codecs list, but shift it to the end of the list so that if transcoding happens, another codec is used - var shiftAudioCodecs = new[] { "ac3", "eac3" }; if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase))) { return; From 3a5503be5ff834ec399189c0ca658385eb17037a Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 17 Mar 2023 11:49:07 +0100 Subject: [PATCH 09/12] Fix condition in CanStreamCopyAudio --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 597503f071..da64702259 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1970,9 +1970,9 @@ namespace MediaBrowser.Controller.MediaEncoding } } - // Video bitrate must fall within requested value + // Audio bitrate must fall within requested value if (request.AudioBitRate.HasValue - && audioStream.BitDepth.HasValue + && audioStream.BitRate.HasValue && audioStream.BitRate.Value > request.AudioBitRate.Value) { return false; From 9799136daf212886bac1fbfb242fc538466c7fe5 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Thu, 13 Oct 2022 18:49:25 +0800 Subject: [PATCH 10/12] Add support for OPUS and fixes for FLAC case issue in HLS Signed-off-by: nyanmisaka --- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 73 +++++++++++++------ Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs | 16 +++- 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 26402c002e..69dc4e1ec5 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -204,6 +204,13 @@ namespace Jellyfin.Api.Helpers if (state.VideoStream != null && state.VideoRequest != null) { + // Provide a workaround for the case issue between flac and fLaC. + var flacWaPlaylist = ApplyFlacCaseWorkaround(state, basicPlaylist.ToString()); + if (!String.IsNullOrEmpty(flacWaPlaylist)) + { + builder.Append(flacWaPlaylist); + } + var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); // Provide SDR HEVC entrance for backward compatibility. @@ -221,26 +228,26 @@ namespace Jellyfin.Api.Helpers var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main"); sdrVideoUrl += "&AllowVideoStreamCopy=false"; - var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); - var sdrOutputAudioBitrate = 0; - if (EncodingHelper.LosslessAudioCodecs.Contains(state.VideoRequest.AudioCodec, StringComparison.OrdinalIgnoreCase)) - { - sdrOutputAudioBitrate = state.AudioStream.BitRate ?? 0; - } - else - { - sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0; - } - - var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; - var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); - - // Provide a workaround for the case issue between flac and fLaC. - flacWaPlaylist = ApplyFlacCaseWorkaround(state, sdrPlaylist.ToString()); - if (!string.IsNullOrEmpty(flacWaPlaylist)) - { - builder.Append(flacWaPlaylist); - } + var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); + var sdrOutputAudioBitrate = 0; + if (EncodingHelper.LosslessAudioCodecs.Contains(state.VideoRequest.AudioCodec, StringComparison.OrdinalIgnoreCase)) + { + sdrOutputAudioBitrate = state.AudioStream.BitRate ?? 0; + } + else + { + sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0; + } + + var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; + var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); + + // Provide a workaround for the case issue between flac and fLaC. + flacWaPlaylist = ApplyFlacCaseWorkaround(state, sdrPlaylist.ToString()); + if (!String.IsNullOrEmpty(flacWaPlaylist)) + { + builder.Append(flacWaPlaylist); + } // Restore the video codec state.OutputVideoCodec = "copy"; @@ -270,6 +277,13 @@ namespace Jellyfin.Api.Helpers state.VideoStream.Level = originalLevel; var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField); builder.Append(newPlaylist); + + // Provide a workaround for the case issue between flac and fLaC. + flacWaPlaylist = ApplyFlacCaseWorkaround(state, newPlaylist); + if (!String.IsNullOrEmpty(flacWaPlaylist)) + { + builder.Append(flacWaPlaylist); + } } } @@ -628,6 +642,11 @@ namespace Jellyfin.Api.Helpers return HlsCodecStringHelpers.GetALACString(); } + if (string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) + { + return HlsCodecStringHelpers.GetOPUSString(); + } + return string.Empty; } @@ -726,7 +745,19 @@ namespace Jellyfin.Api.Helpers return oldPlaylist.Replace( oldValue.ToString(), newValue.ToString(), - StringComparison.OrdinalIgnoreCase); + StringComparison.Ordinal); + } + + private string ApplyFlacCaseWorkaround(StreamState state, string srcPlaylist) + { + if (!string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)) + { + return string.Empty; + } + + var newPlaylist = srcPlaylist.Replace(",flac\"", ",fLaC\"", StringComparison.Ordinal); + + return newPlaylist.Contains(",fLaC\"", StringComparison.Ordinal) ? newPlaylist : string.Empty; } } } diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs index a5369c441c..cbe82979bc 100644 --- a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs +++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs @@ -27,13 +27,18 @@ namespace Jellyfin.Api.Helpers /// /// Codec name for FLAC. /// - public const string FLAC = "fLaC"; + public const string FLAC = "flac"; /// /// Codec name for ALAC. /// public const string ALAC = "alac"; + /// + /// Codec name for OPUS. + /// + public const string OPUS = "opus"; + /// /// Gets a MP3 codec string. /// @@ -101,6 +106,15 @@ namespace Jellyfin.Api.Helpers return ALAC; } + /// + /// Gets an OPUS codec string. + /// + /// OPUS codec string. + public static string GetOPUSString() + { + return OPUS; + } + /// /// Gets a H.264 codec string. /// From e0344353cd04af6a700788b4a6528c5d5bb446ed Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 27 Nov 2022 08:02:32 -0700 Subject: [PATCH 11/12] Apply suggestions from code review Co-authored-by: Bond-009 --- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 69dc4e1ec5..f011990f6f 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -206,7 +206,7 @@ namespace Jellyfin.Api.Helpers { // Provide a workaround for the case issue between flac and fLaC. var flacWaPlaylist = ApplyFlacCaseWorkaround(state, basicPlaylist.ToString()); - if (!String.IsNullOrEmpty(flacWaPlaylist)) + if (!string.IsNullOrEmpty(flacWaPlaylist)) { builder.Append(flacWaPlaylist); } @@ -244,7 +244,7 @@ namespace Jellyfin.Api.Helpers // Provide a workaround for the case issue between flac and fLaC. flacWaPlaylist = ApplyFlacCaseWorkaround(state, sdrPlaylist.ToString()); - if (!String.IsNullOrEmpty(flacWaPlaylist)) + if (!string.IsNullOrEmpty(flacWaPlaylist)) { builder.Append(flacWaPlaylist); } @@ -280,7 +280,7 @@ namespace Jellyfin.Api.Helpers // Provide a workaround for the case issue between flac and fLaC. flacWaPlaylist = ApplyFlacCaseWorkaround(state, newPlaylist); - if (!String.IsNullOrEmpty(flacWaPlaylist)) + if (!string.IsNullOrEmpty(flacWaPlaylist)) { builder.Append(flacWaPlaylist); } From 876a6b9aec81b2facd5f50e5443ca979bf4c3869 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sun, 19 Mar 2023 15:07:01 +0100 Subject: [PATCH 12/12] Add DCA and TrueHD to fMP4 audio codecs to support remuxing --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 682c1155b5..6d1b38bd07 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Model.Dlna private readonly ITranscoderSupport _transcoderSupport; private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc" }; private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" }; - private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus" }; + private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" }; public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger) {