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

The FLT and DBL to S64 conversions in `libswresample/audioconvert.c` scale by 
2^63 and call `llrint()` without clipping. A sample of exactly 1.0 or more does 
not fit in `int64_t`. On x86, `llrint()` then returns `INT64_MIN`, so a 
full-scale positive sample becomes a full-scale negative one. The conversions 
to u8, s16 and s32 already clip.

Example (master fa4daf630e, x86-64): `ffmpeg -f lavfi -i aevalsrc=1.0:d=0.001 
-c:a pcm_s64le out.wav` writes samples that decode as -1.0. The same pipeline 
with `pcm_s32le` gives 0.9999999995. Decoded lossy audio often goes slightly 
above 1.0, so any conversion of such a stream to s64 (`pcm_s64le`, 
`aformat=s64`) turns each overshoot into a full-scale negative click.

The fix clips both conversions to the `int64_t` range. Samples inside (-1.0, 
1.0) and NaN are converted as before.

The second patch adds `fate-swr-dbl-s64-clip` and `fate-swr-flt-s64-clip`. They 
convert samples from -2.0 to 1.5 in steps of 0.5 (all exact in binary) to s64 
from double and from float, then to s32 for the md5. No sample is needed. Both 
tests fail before the fix and pass after it. With only the double conversion 
fixed, the float test still fails, so each test covers its own path. `fate-swr` 
(414 tests) passes unchanged, with release and ASan builds.

This issue was found by an LLM-Enhanced Static Analysis Engine and verified 
manually.

🤖 Generated with [Claude Code](https://claude.com/claude-code)



From e8b860d329c9bc0b45fde54c08a5c50f71156d5d Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Tue, 27 Oct 2026 14:41:06 +0800
Subject: [PATCH 1/2] swresample/audioconvert: clip float and double samples
 converted to s64

The FLT and DBL to S64 conversions scale by 2^63 and call llrint()
without clipping. A sample of exactly 1.0 or more does not fit in
int64_t, and on x86 llrint() then returns INT64_MIN, so a full-scale
positive sample becomes a full-scale negative one. The conversions to
the other integer formats clip.

Clip to the int64_t range like the s32 conversions.

Signed-off-by: Martin Mirchev <[email protected]>
---
 libswresample/audioconvert.c | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/libswresample/audioconvert.c b/libswresample/audioconvert.c
index 5956f29676e..f4098f963ce 100644
--- a/libswresample/audioconvert.c
+++ b/libswresample/audioconvert.c
@@ -52,6 +52,25 @@ static void CONV_FUNC_NAME(ofmt, ifmt)(DSDContext *st, 
uint8_t *po, const uint8_
     }\
 }
 
+/* 1.0 * 2^63 does not fit in int64_t, so clip before converting */
+static av_always_inline int64_t flt_to_s64(float v)
+{
+    if (v >= 1.0f)
+        return INT64_MAX;
+    if (v <= -1.0f)
+        return INT64_MIN;
+    return llrintf(v * (UINT64_C(1)<<63));
+}
+
+static av_always_inline int64_t dbl_to_s64(double v)
+{
+    if (v >= 1.0)
+        return INT64_MAX;
+    if (v <= -1.0)
+        return INT64_MIN;
+    return llrint(v * (UINT64_C(1)<<63));
+}
+
 //FIXME put things below under ifdefs so we do not waste space for cases no 
