PR #24433 opened by AYOUB NABIL BOUBAGRAT (ayoubnabil)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24433
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24433.patch

this scales overflowing coupling bands instead of clipping their coefficients. 
SNR improves by 17.3 dB on the targeted synthetic 5.1 case at 192 kbit/s with 
ac3, ac3_fixed and eac3. tested real stereo and 5.1 excerpts remain 
bit-identical.

all 36 fate-ac3 tests pass locally, including new decoded PCM tests with level 
transitions.

still a draft, ac3 float takes about 10% more CPU time on the strongly 
correlated test signal, and six synthetic opposite-phase cases lose up to 0.028 
dB of SNR. listening comparisons are pending.




>From 729e90bdb5fae8df31577c4eb3e2704dab93fa6f Mon Sep 17 00:00:00 2001
From: Ayoub Nabil Boubagrat <[email protected]>
Date: Wed, 9 Sep 2026 22:16:10 +0200
Subject: [PATCH] avcodec/ac3enc: preserve coupling band shape when
 coefficients overflow

Signed-off-by: Ayoub Nabil Boubagrat 
<[email protected]>
---
 libavcodec/ac3enc_template.c               | 30 ++++++++++++++++-
 tests/fate/ac3.mak                         | 38 ++++++++++++++++++++--
 tests/ref/fate/ac3-fixed-coupling-headroom | 10 ++++++
 3 files changed, 75 insertions(+), 3 deletions(-)
 create mode 100644 tests/ref/fate/ac3-fixed-coupling-headroom

diff --git a/libavcodec/ac3enc_template.c b/libavcodec/ac3enc_template.c
index 6fb2e70545..12d52dc21a 100644
--- a/libavcodec/ac3enc_template.c
+++ b/libavcodec/ac3enc_template.c
@@ -194,9 +194,37 @@ static void apply_channel_coupling(AC3EncodeContext *s)
                 i += s->cpl_band_sizes[bnd];
             }
         }
+    }
 
+    /* Preserve the spectral shape of overflowing coupling bands. Use the same
+     * scale in every block so coordinate reuse does not span scale changes. */
+    i = s->start_freq[CPL_CH];
+    for (bnd = 0; bnd < s->num_cpl_bands; bnd++) {
+        CoefType peak = 0;
+        for (blk = 0; blk < s->num_blocks; blk++) {
+            if (!s->blocks[blk].cpl_in_use)
+                continue;
+            for (j = 0; j < s->cpl_band_sizes[bnd]; j++)
+                peak = FFMAX(peak, FFABS(s->blocks[blk].mdct_coef[CPL_CH][i + 
j]));
+        }
+        /* At most five full-bandwidth channels contribute to the sum. */
+        for (int shift = 0; shift < 3 && peak > COEF_MAX; shift++) {
+            for (blk = 0; blk < s->num_blocks; blk++) {
+                if (!s->blocks[blk].cpl_in_use)
+                    continue;
+                for (j = 0; j < s->cpl_band_sizes[bnd]; j++)
+                    s->blocks[blk].mdct_coef[CPL_CH][i + j] /= 2;
+            }
+            peak /= 2;
+        }
+        i += s->cpl_band_sizes[bnd];
+    }
+
+    for (blk = 0; blk < s->num_blocks; blk++) {
         /* coefficients must be clipped in order to be encoded */
-        clip_coefficients(&s->adsp, cpl_coef, num_cpl_coefs);
+        if (s->blocks[blk].cpl_in_use)
+            clip_coefficients(&s->adsp, s->blocks[blk].mdct_coef[CPL_CH] + 
cpl_start,
+                              num_cpl_coefs);
     }
 
     /* calculate energy in each band in coupling channel and each fbw channel 
*/
diff --git a/tests/fate/ac3.mak b/tests/fate/ac3.mak
index 605a411799..7dc47a04d7 100644
--- a/tests/fate/ac3.mak
+++ b/tests/fate/ac3.mak
@@ -143,6 +143,40 @@ fate-ac3-fixed-encode: CMD = md5 -i $(SRC) -c ac3_fixed 
-ab 128k -f ac3 -flags +
 fate-ac3-fixed-encode: CMP = oneline
 fate-ac3-fixed-encode: REF = e9d78bca187b4bbafc4512bcea8efd3e
 
