Browse Source

Merge pull request #11492 from jellyfin/better-vbr-settings

Add better audio VBR settings
Bond-009 10 months ago
parent
commit
d5cf0ad2c8

+ 10 - 4
Jellyfin.Api/Controllers/AudioController.cs

@@ -83,6 +83,7 @@ public class AudioController : BaseJellyfinApiController
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Audio stream returned.</response>
     /// <response code="200">Audio stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("{itemId}/stream", Name = "GetAudioStream")]
     [HttpGet("{itemId}/stream", Name = "GetAudioStream")]
@@ -138,7 +139,8 @@ public class AudioController : BaseJellyfinApiController
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
-        [FromQuery] Dictionary<string, string>? streamOptions)
+        [FromQuery] Dictionary<string, string>? streamOptions,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         StreamingRequestDto streamingRequest = new StreamingRequestDto
         StreamingRequestDto streamingRequest = new StreamingRequestDto
         {
         {
@@ -189,7 +191,8 @@ public class AudioController : BaseJellyfinApiController
             AudioStreamIndex = audioStreamIndex,
             AudioStreamIndex = audioStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Static,
             Context = context ?? EncodingContext.Static,
-            StreamOptions = streamOptions
+            StreamOptions = streamOptions,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false);
         return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false);
@@ -247,6 +250,7 @@ public class AudioController : BaseJellyfinApiController
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Audio stream returned.</response>
     /// <response code="200">Audio stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("{itemId}/stream.{container}", Name = "GetAudioStreamByContainer")]
     [HttpGet("{itemId}/stream.{container}", Name = "GetAudioStreamByContainer")]
@@ -302,7 +306,8 @@ public class AudioController : BaseJellyfinApiController
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
-        [FromQuery] Dictionary<string, string>? streamOptions)
+        [FromQuery] Dictionary<string, string>? streamOptions,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         StreamingRequestDto streamingRequest = new StreamingRequestDto
         StreamingRequestDto streamingRequest = new StreamingRequestDto
         {
         {
@@ -353,7 +358,8 @@ public class AudioController : BaseJellyfinApiController
             AudioStreamIndex = audioStreamIndex,
             AudioStreamIndex = audioStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Static,
             Context = context ?? EncodingContext.Static,
-            StreamOptions = streamOptions
+            StreamOptions = streamOptions,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false);
         return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false);

+ 39 - 18
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -156,6 +156,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="maxWidth">Optional. The max width.</param>
     /// <param name="maxWidth">Optional. The max width.</param>
     /// <param name="maxHeight">Optional. The max height.</param>
     /// <param name="maxHeight">Optional. The max height.</param>
     /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
     /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Hls live stream retrieved.</response>
     /// <response code="200">Hls live stream retrieved.</response>
     /// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
     [HttpGet("Videos/{itemId}/live.m3u8")]
     [HttpGet("Videos/{itemId}/live.m3u8")]
@@ -213,7 +214,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] int? maxWidth,
         [FromQuery] int? maxWidth,
         [FromQuery] int? maxHeight,
         [FromQuery] int? maxHeight,
