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

Add better audio VBR settings
This commit is contained in:
Bond-009 2024-07-21 16:15:43 +02:00 committed by GitHub
commit d5cf0ad2c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 101 additions and 39 deletions

View File

@ -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);

View File

@ -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)); 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; 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)); 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; args += vbrParam;
} }

View File

@ -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)

View File

@ -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);
} }
} }

View File

@ -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);

View File

@ -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", return " -qscale:a " + bitratePerChannel switch
< 64000 => "6", {
< 88000 => "4", < 64000 => "6",
< 112000 => "2", < 88000 => "4",
_ => "0" < 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)); 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; 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)); 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); audioTranscodeParams.Add(vbrParam);
} }

View File

@ -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)

View File

@ -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)
{ {

View File

@ -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));

View File

@ -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);