audio: Added SDL_AudioStream. Non-power-of-two resampling now works!
authorRyan C. Gordon
Thu, 05 Jan 2017 19:29:38 -0500
changeset 10757329d6d46fb90
parent 10756 073957aca821
child 10758 e0fd49c1a5b7
audio: Added SDL_AudioStream. Non-power-of-two resampling now works!
src/audio/SDL_audio.c
src/audio/SDL_audio_c.h
src/audio/SDL_audiocvt.c
src/audio/SDL_sysaudio.h
     1.1 --- a/src/audio/SDL_audio.c	Thu Jan 05 19:12:20 2017 -0500
     1.2 +++ b/src/audio/SDL_audio.c	Thu Jan 05 19:29:38 2017 -0500
     1.3 @@ -547,10 +547,10 @@
     1.4      SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
     1.5      const int silence = (int) device->spec.silence;
     1.6      const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
     1.7 -    const int stream_len = (device->convert.needed) ? device->convert.len : device->spec.size;
     1.8 +    const int stream_len = device->callbackspec.size;
     1.9      Uint8 *stream;
    1.10      void *udata = device->spec.userdata;
    1.11 -    void (SDLCALL *callback) (void *, Uint8 *, int) = device->spec.callback;
    1.12 +    SDL_AudioCallback callback = device->spec.callback;
    1.13  
    1.14      SDL_assert(!device->iscapture);
    1.15  
    1.16 @@ -564,16 +564,15 @@
    1.17      /* Loop, filling the audio buffers */
    1.18      while (!SDL_AtomicGet(&device->shutdown)) {
    1.19          /* Fill the current buffer with sound */
    1.20 -        if (device->convert.needed) {
    1.21 -            stream = device->convert.buf;
    1.22 -        } else if (SDL_AtomicGet(&device->enabled)) {
    1.23 +        if (!device->stream && SDL_AtomicGet(&device->enabled)) {
    1.24              stream = current_audio.impl.GetDeviceBuf(device);
    1.25          } else {
    1.26              /* if the device isn't enabled, we still write to the
    1.27                 fake_stream, so the app's callback will fire with
    1.28                 a regular frequency, in case they depend on that
    1.29                 for timing or progress. They can use hotplug
    1.30 -               now to know if the device failed. */
    1.31 +               now to know if the device failed.
    1.32 +               Streaming playback uses fake_stream as a work buffer, too. */
    1.33              stream = NULL;
    1.34          }
    1.35  
    1.36 @@ -581,33 +580,45 @@
    1.37              stream = device->fake_stream;
    1.38          }
    1.39  
    1.40 -        /* !!! FIXME: this should be LockDevice. */
    1.41          if ( SDL_AtomicGet(&device->enabled) ) {
    1.42 +            /* !!! FIXME: this should be LockDevice. */
    1.43              SDL_LockMutex(device->mixer_lock);
    1.44              if (SDL_AtomicGet(&device->paused)) {
    1.45                  SDL_memset(stream, silence, stream_len);
    1.46              } else {
    1.47 -                (*callback) (udata, stream, stream_len);
    1.48 +                callback(udata, stream, stream_len);
    1.49              }
    1.50              SDL_UnlockMutex(device->mixer_lock);
    1.51 +        } else {
    1.52 +            SDL_memset(stream, silence, stream_len);
    1.53          }
    1.54  
    1.55 -        /* Convert the audio if necessary */
    1.56 -        if (device->convert.needed && SDL_AtomicGet(&device->enabled)) {
    1.57 -            SDL_ConvertAudio(&device->convert);
    1.58 -            stream = current_audio.impl.GetDeviceBuf(device);
    1.59 -            if (stream == NULL) {
    1.60 -                stream = device->fake_stream;
    1.61 -            } else {
    1.62 -                SDL_memcpy(stream, device->convert.buf,
    1.63 -                           device->convert.len_cvt);
    1.64 +        if (device->stream) {
    1.65 +            /* Stream available audio to device, converting/resampling. */
    1.66 +            /* if this fails...oh well. We'll play silence here. */
    1.67 +            SDL_AudioStreamPut(device->stream, stream, stream_len);
    1.68 +
    1.69 +            while (SDL_AudioStreamAvailable(device->stream) >= device->spec.size) {
    1.70 +                stream = SDL_AtomicGet(&device->enabled) ? current_audio.impl.GetDeviceBuf(device) : NULL;
    1.71 +                if (stream == NULL) {
    1.72 +                    SDL_AudioStreamClear(device->stream);
    1.73 +                    SDL_Delay(delay);
    1.74 +                    break;
    1.75 +                } else {
    1.76 +                    const int got = SDL_AudioStreamGet(device->stream, device->spec.size, stream, device->spec.size);
    1.77 +                    SDL_assert((got < 0) || (got == device->spec.size));
    1.78 +                    if (got != device->spec.size) {
    1.79 +                        SDL_memset(stream, device->spec.silence, device->spec.size);
    1.80 +                    }
    1.81 +                    current_audio.impl.PlayDevice(device);
    1.82 +                    current_audio.impl.WaitDevice(device);
    1.83 +                }
    1.84              }
    1.85 -        }
    1.86 -
    1.87 -        /* Ready current buffer for play and change current buffer */
    1.88 -        if (stream == device->fake_stream) {
    1.89 +        } else if (stream == device->fake_stream) {
    1.90 +            /* nothing to do; pause like we queued a buffer to play. */
    1.91              SDL_Delay(delay);
    1.92 -        } else {
    1.93 +        } else {  /* writing directly to the device. */
    1.94 +            /* queue this buffer and wait for it to finish playing. */
    1.95              current_audio.impl.PlayDevice(device);
    1.96              current_audio.impl.WaitDevice(device);
    1.97          }
    1.98 @@ -628,10 +639,10 @@
    1.99      SDL_AudioDevice *device = (SDL_AudioDevice *) devicep;
   1.100      const int silence = (int) device->spec.silence;
   1.101      const Uint32 delay = ((device->spec.samples * 1000) / device->spec.freq);
   1.102 -    const int stream_len = (device->convert.needed) ? device->convert.len : device->spec.size;
   1.103 +    const int stream_len = device->spec.size;
   1.104      Uint8 *stream;
   1.105      void *udata = device->spec.userdata;
   1.106 -    void (SDLCALL *callback) (void *, Uint8 *, int) = device->spec.callback;
   1.107 +    SDL_AudioCallback callback = device->spec.callback;
   1.108  
   1.109      SDL_assert(device->iscapture);
   1.110  
   1.111 @@ -649,18 +660,21 @@
   1.112  
   1.113          if (!SDL_AtomicGet(&device->enabled) || SDL_AtomicGet(&device->paused)) {
   1.114              SDL_Delay(delay);  /* just so we don't cook the CPU. */
   1.115 +            if (device->stream) {
   1.116 +                SDL_AudioStreamClear(device->stream);
   1.117 +            }
   1.118              current_audio.impl.FlushCapture(device);  /* dump anything pending. */
   1.119              continue;
   1.120          }
   1.121  
   1.122          /* Fill the current buffer with sound */
   1.123          still_need = stream_len;
   1.124 -        if (device->convert.needed) {
   1.125 -            ptr = stream = device->convert.buf;
   1.126 -        } else {
   1.127 -            /* just use the "fake" stream to hold data read from the device. */
   1.128 -            ptr = stream = device->fake_stream;
   1.129 -        }
   1.130 +
   1.131 +        /* just use the "fake" stream to hold data read from the device. */
   1.132 +        stream = device->fake_stream;
   1.133 +        SDL_assert(stream != NULL);
   1.134 +
   1.135 +        ptr = stream;
   1.136  
   1.137          /* We still read from the device when "paused" to keep the state sane,
   1.138             and block when there isn't data so this thread isn't eating CPU.
   1.139 @@ -683,18 +697,32 @@
   1.140              SDL_memset(ptr, silence, still_need);
   1.141          }
   1.142  
   1.143 -        if (device->convert.needed) {
   1.144 -            SDL_ConvertAudio(&device->convert);
   1.145 -        }
   1.146 +        if (device->stream) {
   1.147 +            /* if this fails...oh well. */
   1.148 +            SDL_AudioStreamPut(device->stream, stream, stream_len);
   1.149 +
   1.150 +            while (SDL_AudioStreamAvailable(device->stream) >= device->callbackspec.size) {
   1.151 +                const int got = SDL_AudioStreamGet(device->stream, device->callbackspec.size, device->fake_stream, device->fake_stream_len);
   1.152 +                SDL_assert((got < 0) || (got == device->callbackspec.size));
   1.153 +                if (got != device->callbackspec.size) {
   1.154 +                    SDL_memset(device->fake_stream, device->spec.silence, device->callbackspec.size);
   1.155 +                }
   1.156  
   1.157 -        /* !!! FIXME: this should be LockDevice. */
   1.158 -        SDL_LockMutex(device->mixer_lock);
   1.159 -        if (SDL_AtomicGet(&device->paused)) {
   1.160 -            current_audio.impl.FlushCapture(device);  /* one snuck in! */
   1.161 -        } else {
   1.162 -            (*callback)(udata, stream, stream_len);
   1.163 +                /* !!! FIXME: this should be LockDevice. */
   1.164 +                SDL_LockMutex(device->mixer_lock);
   1.165 +                if (!SDL_AtomicGet(&device->paused)) {
   1.166 +                    callback(udata, device->fake_stream, device->callbackspec.size);
   1.167 +                }
   1.168 +                SDL_UnlockMutex(device->mixer_lock);
   1.169 +            }
   1.170 +        } else {  /* feeding user callback directly without streaming. */
   1.171 +            /* !!! FIXME: this should be LockDevice. */
   1.172 +            SDL_LockMutex(device->mixer_lock);
   1.173 +            if (!SDL_AtomicGet(&device->paused)) {
   1.174 +                callback(udata, stream, device->callbackspec.size);
   1.175 +            }
   1.176 +            SDL_UnlockMutex(device->mixer_lock);
   1.177          }
   1.178 -        SDL_UnlockMutex(device->mixer_lock);
   1.179      }
   1.180  
   1.181      current_audio.impl.FlushCapture(device);
   1.182 @@ -929,15 +957,16 @@
   1.183      if (device->mixer_lock != NULL) {
   1.184          SDL_DestroyMutex(device->mixer_lock);
   1.185      }
   1.186 +
   1.187      SDL_free(device->fake_stream);
   1.188 -    if (device->convert.needed) {
   1.189 -        SDL_free(device->convert.buf);
   1.190 -    }
   1.191 +    SDL_FreeAudioStream(device->stream);
   1.192 +
   1.193      if (device->hidden != NULL) {
   1.194          current_audio.impl.CloseDevice(device);
   1.195      }
   1.196  
   1.197      SDL_FreeDataQueue(device->buffer_queue);
   1.198 +
   1.199      SDL_free(device);
   1.200  }
   1.201  
   1.202 @@ -1013,7 +1042,7 @@
   1.203      SDL_AudioDeviceID id = 0;
   1.204      SDL_AudioSpec _obtained;
   1.205      SDL_AudioDevice *device;
   1.206 -    SDL_bool build_cvt;
   1.207 +    SDL_bool build_stream;
   1.208      void *handle = NULL;
   1.209      int i = 0;
   1.210  
   1.211 @@ -1148,69 +1177,63 @@
   1.212      SDL_assert(device->hidden != NULL);
   1.213  
   1.214      /* See if we need to do any conversion */
   1.215 -    build_cvt = SDL_FALSE;
   1.216 +    build_stream = SDL_FALSE;
   1.217      if (obtained->freq != device->spec.freq) {
   1.218          if (allowed_changes & SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) {
   1.219              obtained->freq = device->spec.freq;
   1.220          } else {
   1.221 -            build_cvt = SDL_TRUE;
   1.222 +            build_stream = SDL_TRUE;
   1.223          }
   1.224      }
   1.225      if (obtained->format != device->spec.format) {
   1.226          if (allowed_changes & SDL_AUDIO_ALLOW_FORMAT_CHANGE) {
   1.227              obtained->format = device->spec.format;
   1.228          } else {
   1.229 -            build_cvt = SDL_TRUE;
   1.230 +            build_stream = SDL_TRUE;
   1.231          }
   1.232      }
   1.233      if (obtained->channels != device->spec.channels) {
   1.234          if (allowed_changes & SDL_AUDIO_ALLOW_CHANNELS_CHANGE) {
   1.235              obtained->channels = device->spec.channels;
   1.236          } else {
   1.237 -            build_cvt = SDL_TRUE;
   1.238 +            build_stream = SDL_TRUE;
   1.239          }
   1.240      }
   1.241  
   1.242 -    /* If the audio driver changes the buffer size, accept it.
   1.243 -       This needs to be done after the format is modified above,
   1.244 -       otherwise it might not have the correct buffer size.
   1.245 +    /* !!! FIXME in 2.1: add SDL_AUDIO_ALLOW_SAMPLES_CHANGE flag?
   1.246 +       As of 2.0.6, we will build a stream to buffer the difference between
   1.247 +       what the app wants to feed and the device wants to eat, so everyone
   1.248 +       gets their way. In prior releases, SDL would force the callback to
   1.249 +       feed at the rate the device requested, adjusted for resampling.
   1.250       */
   1.251      if (device->spec.samples != obtained->samples) {
   1.252 -        obtained->samples = device->spec.samples;
   1.253 -        SDL_CalculateAudioSpec(obtained);
   1.254 +        build_stream = SDL_TRUE;
   1.255      }
   1.256  
   1.257 -    if (build_cvt) {
   1.258 -        /* Build an audio conversion block */
   1.259 -        if (SDL_BuildAudioCVT(&device->convert,
   1.260 -                              obtained->format, obtained->channels,
   1.261 -                              obtained->freq,
   1.262 -                              device->spec.format, device->spec.channels,
   1.263 -                              device->spec.freq) < 0) {
   1.264 +    SDL_CalculateAudioSpec(obtained);  /* recalc after possible changes. */
   1.265 +
   1.266 +    device->callbackspec = *obtained;
   1.267 +
   1.268 +    if (build_stream) {
   1.269 +        if (iscapture) {
   1.270 +            device->stream = SDL_NewAudioStream(device->spec.format,
   1.271 +                                  device->spec.channels, device->spec.freq,
   1.272 +                                  obtained->format, obtained->channels, obtained->freq);
   1.273 +        } else {
   1.274 +            device->stream = SDL_NewAudioStream(obtained->format, obtained->channels,
   1.275 +                                  obtained->freq, device->spec.format,
   1.276 +                                  device->spec.channels, device->spec.freq);
   1.277 +        }
   1.278 +
   1.279 +        if (!device->stream) {
   1.280              close_audio_device(device);
   1.281              return 0;
   1.282          }
   1.283 -        if (device->convert.needed) {
   1.284 -            device->convert.len = (int) (((double) device->spec.samples) /
   1.285 -                                         device->convert.len_ratio);
   1.286 -            device->convert.len *= SDL_AUDIO_BITSIZE(device->spec.format) / 8;
   1.287 -            device->convert.len *= device->spec.channels;
   1.288 -
   1.289 -            device->convert.buf =
   1.290 -                (Uint8 *) SDL_malloc(device->convert.len *
   1.291 -                                            device->convert.len_mult);
   1.292 -            if (device->convert.buf == NULL) {
   1.293 -                close_audio_device(device);
   1.294 -                SDL_OutOfMemory();
   1.295 -                return 0;
   1.296 -            }
   1.297 -        }
   1.298      }
   1.299  
   1.300      if (device->spec.callback == NULL) {  /* use buffer queueing? */
   1.301          /* pool a few packets to start. Enough for two callbacks. */
   1.302 -        const size_t slack = ((device->convert.needed) ? device->convert.len : device->spec.size) * 2;
   1.303 -        device->buffer_queue = SDL_NewDataQueue(SDL_AUDIOBUFFERQUEUE_PACKETLEN, slack);
   1.304 +        device->buffer_queue = SDL_NewDataQueue(SDL_AUDIOBUFFERQUEUE_PACKETLEN, obtained->size * 2);
   1.305          if (!device->buffer_queue) {
   1.306              close_audio_device(device);
   1.307              SDL_SetError("Couldn't create audio buffer queue");
   1.308 @@ -1220,8 +1243,7 @@
   1.309          device->spec.userdata = device;
   1.310      }
   1.311  
   1.312 -    /* add it to our list of open devices. */
   1.313 -    open_devices[id] = device;
   1.314 +    open_devices[id] = device;  /* add it to our list of open devices. */
   1.315  
   1.316      /* Start the audio thread if necessary */
   1.317      if (!current_audio.impl.ProvidesOwnCallbackThread) {
   1.318 @@ -1232,13 +1254,13 @@
   1.319          char threadname[64];
   1.320  
   1.321          /* Allocate a fake audio buffer; only used by our internal threads. */
   1.322 -        Uint32 stream_len = (device->convert.needed) ? device->convert.len_cvt : 0;
   1.323 -        if (device->spec.size > stream_len) {
   1.324 -            stream_len = device->spec.size;
   1.325 +        device->fake_stream_len = build_stream ? device->callbackspec.size : 0;
   1.326 +        if (device->spec.size > device->fake_stream_len) {
   1.327 +            device->fake_stream_len = device->spec.size;
   1.328          }
   1.329 -        SDL_assert(stream_len > 0);
   1.330 +        SDL_assert(device->fake_stream_len > 0);
   1.331  
   1.332 -        device->fake_stream = (Uint8 *) SDL_malloc(stream_len);
   1.333 +        device->fake_stream = (Uint8 *) SDL_malloc(device->fake_stream_len);
   1.334          if (device->fake_stream == NULL) {
   1.335              close_audio_device(device);
   1.336              SDL_OutOfMemory();
   1.337 @@ -1480,13 +1502,7 @@
   1.338      /* Mix the user-level audio format */
   1.339      SDL_AudioDevice *device = get_audio_device(1);
   1.340      if (device != NULL) {
   1.341 -        SDL_AudioFormat format;
   1.342 -        if (device->convert.needed) {
   1.343 -            format = device->convert.src_format;
   1.344 -        } else {
   1.345 -            format = device->spec.format;
   1.346 -        }
   1.347 -        SDL_MixAudioFormat(dst, src, format, len, volume);
   1.348 +        SDL_MixAudioFormat(dst, src, device->callbackspec.format, len, volume);
   1.349      }
   1.350  }
   1.351  
     2.1 --- a/src/audio/SDL_audio_c.h	Thu Jan 05 19:12:20 2017 -0500
     2.2 +++ b/src/audio/SDL_audio_c.h	Thu Jan 05 19:29:38 2017 -0500
     2.3 @@ -54,4 +54,44 @@
     2.4  void SDL_Downsample_Arbitrary(SDL_AudioCVT *cvt, const int channels);
     2.5  void SDL_Downsample_Multiple(SDL_AudioCVT *cvt, const int channels);
     2.6  
     2.7 +
     2.8 +/* SDL_AudioStream is a new audio conversion interface. It
     2.9 +    might eventually become a public API.
    2.10 +   The benefits vs SDL_AudioCVT:
    2.11 +    - it can handle resampling data in chunks without generating
    2.12 +      artifacts, when it doesn't have the complete buffer available.
    2.13 +    - it can handle incoming data in any variable size.
    2.14 +    - You push data as you have it, and pull it when you need it
    2.15 +
    2.16 +    (Note that currently this converts as data is put into the stream, so
    2.17 +    you need to push more than a handful of bytes if you want decent
    2.18 +    resampling. This can be changed later.)
    2.19 + */
    2.20 +
    2.21 +/* this is opaque to the outside world. */
    2.22 +typedef struct SDL_AudioStream SDL_AudioStream;
    2.23 +
    2.24 +/* create a new stream */
    2.25 +SDL_AudioStream *SDL_NewAudioStream(const SDL_AudioFormat src_format,
    2.26 +                                    const Uint8 src_channels,
    2.27 +                                    const int src_rate,
    2.28 +                                    const SDL_AudioFormat dst_format,
    2.29 +                                    const Uint8 dst_channels,
    2.30 +                                    const int dst_rate);
    2.31 +
    2.32 +/* add data to be converted/resampled to the stream */
    2.33 +int SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 len);
    2.34 +
    2.35 +/* get converted/resampled data from the stream */
    2.36 +int SDL_AudioStreamGet(SDL_AudioStream *stream, Uint32 len, void *buf, const Uint32 buflen);
    2.37 +
    2.38 +/* clear any pending data in the stream without converting it. */
    2.39 +void SDL_AudioStreamClear(SDL_AudioStream *stream);
    2.40 +
    2.41 +/* number of converted/resampled bytes available */
    2.42 +int SDL_AudioStreamAvailable(SDL_AudioStream *stream);
    2.43 +
    2.44 +/* dispose of a stream */
    2.45 +void SDL_FreeAudioStream(SDL_AudioStream *stream);
    2.46 +
    2.47  /* vi: set ts=4 sw=4 expandtab: */
     3.1 --- a/src/audio/SDL_audiocvt.c	Thu Jan 05 19:12:20 2017 -0500
     3.2 +++ b/src/audio/SDL_audiocvt.c	Thu Jan 05 19:29:38 2017 -0500
     3.3 @@ -26,6 +26,7 @@
     3.4  #include "SDL_audio_c.h"
     3.5  
     3.6  #include "SDL_assert.h"
     3.7 +#include "../SDL_dataqueue.h"
     3.8  
     3.9  
    3.10  /* Effectively mix right and left channels into a single channel */
    3.11 @@ -590,5 +591,280 @@
    3.12      return (cvt->needed);
    3.13  }
    3.14  
    3.15 +
    3.16 +struct SDL_AudioStream
    3.17 +{
    3.18 +    SDL_AudioCVT cvt_before_resampling;
    3.19 +    SDL_AudioCVT cvt_after_resampling;
    3.20 +    SDL_DataQueue *queue;
    3.21 +    Uint8 *work_buffer;
    3.22 +    int work_buffer_len;
    3.23 +    Uint8 *resample_buffer;
    3.24 +    int resample_buffer_len;
    3.25 +    int src_sample_frame_size;
    3.26 +    SDL_AudioFormat src_format;
    3.27 +    Uint8 src_channels;
    3.28 +    int src_rate;
    3.29 +    int dst_sample_frame_size;
    3.30 +    SDL_AudioFormat dst_format;
    3.31 +    Uint8 dst_channels;
    3.32 +    int dst_rate;
    3.33 +    double rate_incr;
    3.34 +    Uint8 pre_resample_channels;
    3.35 +    SDL_bool resampler_seeded;
    3.36 +    float resampler_state[8];
    3.37 +    int packetlen;
    3.38 +};
    3.39 +
    3.40 +SDL_AudioStream *SDL_NewAudioStream(const SDL_AudioFormat src_format,
    3.41 +                                    const Uint8 src_channels,
    3.42 +                                    const int src_rate,
    3.43 +                                    const SDL_AudioFormat dst_format,
    3.44 +                                    const Uint8 dst_channels,
    3.45 +                                    const int dst_rate)
    3.46 +{
    3.47 +    const int packetlen = 4096;  /* !!! FIXME: good enough for now. */
    3.48 +    Uint8 pre_resample_channels;
    3.49 +    SDL_AudioStream *retval;
    3.50 +
    3.51 +    retval = (SDL_AudioStream *) SDL_calloc(1, sizeof (SDL_AudioStream));
    3.52 +    if (!retval) {
    3.53 +        return NULL;
    3.54 +    }
    3.55 +
    3.56 +    /* If increasing channels, do it after resampling, since we'd just
    3.57 +       do more work to resample duplicate channels. If we're decreasing, do
    3.58 +       it first so we resample the interpolated data instead of interpolating
    3.59 +       the resampled data (!!! FIXME: decide if that works in practice, though!). */
    3.60 +    pre_resample_channels = SDL_min(src_channels, dst_channels);
    3.61 +
    3.62 +    retval->src_sample_frame_size = SDL_AUDIO_BITSIZE(src_format) * src_channels;
    3.63 +    retval->src_format = src_format;
    3.64 +    retval->src_channels = src_channels;
    3.65 +    retval->src_rate = src_rate;
    3.66 +    retval->dst_sample_frame_size = SDL_AUDIO_BITSIZE(dst_format) * dst_channels;
    3.67 +    retval->dst_format = dst_format;
    3.68 +    retval->dst_channels = dst_channels;
    3.69 +    retval->dst_rate = dst_rate;
    3.70 +    retval->pre_resample_channels = pre_resample_channels;
    3.71 +    retval->packetlen = packetlen;
    3.72 +    retval->rate_incr = ((double) dst_rate) / ((double) src_rate);
    3.73 +
    3.74 +    /* Not resampling? It's an easy conversion (and maybe not even that!). */
    3.75 +    if (src_rate == dst_rate) {
    3.76 +        retval->cvt_before_resampling.needed = SDL_FALSE;
    3.77 +        retval->cvt_before_resampling.len_mult = 1;
    3.78 +        if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, src_format, src_channels, dst_rate, dst_format, dst_channels, dst_rate) == -1) {
    3.79 +            SDL_free(retval);
    3.80 +            return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */
    3.81 +        }
    3.82 +    } else {
    3.83 +        /* Don't resample at first. Just get us to Float32 format. */
    3.84 +        /* !!! FIXME: convert to int32 on devices without hardware float. */
    3.85 +        if (SDL_BuildAudioCVT(&retval->cvt_before_resampling, src_format, src_channels, src_rate, AUDIO_F32SYS, pre_resample_channels, src_rate) == -1) {
    3.86 +            SDL_free(retval);
    3.87 +            return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */
    3.88 +        }
    3.89 +
    3.90 +        /* Convert us to the final format after resampling. */
    3.91 +        if (SDL_BuildAudioCVT(&retval->cvt_after_resampling, AUDIO_F32SYS, pre_resample_channels, dst_rate, dst_format, dst_channels, dst_rate) == -1) {
    3.92 +            SDL_free(retval);
    3.93 +            return NULL;  /* SDL_BuildAudioCVT should have called SDL_SetError. */
    3.94 +        }
    3.95 +    }
    3.96 +
    3.97 +    retval->queue = SDL_NewDataQueue(packetlen, packetlen * 2);
    3.98 +    if (!retval->queue) {
    3.99 +        SDL_free(retval);
   3.100 +        return NULL;  /* SDL_NewDataQueue should have called SDL_SetError. */
   3.101 +    }
   3.102 +
   3.103 +    return retval;
   3.104 +}
   3.105 +
   3.106 +
   3.107 +static int
   3.108 +ResampleAudioStream(SDL_AudioStream *stream, const float *inbuf, const int inbuflen, float *outbuf, const int outbuflen)
   3.109 +{
   3.110 +    /* !!! FIXME: this resampler sucks, but not much worse than our usual resampler.  :)  */  /* ... :( */
   3.111 +    const int chans = (int) stream->pre_resample_channels;
   3.112 +    const int framelen = chans * sizeof (float);
   3.113 +    const int total = (inbuflen / framelen);
   3.114 +    const int finalpos = total - chans;
   3.115 +    const double src_incr = 1.0 / stream->rate_incr;
   3.116 +    double idx = 0.0;
   3.117 +    float *dst = outbuf;
   3.118 +    float last_sample[SDL_arraysize(stream->resampler_state)];
   3.119 +    int consumed = 0;
   3.120 +    int i;
   3.121 +
   3.122 +    SDL_assert(chans <= SDL_arraysize(last_sample));
   3.123 +    SDL_assert((inbuflen % framelen) == 0);
   3.124 +
   3.125 +    if (!stream->resampler_seeded) {
   3.126 +        for (i = 0; i < chans; i++) {
   3.127 +            stream->resampler_state[i] = inbuf[i];
   3.128 +        }
   3.129 +        stream->resampler_seeded = SDL_TRUE;
   3.130 +    }
   3.131 +
   3.132 +    for (i = 0; i < chans; i++) {
   3.133 +        last_sample[i] = stream->resampler_state[i];
   3.134 +    }
   3.135 +
   3.136 +    while (consumed < total) {
   3.137 +        const int pos = ((int) idx) * chans;
   3.138 +        const float *src = &inbuf[(pos >= finalpos) ? finalpos : pos];
   3.139 +        SDL_assert(dst < (outbuf + (outbuflen / framelen)));
   3.140 +        for (i = 0; i < chans; i++) {
   3.141 +            const float val = *(src++);
   3.142 +            *(dst++) = (val + last_sample[i]) * 0.5f;
   3.143 +            last_sample[i] = val;
   3.144 +        }
   3.145 +        consumed = pos + chans;
   3.146 +        idx += src_incr;
   3.147 +    }
   3.148 +
   3.149 +    for (i = 0; i < chans; i++) {
   3.150 +        stream->resampler_state[i] = last_sample[i];
   3.151 +    }
   3.152 +
   3.153 +    return (dst - outbuf) * sizeof (float);
   3.154 +}
   3.155 +
   3.156 +static Uint8 *
   3.157 +EnsureBufferSize(Uint8 **buf, int *len, const int newlen)
   3.158 +{
   3.159 +    if (*len < newlen) {
   3.160 +        void *ptr = SDL_realloc(*buf, newlen);
   3.161 +        if (!ptr) {
   3.162 +            SDL_OutOfMemory();
   3.163 +            return NULL;
   3.164 +        }
   3.165 +        *buf = (Uint8 *) ptr;
   3.166 +        *len = newlen;
   3.167 +    }
   3.168 +    return *buf;
   3.169 +}
   3.170 +
   3.171 +int
   3.172 +SDL_AudioStreamPut(SDL_AudioStream *stream, const void *buf, const Uint32 _buflen)
   3.173 +{
   3.174 +    int buflen = (int) _buflen;
   3.175 +
   3.176 +    if (!stream) {
   3.177 +        return SDL_InvalidParamError("stream");
   3.178 +    } else if (!buf) {
   3.179 +        return SDL_InvalidParamError("buf");
   3.180 +    } else if (buflen == 0) {
   3.181 +        return 0;  /* nothing to do. */
   3.182 +    } else if ((buflen % stream->src_sample_frame_size) != 0) {
   3.183 +        return SDL_SetError("Can't add partial sample frames");
   3.184 +    }
   3.185 +
   3.186 +    if (stream->cvt_before_resampling.needed) {
   3.187 +        const int workbuflen = buflen * stream->cvt_before_resampling.len_mult;  /* will be "* 1" if not needed */
   3.188 +        Uint8 *workbuf = EnsureBufferSize(&stream->work_buffer, &stream->work_buffer_len, workbuflen);
   3.189 +        if (workbuf == NULL) {
   3.190 +            return -1;  /* probably out of memory. */
   3.191 +        }
   3.192 +        SDL_memcpy(workbuf, buf, buflen);
   3.193 +        stream->cvt_before_resampling.buf = workbuf;
   3.194 +        stream->cvt_before_resampling.len = buflen;
   3.195 +        if (SDL_ConvertAudio(&stream->cvt_before_resampling) == -1) {
   3.196 +            return -1;   /* uhoh! */
   3.197 +        }
   3.198 +        buf = workbuf;
   3.199 +        buflen = stream->cvt_before_resampling.len_cvt;
   3.200 +    }
   3.201 +
   3.202 +    if (stream->dst_rate != stream->src_rate) {
   3.203 +        const int workbuflen = buflen * ((int) SDL_ceil(stream->rate_incr));
   3.204 +        float *workbuf = (float *) EnsureBufferSize(&stream->resample_buffer, &stream->resample_buffer_len, workbuflen);
   3.205 +        if (workbuf == NULL) {
   3.206 +            return -1;  /* probably out of memory. */
   3.207 +        }
   3.208 +        buflen = ResampleAudioStream(stream, (float *) buf, buflen, workbuf, workbuflen);
   3.209 +        buf = workbuf;
   3.210 +    }
   3.211 +
   3.212 +    if (stream->cvt_after_resampling.needed) {
   3.213 +        const int workbuflen = buflen * stream->cvt_before_resampling.len_mult;  /* will be "* 1" if not needed */
   3.214 +        Uint8 *workbuf;
   3.215 +
   3.216 +        if (buf == stream->resample_buffer) {
   3.217 +            workbuf = EnsureBufferSize(&stream->resample_buffer, &stream->resample_buffer_len, workbuflen);
   3.218 +        } else {
   3.219 +            const int inplace = (buf == stream->work_buffer);
   3.220 +            workbuf = EnsureBufferSize(&stream->work_buffer, &stream->work_buffer_len, workbuflen);
   3.221 +            if (workbuf && !inplace) {
   3.222 +                SDL_memcpy(workbuf, buf, buflen);
   3.223 +            }
   3.224 +        }
   3.225 +
   3.226 +        if (workbuf == NULL) {
   3.227 +            return -1;  /* probably out of memory. */
   3.228 +        }
   3.229 +
   3.230 +        stream->cvt_after_resampling.buf = workbuf;
   3.231 +        stream->cvt_after_resampling.len = buflen;
   3.232 +        if (SDL_ConvertAudio(&stream->cvt_after_resampling) == -1) {
   3.233 +            return -1;   /* uhoh! */
   3.234 +        }
   3.235 +        buf = workbuf;
   3.236 +        buflen = stream->cvt_after_resampling.len_cvt;
   3.237 +    }
   3.238 +
   3.239 +    return SDL_WriteToDataQueue(stream->queue, buf, buflen);
   3.240 +}
   3.241 +
   3.242 +void
   3.243 +SDL_AudioStreamClear(SDL_AudioStream *stream)
   3.244 +{
   3.245 +    if (!stream) {
   3.246 +        SDL_InvalidParamError("stream");
   3.247 +    } else {
   3.248 +        SDL_ClearDataQueue(stream->queue, stream->packetlen * 2);
   3.249 +        stream->resampler_seeded = SDL_FALSE;
   3.250 +    }
   3.251 +}
   3.252 +
   3.253 +
   3.254 +/* get converted/resampled data from the stream */
   3.255 +int
   3.256 +SDL_AudioStreamGet(SDL_AudioStream *stream, Uint32 len, void *buf, const Uint32 buflen)
   3.257 +{
   3.258 +    if (!stream) {
   3.259 +        return SDL_InvalidParamError("stream");
   3.260 +    } else if (!buf) {
   3.261 +        return SDL_InvalidParamError("buf");
   3.262 +    } else if (len == 0) {
   3.263 +        return 0;  /* nothing to do. */
   3.264 +    } else if ((len % stream->dst_sample_frame_size) != 0) {
   3.265 +        return SDL_SetError("Can't request partial sample frames");
   3.266 +    }
   3.267 +
   3.268 +    return SDL_ReadFromDataQueue(stream->queue, buf, buflen);
   3.269 +}
   3.270 +
   3.271 +/* number of converted/resampled bytes available */
   3.272 +int
   3.273 +SDL_AudioStreamAvailable(SDL_AudioStream *stream)
   3.274 +{
   3.275 +    return stream ? (int) SDL_CountDataQueue(stream->queue) : 0;
   3.276 +}
   3.277 +
   3.278 +/* dispose of a stream */
   3.279 +void
   3.280 +SDL_FreeAudioStream(SDL_AudioStream *stream)
   3.281 +{
   3.282 +    if (stream) {
   3.283 +        SDL_FreeDataQueue(stream->queue);
   3.284 +        SDL_free(stream->work_buffer);
   3.285 +        SDL_free(stream->resample_buffer);
   3.286 +        SDL_free(stream);
   3.287 +    }
   3.288 +}
   3.289 +
   3.290  /* vi: set ts=4 sw=4 expandtab: */
   3.291  
     4.1 --- a/src/audio/SDL_sysaudio.h	Thu Jan 05 19:12:20 2017 -0500
     4.2 +++ b/src/audio/SDL_sysaudio.h	Thu Jan 05 19:29:38 2017 -0500
     4.3 @@ -35,6 +35,8 @@
     4.4  typedef struct SDL_AudioDevice SDL_AudioDevice;
     4.5  #define _THIS   SDL_AudioDevice *_this
     4.6  
     4.7 +typedef struct SDL_AudioStream SDL_AudioStream;
     4.8 +
     4.9  /* Audio targets should call this as devices are added to the system (such as
    4.10     a USB headset being plugged in), and should also be called for
    4.11     for every device found during DetectDevices(). */
    4.12 @@ -123,15 +125,6 @@
    4.13  } SDL_AudioDriver;
    4.14  
    4.15  
    4.16 -/* Streamer */
    4.17 -typedef struct
    4.18 -{
    4.19 -    Uint8 *buffer;
    4.20 -    int max_len;                /* the maximum length in bytes */
    4.21 -    int read_pos, write_pos;    /* the position of the write and read heads in bytes */
    4.22 -} SDL_AudioStreamer;
    4.23 -
    4.24 -
    4.25  /* Define the SDL audio driver structure */
    4.26  struct SDL_AudioDevice
    4.27  {
    4.28 @@ -139,15 +132,14 @@
    4.29      /* Data common to all devices */
    4.30      SDL_AudioDeviceID id;
    4.31  
    4.32 -    /* The current audio specification (shared with audio thread) */
    4.33 +    /* The device's current audio specification */
    4.34      SDL_AudioSpec spec;
    4.35  
    4.36 -    /* An audio conversion block for audio format emulation */
    4.37 -    SDL_AudioCVT convert;
    4.38 +    /* The callback's expected audio specification (converted vs device's spec). */
    4.39 +    SDL_AudioSpec callbackspec;
    4.40  
    4.41 -    /* The streamer, if sample rate conversion necessitates it */
    4.42 -    int use_streamer;
    4.43 -    SDL_AudioStreamer streamer;
    4.44 +    /* Stream that converts and resamples. NULL if not needed. */
    4.45 +    SDL_AudioStream *stream;
    4.46  
    4.47      /* Current state flags */
    4.48      SDL_atomic_t shutdown; /* true if we are signaling the play thread to end. */
    4.49 @@ -158,6 +150,9 @@
    4.50      /* Fake audio buffer for when the audio hardware is busy */
    4.51      Uint8 *fake_stream;
    4.52  
    4.53 +    /* Size, in bytes, of fake_stream. */
    4.54 +    Uint32 fake_stream_len;
    4.55 +
    4.56      /* A mutex for locking the mixing buffers */
    4.57      SDL_mutex *mixer_lock;
    4.58