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

the encoder always formed the stereo coupling channel as L+R and disabled 
phsflginu, which could cancel strongly out-of-phase bands before quantization. 
use an L-R carrier and signal the phase flag only when difference energy 
dominates in every coupling block, while quiet and ambiguous bands retain the 
existing L+R path. this reduces the multitone fate stddev from about 1158 to 
588.



>From 72f7e7d34353ecda31f6ed139ecb9ccd02e53eaa Mon Sep 17 00:00:00 2001
From: Ayoub Nabil Boubagrat <[email protected]>
Date: Mon, 24 Aug 2026 00:30:08 +0200
Subject: [PATCH] avcodec/ac3enc: preserve strongly out-of-phase coupling bands

the encoder always formed the stereo coupling channel as L+R and disabled 
phsflginu, which could cancel strongly out-of-phase bands before quantization. 
use an L-R carrier and signal the phase flag only when difference energy 
dominates in every coupling block, while quiet and ambiguous bands retain the 
existing L+R path. this reduces the multitone fate stddev from about 1158 to 
588.

Signed-off-by: Ayoub Nabil Boubagrat 
<[email protected]>
---
 libavcodec/ac3enc.c          | 16 +++++++-
 libavcodec/ac3enc.h          |  2 +
 libavcodec/ac3enc_template.c | 77 +++++++++++++++++++++++++++++++++++-
 tests/fate/ac3.mak           | 29 +++++++++++++-
 4 files changed, 120 insertions(+), 4 deletions(-)

diff --git a/libavcodec/ac3enc.c b/libavcodec/ac3enc.c
index 0482d9ee58..903ba66ee0 100644
--- a/libavcodec/ac3enc.c
+++ b/libavcodec/ac3enc.c
@@ -1183,16 +1183,22 @@ static void count_frame_bits(AC3EncodeContext *s)
 
         /* coupling coordinates */
         if (block->cpl_in_use) {
+            int cpl_coords_exist = 0;
+
             for (ch = 1; ch <= s->fbw_channels; ch++) {
                 if (block->channel_in_cpl[ch]) {
                     if (!s->eac3 || block->new_cpl_coords[ch] != 2)
                         frame_bits++;
                     if (block->new_cpl_coords[ch]) {
+                        cpl_coords_exist = 1;
                         frame_bits += 2;
                         frame_bits += (4 + 4) * s->num_cpl_bands;
                     }
                 }
             }
+            if (s->channel_mode == AC3_CHMODE_STEREO &&
+                s->phase_flags_in_use && cpl_coords_exist)
+                frame_bits += s->num_cpl_bands;
         }
 
         /* stereo rematrixing */
@@ -1725,7 +1731,7 @@ static void output_audio_block(AC3EncodeContext *s, 
PutBitContext *pb, int blk)
                     put_bits(pb, 1, block->channel_in_cpl[ch]);
             }
             if (s->channel_mode == AC3_CHMODE_STEREO)
-                put_bits(pb, 1, 0); /* phase flags in use */
+                put_bits(pb, 1, s->phase_flags_in_use);
             start_sub = (s->start_freq[CPL_CH] - 37) / 12;
             end_sub   = (s->cpl_end_freq       - 37) / 12;
             put_bits(pb, 4, start_sub);
