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]
