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

Ryan C. Gordon 2017-01-05 19:29:38 -05:00
parent f12ab8f2b3
commit 30178a9b24
4 changed files with 436 additions and 109 deletions

View File

@ -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) { if (!device->stream && SDL_AtomicGet(&device->enabled)) {
stream = device->convert.buf;
} else if (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->stream) {
if (device->convert.needed && SDL_AtomicGet(&device->enabled)) { /* Stream available audio to device, converting/resampling. */
SDL_ConvertAudio(&device->convert); /* if this fails...oh well. We'll play silence here. */
stream = current_audio.impl.GetDeviceBuf(device); 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) { if (stream == NULL) {
stream = device->fake_stream; SDL_AudioStreamClear(device->stream);
} else {
SDL_memcpy(stream, device->convert.buf,
device->convert.len_cvt);
}
}
/* Ready current buffer for play and change current buffer */
if (stream == device->fake_stream) {
SDL_Delay(delay); SDL_Delay(delay);
break;
} else { } 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);
}
}
} else if (stream == device->fake_stream) {
/* nothing to do; pause like we queued a buffer to play. */
SDL_Delay(delay);
} 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. */ /* just use the "fake" stream to hold data read from the device. */
ptr = stream = device->fake_stream; 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,19 +697,33 @@ SDL_CaptureAudio(void *devicep)
SDL_memset(ptr, silence, still_need); SDL_memset(ptr, silence, still_need);
} }
if (device->convert.needed) { if (device->stream) {
SDL_ConvertAudio(&device->convert); /* 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. */ /* !!! 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)) {
current_audio.impl.FlushCapture(device); /* one snuck in! */ callback(udata, device->fake_stream, device->callbackspec.size);
} else {
(*callback)(udata, stream, stream_len);
} }
SDL_UnlockMutex(device->mixer_lock); 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);
}
}
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_FreeAudioStream(device->stream);
SDL_free(device->convert.buf);
}
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. /* !!! FIXME in 2.1: add SDL_AUDIO_ALLOW_SAMPLES_CHANGE flag?
This needs to be done after the format is modified above, As of 2.0.6, we will build a stream to buffer the difference between
otherwise it might not have the correct buffer size. 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; build_stream = SDL_TRUE;
SDL_CalculateAudioSpec(obtained);
} }
if (build_cvt) { SDL_CalculateAudioSpec(obtained); /* recalc after possible changes. */
/* 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) {
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 = device->callbackspec = *obtained;
(Uint8 *) SDL_malloc(device->convert.len *
device->convert.len_mult); if (build_stream) {
if (device->convert.buf == NULL) { if (iscapture) {
close_audio_device(device); device->stream = SDL_NewAudioStream(device->spec.format,
SDL_OutOfMemory(); device->spec.channels, device->spec.freq,
return 0; 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->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, obtained->size * 2);
device->buffer_queue = SDL_NewDataQueue(SDL_AUDIOBUFFERQUEUE_PACKETLEN, slack);
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; /* add it to our list of open devices. */
open_devices[id] = device;
/* 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; device->fake_stream_len = build_stream ? device->callbackspec.size : 0;
if (device->spec.size > stream_len) { if (device->spec.size > device->fake_stream_len) {
stream_len = device->spec.size; 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; SDL_MixAudioFormat(dst, src, device->callbackspec.format, len, volume);
if (device->convert.needed) {
format = device->convert.src_format;
} else {
format = device->spec.format;
}
SDL_MixAudioFormat(dst, src, format, len, volume);
} }
} }

View File

@ -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: */

View File

@ -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: */

View File

@ -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 */ /* The callback's expected audio specification (converted vs device's spec). */
SDL_AudioCVT convert; SDL_AudioSpec callbackspec;
/* The streamer, if sample rate conversion necessitates it */ /* Stream that converts and resamples. NULL if not needed. */
int use_streamer; SDL_AudioStream *stream;
SDL_AudioStreamer streamer;
/* 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;