Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
audio: Implemented buffer queueing for capture devices (SDL_DequeueAu…
…dio()).
  • Loading branch information
icculus committed Aug 6, 2016
1 parent 7bfe494 commit 7315390
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 82 deletions.
95 changes: 81 additions & 14 deletions include/SDL_audio.h
Expand Up @@ -278,7 +278,8 @@ extern DECLSPEC const char *SDLCALL SDL_GetCurrentAudioDriver(void);
* protect data structures that it accesses by calling SDL_LockAudio()
* and SDL_UnlockAudio() in your code. Alternately, you may pass a NULL
* pointer here, and call SDL_QueueAudio() with some frequency, to queue
* more audio samples to be played.
* more audio samples to be played (or for capture devices, call
* SDL_DequeueAudio() with some frequency, to obtain audio samples).
* - \c desired->userdata is passed as the first parameter to your callback
* function. If you passed a NULL callback, this value is ignored.
*
Expand Down Expand Up @@ -482,6 +483,10 @@ extern DECLSPEC void SDLCALL SDL_MixAudioFormat(Uint8 * dst,
/**
* Queue more audio on non-callback devices.
*
* (If you are looking to retrieve queued audio from a non-callback capture
* device, you want SDL_DequeueAudio() instead. This will return -1 to
* signify an error if you use it with capture devices.)
*
* SDL offers two ways to feed audio to the device: you can either supply a
* callback that SDL triggers with some frequency to obtain more audio
* (pull method), or you can supply no callback, and then SDL will expect
Expand Down Expand Up @@ -516,21 +521,76 @@ extern DECLSPEC void SDLCALL SDL_MixAudioFormat(Uint8 * dst,
*/
extern DECLSPEC int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID dev, const void *data, Uint32 len);

/**
* Dequeue more audio on non-callback devices.
*
* (If you are looking to queue audio for output on a non-callback playback
* device, you want SDL_QueueAudio() instead. This will always return 0
* if you use it with playback devices.)
*
* SDL offers two ways to retrieve audio from a capture device: you can
* either supply a callback that SDL triggers with some frequency as the
* device records more audio data, (push method), or you can supply no
* callback, and then SDL will expect you to retrieve data at regular
* intervals (pull method) with this function.
*
* There are no limits on the amount of data you can queue, short of
* exhaustion of address space. Data from the device will keep queuing as
* necessary without further intervention from you. This means you will
* eventually run out of memory if you aren't routinely dequeueing data.
*
* Capture devices will not queue data when paused; if you are expecting
* to not need captured audio for some length of time, use
* SDL_PauseAudioDevice() to stop the capture device from queueing more
* data. This can be useful during, say, level loading times. When
* unpaused, capture devices will start queueing data from that point,
* having flushed any capturable data available while paused.
*
* This function is thread-safe, but dequeueing from the same device from
* two threads at once does not promise which thread will dequeued data
* first.
*
* You may not dequeue audio from a device that is using an
* application-supplied callback; doing so returns an error. You have to use
* the audio callback, or dequeue audio with this function, but not both.
*
* You should not call SDL_LockAudio() on the device before queueing; SDL
* handles locking internally for this function.
*
* \param dev The device ID from which we will dequeue audio.
* \param data A pointer into where audio data should be copied.
* \param len The number of bytes (not samples!) to which (data) points.
* \return number of bytes dequeued, which could be less than requested.
*
* \sa SDL_GetQueuedAudioSize
* \sa SDL_ClearQueuedAudio
*/
extern DECLSPEC Uint32 SDLCALL SDL_DequeueAudio(SDL_AudioDeviceID dev, void *data, Uint32 len);

