Skip to content

Commit

Permalink
audio: Added SDL_AudioStream. Non-power-of-two resampling now works!
Browse files Browse the repository at this point in the history
  • Loading branch information
icculus committed Jan 6, 2017
1 parent f12ab8f commit 30178a9
Show file tree
Hide file tree
Showing 4 changed files with 434 additions and 107 deletions.
200 changes: 108 additions & 92 deletions src/audio/SDL_audio.c
Expand Up @@ -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);

Expand All @@ -564,50 +564,61 @@ 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;
}

if (stream == NULL) {
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);
}
Expand All @@ -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);

Expand All @@ -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.
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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");
Expand All @@ -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) {
Expand All @@ -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();
Expand Down Expand Up @@ -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);
}
}

Expand Down
40 changes: 40 additions & 0 deletions src/audio/SDL_audio_c.h
Expand Up @@ -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: */

0 comments on commit 30178a9

Please sign in to comment.