-        [FromQuery] bool? enableSubtitlesInManifest)
+        [FromQuery] bool? enableSubtitlesInManifest,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         VideoRequestDto streamingRequest = new VideoRequestDto
         VideoRequestDto streamingRequest = new VideoRequestDto
         {
         {
@@ -267,7 +269,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             StreamOptions = streamOptions,
             StreamOptions = streamOptions,
             MaxHeight = maxHeight,
             MaxHeight = maxHeight,
             MaxWidth = maxWidth,
             MaxWidth = maxWidth,
-            EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true
+            EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         // CTS lifecycle is managed internally.
         // CTS lifecycle is managed internally.
@@ -393,6 +396,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
     /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
     /// <param name="enableTrickplay">Enable trickplay image playlists being added to master playlist.</param>
     /// <param name="enableTrickplay">Enable trickplay image playlists being added to master playlist.</param>
+    /// <param name="enableAudioVbrEncoding">Whether to enable Audio Encoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
     [HttpGet("Videos/{itemId}/master.m3u8")]
     [HttpGet("Videos/{itemId}/master.m3u8")]
@@ -451,7 +455,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] bool enableAdaptiveBitrateStreaming = true,
         [FromQuery] bool enableAdaptiveBitrateStreaming = true,
-        [FromQuery] bool enableTrickplay = true)
+        [FromQuery] bool enableTrickplay = true,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         var streamingRequest = new HlsVideoRequestDto
         var streamingRequest = new HlsVideoRequestDto
         {
         {
@@ -505,7 +510,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
             StreamOptions = streamOptions,
             StreamOptions = streamOptions,
             EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming,
             EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming,
-            EnableTrickplay = enableTrickplay
+            EnableTrickplay = enableTrickplay,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
         return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
@@ -564,6 +570,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
     /// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Audio stream returned.</response>
     /// <response code="200">Audio stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
     [HttpGet("Audio/{itemId}/master.m3u8")]
     [HttpGet("Audio/{itemId}/master.m3u8")]
@@ -620,7 +627,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
         [FromQuery] Dictionary<string, string> streamOptions,
         [FromQuery] Dictionary<string, string> streamOptions,
-        [FromQuery] bool enableAdaptiveBitrateStreaming = true)
+        [FromQuery] bool enableAdaptiveBitrateStreaming = true,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         var streamingRequest = new HlsAudioRequestDto
         var streamingRequest = new HlsAudioRequestDto
         {
         {
@@ -671,7 +679,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
             StreamOptions = streamOptions,
             StreamOptions = streamOptions,
-            EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
+            EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
         return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false);
@@ -730,6 +739,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("Videos/{itemId}/main.m3u8")]
     [HttpGet("Videos/{itemId}/main.m3u8")]
@@ -785,7 +795,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
-        [FromQuery] Dictionary<string, string> streamOptions)
+        [FromQuery] Dictionary<string, string> streamOptions,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         using var cancellationTokenSource = new CancellationTokenSource();
         using var cancellationTokenSource = new CancellationTokenSource();
         var streamingRequest = new VideoRequestDto
         var streamingRequest = new VideoRequestDto
@@ -838,7 +849,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             AudioStreamIndex = audioStreamIndex,
             AudioStreamIndex = audioStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
-            StreamOptions = streamOptions
+            StreamOptions = streamOptions,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
         return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
@@ -897,6 +909,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Audio stream returned.</response>
     /// <response code="200">Audio stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("Audio/{itemId}/main.m3u8")]
     [HttpGet("Audio/{itemId}/main.m3u8")]
@@ -951,7 +964,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
-        [FromQuery] Dictionary<string, string> streamOptions)
+        [FromQuery] Dictionary<string, string> streamOptions,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         using var cancellationTokenSource = new CancellationTokenSource();
         using var cancellationTokenSource = new CancellationTokenSource();
         var streamingRequest = new StreamingRequestDto
         var streamingRequest = new StreamingRequestDto
@@ -1002,7 +1016,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             AudioStreamIndex = audioStreamIndex,
             AudioStreamIndex = audioStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
-            StreamOptions = streamOptions
+            StreamOptions = streamOptions,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
         return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource)
@@ -1067,6 +1082,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
     [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
@@ -1128,7 +1144,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
-        [FromQuery] Dictionary<string, string> streamOptions)
+        [FromQuery] Dictionary<string, string> streamOptions,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         var streamingRequest = new VideoRequestDto
         var streamingRequest = new VideoRequestDto
         {
         {
@@ -1183,7 +1200,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             AudioStreamIndex = audioStreamIndex,
             AudioStreamIndex = audioStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
-            StreamOptions = streamOptions
+            StreamOptions = streamOptions,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         return await GetDynamicSegment(streamingRequest, segmentId)
         return await GetDynamicSegment(streamingRequest, segmentId)
@@ -1247,6 +1265,7 @@ public class DynamicHlsController : BaseJellyfinApiController
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
     [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
@@ -1307,7 +1326,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
-        [FromQuery] Dictionary<string, string> streamOptions)
+        [FromQuery] Dictionary<string, string> streamOptions,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         var streamingRequest = new StreamingRequestDto
         var streamingRequest = new StreamingRequestDto
         {
         {
@@ -1360,7 +1380,8 @@ public class DynamicHlsController : BaseJellyfinApiController
             AudioStreamIndex = audioStreamIndex,
             AudioStreamIndex = audioStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
-            StreamOptions = streamOptions
+            StreamOptions = streamOptions,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         return await GetDynamicSegment(streamingRequest, segmentId)
         return await GetDynamicSegment(streamingRequest, segmentId)
@@ -1671,8 +1692,8 @@ public class DynamicHlsController : BaseJellyfinApiController
 
 
             if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase))
             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)
+                var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value, audioChannels ?? 2);
+                if (_encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
                 {
                 {
                     audioTranscodeParams += vbrParam;
                     audioTranscodeParams += vbrParam;
                 }
                 }
@@ -1724,8 +1745,8 @@ public class DynamicHlsController : BaseJellyfinApiController
         var bitrate = state.OutputAudioBitrate;
         var bitrate = state.OutputAudioBitrate;
         if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase))
         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)
