PR #25515 opened by Forgejo_Fairy
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25515
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25515.patch

Negative-zero samples can replace a zero period peak in speechnorm's 
nonnegative analysis branch. Dividing the target peak by that value produces 
negative infinite gain, corrupting silence and subsequent nonzero audio; linked 
processing can spread the corruption to other channels.

Take the absolute value when accumulating the nonnegative half-period's peak. 
This treats both signs of zero identically without changing finite nonzero peak 
values.

Add four synthetic FATE tests covering float and double processing, linked and 
independent channels, signed silence, and transitions to and from nonzero 
input. Expected sample counts and NaN/infinity statistics reside in 
tests/ref/fate/filter-speechnorm; no external sample is needed.

Validation on Linux AArch64 with GCC 13.3.0: all four tests fail with the 
unpatched binary and pass with this change. The synthetic cases and attached 
WAV from #25434 produce finite output after the fix, and AAC encoding succeeds 
with linking enabled and disabled. Ordinary sine output remains byte-identical 
in both formats and linking modes.

Fixes #25434.


>From dfde5f140545a93529ac297b10c7b83d138ed32f Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 27 Oct 2026 03:55:37 +0000
Subject: [PATCH] avfilter/af_speechnorm: keep peak magnitudes nonnegative

Negative zero enters the nonnegative analysis branch and can replace a
zero peak through FFMAX. Dividing the target peak by that negative zero
then produces a negative infinite gain, corrupting both silence and later
nonzero input.

Take the absolute value when accumulating the nonnegative half-period's
peak. This preserves finite nonzero peaks while treating both signs of
zero identically.

Add synthetic FATE coverage for both sample formats, linked and unlinked
channels, and transitions between signed silence and nonzero samples.
The tests compare the sample count and NaN/Inf statistics with a reference.

Fixes #25434.

Assisted-by: Fairy
---
 libavfilter/af_speechnorm.c      |  2 +-
 tests/fate/filter-audio.mak      | 12 ++++++++++++
 tests/ref/fate/filter-speechnorm |  4 ++++
 3 files changed, 17 insertions(+), 1 deletion(-)
 create mode 100644 tests/ref/fate/filter-speechnorm

diff --git a/libavfilter/af_speechnorm.c b/libavfilter/af_speechnorm.c
index 9e2ba6381c5..729c4d2dc04 100644
--- a/libavfilter/af_speechnorm.c
+++ b/libavfilter/af_speechnorm.c
@@ -283,7 +283,7 @@ static void analyze_channel_## name (AVFilterContext *ctx, 
ChannelContext *cc,
         new_size = pi[pi_end].size;                                            
 \
         if (cc->state) {                                                       
 \
             while (src[n] >= zero) {                                           
 \
-                new_max_peak = FFMAX(new_max_peak,  src[n]);                   
 \
+                new_max_peak = FFMAX(new_max_peak, fabs(src[n]));              
 \
                 new_rms_sum += src[n] * src[n];                                
 \
                 new_size++;                                                    
 \
                 n++;                                                           
 \
diff --git a/tests/fate/filter-audio.mak b/tests/fate/filter-audio.mak
index f1035bba24a..00376350edb 100644
--- a/tests/fate/filter-audio.mak
+++ b/tests/fate/filter-audio.mak
@@ -458,6 +458,18 @@ fate-filter-crazychannels: 
tests/data/filtergraphs/crazychannels
 fate-filter-crazychannels: CMD = framecrc -auto_conversion_filters 
-/filter_complex $(TARGET_PATH)/tests/data/filtergraphs/crazychannels
 FATE_AFILTER-$(call FILTERFRAMECRC, ARESAMPLE SINE JOIN ATRIM CHANNELMAP 
CHANNELSPLIT, FILE_PROTOCOL) += fate-filter-crazychannels
 
+# Exercise signed silence and transitions to/from nonzero input across frames.
+FATE_SPEECHNORM = fate-filter-speechnorm-fltp fate-filter-speechnorm-dblp \
+                 fate-filter-speechnorm-fltp-linked 
fate-filter-speechnorm-dblp-linked
+
+fate-filter-speechnorm-fltp fate-filter-speechnorm-fltp-linked: SPEECHNORM_FMT 
= fltp
+fate-filter-speechnorm-dblp fate-filter-speechnorm-dblp-linked: SPEECHNORM_FMT 
= dblp
+$(FATE_SPEECHNORM): SPEECHNORM_LINK = 0
+fate-filter-speechnorm-fltp-linked fate-filter-speechnorm-dblp-linked: 
SPEECHNORM_LINK = 1
+$(FATE_SPEECHNORM): CMD = ffmpeg -auto_conversion_filters -f lavfi -i 
"aevalsrc=0|-0|if(lt(t\,0.5)\,-0\,0.1)|if(lt(t\,0.5)\,-0.1\,-0):s=48000:d=1" 
-af 
"aformat=sample_fmts=$(SPEECHNORM_FMT),speechnorm=link=$(SPEECHNORM_LINK),asetnsamples=n=48000:p=0,astats=metadata=1:measure_perchannel=none:measure_overall=Number_of_samples+Number_of_NaNs+Number_of_Infs,ametadata=mode=print:file=-"
 -c:a pcm_f64le -f null -
+$(FATE_SPEECHNORM): REF = $(SRC_PATH)/tests/ref/fate/filter-speechnorm
+FATE_AFILTER-$(call ALLYES, LAVFI_INDEV AEVALSRC_FILTER AFORMAT_FILTER 
ARESAMPLE_FILTER SPEECHNORM_FILTER ASETNSAMPLES_FILTER ASTATS_FILTER 
AMETADATA_FILTER PCM_F64$(if $(filter yes,$(HAVE_BIGENDIAN)),BE,LE)_DECODER 
PCM_F64LE_ENCODER NULL_MUXER PIPE_PROTOCOL) += $(FATE_SPEECHNORM)
+
 FATE_AFILTER-yes += fate-filter-formats
 fate-filter-formats: libavfilter/tests/formats$(EXESUF)
 fate-filter-formats: CMD = run libavfilter/tests/formats$(EXESUF)
diff --git a/tests/ref/fate/filter-speechnorm b/tests/ref/fate/filter-speechnorm
new file mode 100644
index 00000000000..91a775f1e34
--- /dev/null
+++ b/tests/ref/fate/filter-speechnorm
@@ -0,0 +1,4 @@
+frame:0    pts:0       pts_time:0
+lavfi.astats.Overall.Number_of_samples=48000.000000
+lavfi.astats.Number of NaNs=0.000000
+lavfi.astats.Number of Infs=0.000000
-- 
2.54.0

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

Reply via email to