This is an automated email from the git hooks/post-receive script.
Git pushed a commit to branch master
in repository ffmpeg.
The following commit(s) were added to refs/heads/master by this push:
new fea2db1be1 avcodec/ac3enc: preserve strongly out-of-phase coupling
bands
fea2db1be1 is described below
commit fea2db1be1bcda293513042401b86f07ade8e225
Author: Ayoub Nabil Boubagrat
<[email protected]>
AuthorDate: Mon Aug 24 00:30:08 2026 +0200
Commit: James Almer <[email protected]>
CommitDate: Wed Aug 26 15:53:44 2026 +0000
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)
--
To stop receiving notification emails like this one, please contact
[email protected].
_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]