|
@@ -89,6 +89,16 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|
{ "truehd", 6 },
|
|
{ "truehd", 6 },
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+ public static readonly string[] LosslessAudioCodecs = new string[]
|
|
|
|
+ {
|
|
|
|
+ "alac",
|
|
|
|
+ "ape",
|
|
|
|
+ "flac",
|
|
|
|
+ "mlp",
|
|
|
|
+ "truehd",
|
|
|
|
+ "wavpack"
|
|
|
|
+ };
|
|
|
|
+
|
|
public EncodingHelper(
|
|
public EncodingHelper(
|
|
IApplicationPaths appPaths,
|
|
IApplicationPaths appPaths,
|
|
IMediaEncoder mediaEncoder,
|
|
IMediaEncoder mediaEncoder,
|
|
@@ -620,6 +630,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|
return "flac";
|
|
return "flac";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ return "dca";
|
|
|
|
+ }
|
|
|
|
+
|
|
return codec.ToLowerInvariant();
|
|
return codec.ToLowerInvariant();
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2050,9 +2065,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // Video bitrate must fall within requested value
|
|
|
|
|
|
+ // Audio bitrate must fall within requested value
|
|
if (request.AudioBitRate.HasValue
|
|
if (request.AudioBitRate.HasValue
|
|
- && audioStream.BitDepth.HasValue
|
|
|
|
|
|
+ && audioStream.BitRate.HasValue
|
|
&& audioStream.BitRate.Value > request.AudioBitRate.Value)
|
|
&& audioStream.BitRate.Value > request.AudioBitRate.Value)
|
|
{
|
|
{
|
|
return false;
|
|
return false;
|
|
@@ -2161,56 +2176,96 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|
return Convert.ToInt32(scaleFactor * bitrate);
|
|
return Convert.ToInt32(scaleFactor * bitrate);
|
|
}
|
|
}
|
|
|
|
|
|
- public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream)
|
|
|
|
|
|
+ public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChannels)
|
|
{
|
|
{
|
|
- return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream);
|
|
|
|
|
|
+ return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
|
|
}
|
|
}
|
|
|
|
|
|
- public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream)
|
|
|
|
|
|
+ public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudioChannels)
|
|
{
|
|
{
|
|
if (audioStream is null)
|
|
if (audioStream is null)
|
|
{
|
|
{
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|
|
- if (audioBitRate.HasValue && string.IsNullOrEmpty(audioCodec))
|
|
|
|
|
|
+ var inputChannels = audioStream.Channels ?? 0;
|
|
|
|
+ var outputChannels = outputAudioChannels ?? 0;
|
|
|
|
+ var bitrate = audioBitRate ?? int.MaxValue;
|
|
|
|
+
|
|
|
|
+ if (string.IsNullOrEmpty(audioCodec)
|
|
|
|
+ || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
|
|
|
|
+ || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
|
|
|
|
+ || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|
|
|
|
+ || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
|
|
|
|
+ || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|
|
|
|
+ || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
- return Math.Min(384000, audioBitRate.Value);
|
|
|
|
|
|
+ return (inputChannels, outputChannels) switch
|
|
|
|
+ {
|
|
|
|
+ (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
|
|
|
|
+ (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
|
|
|
|
+ (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
|
|
|
|
+ (_, _) => Math.Min(384000, bitrate)
|
|
|
|
+ };
|
|
}
|
|
}
|
|
|
|
|
|
- if (audioBitRate.HasValue && !string.IsNullOrEmpty(audioCodec))
|
|
|
|
|
|
+ if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
|
|
|
|
+ || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
- if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
|
|
|
|
- || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
|
|
|
|
- || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|
|
|
|
- || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
|
|
|
|
- || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|
|
|
|
- || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
+ return (inputChannels, outputChannels) switch
|
|
{
|
|
{
|
|
- if ((audioStream.Channels ?? 0) >= 6)
|
|
|
|
- {
|
|
|
|
- return Math.Min(640000, audioBitRate.Value);
|
|
|
|
- }
|
|
|
|
|
|
+ (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
|
|
|
|
+ (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
|
|
|
|
+ (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
|
|
|
|
+ (_, _) => Math.Min(672000, bitrate)
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
|
|
- return Math.Min(384000, audioBitRate.Value);
|
|
|
|
- }
|
|
|
|
|
|
+ // Empty bitrate area is not allow on iOS
|
|
|
|
+ // Default audio bitrate to 128K per channel if we don't have codec specific defaults
|
|
|
|
+ // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
|
|
|
|
+ return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
|
|
|
|
+ }
|
|
|
|
|
|
- if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase)
|
|
|
|
- || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
+ public string GetAudioVbrModeParam(string encoder, int bitratePerChannel)
|
|
|
|
+ {
|
|
|
|
+ if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ return " -vbr:a " + bitratePerChannel switch
|
|
{
|
|
{
|
|
- if ((audioStream.Channels ?? 0) >= 6)
|
|
|
|
- {
|
|
|
|
- return Math.Min(3584000, audioBitRate.Value);
|
|
|
|
- }
|
|
|
|
|
|
+ < 32000 => "1",
|
|
|
|
+ < 48000 => "2",
|
|
|
|
+ < 64000 => "3",
|
|
|
|
+ < 96000 => "4",
|
|
|
|
+ _ => "5"
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
|
|
- return Math.Min(1536000, audioBitRate.Value);
|
|
|
|
- }
|
|
|
|
|
|
+ if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ return " -qscale:a " + bitratePerChannel switch
|
|
|
|
+ {
|
|
|
|
+ < 48000 => "8",
|
|
|
|
+ < 64000 => "6",
|
|
|
|
+ < 88000 => "4",
|
|
|
|
+ < 112000 => "2",
|
|
|
|
+ _ => "0"
|
|
|
|
+ };
|
|
}
|
|
}
|
|
|
|
|
|
- // Empty bitrate area is not allow on iOS
|
|
|
|
- // Default audio bitrate to 128K if it is not being requested
|
|
|
|
- // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
|
|
|
|
- return 128000;
|
|
|
|
|
|
+ if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
|
|
|
|
+ {
|
|
|
|
+ return " -qscale:a " + bitratePerChannel switch
|
|
|
|
+ {
|
|
|
|
+ < 40000 => "0",
|
|
|
|
+ < 56000 => "2",
|
|
|
|
+ < 80000 => "4",
|
|
|
|
+ < 112000 => "6",
|
|
|
|
+ _ => "8"
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return null;
|
|
}
|
|
}
|
|
|
|
|
|
public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
|
|
public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
|
|
@@ -5670,14 +5725,22 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|
}
|
|
}
|
|
|
|
|
|
var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
|
|
var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
|
|
|
|
+ var shiftAudioCodecs = new List<string>();
|
|
if (inputChannels >= 6)
|
|
if (inputChannels >= 6)
|
|
{
|
|
{
|
|
- return;
|
|
|
|
|
|
+ // DTS and TrueHD are not supported by HLS
|
|
|
|
+ // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
|
|
|
|
+ shiftAudioCodecs.Add("dca");
|
|
|
|
+ shiftAudioCodecs.Add("truehd");
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
|
|
|
|
+ // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
|
|
|
|
+ shiftAudioCodecs.Add("ac3");
|
|
|
|
+ shiftAudioCodecs.Add("eac3");
|
|
}
|
|
}
|
|
|
|
|
|
- // Transcoding to 2ch ac3 almost always causes a playback failure
|
|
|
|
- // Keep it in the supported codecs list, but shift it to the end of the list so that if transcoding happens, another codec is used
|
|
|
|
- var shiftAudioCodecs = new[] { "ac3", "eac3" };
|
|
|
|
if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
|
|
if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
{
|
|
return;
|
|
return;
|
|
@@ -5919,10 +5982,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|
}
|
|
}
|
|
|
|
|
|
var bitrate = state.OutputAudioBitrate;
|
|
var bitrate = state.OutputAudioBitrate;
|
|
-
|
|
|
|
- if (bitrate.HasValue)
|
|
|
|
|
|
+ if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
- args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
|
|
|
|
|
|
+ var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2));
|
|
|
|
+ if (encodingOptions.EnableAudioVbr && vbrParam is not null)
|
|
|
|
+ {
|
|
|
|
+ args += vbrParam;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
if (state.OutputAudioSampleRate.HasValue)
|
|
if (state.OutputAudioSampleRate.HasValue)
|
|
@@ -5940,23 +6010,33 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|
var audioTranscodeParams = new List<string>();
|
|
var audioTranscodeParams = new List<string>();
|
|
|
|
|
|
var bitrate = state.OutputAudioBitrate;
|
|
var bitrate = state.OutputAudioBitrate;
|
|
|
|
+ var channels = state.OutputAudioChannels;
|
|
|
|
+ var outputCodec = state.OutputAudioCodec;
|
|
|
|
|
|
- if (bitrate.HasValue)
|
|
|
|
|
|
+ if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
- audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
|
|
+ var vbrParam = GetAudioVbrModeParam(outputCodec, bitrate.Value / (channels ?? 2));
|
|
|
|
+ if (encodingOptions.EnableAudioVbr && vbrParam is not null)
|
|
|
|
+ {
|
|
|
|
+ audioTranscodeParams.Add(vbrParam);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- if (state.OutputAudioChannels.HasValue)
|
|
|
|
|
|
+ if (channels.HasValue)
|
|
{
|
|
{
|
|
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
|
|
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
}
|
|
|
|
|
|
- if (!string.IsNullOrEmpty(state.OutputAudioCodec))
|
|
|
|
|
|
+ if (!string.IsNullOrEmpty(outputCodec))
|
|
{
|
|
{
|
|
audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
|
|
audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
|
|
}
|
|
}
|
|
|
|
|
|
- if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
+ if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
{
|
|
// opus only supports specific sampling rates
|
|
// opus only supports specific sampling rates
|
|
var sampleRate = state.OutputAudioSampleRate;
|
|
var sampleRate = state.OutputAudioSampleRate;
|