From 47fea7f06bac09daa402438966c3f8753510481e Mon Sep 17 00:00:00 2001
From: Brick <[EMAIL REDACTED]>
Date: Sun, 20 Aug 2023 16:08:19 +0100
Subject: [PATCH] Used fixed-point arithmetic in ResampleAudio
---
build-scripts/gen_audio_resampler_filter.c | 6 +-
src/audio/SDL_audio_resampler_filter.h | 3 +-
src/audio/SDL_audiocvt.c | 68 ++++++++++------------
src/audio/SDL_sysaudio.h | 2 +-
4 files changed, 37 insertions(+), 42 deletions(-)
diff --git a/build-scripts/gen_audio_resampler_filter.c b/build-scripts/gen_audio_resampler_filter.c
index 3aab606ddcc6..7d2ca04c07be 100644
--- a/build-scripts/gen_audio_resampler_filter.c
+++ b/build-scripts/gen_audio_resampler_filter.c
@@ -47,7 +47,8 @@ gcc -o genfilter build-scripts/gen_audio_resampler_filter.c -lm && ./genfilter >
#define RESAMPLER_ZERO_CROSSINGS 5
#define RESAMPLER_BITS_PER_SAMPLE 16
-#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1))
+#define RESAMPLER_BITS_PER_ZERO_CROSSING ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1)
+#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)
#define RESAMPLER_FILTER_SIZE (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * RESAMPLER_ZERO_CROSSINGS)
/* This is a "modified" bessel function, so you can't use POSIX j0() */
@@ -135,7 +136,8 @@ int main(void)
"\n"
"#define RESAMPLER_ZERO_CROSSINGS %d\n"
"#define RESAMPLER_BITS_PER_SAMPLE %d\n"
- "#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1))\n"
+ "#define RESAMPLER_BITS_PER_ZERO_CROSSING ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1)\n"
+ "#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)\n"
"#define RESAMPLER_FILTER_SIZE (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * RESAMPLER_ZERO_CROSSINGS)\n"
"\n", RESAMPLER_ZERO_CROSSINGS, RESAMPLER_BITS_PER_SAMPLE
);
diff --git a/src/audio/SDL_audio_resampler_filter.h b/src/audio/SDL_audio_resampler_filter.h
index 94e98e346102..08b615547a93 100644
--- a/src/audio/SDL_audio_resampler_filter.h
+++ b/src/audio/SDL_audio_resampler_filter.h
@@ -23,7 +23,8 @@
#define RESAMPLER_ZERO_CROSSINGS 5
#define RESAMPLER_BITS_PER_SAMPLE 16
-#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1))
+#define RESAMPLER_BITS_PER_ZERO_CROSSING ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1)
+#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)
#define RESAMPLER_FILTER_SIZE (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * RESAMPLER_ZERO_CROSSINGS)
static const float ResamplerFilter[RESAMPLER_FILTER_SIZE] = {
diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c
index a3c7dada305d..68a0976adfbb 100644
--- a/src/audio/SDL_audiocvt.c
+++ b/src/audio/SDL_audiocvt.c
@@ -32,22 +32,21 @@
#include "SDL_audio_resampler_filter.h"
-static int GetResamplerPaddingFrames(const int iinrate, const int ioutrate)
+static Sint64 GetResampleRate(const int inrate, const int outrate)
{
- /* This function uses integer arithmetics to avoid precision loss caused
- * by large floating point numbers. Sint32 is needed for the large number
- * multiplication. The integers are assumed to be non-negative so that
- * division rounds by truncation. */
- const Sint32 inrate = (Sint32) iinrate;
- const Sint32 outrate = (Sint32) ioutrate;
- SDL_assert(inrate >= 0);
- SDL_assert(outrate >= 0);
+ return ((Sint64)inrate << 32) / (Sint64)outrate;
+}
+
+static int GetResamplerPaddingFrames(const int inrate, const int outrate)
+{
+ SDL_assert(inrate > 0);
+ SDL_assert(outrate > 0);
+
if (inrate == outrate) {
return 0;
- } else if (inrate > outrate) {
- return (int) (((RESAMPLER_SAMPLES_PER_ZERO_CROSSING * inrate) + (outrate - 1)) / outrate);
}
- return RESAMPLER_SAMPLES_PER_ZERO_CROSSING;
+
+ return RESAMPLER_ZERO_CROSSINGS + 1;
}
static int GetHistoryBufferSampleFrames(const Sint32 required_resampler_frames)
@@ -68,36 +67,28 @@ static int GetHistoryBufferSampleFrames(const Sint32 required_resampler_frames)
static void ResampleAudio(const int chans, const int inrate, const int outrate,
const float *lpadding, const float *rpadding,
const float *inbuf, const int inframes,
- float *outbuf, const int outframes, const int offset)
+ float *outbuf, const int outframes, const Sint64 offset)
{
- /* This function uses integer arithmetics to avoid precision loss caused
- * by large floating point numbers. For some operations, Sint32 or Sint64
- * are needed for the large number multiplications. The input integers are
- * assumed to be non-negative so that division rounds by truncation and
- * modulo is always non-negative. Note that the operator order is important
- * for these integer divisions. */
const int paddinglen = GetResamplerPaddingFrames(inrate, outrate);
float *dst = outbuf;
int i, j, chan;
+ const Sint64 srcstep = GetResampleRate(inrate, outrate);
+ Sint64 srcpos = offset;
+
for (i = 0; i < outframes; i++) {
- /* Offset by outrate to avoid negative numbers (which don't round correctly) */
- Sint64 srcpos = ((Sint64)i * inrate) + offset + outrate;
- int srcindex = (int)(srcpos / outrate) - 1;
+ int srcindex = (int)(Sint32)(srcpos >> 32);
+ Uint32 srcfraction = (Uint32)(srcpos & 0xFFFFFFFF);
+ srcpos += srcstep;
+
SDL_assert(srcindex >= -1);
SDL_assert(srcindex < inframes);
- /* Calculating the following way avoids subtraction or modulo of large
- * floats which have low result precision.
- * interpolation1
- * = (i / outrate * inrate) - floor(i / outrate * inrate)
- * = mod(i / outrate * inrate, 1)
- * = mod(i * inrate, outrate) / outrate */
- const int srcfraction = (int)(srcpos % outrate);
- const float interpolation1 = ((float)srcfraction) / ((float)outrate);
- const int filterindex1 = (((Sint32)srcfraction) * RESAMPLER_SAMPLES_PER_ZERO_CROSSING / outrate) * RESAMPLER_ZERO_CROSSINGS;
+ const float interpolation1 = (float)srcfraction * 0x1p-32f;
+ const int filterindex1 = (int)(srcfraction >> (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)) * RESAMPLER_ZERO_CROSSINGS;
+
const float interpolation2 = 1.0f - interpolation1;
- const int filterindex2 = RESAMPLER_FILTER_SIZE - filterindex1 - RESAMPLER_ZERO_CROSSINGS;
+ const int filterindex2 = RESAMPLER_FILTER_SIZE - RESAMPLER_ZERO_CROSSINGS - filterindex1;
for (chan = 0; chan < chans; chan++) {
float outsample = 0.0f;
@@ -834,7 +825,7 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
int future_buffer_filled_frames = stream->future_buffer_filled_frames;
Uint8 *future_buffer = stream->future_buffer;
Uint8 *history_buffer = stream->history_buffer;
- int resample_offset = stream->resample_offset;
+ Sint64 resample_offset = stream->resample_offset;
float *resample_outbuf;
int input_frames;
int output_frames;
@@ -861,13 +852,14 @@ static int GetAudioStreamDataInternal(SDL_AudioStream *stream, void *buf, int le
input_frames = output_frames; // total sample frames caller wants
if (dst_rate != src_rate) {
- Sint64 last_offset = ((Sint64)(input_frames - 1) * src_rate) + resample_offset;
- input_frames = (int)(last_offset / dst_rate) + 1;
+ // Make sure this matches the logic used in ResampleAudio
+ const Sint64 srcstep = GetResampleRate(src_rate, dst_rate);
- Sint64 next_offset = last_offset + src_rate;
- stream->resample_offset = (int)(next_offset - ((Sint64)input_frames * dst_rate));
+ Sint64 nextpos = (output_frames * srcstep) + resample_offset;
+ Sint64 lastpos = nextpos - srcstep;
- // SDL_Log("Fraction: %i, %i", resampler_fraction, stream->resampler_fraction);
+ input_frames = (int)(Sint32)(lastpos >> 32) + 1;
+ stream->resample_offset = nextpos - ((Sint64)input_frames << 32);
if (input_frames == 0) { // uhoh, not enough input frames!
// if they are upsampling and we end up needing less than a frame of input, we reject it because it would cause artifacts on future reads to eat a full input frame.
diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h
index 487514daa8f5..0ed53165d4ba 100644
--- a/src/audio/SDL_sysaudio.h
+++ b/src/audio/SDL_sysaudio.h
@@ -179,7 +179,7 @@ struct SDL_AudioStream
int resampler_padding_frames;
int history_buffer_frames;
int future_buffer_filled_frames;
- int resample_offset;
+ Sint64 resample_offset;
SDL_AudioSpec src_spec;
SDL_AudioSpec dst_spec;