Browse Source

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

Multiple HLS codec and bitrate fixes (10.8.z)
Bond-009 2 years ago
parent
commit
e6313d01eb

+ 8 - 8
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -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;
-
-            if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
-                || string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
-                || string.Equals(state.ActualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
-                || string.Equals(state.ActualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase))
+            var actualOutputAudioCodec = state.ActualOutputAudioCodec;
+            if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
+                || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
+                || string.Equals(actualOutputAudioCodec, "dts", 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)
+            if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase))
             {
             {
                 args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
                 args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
             }
             }

+ 50 - 3
Jellyfin.Api/Helpers/DynamicHlsHelper.cs

@@ -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;
+                        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;
+                        }
+
                         var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
                         var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
+                        var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);
 
 
-                        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;
         }
         }
     }
     }
 }
 }

+ 15 - 1
Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs

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

+ 11 - 5
Jellyfin.Api/Helpers/StreamingHelpers.cs

@@ -182,12 +182,18 @@ namespace Jellyfin.Api.Helpers
                     : GetOutputFileExtension(state, mediaSource);
                     : GetOutputFileExtension(state, mediaSource);
             }
             }
 
 
-            state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
-
-            state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream);
-
-            state.OutputAudioCodec = streamingRequest.AudioCodec;
+            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.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)

+ 71 - 42
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -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))
-            {
-                return Math.Min(384000, audioBitRate.Value);
-            }
+            var inputChannels = audioStream.Channels ?? 0;
+            var outputChannels = outputAudioChannels ?? 0;
+            var bitrate = audioBitRate ?? int.MaxValue;
 
 
-            if (audioBitRate.HasValue && !string.IsNullOrEmpty(audioCodec))
+            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))
             {
             {
-                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);
-                    }
-
-                    return Math.Min(384000, audioBitRate.Value);
-                }
+                    (>= 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 (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase)
-                    || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
+                || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
+            {
+                return (inputChannels, outputChannels) switch
                 {
                 {
-                    if ((audioStream.Channels ?? 0) >= 6)
-                    {
-                        return Math.Min(3584000, audioBitRate.Value);
-                    }
-
-                    return Math.Min(1536000, 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)
+                };
             }
             }
 
 
             // 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;

+ 4 - 1
MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs

@@ -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",

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

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