[PR] avcodec/ac3enc: preserve strongly out-of-phase coupling bands (PR #24253)
AYOUB NABIL BOUBAGRAT via ffmpeg-devel <[email protected]>
| Newsgroups | gmane.comp.video.ffmpeg.devel |
|---|---|
| Message-ID | <[email protected]> |
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]