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;
     const int silence = (int) device->spec.silence;
     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;
     void *udata = device->spec.userdata;
-    void (SDLCALL *callback) (void *, Uint8 *, int) = device->spec.callback;
+    SDL_AudioCallback callback = device->spec.callback;
 
     SDL_assert(!device->iscapture);
 
@@ -564,16 +564,15 @@ SDL_RunAudio(void *devicep)
     /* Loop, filling the audio buffers */
     while (!SDL_AtomicGet(&device->shutdown)) {
         /* 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);
         } else {
             /* if the device isn't enabled, we still write to the
                fake_stream, so the app's callback will fire with
                a regular frequency, in case they depend on that
                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;
         }
 
@@ -581,33 +580,45 @@ SDL_RunAudio(void *devicep)
             stream = device->fake_stream;
         }
 
-        /* !!! FIXME: this should be LockDevice. */
         if ( SDL_AtomicGet(&device->enabled) ) {
+            /* !!! FIXME: this should be LockDevice. */
             SDL_LockMutex(device->mixer_lock);
             if (SDL_AtomicGet(&device->paused)) {
                 SDL_memset(stream, silence, stream_len);
             } else {
-                (*callback) (udata, stream, stream_len);
+                callback(udata, stream, stream_len);
             }
             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);
-        } 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.WaitDevice(device);
         }
@@ -628,10 +639,10 @@ SDL_CaptureAudio(void *devicep)
     SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
     const int silence = (int) device->spec.silence;
     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;
     void *udata = device->spec.userdata;
-    void (SDLCALL *callback) (void *, Uint8 *, int) = device->spec.callback;
+    SDL_AudioCallback callback = device->spec.callback;
 
     SDL_assert(device->iscapture);
 
@@ -649,18 +660,21 @@ SDL_CaptureAudio(void *devicep)
 
         if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) {
             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. */
             continue;
         }
 
         /* Fill the current buffer with sound */
         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,
            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);
         }
 
-        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);
@@ -929,15 +957,16 @@ close_audio_device(SDL_AudioDevice * device)
     if (device->mixer_lock != NULL) {
         SDL_DestroyMutex(device->mixer_lock);
     }
+
     SDL_free(device->fake_stream);
-    if (device->convert.needed) {
-        SDL_free(device->convert.buf);
-    }
+    SDL_FreeAudioStream(device->stream);
+
     if (device->hidden != NULL) {
         current_audio.impl.CloseDevice(device);
     }
 
     SDL_FreeDataQueue(device->buffer_queue);
+
     SDL_free(device);
 }
 
@@ -1013,7 +1042,7 @@ open_audio_device(const char *devname, int iscapture,
     SDL_AudioDeviceID id = 0;
     SDL_AudioSpec _obtained;
     SDL_AudioDevice *device;
-    SDL_bool build_cvt;
+    SDL_bool build_stream;
     void *handle = NULL;
     int i = 0;
 
@@ -1148,69 +1177,63 @@ open_audio_device(const char *devname, int iscapture,
     SDL_assert(device->hidden != NULL);
 
     /* See if we need to do any conversion */
-    build_cvt = SDL_FALSE;
+    build_stream = SDL_FALSE;
     if (obtained->freq != device->spec.freq) {
         if (allowed_changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) {
             obtained->freq = device->spec.freq;
         } else {
-            build_cvt = SDL_TRUE;
+            build_stream = SDL_TRUE;
         }
     }
     if (obtained->format != device->spec.format) {
         if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) {
             obtained->format = device->spec.format;
         } else {
-            build_cvt = SDL_TRUE;
+            build_stream = SDL_TRUE;
         }
     }
     if (obtained->channels != device->spec.channels) {
         if (allowed_changes & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) {
             obtained->channels = device->spec.channels;
         } 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) {
-        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);
             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? */
         /* 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) {
             close_audio_device(device);
             SDL_SetError("Couldn't create audio buffer queue");
@@ -1220,8 +1243,7 @@ open_audio_device(const char *devname, int iscapture,
         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 */
     if (!current_audio.impl.ProvidesOwnCallbackThread) {
@@ -1232,13 +1254,13 @@ open_audio_device(const char *devname, int iscapture,
         char threadname[64];
 
         /* 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) {
             close_audio_device(device);
             SDL_OutOfMemory();
@@ -1480,13 +1502,7 @@ SDL_MixAudio(Uint8 * dst, const Uint8 * src, Uint32 len, int volume)
     /* Mix the user-level audio format */
     SDL_AudioDevice *device = get_audio_device(1);
     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_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: */

+ 276 - 0
src/audio/SDL_audiocvt.c

@@ -26,6 +26,7 @@
 #include "SDL_audio_c.h"
 
 #include "SDL_assert.h"
+#include "../SDL_dataqueue.h"
 
 
 /* Effectively mix right and left channels into a single channel */
@@ -590,5 +591,280 @@ SDL_BuildAudioCVT(SDL_AudioCVT * cvt,
     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: */
 

+ 10 - 15
src/audio/SDL_sysaudio.h

@@ -35,6 +35,8 @@
 typedef struct SDL_AudioDevice SDL_AudioDevice;
 #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
    a USB headset being plugged in), and should also be called for
    for every device found during DetectDevices(). */
@@ -123,15 +125,6 @@ typedef struct 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 */
 struct SDL_AudioDevice
 {
@@ -139,15 +132,14 @@ struct SDL_AudioDevice
     /* Data common to all devices */
     SDL_AudioDeviceID id;
 
-    /* The current audio specification (shared with audio thread) */
+    /* The device's current audio specification */
     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 */
     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 */
     Uint8 *fake_stream;
 
+    /* Size, in bytes, of fake_stream. */
+    Uint32 fake_stream_len;
+
     /* A mutex for locking the mixing buffers */
     SDL_mutex *mixer_lock;