Browse Source

audio: Added SDL_AudioStream. Non-power-of-two resampling now works!

Ryan C. Gordon 8 years ago
parent
commit
30178a9b24
4 changed files with 434 additions and 107 deletions
  1. 108 92
      src/audio/SDL_audio.c
  2. 40 0
      src/audio/SDL_audio_c.h
  3. 276 0
      src/audio/SDL_audiocvt.c
  4. 10 15
      src/audio/SDL_sysaudio.h

+ 108 - 92
src/audio/SDL_audio.c

@@ -547,10 +547,10 @@ SDL_RunAudio(void *devicep)
     SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
     SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
     const int silence = (int) device->spec.silence;
     const int silence = (int) device->spec.silence;
     const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
     const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
-    const int stream_len = (device->convert.needed) ? device->convert.len : device->spec.size;
+    const int stream_len = device->callbackspec.size;
     Uint8 *stream;
     Uint8 *stream;
     void *udata = device->spec.userdata;
     void *udata = device->spec.userdata;
-    void (SDLCALL *callback) (void *, Uint8 *, int) = device->spec.callback;
+    SDL_AudioCallback callback = device->spec.callback;
 
 
     SDL_assert(!device->iscapture);
     SDL_assert(!device->iscapture);
 
 
@@ -564,16 +564,15 @@ SDL_RunAudio(void *devicep)
     /* Loop, filling the audio buffers */
     /* Loop, filling the audio buffers */
     while (!SDL_AtomicGet(&device->shutdown)) {
     while (!SDL_AtomicGet(&device->shutdown)) {
         /* Fill the current buffer with sound */
         /* Fill the current buffer with sound */
-        if (device->convert.needed) {
-            stream = device->convert.buf;
-        } else if (SDL_AtomicGet(&device->enabled)) {
+        if (!device->stream && SDL_AtomicGet(&device->enabled)) {
             stream = current_audio.impl.GetDeviceBuf(device);
             stream = current_audio.impl.GetDeviceBuf(device);
         } else {
         } else {
             /* if the device isn't enabled, we still write to the
             /* if the device isn't enabled, we still write to the
                fake_stream, so the app's callback will fire with
                fake_stream, so the app's callback will fire with
                a regular frequency, in case they depend on that
                a regular frequency, in case they depend on that
                for timing or progress. They can use hotplug
                for timing or progress. They can use hotplug
-               now to know if the device failed. */
+               now to know if the device failed.
+               Streaming playback uses fake_stream as a work buffer, too. */
             stream = NULL;
             stream = NULL;
         }
         }
 
 
@@ -581,33 +580,45 @@ SDL_RunAudio(void *devicep)
             stream = device->fake_stream;
             stream = device->fake_stream;
         }
         }
 
 
-        /* !!! FIXME: this should be LockDevice. */
         if ( SDL_AtomicGet(&device->enabled) ) {
         if ( SDL_AtomicGet(&device->enabled) ) {
+            /* !!! FIXME: this should be LockDevice. */
             SDL_LockMutex(device->mixer_lock);
             SDL_LockMutex(device->mixer_lock);
             if (SDL_AtomicGet(&device->paused)) {
             if (SDL_AtomicGet(&device->paused)) {
                 SDL_memset(stream, silence, stream_len);
                 SDL_memset(stream, silence, stream_len);
             } else {
             } else {
-                (*callback) (udata, stream, stream_len);
+                callback(udata, stream, stream_len);
             }
             }
             SDL_UnlockMutex(device->mixer_lock);
             SDL_UnlockMutex(device->mixer_lock);
+        } else {
+            SDL_memset(stream, silence, stream_len);
         }
         }
 
 
