Browse Source

Used fixed-point arithmetic in ResampleAudio

Brick 1 year ago
parent
commit
47fea7f06b

+ 4 - 2
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
     );

+ 2 - 1
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] = {

+ 30 - 38
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.

+ 1 - 1
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;