aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadowghost <Shadowghost@users.noreply.github.com>2026-09-27 16:30:57 -0400
committerCody Robibero <cody@robibe.ro>2026-09-27 16:30:57 -0400
commit3142fcd12fc063ea4b4a181c2af7384e6f2c1683 (patch)
tree3b4e878f26cf6d2f188a495330fa4c59ca27f6b4
parent8bce631afa58e553183435b9568e5bac293c1353 (diff)
Backport pull request #18195 from jellyfin/release-12.z
Apply stereo downmix filter consistently to progressive audio transcodes Original-merge: 4d99715770afb0882b730fa78f0105bcea893a1d Merged-by: crobibero <cody@robibe.ro> Backported-by: Cody Robibero <cody@robibe.ro>
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs11
-rw-r--r--tests/Jellyfin.Controller.Tests/MediaEncoding/EncodingHelperTests.cs60
2 files changed, 70 insertions, 1 deletions
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 6f010c0242..58d7495fa4 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -7832,7 +7832,9 @@ namespace MediaBrowser.Controller.MediaEncoding
var channels = state.OutputAudioChannels;
- var useDownMixAlgorithm = state.AudioStream is not null
+ // Must match the condition under which GetAudioFilterParam emits the downmix filter.
+ var useDownMixAlgorithm = channels == 2
+ && state.AudioStream?.Channels > 2
&& DownMixAlgorithmsHelper.AlgorithmFilterStrings.ContainsKey((encodingOptions.DownMixStereoAlgorithm, DownMixAlgorithmsHelper.InferChannelLayout(state.AudioStream)));
if (channels.HasValue && !useDownMixAlgorithm)
@@ -7927,6 +7929,13 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ar " + sampleRateValue.ToString(CultureInfo.InvariantCulture));
}
+ // Without the downmix filter, -ac 2 alone drops the LFE channel.
+ var audioFilterParam = GetAudioFilterParam(state, encodingOptions);
+ if (!string.IsNullOrEmpty(audioFilterParam))
+ {
+ audioTranscodeParams.Add(audioFilterParam.TrimStart());
+ }
+
// Copy the movflags from GetProgressiveVideoFullCommandLine
// See #9248 and the associated PR for why this is needed
if (_mp4ContainerNames.Contains(state.OutputContainer))
diff --git a/tests/Jellyfin.Controller.Tests/MediaEncoding/EncodingHelperTests.cs b/tests/Jellyfin.Controller.Tests/MediaEncoding/EncodingHelperTests.cs
index 2b009b4673..aca55fbcc1 100644
--- a/tests/Jellyfin.Controller.Tests/MediaEncoding/EncodingHelperTests.cs
+++ b/tests/Jellyfin.Controller.Tests/MediaEncoding/EncodingHelperTests.cs
@@ -261,6 +261,66 @@ public class EncodingHelperTests
Assert.Contains("-ar 48000", args, StringComparison.Ordinal);
}
+ [Fact]
+ public void GetProgressiveAudioFullCommandLine_StereoDownmix_AppliesDownMixAlgorithm()
+ {
+ // Issue #18194: -ac 2 alone drops the LFE channel, the configured downmix filter must be applied.
+ var state = BuildAudioState("aac", 48000);
+ state.AudioStream.Channels = 6;
+ state.AudioStream.ChannelLayout = "5.1";
+ state.OutputAudioChannels = 2;
+ var options = new EncodingOptions { DownMixStereoAlgorithm = DownMixStereoAlgorithms.Dave750, DownMixAudioBoost = 1 };
+ var args = CreateHelper().GetProgressiveAudioFullCommandLine(state, options, "/tmp/out");
+
+ Assert.Contains(
+ "-af \"" + DownMixAlgorithmsHelper.AlgorithmFilterStrings[(DownMixStereoAlgorithms.Dave750, "5.1")] + "\"",
+ args,
+ StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void GetProgressiveAudioFullCommandLine_NoDownmix_EmitsNoAudioFilter()
+ {
+ var state = BuildAudioState("aac", 48000);
+ state.AudioStream.Channels = 2;
+ state.OutputAudioChannels = 2;
+ var options = new EncodingOptions { DownMixStereoAlgorithm = DownMixStereoAlgorithms.Dave750 };
+ var args = CreateHelper().GetProgressiveAudioFullCommandLine(state, options, "/tmp/out");
+
+ Assert.DoesNotContain("-af", args, StringComparison.Ordinal);
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(6)]
+ public void GetProgressiveVideoAudioArguments_NonStereoOutput_KeepsChannelCount(int outputChannels)
+ {
+ // The downmix filter only applies to stereo output, so -ac must not be dropped otherwise.
+ var state = BuildAudioState("aac", 48000);
+ state.AudioStream.Channels = 6;
+ state.AudioStream.ChannelLayout = "5.1";
+ state.OutputAudioChannels = outputChannels;
+ var options = new EncodingOptions { DownMixStereoAlgorithm = DownMixStereoAlgorithms.Dave750 };
+ var args = CreateHelper().GetProgressiveVideoAudioArguments(state, options);
+
+ Assert.Contains("-ac " + outputChannels, args, StringComparison.Ordinal);
+ Assert.DoesNotContain("pan=", args, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void GetProgressiveVideoAudioArguments_StereoDownmix_UsesFilterInsteadOfChannelCount()
+ {
+ var state = BuildAudioState("aac", 48000);
+ state.AudioStream.Channels = 6;
+ state.AudioStream.ChannelLayout = "5.1";
+ state.OutputAudioChannels = 2;
+ var options = new EncodingOptions { DownMixStereoAlgorithm = DownMixStereoAlgorithms.Dave750 };
+ var args = CreateHelper().GetProgressiveVideoAudioArguments(state, options);
+
+ Assert.DoesNotContain("-ac ", args, StringComparison.Ordinal);
+ Assert.Contains("pan=stereo", args, StringComparison.Ordinal);
+ }
+
private static EncodingJobInfo BuildAudioState(string audioCodec, int requestedSampleRate, string? outputContainer = null)
{
var audio = new MediaStream { Index = 0, Type = MediaStreamType.Audio, Codec = "flac", SampleRate = 96000 };