/**
* Get the number of bytes of still-queued audio.
*
* This is the number of bytes that have been queued for playback with
* SDL_QueueAudio(), but have not yet been sent to the hardware.
* For playback device:
*
* This is the number of bytes that have been queued for playback with
* SDL_QueueAudio(), but have not yet been sent to the hardware. This
* number may shrink at any time, so this only informs of pending data.
*
* Once we've sent it to the hardware, this function can not decide the
* exact byte boundary of what has been played. It's possible that we just
* gave the hardware several kilobytes right before you called this
* function, but it hasn't played any of it yet, or maybe half of it, etc.
*
* For capture devices:
*
* Once we've sent it to the hardware, this function can not decide the exact
* byte boundary of what has been played. It's possible that we just gave the
* hardware several kilobytes right before you called this function, but it
* hasn't played any of it yet, or maybe half of it, etc.
* This is the number of bytes that have been captured by the device and
* are waiting for you to dequeue. This number may grow at any time, so
* this only informs of the lower-bound of available data.
*
* You may not queue audio on a device that is using an application-supplied
* callback; calling this function on such a device always returns 0.
* You have to use the audio callback or queue audio with SDL_QueueAudio(),
* but not both.
* You have to queue audio with SDL_QueueAudio()/SDL_DequeueAudio(), or use
* the audio callback, but not both.
*
* You should not call SDL_LockAudio() on the device before querying; SDL
* handles locking internally for this function.
Expand All @@ -544,10 +604,17 @@ extern DECLSPEC int SDLCALL SDL_QueueAudio(SDL_AudioDeviceID dev, const void *da
extern DECLSPEC Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev);

/**
* Drop any queued audio data waiting to be sent to the hardware.
* Drop any queued audio data. For playback devices, this is any queued data
* still waiting to be submitted to the hardware. For capture devices, this
* is any data that was queued by the device that hasn't yet been dequeued by
* the application.
*
* Immediately after this call, SDL_GetQueuedAudioSize() will return 0 and
* the hardware will start playing silence if more audio isn't queued.
* Immediately after this call, SDL_GetQueuedAudioSize() will return 0. For
* playback devices, the hardware will start playing silence if more audio
* isn't queued. Unpaused capture devices will start filling the queue again
* as soon as they have more data available (which, depending on the state
* of the hardware and the thread, could be before this function call
* returns!).
*
* This will not prevent playback of queued audio that's already been sent
* to the hardware, as we can not undo that, so expect there to be some
Expand All @@ -557,8 +624,8 @@ extern DECLSPEC Uint32 SDLCALL SDL_GetQueuedAudioSize(SDL_AudioDeviceID dev);
*
* You may not queue audio on a device that is using an application-supplied
* callback; calling this function on such a device is always a no-op.
* You have to use the audio callback or queue audio with SDL_QueueAudio(),
* but not both.
* You have to queue audio with SDL_QueueAudio()/SDL_DequeueAudio(), or use
* the audio callback, but not both.
*
* You should not call SDL_LockAudio() on the device before clearing the
* queue; SDL handles locking internally for this function.
Expand Down
203 changes: 135 additions & 68 deletions src/audio/SDL_audio.c
Expand Up @@ -433,77 +433,24 @@ SDL_RemoveAudioDevice(const int iscapture, void *handle)

/* this expects that you managed thread safety elsewhere. */
static void
free_audio_queue(SDL_AudioBufferQueue *buffer)
free_audio_queue(SDL_AudioBufferQueue *packet)
{
while (buffer) {
SDL_AudioBufferQueue *next = buffer->next;
SDL_free(buffer);
buffer = next;
while (packet) {
SDL_AudioBufferQueue *next = packet->next;
SDL_free(packet);
packet = next;
}
}

static void SDLCALL
SDL_BufferQueueDrainCallback(void *userdata, Uint8 *stream, int _len)
{
/* this function always holds the mixer lock before being called. */
Uint32 len = (Uint32) _len;
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
SDL_AudioBufferQueue *buffer;

SDL_assert(device != NULL); /* this shouldn't ever happen, right?! */
SDL_assert(_len >= 0); /* this shouldn't ever happen, right?! */

while ((len > 0) && ((buffer = device->buffer_queue_head) != NULL)) {
const Uint32 avail = buffer->datalen - buffer->startpos;
const Uint32 cpy = SDL_min(len, avail);
SDL_assert(device->queued_bytes >= avail);

SDL_memcpy(stream, buffer->data + buffer->startpos, cpy);
buffer->startpos += cpy;
stream += cpy;
device->queued_bytes -= cpy;
len -= cpy;

if (buffer->startpos == buffer->datalen) { /* packet is done, put it in the pool. */
device->buffer_queue_head = buffer->next;
SDL_assert((buffer->next != NULL) || (buffer == device->buffer_queue_tail));
buffer->next = device->buffer_queue_pool;
device->buffer_queue_pool = buffer;
}
}

SDL_assert((device->buffer_queue_head != NULL) == (device->queued_bytes != 0));

if (len > 0) { /* fill any remaining space in the stream with silence. */
SDL_assert(device->buffer_queue_head == NULL);
SDL_memset(stream, device->spec.silence, len);
}

if (device->buffer_queue_head == NULL) {
device->buffer_queue_tail = NULL; /* in case we drained the queue entirely. */
}
}

int
SDL_QueueAudio(SDL_AudioDeviceID devid, const void *_data, Uint32 len)
/* NOTE: This assumes you'll hold the mixer lock before calling! */
static int
queue_audio_to_device(SDL_AudioDevice *device, const Uint8 *data, Uint32 len)
{
SDL_AudioDevice *device = get_audio_device(devid);
const Uint8 *data = (const Uint8 *) _data;
SDL_AudioBufferQueue *orighead;
SDL_AudioBufferQueue *origtail;
Uint32 origlen;
Uint32 datalen;

if (!device) {
return -1; /* get_audio_device() will have set the error state */
}

if (device->spec.callback != SDL_BufferQueueDrainCallback) {
return SDL_SetError("Audio device has a callback, queueing not allowed");
}

current_audio.impl.LockDevice(device);

orighead = device->buffer_queue_head;
origtail = device->buffer_queue_tail;
origlen = origtail ? origtail->datalen : 0;
Expand Down Expand Up @@ -533,8 +480,6 @@ SDL_QueueAudio(SDL_AudioDeviceID devid, const void *_data, Uint32 len)
device->buffer_queue_tail = origtail;
device->buffer_queue_pool = NULL;

current_audio.impl.UnlockDevice(device);

free_audio_queue(packet); /* give back what we can. */

return SDL_OutOfMemory();
Expand All @@ -561,22 +506,142 @@ SDL_QueueAudio(SDL_AudioDeviceID devid, const void *_data, Uint32 len)
device->queued_bytes += datalen;
}

current_audio.impl.UnlockDevice(device);

return 0;
}