+# Correlated full-bandwidth channels can overflow the coupling coefficient
+# range. Preserve both tones instead of clipping the larger coefficient.
+AC3_HEADROOM_EXPR = 0.9*(0.7*sin(2*PI*6000*t)+0.3*sin(2*PI*6125*t))
+FATE_AC3_HEADROOM-$(call FILTERFRAMECRC, AEVALSRC ARESAMPLE, AC3_FIXED_ENCODER 
PCM_S16LE_DECODER LAVFI_INDEV) += fate-ac3-fixed-coupling-headroom
+fate-ac3-fixed-coupling-headroom: CMD = framecrc -auto_conversion_filters -f 
lavfi \
+    -i 
"aevalsrc=$(AC3_HEADROOM_EXPR)|$(AC3_HEADROOM_EXPR)|$(AC3_HEADROOM_EXPR)|0|$(AC3_HEADROOM_EXPR)|$(AC3_HEADROOM_EXPR):s=48000:d=0.128:c=5.1,aresample=osf=s16"
 \
+    -c:a ac3_fixed -b:a 192k -flags +bitexact
+
+# Exercise gain changes within a frame as well as across frame boundaries.
+# Compare decoded PCM with the input, not only the encoded packet checksum.
+AC3_HEADROOM_TRANSITION_EXPR = 
$(AC3_HEADROOM_EXPR)*if(lt(mod(t\,0.048)\,0.024)\,1\,0.15)
+tests/data/fate/ac3-headroom-transition.wav: TAG = GEN
+tests/data/fate/ac3-headroom-transition.wav: ffmpeg$(PROGSSUF)$(EXESUF) | 
tests/data/fate
+       $(M)$(TARGET_EXEC) $(TARGET_PATH)/$< -nostdin -v error -f lavfi \
+       -i 
"aevalsrc=$(AC3_HEADROOM_TRANSITION_EXPR)|$(AC3_HEADROOM_TRANSITION_EXPR)|$(AC3_HEADROOM_TRANSITION_EXPR)|0|$(AC3_HEADROOM_TRANSITION_EXPR)|$(AC3_HEADROOM_TRANSITION_EXPR):s=48000:d=0.384:c=5.1"
 \
