From 8bf6fce0d0ad61de5543f39be142fc15ec1ec128 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Wed, 14 Jan 2026 10:35:45 -0600 Subject: [PATCH] downmix ac3 to stereo to match output layout --- CHANGELOG.md | 1 + ErsatzTV.FFmpeg/Decoder/DecoderAc3Downmix.cs | 35 +++++++++++++++++++ ErsatzTV.FFmpeg/Format/AudioLayout.cs | 6 ++++ .../Pipeline/PipelineBuilderBase.cs | 12 +++++++ 4 files changed, 54 insertions(+) create mode 100644 ErsatzTV.FFmpeg/Decoder/DecoderAc3Downmix.cs create mode 100644 ErsatzTV.FFmpeg/Format/AudioLayout.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index af8866d9e..15e310518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Use configured searching log level on startup, instead of the default log level of `Information` - MySql: fix searching for shows and seasons in schedule items editor - Fix 500 errors when serving XMLTV due to concurrent file reads and writes +- Fix playback of AC3 audio when targeting stereo output and input layout changes mid-stream ## [26.1.1] - 2026-01-08 ### Fixed diff --git a/ErsatzTV.FFmpeg/Decoder/DecoderAc3Downmix.cs b/ErsatzTV.FFmpeg/Decoder/DecoderAc3Downmix.cs new file mode 100644 index 000000000..333a3be56 --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/DecoderAc3Downmix.cs @@ -0,0 +1,35 @@ +using ErsatzTV.FFmpeg.Environment; +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Decoder; + +public class DecoderAc3Downmix(int sourceChannels, int desiredChannels) : IDecoder +{ + public string Name => "ac3"; + public EnvironmentVariable[] EnvironmentVariables => []; + public string[] GlobalOptions => []; + public string[] FilterOptions => []; + public string[] OutputOptions => []; + + public string[] InputOptions(InputFile inputFile) + { + if (sourceChannels >= 2 && desiredChannels == 2) + { + return ["-c:a", "ac3", "-downmix", AudioLayout.Stereo]; + } + + // unsupported downmix; hopefully the layout doesn't change + return []; + } + + public FrameState NextState(FrameState currentState) => currentState; + + public bool AppliesTo(AudioInputFile audioInputFile) => true; + + public bool AppliesTo(VideoInputFile videoInputFile) => false; + + public bool AppliesTo(ConcatInputFile concatInputFile) => false; + + public bool AppliesTo(GraphicsEngineInput graphicsEngineInput) => false; + +} diff --git a/ErsatzTV.FFmpeg/Format/AudioLayout.cs b/ErsatzTV.FFmpeg/Format/AudioLayout.cs new file mode 100644 index 000000000..6b8552486 --- /dev/null +++ b/ErsatzTV.FFmpeg/Format/AudioLayout.cs @@ -0,0 +1,6 @@ +namespace ErsatzTV.FFmpeg.Format; + +public static class AudioLayout +{ + public const string Stereo = "stereo"; +} diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index 96702c581..2a74c1abe 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -466,6 +466,18 @@ public abstract class PipelineBuilderBase : IPipelineBuilder } } + // workaround for ac3 audio layout changes + // downmix from decoder so filter doesn't need to reinit (it can't, it will fail) + // this only really works when the desired output format matches the minimal input format + foreach (var audioInputStream in audioInputFile.Streams.OfType() + .Where(s => s.Codec == AudioFormat.Ac3).HeadOrNone()) + { + foreach (int desiredAudioChannels in audioInputFile.DesiredState.AudioChannels) + { + audioInputFile.AddOption(new DecoderAc3Downmix(audioInputStream.Channels, desiredAudioChannels)); + } + } + // always need to specify audio codec so ffmpeg doesn't default to a codec we don't want foreach (IEncoder step in AvailableEncoders.ForAudioFormat(ffmpegState, audioInputFile.DesiredState, _logger)) {