/* NOTE: This assumes you'll hold the mixer lock before calling! */
static Uint32
dequeue_audio_from_device(SDL_AudioDevice *device, Uint8 *stream, Uint32 len)
{
SDL_AudioBufferQueue *packet;
Uint8 *ptr = stream;

while ((len > 0) && ((packet = device->buffer_queue_head) != NULL)) {
const Uint32 avail = packet->datalen - packet->startpos;
const Uint32 cpy = SDL_min(len, avail);
SDL_assert(device->queued_bytes >= avail);

SDL_memcpy(ptr, packet->data + packet->startpos, cpy);
packet->startpos += cpy;
ptr += cpy;
device->queued_bytes -= cpy;
len -= cpy;

if (packet->startpos == packet->datalen) { /* packet is done, put it in the pool. */
device->buffer_queue_head = packet->next;
SDL_assert((packet->next != NULL) || (packet == device->buffer_queue_tail));
packet->next = device->buffer_queue_pool;
device->buffer_queue_pool = packet;
}
}

SDL_assert((device->buffer_queue_head != NULL) == (device->queued_bytes != 0));

if (device->buffer_queue_head == NULL) {
device->buffer_queue_tail = NULL; /* in case we drained the queue entirely. */
}

return (Uint32) (ptr - stream);
}

static void SDLCALL
SDL_BufferQueueDrainCallback(void *userdata, Uint8 *stream, int len)
{
/* this function always holds the mixer lock before being called. */
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;
Uint32 written;

SDL_assert(device != NULL); /* this shouldn't ever happen, right?! */
SDL_assert(!device->iscapture); /* this shouldn't ever happen, right?! */
SDL_assert(len >= 0); /* this shouldn't ever happen, right?! */

written = dequeue_audio_from_device(device, stream, (Uint32) len);
stream += written;
len -= (int) written;

if (len > 0) { /* fill any remaining space in the stream with silence. */
SDL_assert(device->buffer_queue_head == NULL);
SDL_memset(stream, device->spec.silence, len);
}
}

