From 78929418cc3aa783db3abaf99d15ba55e41cc2ac Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 22 Apr 2024 21:02:20 +0800 Subject: [PATCH 1/8] Use better audio VBR settings LAME's VBR mode only has advantage over a certain bitrate range. For very low and very high bitrate, use the ABR mode instead. aac_at's CVBR mode produces very good quality and is not worse than its TVBR mode in blind testing. Use this mode for convenience. The ffmpeg native aac encoder will have quality regression with its VBR mode. Always use CBR mode for ffmpeg's native aac encoder. Signed-off-by: gnattu --- .../Controllers/DynamicHlsController.cs | 4 +-- .../MediaEncoding/EncodingHelper.cs | 33 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 68602c80d5..1e99a9ee3a 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1671,7 +1671,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value / (audioChannels ?? 2)); + var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value, audioChannels ?? 2); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams += vbrParam; @@ -1724,7 +1724,7 @@ public class DynamicHlsController : BaseJellyfinApiController var bitrate = state.OutputAudioBitrate; if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value / (channels ?? 2)); + var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value, channels ?? 2); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9d7d2fd127..a122f3d19f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2586,8 +2586,9 @@ namespace MediaBrowser.Controller.MediaEncoding return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2); } - public string GetAudioVbrModeParam(string encoder, int bitratePerChannel) + public string GetAudioVbrModeParam(string encoder, int bitrate, int channels) { + var bitratePerChannel = bitrate / channels; if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase)) { return " -vbr:a " + bitratePerChannel switch @@ -2602,14 +2603,26 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase)) { - return " -qscale:a " + bitratePerChannel switch + // lame's VBR is only good for a certain bitrate range + // For very low and very high bitrate, use abr mode + if (bitratePerChannel is < 122500 and > 48000) { - < 48000 => "8", - < 64000 => "6", - < 88000 => "4", - < 112000 => "2", - _ => "0" - }; + return " -qscale:a " + bitratePerChannel switch + { + < 64000 => "6", + < 88000 => "4", + < 112000 => "2", + _ => "0" + }; + } + + return " -abr:a 1" + " -b:a " + bitrate; + } + + if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase)) + { + // aac_at's CVBR mode + return " -aac_at_mode:a 2" + " -b:a " + bitrate; } if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase)) @@ -6962,7 +6975,7 @@ namespace MediaBrowser.Controller.MediaEncoding var bitrate = state.OutputAudioBitrate; if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2)); + var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2); if (encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; @@ -6993,7 +7006,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value / (channels ?? 2)); + var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2); if (encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams.Add(vbrParam); From 0381c5a288bc56e20aa5def05e3d41bacf3519a7 Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 6 May 2024 12:48:50 +0800 Subject: [PATCH 2/8] Add EnableAudioVbrEncoding to TranscodingProfile This will allow the client selectively disable VBR audio when it causes problems. Signed-off-by: gnattu --- Jellyfin.Api/Controllers/AudioController.cs | 14 +++-- .../Controllers/DynamicHlsController.cs | 53 +++++++++++++------ Jellyfin.Api/Controllers/VideosController.cs | 14 +++-- .../MediaEncoding/BaseEncodingJobOptions.cs | 2 + .../MediaEncoding/EncodingHelper.cs | 4 +- .../MediaEncoding/EncodingJobInfo.cs | 8 +++ MediaBrowser.Model/Dlna/StreamBuilder.cs | 1 + MediaBrowser.Model/Dlna/StreamInfo.cs | 4 ++ MediaBrowser.Model/Dlna/TranscodingProfile.cs | 4 ++ 9 files changed, 78 insertions(+), 26 deletions(-) diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 72be555133..06b7e6c9fd 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -83,6 +83,7 @@ public class AudioController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Audio stream returned. /// A containing the audio file. [HttpGet("{itemId}/stream", Name = "GetAudioStream")] @@ -138,7 +139,8 @@ public class AudioController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary? streamOptions) + [FromQuery] Dictionary? streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { StreamingRequestDto streamingRequest = new StreamingRequestDto { @@ -189,7 +191,8 @@ public class AudioController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Static, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false); @@ -247,6 +250,7 @@ public class AudioController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Audio stream returned. /// A containing the audio file. [HttpGet("{itemId}/stream.{container}", Name = "GetAudioStreamByContainer")] @@ -302,7 +306,8 @@ public class AudioController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary? streamOptions) + [FromQuery] Dictionary? streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { StreamingRequestDto streamingRequest = new StreamingRequestDto { @@ -353,7 +358,8 @@ public class AudioController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Static, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 1e99a9ee3a..a7fc9928e9 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -156,6 +156,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The max width. /// Optional. The max height. /// Optional. Whether to enable subtitles in the manifest. + /// Optional. Whether to enable Audio Encoding. /// Hls live stream retrieved. /// A containing the hls file. [HttpGet("Videos/{itemId}/live.m3u8")] @@ -213,7 +214,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] Dictionary streamOptions, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, - [FromQuery] bool? enableSubtitlesInManifest) + [FromQuery] bool? enableSubtitlesInManifest, + [FromQuery] bool? enableAudioVbrEncoding) { VideoRequestDto streamingRequest = new VideoRequestDto { @@ -267,7 +269,8 @@ public class DynamicHlsController : BaseJellyfinApiController StreamOptions = streamOptions, MaxHeight = maxHeight, MaxWidth = maxWidth, - EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true + EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; // CTS lifecycle is managed internally. @@ -393,6 +396,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The streaming options. /// Enable adaptive bitrate streaming. /// Enable trickplay image playlists being added to master playlist. + /// Whether to enable Audio Encoding. /// Video stream returned. /// A containing the playlist file. [HttpGet("Videos/{itemId}/master.m3u8")] @@ -451,7 +455,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, [FromQuery] bool enableAdaptiveBitrateStreaming = true, - [FromQuery] bool enableTrickplay = true) + [FromQuery] bool enableTrickplay = true, + [FromQuery] bool enableAudioVbrEncoding = true) { var streamingRequest = new HlsVideoRequestDto { @@ -505,7 +510,8 @@ public class DynamicHlsController : BaseJellyfinApiController Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming, - EnableTrickplay = enableTrickplay + EnableTrickplay = enableTrickplay, + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); @@ -564,6 +570,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The . /// Optional. The streaming options. /// Enable adaptive bitrate streaming. + /// Optional. Whether to enable Audio Encoding. /// Audio stream returned. /// A containing the playlist file. [HttpGet("Audio/{itemId}/master.m3u8")] @@ -620,7 +627,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool enableAdaptiveBitrateStreaming = true) + [FromQuery] bool enableAdaptiveBitrateStreaming = true, + [FromQuery] bool enableAudioVbrEncoding = true) { var streamingRequest = new HlsAudioRequestDto { @@ -671,7 +679,8 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming + EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming, + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); @@ -730,6 +739,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Videos/{itemId}/main.m3u8")] @@ -785,7 +795,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new VideoRequestDto @@ -838,7 +849,8 @@ public class DynamicHlsController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) @@ -897,6 +909,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Audio stream returned. /// A containing the audio file. [HttpGet("Audio/{itemId}/main.m3u8")] @@ -951,7 +964,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new StreamingRequestDto @@ -1002,7 +1016,8 @@ public class DynamicHlsController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) @@ -1067,6 +1082,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")] @@ -1128,7 +1144,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { var streamingRequest = new VideoRequestDto { @@ -1183,7 +1200,8 @@ public class DynamicHlsController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await GetDynamicSegment(streamingRequest, segmentId) @@ -1247,6 +1265,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")] @@ -1307,7 +1326,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { var streamingRequest = new StreamingRequestDto { @@ -1360,7 +1380,8 @@ public class DynamicHlsController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await GetDynamicSegment(streamingRequest, segmentId) @@ -1672,7 +1693,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value, audioChannels ?? 2); - if (_encodingOptions.EnableAudioVbr && vbrParam is not null) + if (_encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) { audioTranscodeParams += vbrParam; } @@ -1725,7 +1746,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value, channels ?? 2); - if (_encodingOptions.EnableAudioVbr && vbrParam is not null) + if (_encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) { args += vbrParam; } diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index a9e1d44846..f6050bdf77 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -306,6 +306,7 @@ public class VideosController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("{itemId}/stream")] @@ -363,7 +364,8 @@ public class VideosController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; // CTS lifecycle is managed internally. @@ -419,7 +421,8 @@ public class VideosController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; var state = await StreamingHelpers.GetStreamingState( @@ -544,6 +547,7 @@ public class VideosController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("{itemId}/stream.{container}")] @@ -601,7 +605,8 @@ public class VideosController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { return GetVideoStream( itemId, @@ -654,6 +659,7 @@ public class VideosController : BaseJellyfinApiController audioStreamIndex, videoStreamIndex, context, - streamOptions); + streamOptions, + enableAudioVbrEncoding); } } diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs index 29dd190ab7..03ec6c658a 100644 --- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs @@ -191,6 +191,8 @@ namespace MediaBrowser.Controller.MediaEncoding public Dictionary StreamOptions { get; set; } + public bool EnableAudioVbrEncoding { get; set; } + public string GetOption(string qualifier, string name) { var value = GetOption(qualifier + "-" + name); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a122f3d19f..ce2414cd1a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -6976,7 +6976,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2); - if (encodingOptions.EnableAudioVbr && vbrParam is not null) + if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) { args += vbrParam; } @@ -7007,7 +7007,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2); - if (encodingOptions.EnableAudioVbr && vbrParam is not null) + if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) { audioTranscodeParams.Add(vbrParam); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index f2a0b906dc..526aa8f99b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -508,6 +508,14 @@ namespace MediaBrowser.Controller.MediaEncoding } } + public bool EnableAudioVbrEncoding + { + get + { + return BaseRequest.EnableAudioVbrEncoding; + } + } + public int HlsListSize => 0; public bool EnableBreakOnNonKeyFrames(string videoCodec) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 55d1c3d51a..ba958c0304 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -542,6 +542,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; playlistItem.BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames; + playlistItem.EnableAudioVbrEncoding = transcodingProfile.EnableAudioVbrEncoding; if (transcodingProfile.MinSegments > 0) { diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 75e5b6d180..c8a341d413 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -108,6 +108,8 @@ namespace MediaBrowser.Model.Dlna public string? MediaSourceId => MediaSource?.Id; + public bool EnableAudioVbrEncoding { get; set; } + public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay) && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay; @@ -768,6 +770,8 @@ namespace MediaBrowser.Model.Dlna } list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + + list.Add(new NameValuePair("EnableAudioVbrEncoding", item.EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); } list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty)); diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 891448c664..d535e8c18d 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -70,6 +70,10 @@ namespace MediaBrowser.Model.Dlna public ProfileCondition[] Conditions { get; set; } + [DefaultValue(true)] + [XmlAttribute("enableAudioVbrEncoding")] + public bool EnableAudioVbrEncoding { get; set; } + public string[] GetAudioCodecs() { return ContainerProfile.SplitValue(AudioCodec); From 7ad432be23cb8fdc42e95837e772d8e2538aa9c7 Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 6 May 2024 12:54:59 +0800 Subject: [PATCH 3/8] Default EnableAudioVbrEncoding to true in TranscodingProfile This will match the old behavior when user takes no action for clients Signed-off-by: gnattu --- MediaBrowser.Model/Dlna/TranscodingProfile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index d535e8c18d..a556799deb 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -72,7 +72,7 @@ namespace MediaBrowser.Model.Dlna [DefaultValue(true)] [XmlAttribute("enableAudioVbrEncoding")] - public bool EnableAudioVbrEncoding { get; set; } + public bool EnableAudioVbrEncoding { get; set; } = true; public string[] GetAudioCodecs() { From d5eb8fc1219fb1566ee28a0cfe693c6e5da6f605 Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 6 May 2024 13:16:06 +0800 Subject: [PATCH 4/8] Also make UniversalAudioController check client VBR settings Signed-off-by: gnattu --- Jellyfin.Api/Controllers/UniversalAudioController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 1d4adae067..c858c17ba0 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -81,6 +81,7 @@ public class UniversalAudioController : BaseJellyfinApiController /// Optional. The maximum audio sample rate. /// Optional. The maximum audio bit depth. /// Optional. Whether to enable remote media. + /// Optional. Whether to enable Audio Encoding. /// Optional. Whether to break on non key frames. /// Whether to enable redirection. Defaults to true. /// Audio stream returned. @@ -111,6 +112,7 @@ public class UniversalAudioController : BaseJellyfinApiController [FromQuery] int? maxAudioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] bool? enableRemoteMedia, + [FromQuery] bool? enableAudioVbrEncoding, [FromQuery] bool breakOnNonKeyFrames = false, [FromQuery] bool enableRedirection = true) { @@ -209,7 +211,8 @@ public class UniversalAudioController : BaseJellyfinApiController TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(), Context = EncodingContext.Static, StreamOptions = new Dictionary(), - EnableAdaptiveBitrateStreaming = true + EnableAdaptiveBitrateStreaming = true, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType.Hls, dynamicHlsRequestDto, true) From 24910348a1648f9ec05940e3bec137bffa4814a8 Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 17 Jul 2024 21:09:30 +0800 Subject: [PATCH 5/8] Force channel to be >= 1 Co-authored-by: Cody Robibero --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ce2414cd1a..2c35f0a1ec 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2588,7 +2588,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetAudioVbrModeParam(string encoder, int bitrate, int channels) { - var bitratePerChannel = bitrate / channels; + var bitratePerChannel = bitrate / Math.Max(channels, 1); if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase)) { return " -vbr:a " + bitratePerChannel switch From 9acc93853e4ce79cfdd5e6e998c0f89cdb9e7bed Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 17 Jul 2024 21:27:28 +0800 Subject: [PATCH 6/8] Specify the default value in api spec Signed-off-by: gnattu --- Jellyfin.Api/Controllers/AudioController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 06b7e6c9fd..c620057353 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -140,7 +140,7 @@ public class AudioController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary? streamOptions, - [FromQuery] bool? enableAudioVbrEncoding) + [FromQuery] bool enableAudioVbrEncoding = true) { StreamingRequestDto streamingRequest = new StreamingRequestDto { @@ -192,7 +192,7 @@ public class AudioController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Static, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false); From fd5df98616408fe54944ff39310aacd1751c45b5 Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 17 Jul 2024 21:52:44 +0800 Subject: [PATCH 7/8] Move default value to api spec Signed-off-by: gnattu --- Jellyfin.Api/Controllers/AudioController.cs | 4 ++-- .../Controllers/DynamicHlsController.cs | 20 +++++++++---------- .../Controllers/UniversalAudioController.cs | 4 ++-- Jellyfin.Api/Controllers/VideosController.cs | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index c620057353..8954c8ef5c 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -307,7 +307,7 @@ public class AudioController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary? streamOptions, - [FromQuery] bool? enableAudioVbrEncoding) + [FromQuery] bool enableAudioVbrEncoding = true) { StreamingRequestDto streamingRequest = new StreamingRequestDto { @@ -359,7 +359,7 @@ public class AudioController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Static, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index a7fc9928e9..429cc542ca 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -215,7 +215,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, [FromQuery] bool? enableSubtitlesInManifest, - [FromQuery] bool? enableAudioVbrEncoding) + [FromQuery] bool enableAudioVbrEncoding = true) { VideoRequestDto streamingRequest = new VideoRequestDto { @@ -270,7 +270,7 @@ public class DynamicHlsController : BaseJellyfinApiController MaxHeight = maxHeight, MaxWidth = maxWidth, EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true, - EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true + EnableAudioVbrEncoding = enableAudioVbrEncoding }; // CTS lifecycle is managed internally. @@ -796,7 +796,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool? enableAudioVbrEncoding) + [FromQuery] bool enableAudioVbrEncoding = true) { using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new VideoRequestDto @@ -850,7 +850,7 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) @@ -965,7 +965,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool? enableAudioVbrEncoding) + [FromQuery] bool enableAudioVbrEncoding = true) { using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new StreamingRequestDto @@ -1017,7 +1017,7 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) @@ -1145,7 +1145,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool? enableAudioVbrEncoding) + [FromQuery] bool enableAudioVbrEncoding = true) { var streamingRequest = new VideoRequestDto { @@ -1201,7 +1201,7 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await GetDynamicSegment(streamingRequest, segmentId) @@ -1327,7 +1327,7 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool? enableAudioVbrEncoding) + [FromQuery] bool enableAudioVbrEncoding = true) { var streamingRequest = new StreamingRequestDto { @@ -1381,7 +1381,7 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await GetDynamicSegment(streamingRequest, segmentId) diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index c858c17ba0..9087945128 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -112,7 +112,7 @@ public class UniversalAudioController : BaseJellyfinApiController [FromQuery] int? maxAudioSampleRate, [FromQuery] int? maxAudioBitDepth, [FromQuery] bool? enableRemoteMedia, - [FromQuery] bool? enableAudioVbrEncoding, + [FromQuery] bool enableAudioVbrEncoding = true, [FromQuery] bool breakOnNonKeyFrames = false, [FromQuery] bool enableRedirection = true) { @@ -212,7 +212,7 @@ public class UniversalAudioController : BaseJellyfinApiController Context = EncodingContext.Static, StreamOptions = new Dictionary(), EnableAdaptiveBitrateStreaming = true, - EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType.Hls, dynamicHlsRequestDto, true) diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index f6050bdf77..7f9608378d 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -365,7 +365,7 @@ public class VideosController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool? enableAudioVbrEncoding) + [FromQuery] bool enableAudioVbrEncoding = true) { var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; // CTS lifecycle is managed internally. @@ -422,7 +422,7 @@ public class VideosController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true + EnableAudioVbrEncoding = enableAudioVbrEncoding }; var state = await StreamingHelpers.GetStreamingState( @@ -606,7 +606,7 @@ public class VideosController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool? enableAudioVbrEncoding) + [FromQuery] bool enableAudioVbrEncoding = true) { return GetVideoStream( itemId, From efee37a632f269f72d976b8a93381a2ee4cdf1ac Mon Sep 17 00:00:00 2001 From: gnattu Date: Thu, 18 Jul 2024 02:43:02 +0800 Subject: [PATCH 8/8] Simplify EnableAudioVbrEncoding getter Co-authored-by: Bond-009 --- MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 526aa8f99b..72df7151da 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -508,13 +508,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - public bool EnableAudioVbrEncoding - { - get - { - return BaseRequest.EnableAudioVbrEncoding; - } - } + public bool EnableAudioVbrEncoding => BaseRequest.EnableAudioVbrEncoding; public int HlsListSize => 0;