@@ -1742,11 +1748,14 @@ static void output_audio_block(AC3EncodeContext *s, 
PutBitContext *pb, int blk)
 
     /* coupling coordinates */
     if (block->cpl_in_use) {
+        int cpl_coords_exist = 0;
+
         for (ch = 1; ch <= s->fbw_channels; ch++) {
             if (block->channel_in_cpl[ch]) {
                 if (!s->eac3 || block->new_cpl_coords[ch] != 2)
                     put_bits(pb, 1, block->new_cpl_coords[ch]);
                 if (block->new_cpl_coords[ch]) {
+                    cpl_coords_exist = 1;
                     put_bits(pb, 2, block->cpl_master_exp[ch]);
                     for (bnd = 0; bnd < s->num_cpl_bands; bnd++) {
                         put_bits(pb, 4, block->cpl_coord_exp [ch][bnd]);
@@ -1755,6 +1764,11 @@ static void output_audio_block(AC3EncodeContext *s, 
PutBitContext *pb, int blk)
                 }
             }
         }
+        if (s->channel_mode == AC3_CHMODE_STEREO &&
+            s->phase_flags_in_use && cpl_coords_exist) {
+            for (bnd = 0; bnd < s->num_cpl_bands; bnd++)
+                put_bits(pb, 1, s->phase_flags[bnd]);
+        }
     }
 
     /* stereo rematrixing */
diff --git a/libavcodec/ac3enc.h b/libavcodec/ac3enc.h
index 3e92af17f0..e63867d0e7 100644
--- a/libavcodec/ac3enc.h
+++ b/libavcodec/ac3enc.h
@@ -213,6 +213,8 @@ typedef struct AC3EncodeContext {
 
     int cpl_on;                             ///< coupling turned on for this 
frame
     int cpl_enabled;                        ///< coupling enabled for all 
frames
+    int phase_flags_in_use;                 ///< coupling phase flags are in 
use
+    uint8_t phase_flags[AC3_MAX_CPL_BANDS]; ///< coupling phase flags          
         (phsflg)
     int num_cpl_subbands;                   ///< number of coupling subbands   
         (ncplsubnd)
     int num_cpl_bands;                      ///< number of coupling bands      
         (ncplbnd)
     uint8_t cpl_band_sizes[AC3_MAX_CPL_BANDS];  ///< number of coeffs in each 
coupling band
diff --git a/libavcodec/ac3enc_template.c b/libavcodec/ac3enc_template.c
index 5331b45cb9..6fb2e70545 100644
--- a/libavcodec/ac3enc_template.c
+++ b/libavcodec/ac3enc_template.c
@@ -44,6 +44,10 @@
 #define RENAME(element) element ## _fixed
 #endif
 
+/* power ratios for the -42 dB band floor and the 12 dB difference margin. */
+#define PHASE_BAND_ENERGY_DENOMINATOR (1 << 14)
+#define PHASE_DIFF_ENERGY_FACTOR       (1 << 4)
+
 /*
  * Apply the MDCT to input samples to generate frequency coefficients.
  * This applies the KBD window and normalizes the input to reduce precision
@@ -97,6 +101,7 @@ static void apply_channel_coupling(AC3EncodeContext *s)
     CoefSumType energy[AC3_MAX_BLOCKS][AC3_MAX_CHANNELS][16] = {{{0}}};
     int cpl_start, num_cpl_coefs;
 
+    s->phase_flags_in_use = 0;
     memset(cpl_coords,       0, AC3_MAX_BLOCKS * sizeof(*cpl_coords));
 #if AC3ENC_FLOAT
     memset(fixed_cpl_coords, 0, AC3_MAX_BLOCKS * sizeof(*cpl_coords));
@@ -108,6 +113,59 @@ static void apply_channel_coupling(AC3EncodeContext *s)
     num_cpl_coefs = FFALIGN(s->num_cpl_subbands * 12 + 1, 32);
     cpl_start     = FFMIN(256, cpl_start + num_cpl_coefs) - num_cpl_coefs;
 
+    if (s->channel_mode == AC3_CHMODE_STEREO) {
+        uint8_t phase_flags[AC3_MAX_CPL_BANDS];
+        int cpl_blocks = 0;
+
+        /* use a single phase strategy for the frame. a difference carrier is
+         * selected only when it has at least 12 dB more energy and the band
+         * is within 42 dB of the coded channel energy in every coupling
+         * block. */
+        memset(phase_flags, 1, s->num_cpl_bands);
+        for (blk = 0; blk < s->num_blocks; blk++) {
+            AC3Block *block = &s->blocks[blk];
+            CoefSumType sum[AC3_MAX_CPL_BANDS][4];
+            /* the DSP also returns sum and difference energy in slots 2/3. */
+            CoefSumType block_energy[4];
+            CoefSumType max_energy;
+
+            if (!block->cpl_in_use)
+                continue;
+            cpl_blocks++;
+            sum_square_butterfly(s, block_energy,
+                                 block->mdct_coef[1], block->mdct_coef[2],
+                                 s->start_freq[CPL_CH]);
+            i = s->start_freq[CPL_CH];
+            for (bnd = 0; bnd < s->num_cpl_bands; bnd++) {
+                sum_square_butterfly(s, sum[bnd],
+                                     block->mdct_coef[1] + i,
+                                     block->mdct_coef[2] + i,
+                                     s->cpl_band_sizes[bnd]);
+                /* reused by the coupling coordinate calculation below. */
+                energy[blk][1][bnd] = sum[bnd][0];
+                energy[blk][2][bnd] = sum[bnd][1];
+                block_energy[0] += sum[bnd][0];
+                block_energy[1] += sum[bnd][1];
+                i += s->cpl_band_sizes[bnd];
+            }
+            max_energy = FFMAX(block_energy[0], block_energy[1]);
+            for (bnd = 0; bnd < s->num_cpl_bands; bnd++) {
+                CoefSumType band_energy = FFMAX(sum[bnd][0], sum[bnd][1]);
+                int significant = band_energy >
+                    max_energy / PHASE_BAND_ENERGY_DENOMINATOR;
+
+                phase_flags[bnd] &= significant &&
+                    sum[bnd][3] / PHASE_DIFF_ENERGY_FACTOR > sum[bnd][2];
+            }
+        }
+        for (bnd = 0; bnd < s->num_cpl_bands; bnd++) {
+            int phase = phase_flags[bnd] && cpl_blocks;
+
+            s->phase_flags[bnd] = phase;
+            s->phase_flags_in_use |= phase;
+        }
+    }
+
     /* calculate coupling channel from fbw channels */
     for (blk = 0; blk < s->num_blocks; blk++) {
         AC3Block *block = &s->blocks[blk];
@@ -123,6 +181,20 @@ static void apply_channel_coupling(AC3EncodeContext *s)
                 cpl_coef[i] += ch_coef[i];
         }
 
+        if (s->channel_mode == AC3_CHMODE_STEREO) {
+            CoefType *left  = block->mdct_coef[1];
+            CoefType *right = block->mdct_coef[2];
+
+            i = s->start_freq[CPL_CH];
+            for (bnd = 0; bnd < s->num_cpl_bands; bnd++) {
+                if (s->phase_flags[bnd]) {
+                    for (j = 0; j < s->cpl_band_sizes[bnd]; j++)
+                        block->mdct_coef[CPL_CH][i + j] = left[i + j] - 
right[i + j];
+                }
+                i += s->cpl_band_sizes[bnd];
+            }
+        }
+
         /* coefficients must be clipped in order to be encoded */
         clip_coefficients(&s->adsp, cpl_coef, num_cpl_coefs);
     }
