From 30178a9b24edea5f8593615c7aeda5a6784ebf05 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 5 Jan 2017 19:29:38 -0500 Subject: [PATCH] audio: Added SDL_AudioStream. Non-power-of-two resampling now works! --- src/audio/SDL_audio.c | 200 +++++++++++++++------------- src/audio/SDL_audio_c.h | 40 ++++++ src/audio/SDL_audiocvt.c | 276 +++++++++++++++++++++++++++++++++++++++ src/audio/SDL_sysaudio.h | 25 ++-- 4 files changed, 434 insertions(+), 107 deletions(-) diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 71b1c291eb31f..9762675131c7d 100644 --- a/src/audio/SDL_audio.c +++ b/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); } } diff --git a/src/audio/SDL_audio_c.h b/src/audio/SDL_audio_c.h index f1be201f28df8..5d1d554ee79e3 100644 --- a/src/audio/SDL_audio_c.h +++ b/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: */ diff --git a/src/audio/SDL_audiocvt.c b/src/audio/SDL_audiocvt.c index e48deb1ec9c08..ae909aafecc11 100644 --- a/src/audio/SDL_audiocvt.c +++ b/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: */ diff --git a/src/audio/SDL_sysaudio.h b/src/audio/SDL_sysaudio.h index c707711d6f19d..bad295088edd9 100644 --- a/src/audio/SDL_sysaudio.h +++ b/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;