codec will need
 CONV_FUNC(AV_SAMPLE_FMT_U8 , uint8_t, AV_SAMPLE_FMT_U8 ,  *(const uint8_t*)pi)
 CONV_FUNC(AV_SAMPLE_FMT_S16, int16_t, AV_SAMPLE_FMT_U8 , (*(const uint8_t*)pi 
- 0x80U)<<8)
@@ -80,13 +99,13 @@ CONV_FUNC(AV_SAMPLE_FMT_DBL, double , AV_SAMPLE_FMT_S64,  
*(const int64_t*)pi*(1
 CONV_FUNC(AV_SAMPLE_FMT_U8 , uint8_t, AV_SAMPLE_FMT_FLT, av_clip_uint8(  
lrintf(*(const float*)pi * (1<<7)) + 0x80))
 CONV_FUNC(AV_SAMPLE_FMT_S16, int16_t, AV_SAMPLE_FMT_FLT, av_clip_int16(  
lrintf(*(const float*)pi * (1<<15))))
 CONV_FUNC(AV_SAMPLE_FMT_S32, int32_t, AV_SAMPLE_FMT_FLT, 
av_clipl_int32(llrintf(*(const float*)pi * (1U<<31))))
-CONV_FUNC(AV_SAMPLE_FMT_S64, int64_t, AV_SAMPLE_FMT_FLT, llrintf(*(const 
float*)pi * (UINT64_C(1)<<63)))
+CONV_FUNC(AV_SAMPLE_FMT_S64, int64_t, AV_SAMPLE_FMT_FLT, flt_to_s64(*(const 
float*)pi))
 CONV_FUNC(AV_SAMPLE_FMT_FLT, float  , AV_SAMPLE_FMT_FLT, *(const float*)pi)
 CONV_FUNC(AV_SAMPLE_FMT_DBL, double , AV_SAMPLE_FMT_FLT, *(const float*)pi)
 CONV_FUNC(AV_SAMPLE_FMT_U8 , uint8_t, AV_SAMPLE_FMT_DBL, av_clip_uint8(  
lrint(*(const double*)pi * (1<<7)) + 0x80))
 CONV_FUNC(AV_SAMPLE_FMT_S16, int16_t, AV_SAMPLE_FMT_DBL, av_clip_int16(  
lrint(*(const double*)pi * (1<<15))))
 CONV_FUNC(AV_SAMPLE_FMT_S32, int32_t, AV_SAMPLE_FMT_DBL, 
av_clipl_int32(llrint(*(const double*)pi * (1U<<31))))
-CONV_FUNC(AV_SAMPLE_FMT_S64, int64_t, AV_SAMPLE_FMT_DBL, llrint(*(const 
double*)pi * (UINT64_C(1)<<63)))
+CONV_FUNC(AV_SAMPLE_FMT_S64, int64_t, AV_SAMPLE_FMT_DBL, dbl_to_s64(*(const 
double*)pi))
 CONV_FUNC(AV_SAMPLE_FMT_FLT, float  , AV_SAMPLE_FMT_DBL, *(const double*)pi)
 CONV_FUNC(AV_SAMPLE_FMT_DBL, double , AV_SAMPLE_FMT_DBL, *(const double*)pi)
 CONV_FUNC(AV_SAMPLE_FMT_DSD, uint8_t, AV_SAMPLE_FMT_DSD, *(const uint8_t*)pi)
-- 
2.54.0


From 73cab9de85b22f6eab342cc59b0c73199028cb94 Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Tue, 27 Oct 2026 14:47:46 +0800
Subject: [PATCH 2/2] fate/libswresample: add tests for float and double to s64
 clipping

Convert samples from -2.0 to 1.5 to s64 from double and from float
input. Samples of 1.0 and more must become the largest positive value.

Signed-off-by: Martin Mirchev <[email protected]>
---
 tests/fate/libswresample.mak | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/tests/fate/libswresample.mak b/tests/fate/libswresample.mak
index bd3b228a584..4d7be7b9935 100644
--- a/tests/fate/libswresample.mak
+++ b/tests/fate/libswresample.mak
@@ -1116,6 +1116,21 @@ fate-swr-custom-reorder: REF = 
3621f90f05eb313858791b3915b9ca18
 
 FATE_SWR += $(FATE_SWR_CUSTOM_REORDER-yes)
 
+# -2.0 to 1.5 in steps of 0.5: values of 1.0 and more must clip to the
+# largest positive sample when converted to s64
+FATE_SWR_S64_SRC = (mod(n\,8)-4)/2
+
+FATE_SWR_S64_CLIP-$(call ENCMUX, PCM_S32LE, PCM_S32LE, AEVALSRC_FILTER 
ARESAMPLE_FILTER AFORMAT_FILTER FILE_PROTOCOL) += fate-swr-dbl-s64-clip 
fate-swr-flt-s64-clip
+fate-swr-dbl-s64-clip: CMD = md5 -filter_complex 
"aevalsrc=$(FATE_SWR_S64_SRC):d=0.01,aresample,aformat=f=s64,aresample,aformat=f=s32[OUT]"
 -map [OUT] -f s32le
+fate-swr-dbl-s64-clip: CMP = oneline
+fate-swr-dbl-s64-clip: REF = e47630b4a3176417841c079ae76d2b85
+
+fate-swr-flt-s64-clip: CMD = md5 -filter_complex 
"aevalsrc=$(FATE_SWR_S64_SRC):d=0.01,aresample,aformat=f=flt,aresample,aformat=f=s64,aresample,aformat=f=s32[OUT]"
 -map [OUT] -f s32le
+fate-swr-flt-s64-clip: CMP = oneline
+fate-swr-flt-s64-clip: REF = e47630b4a3176417841c079ae76d2b85
+
+FATE_SWR += $(FATE_SWR_S64_CLIP-yes)
+
 FATE_SWR_22_2_LAYOUTS   = 9.1.6 9.1.4 7.2.3 7.1.6 7.1.4 7.1.2 5.1.6 5.1.4 
5.1.2 7.1 5.1 stereo mono
 FATE_SWR_9_1_6_LAYOUTS  = 9.1.4 7.2.3 7.1.6 7.1.4 7.1.2 5.1.6 5.1.4 5.1.2 7.1 
5.1 stereo mono
 FATE_SWR_9_1_4_LAYOUTS  = 7.2.3 7.1.4 7.1.2 5.1.4 5.1.2 7.1 5.1 stereo mono
-- 
2.54.0

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

Reply via email to