+       -c:a pcm_s16le -y $(TARGET_PATH)/$@
+
+AC3_HEADROOM_DEPS = FFMPEG LAVFI_INDEV AEVALSRC_FILTER ARESAMPLE_FILTER \
+                    PCM_F64LE_DECODER PCM_S16LE_DECODER PCM_S16LE_ENCODER \
+                    MP4_MUXER MOV_DEMUXER WAV_MUXER WAV_DEMUXER FILE_PROTOCOL 
PIPE_PROTOCOL
+FATE_AC3_HEADROOM-$(call ALLYES, $(AC3_HEADROOM_DEPS) AC3_ENCODER AC3_DECODER) 
+= fate-ac3-coupling-headroom-pcm
+FATE_AC3_HEADROOM-$(call ALLYES, $(AC3_HEADROOM_DEPS) AC3_FIXED_ENCODER 
AC3_DECODER) += fate-ac3-fixed-coupling-headroom-pcm
+FATE_AC3_HEADROOM-$(call ALLYES, $(AC3_HEADROOM_DEPS) EAC3_ENCODER 
EAC3_DECODER) += fate-eac3-coupling-headroom-pcm
+fate-ac3-coupling-headroom-pcm: CMD = enc_dec_pcm mp4 wav s16le 
$(TARGET_PATH)/tests/data/fate/ac3-headroom-transition.wav -c:a ac3 -b:a 192k
+fate-ac3-fixed-coupling-headroom-pcm: CMD = enc_dec_pcm mp4 wav s16le 
$(TARGET_PATH)/tests/data/fate/ac3-headroom-transition.wav -c:a ac3_fixed -b:a 
192k
+fate-eac3-coupling-headroom-pcm: CMD = enc_dec_pcm mp4 wav s16le 
$(TARGET_PATH)/tests/data/fate/ac3-headroom-transition.wav -c:a eac3 -b:a 192k
+fate-ac3-coupling-headroom-pcm fate-ac3-fixed-coupling-headroom-pcm 
fate-eac3-coupling-headroom-pcm: tests/data/fate/ac3-headroom-transition.wav
+fate-ac3-coupling-headroom-pcm fate-ac3-fixed-coupling-headroom-pcm 
fate-eac3-coupling-headroom-pcm: CMP = stddev
+# Before normalization the stddev is approximately 1895, 1904 and 1895.
+fate-ac3-coupling-headroom-pcm: CMP_TARGET = 589.58
+fate-ac3-fixed-coupling-headroom-pcm: CMP_TARGET = 596.65
+fate-eac3-coupling-headroom-pcm: CMP_TARGET = 625.73
+fate-ac3-coupling-headroom-pcm fate-ac3-fixed-coupling-headroom-pcm 
fate-eac3-coupling-headroom-pcm: REF = 
tests/data/fate/ac3-headroom-transition.wav
+
 # This tests that the LFE does not get lost when converting the input 7.1
 # to a channel layout supported by the encoder.
 FATE_AC3-$(call FRAMECRC, WAV, PCM_S16LE, ARESAMPLE_FILTER AC3_FIXED_ENCODER) 
+= fate-ac3-fixed-encode-2
@@ -214,6 +248,6 @@ fate-eac3-core-bsf: CMP = oneline
 fate-eac3-core-bsf: REF = b704bf851e99b7442e9bed368b60e6ca
 
 FATE_SAMPLES_AVCONV += $(FATE_AC3-yes) $(FATE_EAC3-yes)
-FATE_FFMPEG += $(FATE_AC3_DITHER-yes) $(FATE_AC3_FIXED_DEXP24-yes) 
$(FATE_AC3_PHASE-yes)
+FATE_FFMPEG += $(FATE_AC3_DITHER-yes) $(FATE_AC3_FIXED_DEXP24-yes) 
$(FATE_AC3_PHASE-yes) $(FATE_AC3_HEADROOM-yes)
 
-fate-ac3: $(FATE_AC3-yes) $(FATE_EAC3-yes) $(FATE_AC3_DITHER-yes) 
$(FATE_AC3_FIXED_DEXP24-yes) $(FATE_AC3_PHASE-yes)
+fate-ac3: $(FATE_AC3-yes) $(FATE_EAC3-yes) $(FATE_AC3_DITHER-yes) 
$(FATE_AC3_FIXED_DEXP24-yes) $(FATE_AC3_PHASE-yes) $(FATE_AC3_HEADROOM-yes)
diff --git a/tests/ref/fate/ac3-fixed-coupling-headroom 
b/tests/ref/fate/ac3-fixed-coupling-headroom
new file mode 100644
index 0000000000..4100fb8524
--- /dev/null
+++ b/tests/ref/fate/ac3-fixed-coupling-headroom
@@ -0,0 +1,10 @@
+#tb 0: 1/48000
+#media_type 0: audio
+#codec_id 0: ac3
+#sample_rate 0: 48000
+#channel_layout_name 0: 5.1
+0,       -256,       -256,     1536,      768, 0x95933c6a, S=1, Skip Samples,  
     10, 0x00090001
+0,       1280,       1280,     1536,      768, 0x4715282a
+0,       2816,       2816,     1536,      768, 0x4715282a
+0,       4352,       4352,     1536,      768, 0x4715282a
+0,       5888,       5888,      256,      768, 0x33c66cbf, S=1, Skip Samples,  
     10, 0x00190005
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to