Merge pull request #9409 from Shadowghost/output-bitrate-channels-release

Multiple HLS codec and bitrate fixes (10.8.z)
This commit is contained in:
Bond-009 2023-03-19 15:30:53 +01:00 committed by GitHub
commit e6313d01eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 184 additions and 61 deletions

View File

@ -13,6 +13,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Extensions;
using Jellyfin.MediaEncoding.Hls.Playlist; using Jellyfin.MediaEncoding.Hls.Playlist;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -1694,7 +1695,7 @@ namespace Jellyfin.Api.Controllers
audioTranscodeParams += "-acodec " + audioCodec; audioTranscodeParams += "-acodec " + audioCodec;
if (state.OutputAudioBitrate.HasValue) if (state.OutputAudioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase))
{ {
audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture); audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture);
} }
@ -1715,11 +1716,11 @@ namespace Jellyfin.Api.Controllers
// dts, flac, opus and truehd are experimental in mp4 muxer // dts, flac, opus and truehd are experimental in mp4 muxer
var strictArgs = string.Empty; var strictArgs = string.Empty;
var actualOutputAudioCodec = state.ActualOutputAudioCodec;
if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase))
{ {
strictArgs = " -strict -2"; strictArgs = " -strict -2";
} }
@ -1748,8 +1749,7 @@ namespace Jellyfin.Api.Controllers
} }
var bitrate = state.OutputAudioBitrate; var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase))
if (bitrate.HasValue)
{ {
args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
} }

View File

@ -8,6 +8,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -203,6 +204,13 @@ namespace Jellyfin.Api.Helpers
if (state.VideoStream != null && state.VideoRequest != null) if (state.VideoStream != null && state.VideoRequest != null)
{ {
// Provide a workaround for the case issue between flac and fLaC.
var flacWaPlaylist = ApplyFlacCaseWorkaround(state, basicPlaylist.ToString());
if (!string.IsNullOrEmpty(flacWaPlaylist))
{
builder.Append(flacWaPlaylist);
}
var encodingOptions = _serverConfigurationManager.GetEncodingOptions(); var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
// Provide SDR HEVC entrance for backward compatibility. // Provide SDR HEVC entrance for backward compatibility.
@ -221,10 +229,25 @@ namespace Jellyfin.Api.Helpers
sdrVideoUrl += "&AllowVideoStreamCopy=false"; sdrVideoUrl += "&AllowVideoStreamCopy=false";
var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0; var sdrOutputAudioBitrate = 0;
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; if (EncodingHelper.LosslessAudioCodecs.Contains(state.VideoRequest.AudioCodec, StringComparison.OrdinalIgnoreCase))
{
sdrOutputAudioBitrate = state.AudioStream.BitRate ?? 0;
}
else
{
sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0;
}
AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);
// Provide a workaround for the case issue between flac and fLaC.
flacWaPlaylist = ApplyFlacCaseWorkaround(state, sdrPlaylist.ToString());
if (!string.IsNullOrEmpty(flacWaPlaylist))
{
builder.Append(flacWaPlaylist);
}
// Restore the video codec // Restore the video codec
state.OutputVideoCodec = "copy"; state.OutputVideoCodec = "copy";
@ -254,6 +277,13 @@ namespace Jellyfin.Api.Helpers
state.VideoStream.Level = originalLevel; state.VideoStream.Level = originalLevel;
var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField); var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField);
builder.Append(newPlaylist); builder.Append(newPlaylist);
// Provide a workaround for the case issue between flac and fLaC.
flacWaPlaylist = ApplyFlacCaseWorkaround(state, newPlaylist);
if (!string.IsNullOrEmpty(flacWaPlaylist))
{
builder.Append(flacWaPlaylist);
}
} }
} }
@ -612,6 +642,11 @@ namespace Jellyfin.Api.Helpers
return HlsCodecStringHelpers.GetALACString(); return HlsCodecStringHelpers.GetALACString();
} }
if (string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
return HlsCodecStringHelpers.GetOPUSString();
}
return string.Empty; return string.Empty;
} }
@ -710,7 +745,19 @@ namespace Jellyfin.Api.Helpers
return oldPlaylist.Replace( return oldPlaylist.Replace(
oldValue.ToString(), oldValue.ToString(),
newValue.ToString(), newValue.ToString(),
StringComparison.OrdinalIgnoreCase); StringComparison.Ordinal);
}
private string ApplyFlacCaseWorkaround(StreamState state, string srcPlaylist)
{
if (!string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase))
{
return string.Empty;
}
var newPlaylist = srcPlaylist.Replace(",flac\"", ",fLaC\"", StringComparison.Ordinal);
return newPlaylist.Contains(",fLaC\"", StringComparison.Ordinal) ? newPlaylist : string.Empty;
} }
} }
} }

View File

@ -27,13 +27,18 @@ namespace Jellyfin.Api.Helpers
/// <summary> /// <summary>
/// Codec name for FLAC. /// Codec name for FLAC.
/// </summary> /// </summary>
public const string FLAC = "fLaC"; public const string FLAC = "flac";
/// <summary> /// <summary>
/// Codec name for ALAC. /// Codec name for ALAC.
/// </summary> /// </summary>
public const string ALAC = "alac"; public const string ALAC = "alac";
/// <summary>
/// Codec name for OPUS.
/// </summary>
public const string OPUS = "opus";
/// <summary> /// <summary>
/// Gets a MP3 codec string. /// Gets a MP3 codec string.
/// </summary> /// </summary>
@ -101,6 +106,15 @@ namespace Jellyfin.Api.Helpers
return ALAC; return ALAC;
} }
/// <summary>
/// Gets an OPUS codec string.
/// </summary>
/// <returns>OPUS codec string.</returns>
public static string GetOPUSString()
{
return OPUS;
}
/// <summary> /// <summary>
/// Gets a H.264 codec string. /// Gets a H.264 codec string.
/// </summary> /// </summary>