static void SDLCALL
SDL_BufferQueueFillCallback(void *userdata, Uint8 *stream, int len)
{
/* this function always holds the mixer lock before being called. */
SDL_AudioDevice *device = (SDL_AudioDevice *) userdata;

SDL_assert(device != NULL); /* this shouldn't ever happen, right?! */
SDL_assert(device->iscapture); /* this shouldn't ever happen, right?! */
SDL_assert(len >= 0); /* this shouldn't ever happen, right?! */

/* note that if this needs to allocate more space and run out of memory,
we have no choice but to quietly drop the data and hope it works out
later, but you probably have bigger problems in this case anyhow. */
queue_audio_to_device(device, stream, (Uint32) len);
}

int
SDL_QueueAudio(SDL_AudioDeviceID devid, const void *data, Uint32 len)
{
SDL_AudioDevice *device = get_audio_device(devid);
int rc = 0;

if (!device) {
return -1; /* get_audio_device() will have set the error state */
} else if (device->iscapture) {
return SDL_SetError("This is a capture device, queueing not allowed");
} else if (device->spec.callback != SDL_BufferQueueDrainCallback) {
return SDL_SetError("Audio device has a callback, queueing not allowed");
}

if (len > 0) {
current_audio.impl.LockDevice(device);
rc = queue_audio_to_device(device, data, len);
current_audio.impl.UnlockDevice(device);
}

return rc;
}

Uint32
SDL_DequeueAudio(SDL_AudioDeviceID devid, void *data, Uint32 len)
{
SDL_AudioDevice *device = get_audio_device(devid);
Uint32 rc;

if ( (len == 0) || /* nothing to do? */
(!device) || /* called with bogus device id */
(!device->iscapture) || /* playback devices can't dequeue */
(device->spec.callback != SDL_BufferQueueFillCallback) ) { /* not set for queueing */
return 0; /* just report zero bytes dequeued. */
}

current_audio.impl.LockDevice(device);
rc = dequeue_audio_from_device(device, data, len);
current_audio.impl.UnlockDevice(device);
return rc;
}

Uint32
SDL_GetQueuedAudioSize(SDL_AudioDeviceID devid)
{
Uint32 retval = 0;
SDL_AudioDevice *device = get_audio_device(devid);

if (!device) {
return 0;
}

/* Nothing to do unless we're set up for queueing. */
if (device && (device->spec.callback == SDL_BufferQueueDrainCallback)) {
if (device->spec.callback == SDL_BufferQueueDrainCallback) {
current_audio.impl.LockDevice(device);
retval = device->queued_bytes + current_audio.impl.GetPendingBytes(device);
current_audio.impl.UnlockDevice(device);
} else if (device->spec.callback == SDL_BufferQueueFillCallback) {
current_audio.impl.LockDevice(device);
retval = device->queued_bytes;
current_audio.impl.UnlockDevice(device);
}

return retval;
Expand Down Expand Up @@ -1305,7 +1370,7 @@ open_audio_device(const char *devname, int iscapture,
}
}

device->spec.callback = SDL_BufferQueueDrainCallback;
device->spec.callback = iscapture ? SDL_BufferQueueFillCallback : SDL_BufferQueueDrainCallback;
device->spec.userdata = device;
}

Expand All @@ -1319,7 +1384,9 @@ open_audio_device(const char *devname, int iscapture,
/* !!! FIXME: we don't force the audio thread stack size here because it calls into user code, but maybe we should? */
/* buffer queueing callback only needs a few bytes, so make the stack tiny. */
char name[64];
const SDL_bool is_internal_thread = (device->spec.callback == SDL_BufferQueueDrainCallback);
const SDL_bool is_internal_thread =
(device->spec.callback == SDL_BufferQueueDrainCallback) ||
(device->spec.callback == SDL_BufferQueueFillCallback);
const size_t stacksize = is_internal_thread ? 64 * 1024 : 0;

SDL_snprintf(name, sizeof (name), "SDLAudioDev%d", (int) device->id);
Expand Down

0 comments on commit 7315390

Please sign in to comment.