From 3e74377036ff0aea33b09381fb5e15efb0a6ded8 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 245239233c..555babc0e1 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -223,7 +223,7 @@ public class DynamicHlsHelper 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; var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 9b5a14c4de..6ce98d2317 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -183,7 +183,7 @@ public static class StreamingHelpers 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 e02a932b12..648358b598 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2111,50 +2111,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 is 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 aa99aaebc4e37ca1e16c11f72dd4a57038200179 Mon Sep 17 00:00:00 2001 From: knackebrot Date: Mon, 7 Nov 2022 00:15:04 +0100 Subject: [PATCH 02/12] Add audio vbr calculation --- .../Controllers/DynamicHlsController.cs | 29 +++++++-- .../MediaEncoding/EncodingHelper.cs | 64 ++++++++++++++++++- .../Configuration/EncodingOptions.cs | 6 ++ 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 4d8b4de24f..7b1830761c 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1685,14 +1685,25 @@ public class DynamicHlsController : BaseJellyfinApiController audioTranscodeParams += "-acodec " + audioCodec; - if (state.OutputAudioBitrate.HasValue) + var audioBitrate = state.OutputAudioBitrate; + var audioChannels = state.OutputAudioChannels; + + if (audioBitrate.HasValue) { - audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture); + string vbrParam; + if (_encodingOptions.EnableAudioVbr && (vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, audioBitrate.Value / audioChannels ?? 2)) != null) + { + audioTranscodeParams += vbrParam; + } + else + { + audioTranscodeParams += " -ab " + audioBitrate.Value.ToString(CultureInfo.InvariantCulture); + } } - if (state.OutputAudioChannels.HasValue) + if (audioChannels.HasValue) { - audioTranscodeParams += " -ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture); + audioTranscodeParams += " -ac " + audioChannels.Value.ToString(CultureInfo.InvariantCulture); } if (state.OutputAudioSampleRate.HasValue) @@ -1747,7 +1758,15 @@ public class DynamicHlsController : BaseJellyfinApiController if (bitrate.HasValue) { - args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); + string vbrParam; + if (_encodingOptions.EnableAudioVbr && (vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / channels ?? 2)) != null) + { + args += vbrParam; + } + else + { + args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); + } } if (state.OutputAudioSampleRate.HasValue) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 648358b598..551160934d 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2160,6 +2160,47 @@ namespace MediaBrowser.Controller.MediaEncoding return 128000; } + public string GetAudioVbrModeParam(string encoder, int bitratePerChannel) + { + if (encoder == "libfdk_aac") + { + return " -vbr:a " + bitratePerChannel switch + { + < 32000 => "1", + < 48000 => "2", + < 64000 => "3", + < 96000 => "4", + _ => "5" + }; + } + + if (encoder == "libmp3lame") + { + return " -qscale:a " + bitratePerChannel switch + { + < 48000 => "8", + < 64000 => "6", + < 88000 => "4", + < 112000 => "2", + _ => "0" + }; + } + + if (encoder == "libvorbis") + { + return " -qscale:a " + bitratePerChannel switch + { + < 40000 => "0", + < 56000 => "2", + < 80000 => "4", + < 112000 => "6", + _ => "8" + }; + } + + return null; + } + public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions) { var channels = state.OutputAudioChannels; @@ -5801,7 +5842,15 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue) { - args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); + string vbrParam; + if (encodingOptions.EnableAudioVbr && (vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / channels ?? 2)) != null) + { + args += vbrParam; + } + else + { + args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); + } } if (state.OutputAudioSampleRate.HasValue) @@ -5819,13 +5868,22 @@ namespace MediaBrowser.Controller.MediaEncoding var audioTranscodeParams = new List(); var bitrate = state.OutputAudioBitrate; + var channels = state.OutputAudioChannels; if (bitrate.HasValue) { - audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture)); + string vbrParam; + if (encodingOptions.EnableAudioVbr && (vbrParam = GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / channels ?? 2)) != null) + { + audioTranscodeParams.Add(vbrParam); + } + else + { + audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture)); + } } - if (state.OutputAudioChannels.HasValue) + if (channels.HasValue) { audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 0ff95a2e1f..b43e0f024b 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -14,6 +14,7 @@ public class EncodingOptions public EncodingOptions() { EnableFallbackFont = false; + EnableAudioVbr = false; DownMixAudioBoost = 2; DownMixStereoAlgorithm = DownMixStereoAlgorithms.None; MaxMuxingQueueSize = 2048; @@ -70,6 +71,11 @@ public class EncodingOptions /// public bool EnableFallbackFont { get; set; } + /// + /// Gets or sets a value indicating whether audio VBR is enabled. + /// + public bool EnableAudioVbr { get; set; } + /// /// Gets or sets the audio boost applied when downmixing audio. /// From 2e3b4bda7bad0b7d72f8b7bbd190d7c9cdb84061 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 24 Feb 2023 14:31:57 +0100 Subject: [PATCH 03/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 551160934d..bbb18e737c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2146,7 +2146,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); } @@ -2155,9 +2155,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 GetAudioVbrModeParam(string encoder, int bitratePerChannel) From f3840e0fdbc85d9009666b51b07bd3a21786cb39 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 24 Feb 2023 15:04:52 +0100 Subject: [PATCH 04/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 bbb18e737c..bcb16eb38e 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -614,6 +614,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 540d50bf15..3980353d15 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", @@ -59,10 +60,12 @@ namespace MediaBrowser.MediaEncoding.Encoder "aac_at", "libfdk_aac", "ac3", + "dca", "libmp3lame", "libopus", "libvorbis", "flac", + "truehd", "srt", "h264_amf", "hevc_amf", From 4a1498f614ca3f51908e8e7ead0ea921222f0f2b Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 24 Feb 2023 15:21:22 +0100 Subject: [PATCH 05/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 bcb16eb38e..c08e3a076c 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2130,37 +2130,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 9880a2b3e1ae0b88b5f5545681bd7394f3d35e84 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 27 Feb 2023 00:08:25 +0100 Subject: [PATCH 06/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 ab81bfb34c..b3982fefa5 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -799,6 +799,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 is not null) { @@ -834,6 +842,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 is not null) From c760a50d59280d7c2f78b44ba9788751ab980046 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 27 Feb 2023 16:03:12 +0100 Subject: [PATCH 07/12] Apply review suggestions --- .../MediaEncoding/EncodingHelper.cs | 16 ++++++++-------- MediaBrowser.Model/Dlna/StreamBuilder.cs | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index c08e3a076c..5f38ddbba3 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2130,7 +2130,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) @@ -2190,7 +2190,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetAudioVbrModeParam(string encoder, int bitratePerChannel) { - if (encoder == "libfdk_aac") + if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase)) { return " -vbr:a " + bitratePerChannel switch { @@ -2202,7 +2202,7 @@ namespace MediaBrowser.Controller.MediaEncoding }; } - if (encoder == "libmp3lame") + if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase)) { return " -qscale:a " + bitratePerChannel switch { @@ -2214,7 +2214,7 @@ namespace MediaBrowser.Controller.MediaEncoding }; } - if (encoder == "libvorbis") + if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase)) { return " -qscale:a " + bitratePerChannel switch { @@ -5870,8 +5870,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue) { - string vbrParam; - if (encodingOptions.EnableAudioVbr && (vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / channels ?? 2)) != null) + var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2)); + if (encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; } @@ -5900,8 +5900,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue) { - string vbrParam; - if (encodingOptions.EnableAudioVbr && (vbrParam = GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / channels ?? 2)) != null) + var vbrParam = GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / (channels ?? 2)); + if (encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams.Add(vbrParam); } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index b3982fefa5..1e05aea275 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -25,6 +25,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" }; /// /// Initializes a new instance of the class. @@ -803,8 +806,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; @@ -846,16 +848,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 16f2cca882de67622aa80a1964a788a2977253ff Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 28 Feb 2023 15:12:43 +0100 Subject: [PATCH 08/12] Apply review suggestions --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 7b1830761c..40ca2fcf76 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1690,8 +1690,8 @@ public class DynamicHlsController : BaseJellyfinApiController if (audioBitrate.HasValue) { - string vbrParam; - if (_encodingOptions.EnableAudioVbr && (vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, audioBitrate.Value / audioChannels ?? 2)) != null) + var vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, audioBitrate.Value / (audioChannels ?? 2)); + if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams += vbrParam; } @@ -1758,8 +1758,8 @@ public class DynamicHlsController : BaseJellyfinApiController if (bitrate.HasValue) { - string vbrParam; - if (_encodingOptions.EnableAudioVbr && (vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / channels ?? 2)) != null) + var vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / (channels ?? 2)); + if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; } From 1f15724398c1e05a3c5b72b7d7bd062413529890 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Thu, 2 Mar 2023 20:57:59 +0100 Subject: [PATCH 09/12] Use source audio bitrate if requested codec is lossless --- .../Controllers/DynamicHlsController.cs | 18 ++++---- Jellyfin.Api/Helpers/DynamicHlsHelper.cs | 13 +++++- Jellyfin.Api/Helpers/StreamingHelpers.cs | 16 ++++--- .../MediaEncoding/EncodingHelper.cs | 45 +++++++------------ 4 files changed, 48 insertions(+), 44 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 40ca2fcf76..4232d4c8a0 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -12,6 +12,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Extensions; using Jellyfin.MediaEncoding.Hls.Playlist; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; @@ -1688,7 +1689,7 @@ public class DynamicHlsController : BaseJellyfinApiController var audioBitrate = state.OutputAudioBitrate; var audioChannels = state.OutputAudioChannels; - if (audioBitrate.HasValue) + if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, audioBitrate.Value / (audioChannels ?? 2)); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) @@ -1717,11 +1718,11 @@ public class DynamicHlsController : BaseJellyfinApiController // 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"; } @@ -1755,10 +1756,9 @@ public class DynamicHlsController : BaseJellyfinApiController } var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) + if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = _encodingHelper.GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / (channels ?? 2)); + var vbrParam = _encodingHelper.GetAudioVbrModeParam(actualOutputAudioCodec, bitrate.Value / (channels ?? 2)); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; diff --git a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs index 555babc0e1..4486954c62 100644 --- a/Jellyfin.Api/Helpers/DynamicHlsHelper.cs +++ b/Jellyfin.Api/Helpers/DynamicHlsHelper.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Extensions; using Jellyfin.Api.Models.StreamingDtos; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; @@ -223,9 +224,17 @@ public class DynamicHlsHelper 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 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. diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 6ce98d2317..9c91dcc6fe 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -181,12 +181,18 @@ public static class StreamingHelpers : 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 is not null) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5f38ddbba3..b6118e600a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -86,6 +86,16 @@ namespace MediaBrowser.Controller.MediaEncoding { "truehd", 6 }, }; + public static readonly string[] LosslessAudioCodecs = new string[] + { + "alac", + "ape", + "flac", + "mlp", + "truehd", + "wavpack" + }; + public EncodingHelper( IApplicationPaths appPaths, IMediaEncoder mediaEncoder, @@ -2149,17 +2159,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)) { @@ -2172,20 +2171,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 GetAudioVbrModeParam(string encoder, int bitratePerChannel) @@ -5867,8 +5856,7 @@ namespace MediaBrowser.Controller.MediaEncoding } var bitrate = state.OutputAudioBitrate; - - if (bitrate.HasValue) + if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2)); if (encodingOptions.EnableAudioVbr && vbrParam is not null) @@ -5897,10 +5885,11 @@ namespace MediaBrowser.Controller.MediaEncoding var bitrate = state.OutputAudioBitrate; var channels = state.OutputAudioChannels; + var outputCodec = state.OutputAudioCodec; - if (bitrate.HasValue) + if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = GetAudioVbrModeParam(state.OutputAudioCodec, bitrate.Value / (channels ?? 2)); + var vbrParam = GetAudioVbrModeParam(outputCodec, bitrate.Value / (channels ?? 2)); if (encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams.Add(vbrParam); @@ -5916,12 +5905,12 @@ namespace MediaBrowser.Controller.MediaEncoding audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); } - if (!string.IsNullOrEmpty(state.OutputAudioCodec)) + if (!string.IsNullOrEmpty(outputCodec)) { audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state)); } - if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase)) { // opus only supports specific sampling rates var sampleRate = state.OutputAudioSampleRate; From ee4ffd64e132fce99cb6c297453623729e20bad4 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Wed, 15 Mar 2023 10:13:06 +0100 Subject: [PATCH 10/12] Prefer other codecs over DTS and TrueHD on transcode --- .../MediaEncoding/EncodingHelper.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b6118e600a..562b5d0229 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -5609,14 +5609,22 @@ namespace MediaBrowser.Controller.MediaEncoding } 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 3f6a23d7d094ddd36b01d9b565ce7b3ecce9c0ee Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 17 Mar 2023 11:49:07 +0100 Subject: [PATCH 11/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 562b5d0229..bf73321ab9 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2023,9 +2023,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 e57c33c4420da4336dacb59c0fc784a7000e71a0 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 1e05aea275..21e581801f 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -27,7 +27,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" }; /// /// Initializes a new instance of the class.