From 8cb11692a9628c59a7238d7ecbfbabb268959392 Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 3 Feb 2025 16:56:01 -0500 Subject: Backport pull request #13209 from jellyfin/release-10.10.z Transcode to audio codec satisfied other conditions when copy check failed. Original-merge: 8aa41d59041c792571530c514dd6d21ba22a1881 Merged-by: crobibero Backported-by: Bond_009 --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 46 ++++++++++++++++++++++++--- MediaBrowser.Model/Dlna/TranscodingProfile.cs | 29 +++++++++++++++++ 2 files changed, 70 insertions(+), 5 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 8771e4f1c..e87638a13 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -862,18 +862,37 @@ namespace MediaBrowser.Model.Dlna if (options.AllowAudioStreamCopy) { - if (ContainerHelper.ContainsContainer(transcodingProfile.AudioCodec, audioCodec)) + // For Audio stream, we prefer the audio codec that can be directly copied, then the codec that can otherwise satisfies + // the transcoding conditions, then the one does not satisfy the transcoding conditions. + // For example: A client can support both aac and flac, but flac only supports 2 channels while aac supports 6. + // When the source audio is 6 channel flac, we should transcode to 6 channel aac, instead of down-mix to 2 channel flac. + var transcodingAudioCodecs = ContainerHelper.Split(transcodingProfile.AudioCodec); + + foreach (var transcodingAudioCodec in transcodingAudioCodecs) { var appliedVideoConditions = options.Profile.CodecProfiles .Where(i => i.Type == CodecType.VideoAudio && - i.ContainsAnyCodec(audioCodec, container) && + i.ContainsAnyCodec(transcodingAudioCodec, container) && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false))) .Select(i => i.Conditions.All(condition => ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, false))); // An empty appliedVideoConditions means that the codec has no conditions for the current audio stream var conditionsSatisfied = appliedVideoConditions.All(satisfied => satisfied); - rank.Audio = conditionsSatisfied ? 1 : 2; + + var rankAudio = 3; + + if (conditionsSatisfied) + { + rankAudio = string.Equals(transcodingAudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) ? 1 : 2; + } + + rank.Audio = Math.Min(rank.Audio, rankAudio); + + if (rank.Audio == 1) + { + break; + } } } @@ -963,9 +982,26 @@ namespace MediaBrowser.Model.Dlna var audioStreamWithSupportedCodec = candidateAudioStreams.Where(stream => ContainerHelper.ContainsContainer(audioCodecs, false, stream.Codec)).FirstOrDefault(); - var directAudioStream = audioStreamWithSupportedCodec?.Channels is not null && audioStreamWithSupportedCodec.Channels.Value <= (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue) ? audioStreamWithSupportedCodec : null; + var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && audioStreamWithSupportedCodec.Channels > (playlistItem.TranscodingMaxAudioChannels ?? int.MaxValue); + + var directAudioStreamSatisfied = audioStreamWithSupportedCodec is not null && !channelsExceedsLimit + && options.Profile.CodecProfiles + .Where(i => i.Type == CodecType.VideoAudio + && i.ContainsAnyCodec(audioStreamWithSupportedCodec.Codec, container) + && i.ApplyConditions.All(applyCondition => ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioStreamWithSupportedCodec.Channels, audioStreamWithSupportedCodec.BitRate, audioStreamWithSupportedCodec.SampleRate, audioStreamWithSupportedCodec.BitDepth, audioStreamWithSupportedCodec.Profile, false))) + .Select(i => i.Conditions.All(condition => + { + var satisfied = ConditionProcessor.IsVideoAudioConditionSatisfied(condition, audioStreamWithSupportedCodec.Channels, audioStreamWithSupportedCodec.BitRate, audioStreamWithSupportedCodec.SampleRate, audioStreamWithSupportedCodec.BitDepth, audioStreamWithSupportedCodec.Profile, false); + if (!satisfied) + { + playlistItem.TranscodeReasons |= GetTranscodeReasonForFailedCondition(condition); + } + + return satisfied; + })) + .All(satisfied => satisfied); - var channelsExceedsLimit = audioStreamWithSupportedCodec is not null && directAudioStream is null; + var directAudioStream = directAudioStreamSatisfied ? audioStreamWithSupportedCodec : null; if (channelsExceedsLimit && playlistItem.TargetAudioStream is not null) { diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 5a9fa22ae..5797d4250 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel; using System.Xml.Serialization; using Jellyfin.Data.Enums; @@ -6,6 +7,7 @@ namespace MediaBrowser.Model.Dlna; /// /// A class for transcoding profile information. +/// Note for client developers: Conditions defined in has higher priority and can override values defined here. /// public class TranscodingProfile { @@ -17,6 +19,33 @@ public class TranscodingProfile Conditions = []; } + /// + /// Initializes a new instance of the class copying the values from another instance. + /// + /// Another instance of to be copied. + public TranscodingProfile(TranscodingProfile other) + { + ArgumentNullException.ThrowIfNull(other); + + Container = other.Container; + Type = other.Type; + VideoCodec = other.VideoCodec; + AudioCodec = other.AudioCodec; + Protocol = other.Protocol; + EstimateContentLength = other.EstimateContentLength; + EnableMpegtsM2TsMode = other.EnableMpegtsM2TsMode; + TranscodeSeekInfo = other.TranscodeSeekInfo; + CopyTimestamps = other.CopyTimestamps; + Context = other.Context; + EnableSubtitlesInManifest = other.EnableSubtitlesInManifest; + MaxAudioChannels = other.MaxAudioChannels; + MinSegments = other.MinSegments; + SegmentLength = other.SegmentLength; + BreakOnNonKeyFrames = other.BreakOnNonKeyFrames; + Conditions = other.Conditions; + EnableAudioVbrEncoding = other.EnableAudioVbrEncoding; + } + /// /// Gets or sets the container. /// -- cgit v1.2.3 From e79fc6b851ea851a79668195e573b5a05e55a63d Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 3 Feb 2025 16:59:04 -0500 Subject: Backport pull request #13288 from jellyfin/release-10.10.z Fix DTS in HLS Original-merge: cea0c9594220778658c30acf640108c26191706e Merged-by: crobibero Backported-by: Bond_009 --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index db0954368..72ac26beb 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -7069,7 +7069,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // 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("dts"); shiftAudioCodecs.Add("truehd"); } else diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index e87638a13..97262fbac 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -30,7 +30,7 @@ namespace MediaBrowser.Model.Dlna private readonly ITranscoderSupport _transcoderSupport; private static readonly string[] _supportedHlsVideoCodecs = ["h264", "hevc", "vp9", "av1"]; private static readonly string[] _supportedHlsAudioCodecsTs = ["aac", "ac3", "eac3", "mp3"]; - private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dca", "truehd"]; + private static readonly string[] _supportedHlsAudioCodecsMp4 = ["aac", "ac3", "eac3", "mp3", "alac", "flac", "opus", "dts", "truehd"]; /// /// Initializes a new instance of the class. -- cgit v1.2.3 From ceba3475fb241610993641da27e0d676f27660a6 Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 3 Feb 2025 16:59:06 -0500 Subject: Backport pull request #13345 from jellyfin/release-10.10.z Never treat matroska as webm for audio playback Original-merge: 344cc8b97baf5026d0c95824d5b5ee2522a7c284 Merged-by: Bond-009 Backported-by: Bond_009 --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 97262fbac..35700d99f 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -2249,7 +2249,7 @@ namespace MediaBrowser.Model.Dlna } } - private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) + private static bool IsAudioContainerSupported(DirectPlayProfile profile, MediaSourceInfo item) { // Check container type if (!profile.SupportsContainer(item.Container)) @@ -2257,6 +2257,20 @@ namespace MediaBrowser.Model.Dlna return false; } + // Never direct play audio in matroska when the device only declare support for webm. + // The first check is not enough because mkv is assumed can be webm. + // See https://github.com/jellyfin/jellyfin/issues/13344 + return !ContainerHelper.ContainsContainer("mkv", item.Container) + || profile.SupportsContainer("mkv"); + } + + private static bool IsAudioDirectPlaySupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream) + { + if (!IsAudioContainerSupported(profile, item)) + { + return false; + } + // Check audio codec string? audioCodec = audioStream?.Codec; if (!profile.SupportsAudioCodec(audioCodec)) @@ -2271,19 +2285,16 @@ namespace MediaBrowser.Model.Dlna { // Check container type, this should NOT be supported // If the container is supported, the file should be directly played - if (!profile.SupportsContainer(item.Container)) + if (IsAudioContainerSupported(profile, item)) { - // Check audio codec, we cannot use the SupportsAudioCodec here - // Because that one assumes empty container supports all codec, which is just useless - string? audioCodec = audioStream?.Codec; - if (string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) || - string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return true; - } + return false; } - return false; + // Check audio codec, we cannot use the SupportsAudioCodec here + // Because that one assumes empty container supports all codec, which is just useless + string? audioCodec = audioStream?.Codec; + return string.Equals(profile.AudioCodec, audioCodec, StringComparison.OrdinalIgnoreCase) + || string.Equals(profile.Container, audioCodec, StringComparison.OrdinalIgnoreCase); } private int GetRank(ref TranscodeReason a, TranscodeReason[] rankings) -- cgit v1.2.3 From c05a41cc3c208b078950452357b65161080eb1bd Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 3 Feb 2025 17:07:11 -0500 Subject: Backport pull request #13423 from jellyfin/release-10.10.z Don't select audio stream and codec explicitly for copy when bitrate exceeds limit Original-merge: e8514de33b59e24184cc7eb7f8df9cd5e850a7de Merged-by: crobibero Backported-by: Bond_009 --- MediaBrowser.Model/Dlna/StreamBuilder.cs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'MediaBrowser.Model') diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 35700d99f..1ed493708 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -1001,6 +1001,8 @@ namespace MediaBrowser.Model.Dlna })) .All(satisfied => satisfied); + directAudioStreamSatisfied = directAudioStreamSatisfied && !playlistItem.TranscodeReasons.HasFlag(TranscodeReason.ContainerBitrateExceedsLimit); + var directAudioStream = directAudioStreamSatisfied ? audioStreamWithSupportedCodec : null; if (channelsExceedsLimit && playlistItem.TargetAudioStream is not null) -- cgit v1.2.3