-        /* Convert the audio if necessary */
-        if (device->convert.needed && SDL_AtomicGet(&device->enabled)) {
-            SDL_ConvertAudio(&device->convert);
-            stream = current_audio.impl.GetDeviceBuf(device);
-            if (stream == NULL) {
-                stream = device->fake_stream;
-            } else {
-                SDL_memcpy(stream, device->convert.buf,
-                           device->convert.len_cvt);
+        if (device->stream) {
+            /* Stream available audio to device, converting/resampling. */
+            /* if this fails...oh well. We'll play silence here. */
+            SDL_AudioStreamPut(device->stream, stream, stream_len);
+
+            while (SDL_AudioStreamAvailable(device->stream) >= device->spec.size) {
+                stream = SDL_AtomicGet(&device->enabled) ? current_audio.impl.GetDeviceBuf(device) : NULL;
+                if (stream == NULL) {
+                    SDL_AudioStreamClear(device->stream);
+                    SDL_Delay(delay);
+                    break;
+                } else {
+                    const int got = SDL_AudioStreamGet(device->stream, device->spec.size, stream, device->spec.size);
+                    SDL_assert((got < 0) || (got == device->spec.size));
+                    if (got != device->spec.size) {
+                        SDL_memset(stream, device->spec.silence, device->spec.size);
+                    }
+                    current_audio.impl.PlayDevice(device);
+                    current_audio.impl.WaitDevice(device);
+                }
             }
             }
-        }
-
-        /* Ready current buffer for play and change current buffer */
-        if (stream == device->fake_stream) {
+        } else if (stream == device->fake_stream) {
+            /* nothing to do; pause like we queued a buffer to play. */
             SDL_Delay(delay);
             SDL_Delay(delay);
-        } else {
+        } else {  /* writing directly to the device. */
+            /* queue this buffer and wait for it to finish playing. */
             current_audio.impl.PlayDevice(device);
             current_audio.impl.PlayDevice(device);
             current_audio.impl.WaitDevice(device);
             current_audio.impl.WaitDevice(device);
         }
         }
@@ -628,10 +639,10 @@ SDL_CaptureAudio(void *devicep)
     SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
     SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
     const int silence = (int) device->spec.silence;
     const int silence = (int) device->spec.silence;
     const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
     const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
-    const int stream_len = (device->convert.needed) ? device->convert.len : device->spec.size;
+    const int stream_len = device->spec.size;
     Uint8 *stream;
     Uint8 *stream;
     void *udata = device->spec.userdata;
     void *udata = device->spec.userdata;
-    void (SDLCALL *callback) (void *, Uint8 *, int) = device->spec.callback;
+    SDL_AudioCallback callback = device->spec.callback;
 
 
     SDL_assert(device->iscapture);
     SDL_assert(device->iscapture);
 
 
@@ -649,18 +660,21 @@ SDL_CaptureAudio(void *devicep)
 
 
         if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) {
         if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) {
             SDL_Delay(delay);  /* just so we don't cook the CPU. */
             SDL_Delay(delay);  /* just so we don't cook the CPU. */
+            if (device->stream) {
+                SDL_AudioStreamClear(device->stream);
+            }
             current_audio.impl.FlushCapture(device);  /* dump anything pending. */
             current_audio.impl.FlushCapture(device);  /* dump anything pending. */
             continue;
             continue;
         }
         }
 
 
         /* Fill the current buffer with sound */
         /* Fill the current buffer with sound */
         still_need = stream_len;
         still_need = stream_len;
-        if (device->convert.needed) {
-            ptr = stream = device->convert.buf;
-        } else {
-            /* just use the "fake" stream to hold data read from the device. */
-            ptr = stream = device->fake_stream;
-        }
+
+        /* just use the "fake" stream to hold data read from the device. */
+        stream = device->fake_stream;
+        SDL_assert(stream != NULL);
+
+        ptr = stream;
 
 
         /* We still read from the device when "paused" to keep the state sane,
         /* We still read from the device when "paused" to keep the state sane,
            and block when there isn't data so this thread isn't eating CPU.
            and block when there isn't data so this thread isn't eating CPU.
@@ -683,18 +697,32 @@ SDL_CaptureAudio(void *devicep)
             SDL_memset(ptr, silence, still_need);
             SDL_memset(ptr, silence, still_need);
         }
         }
 
 
-        if (device->convert.needed) {
-            SDL_ConvertAudio(&device->convert);
-        }
-
-        /* !!! FIXME: this should be LockDevice. */
-        SDL_LockMutex(device->mixer_lock);
-        if (SDL_AtomicGet(&device->paused)) {
-            current_audio.impl.FlushCapture(device);  /* one snuck in! */
-        } else {
-            (*callback)(udata, stream, stream_len);
+        if (device->stream) {
+            /* if this fails...oh well. */
+            SDL_AudioStreamPut(device->stream, stream, stream_len);
+
+            while (SDL_AudioStreamAvailable(device->stream) >= device->callbackspec.size) {
+                const int got = SDL_AudioStreamGet(device->stream, device->callbackspec.size, device->fake_stream, device->fake_stream_len);
+                SDL_assert((got < 0) || (got == device->callbackspec.size));
+                if (got != device->callbackspec.size) {
+                    SDL_memset(device->fake_stream, device->spec.silence, device->callbackspec.size);
+                }
+
+                /* !!! FIXME: this should be LockDevice. */
+                SDL_LockMutex(device->mixer_lock);
+                if (!SDL_AtomicGet(&device->paused)) {
+                    callback(udata, device->fake_stream, device->callbackspec.size);
+                }
+                SDL_UnlockMutex(device->mixer_lock);
+            }
+        } else {  /* feeding user callback directly without streaming. */
+            /* !!! FIXME: this should be LockDevice. */
+            SDL_LockMutex(device->mixer_lock);
+            if (!SDL_AtomicGet(&device->paused)) {
+                callback(udata, stream, device->callbackspec.size);
+            }
+            SDL_UnlockMutex(device->mixer_lock);
         }
         }
