From a1ff5a75ba4997f1a54fe1820624b600312eb50c Mon Sep 17 00:00:00 2001 From: Hans Keksnascher <21247718+HansKeksnascher@users.noreply.github.com> Date: Thu, 24 Sep 2026 00:08:57 +0200 Subject: [PATCH] fix: saturate eSBR QMF synthesis output on all backends ixheaacd_esbr_qmfsyn64_winadd() and ixheaacd_esbr_qmfsyn32_winadd() in the generic C and ARMv7 paths cast (syn_out >> 31) straight to WORD32. The WORD64 accumulator can exceed WORD32 range on a full-scale sample, so the cast wraps it to a negative spike instead of clipping - audible as crackling / overdrive on loud material. The ARMv8 64-band implementation already does the saturating narrowing with ixheaac_sat64_32(). Apply the same to the generic C, ARMv7 C and ARMv8 32-band variants, and use VQSHRN.S64 instead of VSHRN.S64 in the ARMv7 assembly. --- decoder/armv7/ixheaacd_esbr_qmfsyn64_winadd.s | 16 ++++++++-------- decoder/armv7/ixheaacd_qmf_dec_armv7.c | 2 +- decoder/armv8/ixheaacd_qmf_dec_armv8.c | 2 +- decoder/generic/ixheaacd_qmf_dec_generic.c | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/decoder/armv7/ixheaacd_esbr_qmfsyn64_winadd.s b/decoder/armv7/ixheaacd_esbr_qmfsyn64_winadd.s index ebfca727..0483ca7e 100644 --- a/decoder/armv7/ixheaacd_esbr_qmfsyn64_winadd.s +++ b/decoder/armv7/ixheaacd_esbr_qmfsyn64_winadd.s @@ -114,12 +114,12 @@ ixheaacd_esbr_qmfsyn64_winadd: @ PROC VMLAL.S32 Q13, D16, D18 VMLAL.S32 Q14, D17, D19 - VSHRN.S64 D26 , Q13, #31 + VQSHRN.S64 D26 , Q13, #31 VST1.32 D26[0], [R3], R5 VST1.32 D26[1], [R3], R5 - VSHRN.S64 D27 , Q14, #31 + VQSHRN.S64 D27 , Q14, #31 VST1.32 D27[0], [R3], R5 VST1.32 D27[1], [R3], R5 @@ -207,12 +207,12 @@ LOOP_1: VMLAL.S32 Q13, D16, D18 VMLAL.S32 Q14, D17, D19 - VSHRN.S64 D26 , Q13, #31 + VQSHRN.S64 D26 , Q13, #31 VST1.32 D26[0], [R3], R5 VST1.32 D26[1], [R3], R5 - VSHRN.S64 D27 , Q14, #31 + VQSHRN.S64 D27 , Q14, #31 VST1.32 D27[0], [R3], R5 VST1.32 D27[1], [R3], R5 @@ -299,12 +299,12 @@ LOOP_1: VMLAL.S32 Q13, D16, D18 VMLAL.S32 Q14, D17, D19 - VSHRN.S64 D26 , Q13, #31 + VQSHRN.S64 D26 , Q13, #31 VST1.32 D26[0], [R3], R5 VST1.32 D26[1], [R3], R5 - VSHRN.S64 D27 , Q14, #31 + VQSHRN.S64 D27 , Q14, #31 VST1.32 D27[0], [R3], R5 VST1.32 D27[1], [R3], R5 @@ -395,12 +395,12 @@ LOOP_1: VMLAL.S32 Q13, D16, D18 VMLAL.S32 Q14, D17, D19 - VSHRN.S64 D26 , Q13, #31 + VQSHRN.S64 D26 , Q13, #31 VST1.32 D26[0], [R3], R5 VST1.32 D26[1], [R3], R5 - VSHRN.S64 D27, Q14, #31 + VQSHRN.S64 D27, Q14, #31 VST1.32 D27[0], [R3], R5 VST1.32 D27[1], [R3], R5 diff --git a/decoder/armv7/ixheaacd_qmf_dec_armv7.c b/decoder/armv7/ixheaacd_qmf_dec_armv7.c index 3a18da41..3b4ae1a6 100644 --- a/decoder/armv7/ixheaacd_qmf_dec_armv7.c +++ b/decoder/armv7/ixheaacd_qmf_dec_armv7.c @@ -485,6 +485,6 @@ VOID ixheaacd_esbr_qmfsyn32_winadd(WORD32 *tmp1, WORD32 *tmp2, WORD32 *inp1, syn_out = ixheaac_add64(syn_out, ixheaac_mult64(tmp2[576 + k], inp1[2 * (k + 288)])); - sample_buffer[ch_fac * k] = (WORD32)(syn_out >> 31); + sample_buffer[ch_fac * k] = ixheaac_sat64_32(syn_out >> 31); } } diff --git a/decoder/armv8/ixheaacd_qmf_dec_armv8.c b/decoder/armv8/ixheaacd_qmf_dec_armv8.c index ebd34d52..035649ed 100644 --- a/decoder/armv8/ixheaacd_qmf_dec_armv8.c +++ b/decoder/armv8/ixheaacd_qmf_dec_armv8.c @@ -1323,7 +1323,7 @@ VOID ixheaacd_esbr_qmfsyn32_winadd(WORD32 *tmp1, WORD32 *tmp2, WORD32 *inp1, syn_out = ixheaac_add64(syn_out, ixheaac_mult64(tmp2[576 + k], inp1[2 * (k + 288)])); - sample_buffer[ch_fac * k] = (WORD32)(syn_out >> 31); + sample_buffer[ch_fac * k] = ixheaac_sat64_32(syn_out >> 31); } } diff --git a/decoder/generic/ixheaacd_qmf_dec_generic.c b/decoder/generic/ixheaacd_qmf_dec_generic.c index e3e8e4ac..0b381e72 100644 --- a/decoder/generic/ixheaacd_qmf_dec_generic.c +++ b/decoder/generic/ixheaacd_qmf_dec_generic.c @@ -1570,7 +1570,7 @@ VOID ixheaacd_esbr_qmfsyn64_winadd(WORD32 *tmp1, WORD32 *tmp2, WORD32 *inp1, syn_out = ixheaac_add64(syn_out, ixheaac_mult64(tmp2[1152 + k], inp1[k + 576])); - sample_buffer[ch_fac * k] = (WORD32)(syn_out >> 31); + sample_buffer[ch_fac * k] = ixheaac_sat64_32(syn_out >> 31); } } @@ -1603,7 +1603,7 @@ VOID ixheaacd_esbr_qmfsyn32_winadd(WORD32 *tmp1, WORD32 *tmp2, WORD32 *inp1, syn_out = ixheaac_add64(syn_out, ixheaac_mult64(tmp2[576 + k], inp1[2 * (k + 288)])); - sample_buffer[ch_fac * k] = (WORD32)(syn_out >> 31); + sample_buffer[ch_fac * k] = ixheaac_sat64_32(syn_out >> 31); } }