+            var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value, channels ?? 2);
+            if (_encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
             {
             {
                 args += vbrParam;
                 args += vbrParam;
             }
             }

+ 4 - 1
Jellyfin.Api/Controllers/UniversalAudioController.cs

@@ -82,6 +82,7 @@ public class UniversalAudioController : BaseJellyfinApiController
     /// <param name="maxAudioSampleRate">Optional. The maximum audio sample rate.</param>
     /// <param name="maxAudioSampleRate">Optional. The maximum audio sample rate.</param>
     /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
     /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
     /// <param name="enableRemoteMedia">Optional. Whether to enable remote media.</param>
     /// <param name="enableRemoteMedia">Optional. Whether to enable remote media.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
     /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
     /// <param name="enableRedirection">Whether to enable redirection. Defaults to true.</param>
     /// <param name="enableRedirection">Whether to enable redirection. Defaults to true.</param>
     /// <response code="200">Audio stream returned.</response>
     /// <response code="200">Audio stream returned.</response>
@@ -112,6 +113,7 @@ public class UniversalAudioController : BaseJellyfinApiController
         [FromQuery] int? maxAudioSampleRate,
         [FromQuery] int? maxAudioSampleRate,
         [FromQuery] int? maxAudioBitDepth,
         [FromQuery] int? maxAudioBitDepth,
         [FromQuery] bool? enableRemoteMedia,
         [FromQuery] bool? enableRemoteMedia,
+        [FromQuery] bool enableAudioVbrEncoding = true,
         [FromQuery] bool breakOnNonKeyFrames = false,
         [FromQuery] bool breakOnNonKeyFrames = false,
         [FromQuery] bool enableRedirection = true)
         [FromQuery] bool enableRedirection = true)
     {
     {
@@ -219,7 +221,8 @@ public class UniversalAudioController : BaseJellyfinApiController
                 TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(),
                 TranscodeReasons = mediaSource.TranscodeReasons == 0 ? null : mediaSource.TranscodeReasons.ToString(),
                 Context = EncodingContext.Static,
                 Context = EncodingContext.Static,
                 StreamOptions = new Dictionary<string, string>(),
                 StreamOptions = new Dictionary<string, string>(),
-                EnableAdaptiveBitrateStreaming = true
+                EnableAdaptiveBitrateStreaming = true,
+                EnableAudioVbrEncoding = enableAudioVbrEncoding
             };
             };
 
 
             return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType.Hls, dynamicHlsRequestDto, true)
             return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType.Hls, dynamicHlsRequestDto, true)

+ 10 - 4
Jellyfin.Api/Controllers/VideosController.cs

@@ -306,6 +306,7 @@ public class VideosController : BaseJellyfinApiController
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("{itemId}/stream")]
     [HttpGet("{itemId}/stream")]
@@ -363,7 +364,8 @@ public class VideosController : BaseJellyfinApiController
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
-        [FromQuery] Dictionary<string, string> streamOptions)
+        [FromQuery] Dictionary<string, string> streamOptions,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
         var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
         // CTS lifecycle is managed internally.
         // CTS lifecycle is managed internally.
@@ -419,7 +421,8 @@ public class VideosController : BaseJellyfinApiController
             AudioStreamIndex = audioStreamIndex,
             AudioStreamIndex = audioStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             VideoStreamIndex = videoStreamIndex,
             Context = context ?? EncodingContext.Streaming,
             Context = context ?? EncodingContext.Streaming,
-            StreamOptions = streamOptions
+            StreamOptions = streamOptions,
+            EnableAudioVbrEncoding = enableAudioVbrEncoding
         };
         };
 
 
         var state = await StreamingHelpers.GetStreamingState(
         var state = await StreamingHelpers.GetStreamingState(
@@ -544,6 +547,7 @@ public class VideosController : BaseJellyfinApiController
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
     /// <param name="streamOptions">Optional. The streaming options.</param>
+    /// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
     /// <response code="200">Video stream returned.</response>
     /// <response code="200">Video stream returned.</response>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
     [HttpGet("{itemId}/stream.{container}")]
     [HttpGet("{itemId}/stream.{container}")]
@@ -601,7 +605,8 @@ public class VideosController : BaseJellyfinApiController
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? audioStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] int? videoStreamIndex,
         [FromQuery] EncodingContext? context,
         [FromQuery] EncodingContext? context,
-        [FromQuery] Dictionary<string, string> streamOptions)
+        [FromQuery] Dictionary<string, string> streamOptions,
+        [FromQuery] bool enableAudioVbrEncoding = true)
     {
     {
         return GetVideoStream(
         return GetVideoStream(
             itemId,
             itemId,
@@ -654,6 +659,7 @@ public class VideosController : BaseJellyfinApiController
             audioStreamIndex,
             audioStreamIndex,
             videoStreamIndex,
             videoStreamIndex,
             context,
             context,
-            streamOptions);
+            streamOptions,
+            enableAudioVbrEncoding);
     }
     }
 }
 }