-        SDL_UnlockMutex(device->mixer_lock);
     }
     }
 
 
     current_audio.impl.FlushCapture(device);
     current_audio.impl.FlushCapture(device);
@@ -929,15 +957,16 @@ close_audio_device(SDL_AudioDevice * device)
     if (device->mixer_lock != NULL) {
     if (device->mixer_lock != NULL) {
         SDL_DestroyMutex(device->mixer_lock);
         SDL_DestroyMutex(device->mixer_lock);
     }
     }
+
     SDL_free(device->fake_stream);
     SDL_free(device->fake_stream);
-    if (device->convert.needed) {
-        SDL_free(device->convert.buf);
-    }
+    SDL_FreeAudioStream(device->stream);
+
     if (device->hidden != NULL) {
     if (device->hidden != NULL) {
         current_audio.impl.CloseDevice(device);
         current_audio.impl.CloseDevice(device);
     }
     }
 
 
     SDL_FreeDataQueue(device->buffer_queue);
     SDL_FreeDataQueue(device->buffer_queue);
+
     SDL_free(device);
     SDL_free(device);
 }
 }
 
 
@@ -1013,7 +1042,7 @@ open_audio_device(const char *devname, int iscapture,
     SDL_AudioDeviceID id = 0;
     SDL_AudioDeviceID id = 0;
     SDL_AudioSpec _obtained;
     SDL_AudioSpec _obtained;
     SDL_AudioDevice *device;
     SDL_AudioDevice *device;
-    SDL_bool build_cvt;
+    SDL_bool build_stream;
     void *handle = NULL;
     void *handle = NULL;
     int i = 0;
     int i = 0;
 
 
@@ -1148,69 +1177,63 @@ open_audio_device(const char *devname, int iscapture,
     SDL_assert(device->hidden != NULL);
     SDL_assert(device->hidden != NULL);
 
 
     /* See if we need to do any conversion */
     /* See if we need to do any conversion */
-    build_cvt = SDL_FALSE;
+    build_stream = SDL_FALSE;
     if (obtained->freq != device->spec.freq) {
     if (obtained->freq != device->spec.freq) {
         if (allowed_changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) {
         if (allowed_changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) {
             obtained->freq = device->spec.freq;
             obtained->freq = device->spec.freq;
         } else {
         } else {
-            build_cvt = SDL_TRUE;
+            build_stream = SDL_TRUE;
         }
         }
     }
     }
     if (obtained->format != device->spec.format) {
     if (obtained->format != device->spec.format) {
         if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) {
         if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) {
             obtained->format = device->spec.format;
             obtained->format = device->spec.format;
         } else {
         } else {
-            build_cvt = SDL_TRUE;
+            build_stream = SDL_TRUE;
         }
         }
     }
     }
     if (obtained->channels != device->spec.channels) {
     if (obtained->channels != device->spec.channels) {
         if (allowed_changes & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) {
         if (allowed_changes & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) {
             obtained->channels = device->spec.channels;
             obtained->channels = device->spec.channels;
         } else {
         } else {
-            build_cvt = SDL_TRUE;
+            build_stream = SDL_TRUE;
         }
         }
     }
     }
 
 