View File

@ -182,12 +182,18 @@ namespace Jellyfin.Api.Helpers
: GetOutputFileExtension(state, mediaSource); : GetOutputFileExtension(state, mediaSource);
} }
var outputAudioCodec = streamingRequest.AudioCodec;
if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec))
{
state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0;
}
else
{
state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels) ?? 0;
}
state.OutputAudioCodec = outputAudioCodec;
state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream);
state.OutputAudioCodec = streamingRequest.AudioCodec;
state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec);
if (state.VideoRequest != null) if (state.VideoRequest != null)

View File

@ -62,6 +62,16 @@ namespace MediaBrowser.Controller.MediaEncoding
"Main10" "Main10"
}; };
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,
@ -548,6 +558,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return "flac"; return "flac";
} }
if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
{
return "dca";
}
return codec.ToLowerInvariant(); return codec.ToLowerInvariant();
} }
@ -1955,9 +1970,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;
@ -2066,56 +2081,55 @@ 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 == null) if (audioStream == 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) return (inputChannels, outputChannels) switch
|| string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
{ {
if ((audioStream.Channels ?? 0) >= 6) (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
{ (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
return Math.Min(640000, audioBitRate.Value); (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
} (_, _) => Math.Min(672000, bitrate)
};
return Math.Min(384000, audioBitRate.Value);
}
if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))
{
if ((audioStream.Channels ?? 0) >= 6)
{
return Math.Min(3584000, audioBitRate.Value);
}
return Math.Min(1536000, audioBitRate.Value);
}
} }
// Empty bitrate area is not allow on iOS // Empty bitrate area is not allow on iOS
// Default audio bitrate to 128K if it is not being requested // Default audio bitrate to 128K per channel if we don't have codec specific defaults
// https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
return 128000; return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
} }
public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions) public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
@ -5285,15 +5299,23 @@ namespace MediaBrowser.Controller.MediaEncoding
return; return;
} }
var inputChannels = audioStream == 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;
@ -5537,7 +5559,7 @@ 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); args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
} }
@ -5557,8 +5579,10 @@ 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)); audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
} }
@ -5568,7 +5592,12 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
} }
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) if (!string.IsNullOrEmpty(outputCodec))
{
audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
}
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;

View File

@ -25,11 +25,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
"mpeg2video", "mpeg2video",
"mpeg4", "mpeg4",
"msmpeg4", "msmpeg4",
"dts", "dca",
"ac3", "ac3",
"aac", "aac",
"mp3", "mp3",
"flac", "flac",
"truehd",
"h264_qsv", "h264_qsv",
"hevc_qsv", "hevc_qsv",
"mpeg2_qsv", "mpeg2_qsv",
@ -58,10 +59,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
"aac", "aac",
"libfdk_aac", "libfdk_aac",
"ac3", "ac3",
"dca",
"libmp3lame", "libmp3lame",
"libopus", "libopus",
"libvorbis", "libvorbis",
"flac", "flac",
"truehd",
"srt", "srt",
"h264_amf", "h264_amf",
"hevc_amf", "hevc_amf",

View File

@ -23,6 +23,9 @@ namespace MediaBrowser.Model.Dlna
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ITranscoderSupport _transcoderSupport; private readonly ITranscoderSupport _transcoderSupport;
private static readonly string[] _supportedHlsVideoCodecs = new string[] { "h264", "hevc" };
private static readonly string[] _supportedHlsAudioCodecsTs = new string[] { "aac", "ac3", "eac3", "mp3" };
private static readonly string[] _supportedHlsAudioCodecsMp4 = new string[] { "aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd" };
public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger) public StreamBuilder(ITranscoderSupport transcoderSupport, ILogger logger)
{ {
@ -770,6 +773,13 @@ namespace MediaBrowser.Model.Dlna
{ {
// Prefer matching video codecs // Prefer matching video codecs
var videoCodecs = ContainerProfile.SplitValue(videoCodec); var videoCodecs = ContainerProfile.SplitValue(videoCodec);
// Enforce HLS video codec restrictions
if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
videoCodecs = videoCodecs.Where(codec => _supportedHlsVideoCodecs.Contains(codec)).ToArray();
}
var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null; var directVideoCodec = ContainerProfile.ContainsContainer(videoCodecs, videoStream?.Codec) ? videoStream?.Codec : null;
if (directVideoCodec != null) if (directVideoCodec != null)
{ {
@ -805,6 +815,20 @@ namespace MediaBrowser.Model.Dlna
// Prefer matching audio codecs, could do better here // Prefer matching audio codecs, could do better here
var audioCodecs = ContainerProfile.SplitValue(audioCodec); var audioCodecs = ContainerProfile.SplitValue(audioCodec);
// Enforce HLS audio codec restrictions
if (string.Equals(playlistItem.SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(playlistItem.Container, "mp4", StringComparison.OrdinalIgnoreCase))
{
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsMp4.Contains(codec)).ToArray();
}
else
{
audioCodecs = audioCodecs.Where(codec => _supportedHlsAudioCodecsTs.Contains(codec)).ToArray();
}
}
var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec)); var directAudioStream = candidateAudioStreams.FirstOrDefault(stream => ContainerProfile.ContainsContainer(audioCodecs, stream.Codec));
playlistItem.AudioCodecs = audioCodecs; playlistItem.AudioCodecs = audioCodecs;
if (directAudioStream != null) if (directAudioStream != null)