@@ -133,7 +205,10 @@ static void apply_channel_coupling(AC3EncodeContext *s)
     i = s->start_freq[CPL_CH];
     while (i < s->cpl_end_freq) {
         int band_size = s->cpl_band_sizes[bnd];
-        for (ch = CPL_CH; ch <= s->fbw_channels; ch++) {
+        /* stereo channel energies were filled during phase analysis above. */
+        int last_ch = s->channel_mode == AC3_CHMODE_STEREO ?
+                      CPL_CH : s->fbw_channels;
+        for (ch = CPL_CH; ch <= last_ch; ch++) {
             for (blk = 0; blk < s->num_blocks; blk++) {
                 AC3Block *block = &s->blocks[blk];
                 if (!block->cpl_in_use || (ch > CPL_CH && 
!block->channel_in_cpl[ch]))
diff --git a/tests/fate/ac3.mak b/tests/fate/ac3.mak
index 2eb85f63b8..605a411799 100644
--- a/tests/fate/ac3.mak
+++ b/tests/fate/ac3.mak
@@ -103,6 +103,31 @@ FATE_AC3-$(call ENCDEC, AC3, MP4 MOV, WAV_MUXER 
WAV_DEMUXER ARESAMPLE_FILTER PCM
 fate-ac3-encode: CMD = enc_dec_pcm mp4 wav s16le $(subst 
$(SAMPLES),$(TARGET_SAMPLES),$(REF)) -c:a ac3 -b:a 128k
 fate-ac3-encode: CMP_TARGET = 404.53
 
+# coupling must not cancel persistent out-of-phase stereo content.
+AC3_PHASE_EXPR = 
0.025*(sin(16000*PI*t)+sin(20000*PI*t)+sin(24000*PI*t)+sin(28000*PI*t))
+tests/data/fate/ac3-phase.wav: TAG = GEN
+tests/data/fate/ac3-phase.wav: ffmpeg$(PROGSSUF)$(EXESUF) | tests/data/fate
+       $(M)$(TARGET_EXEC) $(TARGET_PATH)/$< -nostdin -f lavfi \
+       -i "aevalsrc=$(AC3_PHASE_EXPR)|-$(AC3_PHASE_EXPR):s=48000:d=1" \
+       -c:a pcm_s16le -y $(TARGET_PATH)/$@ 2>/dev/null
+
+AC3_PHASE_DEPS = FFMPEG LAVFI_INDEV AEVALSRC_FILTER ARESAMPLE_FILTER \
+                 MP4_MUXER MOV_DEMUXER WAV_MUXER WAV_DEMUXER \
+                 PCM_S16LE_ENCODER FILE_PROTOCOL PIPE_PROTOCOL
+FATE_AC3_PHASE-$(call ALLYES, $(AC3_PHASE_DEPS) AC3_ENCODER AC3_DECODER) += 
fate-ac3-phase
+FATE_AC3_PHASE-$(call ALLYES, $(AC3_PHASE_DEPS) AC3_FIXED_ENCODER AC3_DECODER) 
+= fate-ac3-fixed-phase
+FATE_AC3_PHASE-$(call ALLYES, $(AC3_PHASE_DEPS) EAC3_ENCODER EAC3_DECODER) += 
fate-eac3-phase
+# without phase restoration the measured stddev is about 1158.
+fate-ac3-phase: CMD = enc_dec_pcm mp4 wav s16le 
$(TARGET_PATH)/tests/data/fate/ac3-phase.wav -c:a ac3 -b:a 128k
+fate-ac3-phase: CMP_TARGET = 588.4
+fate-ac3-fixed-phase: CMD = enc_dec_pcm mp4 wav s16le 
$(TARGET_PATH)/tests/data/fate/ac3-phase.wav -c:a ac3_fixed -b:a 128k
+fate-ac3-fixed-phase: CMP_TARGET = 588.4
+fate-eac3-phase: CMD = enc_dec_pcm mp4 wav s16le 
$(TARGET_PATH)/tests/data/fate/ac3-phase.wav -c:a eac3 -b:a 128k
+fate-eac3-phase: CMP_TARGET = 588.4
+fate-ac3-phase fate-ac3-fixed-phase fate-eac3-phase: 
tests/data/fate/ac3-phase.wav
+fate-ac3-phase fate-ac3-fixed-phase fate-eac3-phase: CMP = stddev
+fate-ac3-phase fate-ac3-fixed-phase fate-eac3-phase: FUZZ = 2
+fate-ac3-phase fate-ac3-fixed-phase fate-eac3-phase: REF = 
tests/data/fate/ac3-phase.wav
 
 FATE_EAC3-$(call ENCDEC, EAC3, MP4 MOV, WAV_MUXER WAV_DEMUXER ARESAMPLE_FILTER 
PCM_S16LE_ENCODER PIPE_PROTOCOL) += fate-eac3-encode
 fate-eac3-encode: CMD = enc_dec_pcm mp4 wav s16le $(subst 
$(SAMPLES),$(TARGET_SAMPLES),$(REF)) -c:a eac3 -b:a 128k
@@ -189,6 +214,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_FFMPEG += $(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: $(FATE_AC3-yes) $(FATE_EAC3-yes) $(FATE_AC3_DITHER-yes) 
$(FATE_AC3_FIXED_DEXP24-yes) $(FATE_AC3_PHASE-yes)
-- 
2.52.0

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

Reply via email to