-    /* If the audio driver changes the buffer size, accept it.
-       This needs to be done after the format is modified above,
-       otherwise it might not have the correct buffer size.
+    /* !!! FIXME in 2.1: add SDL_AUDIO_ALLOW_SAMPLES_CHANGE flag?
+       As of 2.0.6, we will build a stream to buffer the difference between
+       what the app wants to feed and the device wants to eat, so everyone
+       gets their way. In prior releases, SDL would force the callback to
+       feed at the rate the device requested, adjusted for resampling.
      */
      */
     if (device->spec.samples != obtained->samples) {
     if (device->spec.samples != obtained->samples) {
-        obtained->samples = device->spec.samples;
-        SDL_CalculateAudioSpec(obtained);
+        build_stream = SDL_TRUE;
     }
     }
 
 
-    if (build_cvt) {
-        /* Build an audio conversion block */
-        if (SDL_BuildAudioCVT(&device->convert,
-                              obtained->format, obtained->channels,
-                              obtained->freq,
-                              device->spec.format, device->spec.channels,
-                              device->spec.freq) < 0) {
+    SDL_CalculateAudioSpec(obtained);  /* recalc after possible changes. */
+
+    device->callbackspec = *obtained;
+
+    if (build_stream) {
+        if (iscapture) {
+            device->stream = SDL_NewAudioStream(device->spec.format,
+                                  device->spec.channels, device->spec.freq,
+                                  obtained->format, obtained->channels, obtained->freq);
+        } else {
+            device->stream = SDL_NewAudioStream(obtained->format, obtained->channels,
+                                  obtained->freq, device->spec.format,
+                                  device->spec.channels, device->spec.freq);
+        }
+
+        if (!device->stream) {
             close_audio_device(device);
             close_audio_device(device);
             return 0;
             return 0;
         }
         }
-        if (device->convert.needed) {
-            device->convert.len = (int) (((double) device->spec.samples) /
-                                         device->convert.len_ratio);
-            device->convert.len *= SDL_AUDIO_BITSIZE(device->spec.format) / 8;
-            device->convert.len *= device->spec.channels;
-
-            device->convert.buf =
-                (Uint8 *) SDL_malloc(device->convert.len *
-                                            device->convert.len_mult);
-            if (device->convert.buf == NULL) {
-                close_audio_device(device);
-                SDL_OutOfMemory();
-                return 0;
-            }
-        }
     }
     }
 
 
     if (device->spec.callback == NULL) {  /* use buffer queueing? */
     if (device->spec.callback == NULL) {  /* use buffer queueing? */
         /* pool a few packets to start. Enough for two callbacks. */
         /* pool a few packets to start. Enough for two callbacks. */
-        const size_t slack = ((device->convert.needed) ? device->convert.len : device->spec.size) * 2;
-        device->buffer_queue = SDL_NewDataQueue(SDL_AUDIOBUFFERQUEUE_PACKETLEN, slack);
+        device->buffer_queue = SDL_NewDataQueue(SDL_AUDIOBUFFERQUEUE_PACKETLEN, obtained->size * 2);
         if (!device->buffer_queue) {
         if (!device->buffer_queue) {
             close_audio_device(device);
             close_audio_device(device);
             SDL_SetError("Couldn't create audio buffer queue");
             SDL_SetError("Couldn't create audio buffer queue");
@@ -1220,8 +1243,7 @@ open_audio_device(const char *devname, int iscapture,
         device->spec.userdata = device;
         device->spec.userdata = device;
     }
     }
 
 
-    /* add it to our list of open devices. */
-    open_devices[id] = device;
+    open_devices[id] = device;  /* add it to our list of open devices. */
 
 
     /* Start the audio thread if necessary */
     /* Start the audio thread if necessary */
     if (!current_audio.impl.ProvidesOwnCallbackThread) {
     if (!current_audio.impl.ProvidesOwnCallbackThread) {
@@ -1232,13 +1254,13 @@ open_audio_device(const char *devname, int iscapture,
         char threadname[64];
         char threadname[64];
 
 
         /* Allocate a fake audio buffer; only used by our internal threads. */
         /* Allocate a fake audio buffer; only used by our internal threads. */
-        Uint32 stream_len = (device->convert.needed) ? device->convert.len_cvt : 0;
-        if (device->spec.size > stream_len) {
-            stream_len = device->spec.size;
+        device->fake_stream_len = build_stream ? device->callbackspec.size : 0;
+        if (device->spec.size > device->fake_stream_len) {
+            device->fake_stream_len = device->spec.size;
         }
         }
-        SDL_assert(stream_len > 0);
+        SDL_assert(device->fake_stream_len > 0);
 
 
-        device->fake_stream = (Uint8 *) SDL_malloc(stream_len);
+        device->fake_stream = (Uint8 *) SDL_malloc(device->fake_stream_len);
         if (device->fake_stream == NULL) {
         if (device->fake_stream == NULL) {
             close_audio_device(device);
             close_audio_device(device);
             SDL_OutOfMemory();
             SDL_OutOfMemory();
@@ -1480,13 +1502,7 @@ SDL_MixAudio(Uint8 * dst, const Uint8 * src, Uint32 len, int volume)
     /* Mix the user-level audio format */
     /* Mix the user-level audio format */
     SDL_AudioDevice *device = get_audio_device(1);
     SDL_AudioDevice *device = get_audio_device(1);
     if (device != NULL) {
     if (device != NULL) {
-        SDL_AudioFormat format;
-        if (device->convert.needed) {
-            format = device->convert.src_format;
-        } else {
-            format = device->spec.format;
-        }
-        SDL_MixAudioFormat(dst, src, format, len, volume);
+        SDL_MixAudioFormat(dst, src, device->callbackspec.format, len, volume);
     }
     }
 }
 }
 
 

+ 40 - 0
src/audio/SDL_audio_c.h

@@ -54,4 +54,44 @@ void SDL_Upsample_Multiple(SDL_AudioCVT *cvt, const int channels);
 void SDL_Downsample_Arbitrary(SDL_AudioCVT *cvt, const int channels);
 void SDL_Downsample_Arbitrary(SDL_AudioCVT *cvt, const int channels);
 void SDL_Downsample_Multiple(SDL_AudioCVT *cvt, const int channels);
 void SDL_Downsample_Multiple(SDL_AudioCVT *cvt, const int channels);
 
 
+
+/* SDL_AudioStream is a new audio conversion interface. It
+    might eventually become a public API.
+   The benefits vs SDL_AudioCVT:
+    - it can handle resampling data in chunks without generating
+      artifacts, when it doesn't have the complete buffer available.
+    - it can handle incoming data in any variable size.
+    - You push data as you have it, and pull it when you need it
+
+    (Note that currently this converts as data is put into the stream, so
+    you need to push more than a handful of bytes if you want decent
+    resampling. This can be changed later.)
+ */
+
+/* this is opaque to the outside world. */
+typedef struct SDL_AudioStream SDL_AudioStream;
+
+/* create a new stream */
+SDL_AudioStream *SDL_NewAudioStream(const SDL_AudioFormat src_format,
+                                    const Uint8 src_channels,
+                                    const int src_rate,
+                                    const SDL_AudioFormat dst_format,
+                                    const Uint8 dst_channels,
+                                    const int dst_rate);
+
+/* add data to be converted/resampled to the stream */
+int SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 len);
+
+/* get converted/resampled data from the stream */
+int SDL_AudioStreamGet(SDL_AudioStream *stream, Uint32 len, void *buf, const Uint32 buflen);
+
+/* clear any pending data in the stream without converting it. */
+void SDL_AudioStreamClear(SDL_AudioStream *stream);
+
+/* number of converted/resampled bytes available */
+int SDL_AudioStreamAvailable(SDL_AudioStream *stream);
+
+/* dispose of a stream */
+void SDL_FreeAudioStream(SDL_AudioStream *stream);
+
 /* vi: set ts=4 sw=4 expandtab: */
 /* vi: set ts=4 sw=4 expandtab: */

+ 276 - 0
src/audio/SDL_audiocvt.c

@@ -26,6 +26,7 @@
 #include "SDL_audio_c.h"
 #include "SDL_audio_c.h"
 
 
 #include "SDL_assert.h"
 #include "SDL_assert.h"
+#include "../SDL_dataqueue.h"
 
 
 
 
 /* Effectively mix right and left channels into a single channel */
 /* Effectively mix right and left channels into a single channel */
@@ -590,5 +591,280 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
     return (cvt->needed);
     return (cvt->needed);
 }
 }
 
 
+
+struct SDL_AudioStream
+{
+    SDL_AudioCVT cvt_before_resampling;
+    SDL_AudioCVT cvt_after_resampling;
+    SDL_DataQueue *queue;
+    Uint8 *work_buffer;
+    int work_buffer_len;
+    Uint8 *resample_buffer;
+    int resample_buffer_len;
+    int src_sample_frame_size;
+    SDL_AudioFormat src_format;
+    Uint8 src_channels;
+    int src_rate;
+    int dst_sample_frame_size;
+    SDL_AudioFormat dst_format;
+    Uint8 dst_channels;
+    int dst_rate;
+    double rate_incr;
+    Uint8 pre_resample_channels;
+    SDL_bool resampler_seeded;
+    float resampler_state[8];
+    int packetlen;
+};
+
+SDL_AudioStream *SDL_NewAudioStream(const SDL_AudioFormat src_format,
+                                    const Uint8 src_channels,
+                                    const int src_rate,
+                                    const SDL_AudioFormat dst_format,
+                                    const Uint8 dst_channels,
+                                    const int dst_rate)
+{
+    const int packetlen = 4096;  /* !!! FIXME: good enough for now. */
+    Uint8 pre_resample_channels;
+    SDL_AudioStream *retval;
+
+    retval = (SDL_AudioStream *) SDL_calloc(1, sizeof (SDL_AudioStream));
+    if (!retval) {
+        return NULL;
+    }
+
+    /* If increasing channels, do it after resampling, since we'd just
+       do more work to resample duplicate channels. If we're decreasing, do
+       it first so we resample the interpolated data instead of interpolating
+       the resampled data (!!! FIXME: decide if that works in practice, though!). */
+    pre_resample_channels = SDL_min(src_channels, dst_channels);
+
+    retval->src_sample_frame_size = SDL_AUDIO_BITSIZE(src_format) * src_channels;
+    retval->src_format = src_format;
+    retval->src_channels = src_channels;
+    retval->src_rate = src_rate;
+    retval->dst_sample_frame_size = SDL_AUDIO_BITSIZE(dst_format) * dst_channels;
+    retval->dst_format = dst_format;
+    retval->dst_channels = dst_channels;
+    retval->dst_rate = dst_rate;
+    retval->pre_resample_channels = pre_resample_channels;
+    retval->packetlen = packetlen;
+    retval->rate_incr = ((double) dst_rate) / ((double) src_rate);
+
+    /* Not resampling? It's an easy conversion (and maybe not even that!). */
+    if (src_rate == dst_rate) {
+        retval->cvt_before_resampling.needed = SDL_FALSE;
+        retval->cvt_before_resampling.len_mult = 1;
+        if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, src_format, src_channels, dst_rate, dst_format, dst_channels, dst_rate) == -1) {
+            SDL_free(retval);
+            return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */
+        }
+    } else {
+        /* Don't resample at first. Just get us to Float32 format. */
+        /* !!! FIXME: convert to int32 on devices without hardware float. */
+        if (SDL_BuildAudioCVT(&retval->cvt_before_resampling, src_format, src_channels, src_rate, AUDIO_F32SYS, pre_resample_channels, src_rate) == -1) {
+            SDL_free(retval);
+            return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */
+        }
+
+        /* Convert us to the final format after resampling. */
+        if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, AUDIO_F32SYS, pre_resample_channels, dst_rate, dst_format, dst_channels, dst_rate) == -1) {
+            SDL_free(retval);
+            return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */
+        }
+    }
+
+    retval->queue = SDL_NewDataQueue(packetlen, packetlen * 2);
+    if (!retval->queue) {
+        SDL_free(retval);
+        return NULL;  /* SDL_NewDataQueue should have called SDL_SetError. */
+    }
+
+    return retval;
+}
+
+
+static int
+ResampleAudioStream(SDL_AudioStream *stream, const float *inbuf, const int inbuflen, float *outbuf, const int outbuflen)
+{
+    /* !!! FIXME: this resampler sucks, but not much worse than our usual resampler.  :)  */  /* ... :( */
+    const int chans = (int) stream->pre_resample_channels;
+    const int framelen = chans * sizeof (float);
+    const int total = (inbuflen / framelen);
+    const int finalpos = total - chans;
+    const double src_incr = 1.0 / stream->rate_incr;
+    double idx = 0.0;
+    float *dst = outbuf;
+    float last_sample[SDL_arraysize(stream->resampler_state)];
+    int consumed = 0;
+    int i;
+
+    SDL_assert(chans <= SDL_arraysize(last_sample));
+    SDL_assert((inbuflen % framelen) == 0);
+
+    if (!stream->resampler_seeded) {
+        for (i = 0; i < chans; i++) {
+            stream->resampler_state[i] = inbuf[i];
+        }
+        stream->resampler_seeded = SDL_TRUE;
+    }
+
+    for (i = 0; i < chans; i++) {
+        last_sample[i] = stream->resampler_state[i];
+    }
+
+    while (consumed < total) {
+        const int pos = ((int) idx) * chans;
+        const float *src = &inbuf[(pos >= finalpos) ? finalpos : pos];
+        SDL_assert(dst < (outbuf + (outbuflen / framelen)));
+        for (i = 0; i < chans; i++) {
+            const float val = *(src++);
+            *(dst++) = (val + last_sample[i]) * 0.5f;
+            last_sample[i] = val;
+        }
+        consumed = pos + chans;
+        idx += src_incr;
+    }
+
+    for (i = 0; i < chans; i++) {
+        stream->resampler_state[i] = last_sample[i];
+    }
+
+    return (dst - outbuf) * sizeof (float);
+}
+
+static Uint8 *
+EnsureBufferSize(Uint8 **buf, int *len, const int newlen)
+{
+    if (*len < newlen) {
+        void *ptr = SDL_realloc(*buf, newlen);
+        if (!ptr) {
+            SDL_OutOfMemory();
+            return NULL;
+        }
+        *buf = (Uint8 *) ptr;
+        *len = newlen;
+    }
+    return *buf;
+}
+
+int
+SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _buflen)
+{
+    int buflen = (int) _buflen;
+
+    if (!stream) {
+        return SDL_InvalidParamError("stream");
+    } else if (!buf) {
+        return SDL_InvalidParamError("buf");
+    } else if (buflen == 0) {
+        return 0;  /* nothing to do. */
+    } else if ((buflen % stream->src_sample_frame_size) != 0) {
+        return SDL_SetError("Can't add partial sample frames");
+    }
+
+    if (stream->cvt_before_resampling.needed) {
+        const int workbuflen = buflen * stream->cvt_before_resampling.len_mult;  /* will be "* 1" if not needed */
+        Uint8 *workbuf = EnsureBufferSize(&stream->work_buffer, &stream->work_buffer_len, workbuflen);
+        if (workbuf == NULL) {
+            return -1;  /* probably out of memory. */
+        }
+        SDL_memcpy(workbuf, buf, buflen);
+        stream->cvt_before_resampling.buf = workbuf;
+        stream->cvt_before_resampling.len = buflen;
+        if (SDL_ConvertAudio(&stream->cvt_before_resampling) == -1) {
+            return -1;   /* uhoh! */
+        }
+        buf = workbuf;
+        buflen = stream->cvt_before_resampling.len_cvt;
+    }
+
+    if (stream->dst_rate != stream->src_rate) {
+        const int workbuflen = buflen * ((int) SDL_ceil(stream->rate_incr));
+        float *workbuf = (float *) EnsureBufferSize(&stream->resample_buffer, &stream->resample_buffer_len, workbuflen);
+        if (workbuf == NULL) {
+            return -1;  /* probably out of memory. */
+        }
+        buflen = ResampleAudioStream(stream, (float *) buf, buflen, workbuf, workbuflen);
+        buf = workbuf;
+    }
+
+    if (stream->cvt_after_resampling.needed) {
+        const int workbuflen = buflen * stream->cvt_before_resampling.len_mult;  /* will be "* 1" if not needed */
+        Uint8 *workbuf;
+
+        if (buf == stream->resample_buffer) {
+            workbuf = EnsureBufferSize(&stream->resample_buffer, &stream->resample_buffer_len, workbuflen);
+        } else {
+            const int inplace = (buf == stream->work_buffer);
+            workbuf = EnsureBufferSize(&stream->work_buffer, &stream->work_buffer_len, workbuflen);
+            if (workbuf && !inplace) {
+                SDL_memcpy(workbuf, buf, buflen);
+            }
+        }
+
+        if (workbuf == NULL) {
+            return -1;  /* probably out of memory. */
+        }
+
+        stream->cvt_after_resampling.buf = workbuf;
+        stream->cvt_after_resampling.len = buflen;
+        if (SDL_ConvertAudio(&stream->cvt_after_resampling) == -1) {
+            return -1;   /* uhoh! */
+        }
+        buf = workbuf;
+        buflen = stream->cvt_after_resampling.len_cvt;
+    }
+
+    return SDL_WriteToDataQueue(stream->queue, buf, buflen);
+}
+
+void
+SDL_AudioStreamClear(SDL_AudioStream *stream)
+{
+    if (!stream) {
+        SDL_InvalidParamError("stream");
+    } else {
+        SDL_ClearDataQueue(stream->queue, stream->packetlen * 2);
+        stream->resampler_seeded = SDL_FALSE;
+    }
+}
+
+
+/* get converted/resampled data from the stream */
+int
+SDL_AudioStreamGet(SDL_AudioStream *stream, Uint32 len, void *buf, const Uint32 buflen)
+{
+    if (!stream) {
+        return SDL_InvalidParamError("stream");
+    } else if (!buf) {
+        return SDL_InvalidParamError("buf");
+    } else if (len == 0) {
+        return 0;  /* nothing to do. */
+    } else if ((len % stream->dst_sample_frame_size) != 0) {
+        return SDL_SetError("Can't request partial sample frames");
+    }
+
+    return SDL_ReadFromDataQueue(stream->queue, buf, buflen);
+}
+
+/* number of converted/resampled bytes available */
+int
+SDL_AudioStreamAvailable(SDL_AudioStream *stream)
+{
+    return stream ? (int) SDL_CountDataQueue(stream->queue) : 0;
+}
+
+/* dispose of a stream */
+void
+SDL_FreeAudioStream(SDL_AudioStream *stream)
+{
+    if (stream) {
+        SDL_FreeDataQueue(stream->queue);
+        SDL_free(stream->work_buffer);
+        SDL_free(stream->resample_buffer);
+        SDL_free(stream);
+    }
+}
+
 /* vi: set ts=4 sw=4 expandtab: */
 /* vi: set ts=4 sw=4 expandtab: */
 
 