+ 2 - 0
MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs

@@ -191,6 +191,8 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
         public Dictionary<string, string> StreamOptions { get; set; }
         public Dictionary<string, string> StreamOptions { get; set; }
 
 
+        public bool EnableAudioVbrEncoding { get; set; }
+
         public string GetOption(string qualifier, string name)
         public string GetOption(string qualifier, string name)
         {
         {
             var value = GetOption(qualifier + "-" + name);
             var value = GetOption(qualifier + "-" + name);

+ 25 - 12
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -2606,8 +2606,9 @@ namespace MediaBrowser.Controller.MediaEncoding
             return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
             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 / Math.Max(channels, 1);
             if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
             {
             {
                 return " -vbr:a " + bitratePerChannel switch
                 return " -vbr:a " + bitratePerChannel switch
@@ -2622,14 +2623,26 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
             if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
             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))
             if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
@@ -7003,8 +7016,8 @@ namespace MediaBrowser.Controller.MediaEncoding
             var bitrate = state.OutputAudioBitrate;
             var bitrate = state.OutputAudioBitrate;
             if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
             if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
             {
             {
-                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2));
-                if (encodingOptions.EnableAudioVbr && vbrParam is not null)
+                var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2);
+                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
                 {
                 {
                     args += vbrParam;
                     args += vbrParam;
                 }
                 }
@@ -7034,8 +7047,8 @@ namespace MediaBrowser.Controller.MediaEncoding
 
 
             if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
             if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
             {
             {
-                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value / (channels ?? 2));
-                if (encodingOptions.EnableAudioVbr && vbrParam is not null)
+                var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2);
+                if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null)
                 {
                 {
                     audioTranscodeParams.Add(vbrParam);
                     audioTranscodeParams.Add(vbrParam);
                 }
                 }

+ 2 - 0
MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs

@@ -508,6 +508,8 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
             }
         }
         }
 
 
+        public bool EnableAudioVbrEncoding => BaseRequest.EnableAudioVbrEncoding;
+
         public int HlsListSize => 0;
         public int HlsListSize => 0;
 
 
         public bool EnableBreakOnNonKeyFrames(string videoCodec)
         public bool EnableBreakOnNonKeyFrames(string videoCodec)

+ 1 - 0
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -597,6 +597,7 @@ namespace MediaBrowser.Model.Dlna
             playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
             playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
 
 
             playlistItem.BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames;
             playlistItem.BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames;
+            playlistItem.EnableAudioVbrEncoding = transcodingProfile.EnableAudioVbrEncoding;
 
 
             if (transcodingProfile.MinSegments > 0)
             if (transcodingProfile.MinSegments > 0)
             {
             {

+ 4 - 0
MediaBrowser.Model/Dlna/StreamInfo.cs

@@ -108,6 +108,8 @@ namespace MediaBrowser.Model.Dlna
 
 
         public string? MediaSourceId => MediaSource?.Id;
         public string? MediaSourceId => MediaSource?.Id;
 
 
+        public bool EnableAudioVbrEncoding { get; set; }
+
         public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay)
         public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay)
             && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay;
             && 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("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));
             list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty));

+ 4 - 0
MediaBrowser.Model/Dlna/TranscodingProfile.cs

@@ -70,6 +70,10 @@ namespace MediaBrowser.Model.Dlna
 
 
         public ProfileCondition[] Conditions { get; set; }
         public ProfileCondition[] Conditions { get; set; }
 
 
+        [DefaultValue(true)]
+        [XmlAttribute("enableAudioVbrEncoding")]
+        public bool EnableAudioVbrEncoding { get; set; } = true;
+
         public string[] GetAudioCodecs()
         public string[] GetAudioCodecs()
         {
         {
             return ContainerProfile.SplitValue(AudioCodec);
             return ContainerProfile.SplitValue(AudioCodec);