+ 10 - 15
src/audio/SDL_sysaudio.h

@@ -35,6 +35,8 @@
 typedef struct SDL_AudioDevice SDL_AudioDevice;
 typedef struct SDL_AudioDevice SDL_AudioDevice;
 #define _THIS   SDL_AudioDevice *_this
 #define _THIS   SDL_AudioDevice *_this
 
 
+typedef struct SDL_AudioStream SDL_AudioStream;
+
 /* Audio targets should call this as devices are added to the system (such as
 /* Audio targets should call this as devices are added to the system (such as
    a USB headset being plugged in), and should also be called for
    a USB headset being plugged in), and should also be called for
    for every device found during DetectDevices(). */
    for every device found during DetectDevices(). */
@@ -123,15 +125,6 @@ typedef struct SDL_AudioDriver
 } SDL_AudioDriver;
 } SDL_AudioDriver;
 
 
 
 
-/* Streamer */
-typedef struct
-{
-    Uint8 *buffer;
-    int max_len;                /* the maximum length in bytes */
-    int read_pos, write_pos;    /* the position of the write and read heads in bytes */
-} SDL_AudioStreamer;
-
-
 /* Define the SDL audio driver structure */
 /* Define the SDL audio driver structure */
 struct SDL_AudioDevice
 struct SDL_AudioDevice
 {
 {
@@ -139,15 +132,14 @@ struct SDL_AudioDevice
     /* Data common to all devices */
     /* Data common to all devices */
     SDL_AudioDeviceID id;
     SDL_AudioDeviceID id;
 
 
-    /* The current audio specification (shared with audio thread) */
+    /* The device's current audio specification */
     SDL_AudioSpec spec;
     SDL_AudioSpec spec;
 
 
-    /* An audio conversion block for audio format emulation */
-    SDL_AudioCVT convert;
+    /* The callback's expected audio specification (converted vs device's spec). */
+    SDL_AudioSpec callbackspec;
 
 
-    /* The streamer, if sample rate conversion necessitates it */
-    int use_streamer;
-    SDL_AudioStreamer streamer;
+    /* Stream that converts and resamples. NULL if not needed. */
+    SDL_AudioStream *stream;
 
 
     /* Current state flags */
     /* Current state flags */
     SDL_atomic_t shutdown; /* true if we are signaling the play thread to end. */
     SDL_atomic_t shutdown; /* true if we are signaling the play thread to end. */
@@ -158,6 +150,9 @@ struct SDL_AudioDevice
     /* Fake audio buffer for when the audio hardware is busy */
     /* Fake audio buffer for when the audio hardware is busy */
     Uint8 *fake_stream;
     Uint8 *fake_stream;
 
 
+    /* Size, in bytes, of fake_stream. */
+    Uint32 fake_stream_len;
+
     /* A mutex for locking the mixing buffers */
     /* A mutex for locking the mixing buffers */
     SDL_mutex *mixer_lock;
     SDL_mutex *mixer_lock;