mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 02:08:22 -10:00
sdl_audio: move the speaker and microphone queues onto retro_spsc
Both queues in this driver are single-producer/single-consumer: the core thread writes samples in sdl_audio_write() and SDL's playback thread drains them in sdl_audio_playback_cb(); SDL's capture thread pushes in sdl_audio_record_cb() and the core thread pulls in sdl_microphone_read(). They were fifo_buffer_t rings guarded by SDL_LockAudioDevice(), which means every write from the core stalled the device callback (and vice versa), the same shape dsound and asio had before they moved to retro_spsc. Replace both with retro_spsc_t and drop the SDL_LockAudioDevice() around every queue access. The condition variables and their bounded waits are unchanged: the writer still sleeps on sdl->cond when the ring is full and the callback still signals after each pull. retro_spsc rounds capacity up to a power of two, so the requested size is kept alongside the ring (speaker_ring_size / ring_size) and sdl_ring_room() reports room against that, not the physical capacity. sdl_audio_buffer_size() returns the requested size, and the producer never fills past it, so rate control sees exactly the occupancy it saw with the fifo. The pre-fill of one buffer of silence at init is kept. SDL1 (HAVE_SDL) builds are unchanged in behaviour: the SDL1 shim macros are kept as they were and the non-SDL2 speaker path uses the same ring. Compile-checked: SDL2 with and without HAVE_THREADS/HAVE_MICROPHONE, SDL1 with and without HAVE_THREADS. Not run against a device here.
This commit is contained in:
1 parent
df531fefb2
commit
da4e4cabab
1 file changed
+78
-96
+78
-96
@@ -22,7 +22,7 @@
|
||||
|
||||
#include <boolean.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <queues/fifo_queue.h>
|
||||
#include <retro_spsc.h>
|
||||
#include <retro_inline.h>
|
||||
#include <retro_math.h>
|
||||
#include <lists/string_list.h>
|
||||
@@ -58,6 +58,16 @@ static INLINE int sdl_audio_find_num_frames(int rate, int latency)
|
||||
return pow2;
|
||||
}
|
||||
|
||||
/* Room in a ring against its logical size: the physical room less the
|
||||
* capacity that lies beyond the size asked for (retro_spsc rounds up
|
||||
* to a power of two). */
|
||||
static size_t sdl_ring_room(const retro_spsc_t *ring, size_t ring_size)
|
||||
{
|
||||
size_t room = retro_spsc_write_avail(ring);
|
||||
size_t excess = ring->capacity - ring_size;
|
||||
return room > excess ? room - excess : 0;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
#ifdef HAVE_MICROPHONE
|
||||
#include "../microphone_driver.h"
|
||||
@@ -71,8 +81,17 @@ typedef struct sdl_microphone_handle
|
||||
|
||||
/**
|
||||
* The queue used to store incoming samples from the driver.
|
||||
* Single producer (SDL's capture thread in sdl_audio_record_cb),
|
||||
* single consumer (the core thread in sdl_microphone_read), so it
|
||||
* is a lock-free retro_spsc ring and neither side needs
|
||||
* SDL_LockAudioDevice() to touch it. retro_spsc rounds the
|
||||
* capacity up to a power of two; ring_size is the size asked for
|
||||
* and the producer never fills past it, so the queue behaves like
|
||||
* the old fifo of the same size.
|
||||
*/
|
||||
fifo_buffer_t *sample_buffer;
|
||||
retro_spsc_t ring;
|
||||
size_t ring_size;
|
||||
bool ring_init;
|
||||
SDL_AudioDeviceID device_id;
|
||||
SDL_AudioSpec device_spec;
|
||||
} sdl_microphone_handle_t;
|
||||
@@ -112,7 +131,8 @@ static void sdl_microphone_close_mic(void *driver_context, void *mic_context)
|
||||
if (mic->device_id > 0)
|
||||
SDL_CloseAudioDevice(mic->device_id);
|
||||
|
||||
fifo_free(mic->sample_buffer);
|
||||
if (mic->ring_init)
|
||||
retro_spsc_free(&mic->ring);
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
slock_free(mic->lock);
|
||||
@@ -137,10 +157,10 @@ static void sdl_microphone_free(void *data)
|
||||
static void sdl_audio_record_cb(void *data, Uint8 *stream, int len)
|
||||
{
|
||||
sdl_microphone_handle_t *mic = (sdl_microphone_handle_t*)data;
|
||||
size_t avail = FIFO_WRITE_AVAIL(mic->sample_buffer);
|
||||
size_t avail = sdl_ring_room(&mic->ring, mic->ring_size);
|
||||
size_t read_size = MIN(len, (int)avail);
|
||||
/* If the sample buffer is almost full, just write as much as we can into it*/
|
||||
fifo_write(mic->sample_buffer, stream, read_size);
|
||||
retro_spsc_write(&mic->ring, stream, read_size);
|
||||
#ifdef HAVE_THREADS
|
||||
scond_signal(mic->cond);
|
||||
#endif
|
||||
@@ -259,21 +279,17 @@ static void *sdl_microphone_open_mic(void *driver_context, const char *device,
|
||||
/* Create a buffer twice as big as needed and prefill the buffer. */
|
||||
bufsize = mic->device_spec.samples * 2 * (SDL_AUDIO_BITSIZE(mic->device_spec.format) / 8);
|
||||
tmp = calloc(1, bufsize);
|
||||
mic->sample_buffer = fifo_new(bufsize);
|
||||
mic->ring_size = bufsize;
|
||||
mic->ring_init = retro_spsc_init(&mic->ring, bufsize);
|
||||
|
||||
RARCH_DBG("[SDL audio] Initialized microphone sample queue with %u bytes.\n", bufsize);
|
||||
|
||||
/* NULL-check fifo_new: hot-path audio callbacks
|
||||
* (sdl_microphone_read_cb, sdl_microphone_read) index into
|
||||
* mic->sample_buffer unconditionally via FIFO_READ_AVAIL and
|
||||
* fifo_read/fifo_write. fifo_* NULL-deref on a NULL buffer
|
||||
* pointer (see libretro-common/queues/fifo_queue.c). Bail to
|
||||
* the existing 'error' label on OOM - it frees 'mic' and
|
||||
* returns NULL, same as the outer calloc-failure path at the
|
||||
* top of this function. 'tmp' is freed by the 'if (tmp)'
|
||||
* block below, not here (calloc may have succeeded even if
|
||||
* fifo_new failed, so defer tmp cleanup to after the guard). */
|
||||
if (!mic->sample_buffer)
|
||||
/* Bail to the existing 'error' label on OOM - it frees 'mic' and
|
||||
* returns NULL, same as the outer calloc-failure path at the top
|
||||
* of this function. 'tmp' is freed by the 'if (tmp)' block below,
|
||||
* not here (calloc may have succeeded even if the ring init
|
||||
* failed, so defer tmp cleanup to after the guard). */
|
||||
if (!mic->ring_init)
|
||||
{
|
||||
free(tmp);
|
||||
goto error;
|
||||
@@ -281,7 +297,7 @@ static void *sdl_microphone_open_mic(void *driver_context, const char *device,
|
||||
|
||||
if (tmp)
|
||||
{
|
||||
fifo_write(mic->sample_buffer, tmp, bufsize);
|
||||
retro_spsc_write(&mic->ring, tmp, bufsize);
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
@@ -290,7 +306,7 @@ static void *sdl_microphone_open_mic(void *driver_context, const char *device,
|
||||
|
||||
error:
|
||||
/* HAVE_THREADS may have init'd mic->lock and mic->cond between
|
||||
* the SDL_OpenAudioDevice call and the fifo_new below.
|
||||
* the SDL_OpenAudioDevice call and the retro_spsc_init below.
|
||||
* slock_free/scond_free are NULL-tolerant so the earlier goto
|
||||
* error site (device_id == 0) where these are still NULL is
|
||||
* fine too. 'mic' is always non-NULL at this label because
|
||||
@@ -378,12 +394,10 @@ static size_t sdl_microphone_wait_readable(void *driver_context,
|
||||
sdl_microphone_handle_t *mic = (sdl_microphone_handle_t*)mic_context;
|
||||
size_t avail;
|
||||
|
||||
if (!mic || !mic->sample_buffer)
|
||||
if (!mic || !mic->ring_init)
|
||||
return 0;
|
||||
|
||||
SDL_LockAudioDevice(mic->device_id);
|
||||
avail = FIFO_READ_AVAIL(mic->sample_buffer);
|
||||
SDL_UnlockAudioDevice(mic->device_id);
|
||||
avail = retro_spsc_read_avail(&mic->ring);
|
||||
|
||||
if (avail >= len)
|
||||
return avail;
|
||||
@@ -394,10 +408,7 @@ static size_t sdl_microphone_wait_readable(void *driver_context,
|
||||
slock_unlock(mic->lock);
|
||||
#endif
|
||||
|
||||
SDL_LockAudioDevice(mic->device_id);
|
||||
avail = FIFO_READ_AVAIL(mic->sample_buffer);
|
||||
SDL_UnlockAudioDevice(mic->device_id);
|
||||
return avail;
|
||||
return retro_spsc_read_avail(&mic->ring);
|
||||
}
|
||||
|
||||
static int sdl_microphone_read(void *driver_context, void *mic_context, void *sv, size_t len)
|
||||
@@ -413,17 +424,9 @@ static int sdl_microphone_read(void *driver_context, void *mic_context, void *sv
|
||||
/* If we shouldn't block on an empty queue... */
|
||||
if (sdl->nonblock)
|
||||
{
|
||||
size_t avail, read_amt;
|
||||
SDL_LockAudioDevice(mic->device_id); /* Stop the SDL mic thread */
|
||||
avail = FIFO_READ_AVAIL(mic->sample_buffer);
|
||||
read_amt = avail > len ? len : avail;
|
||||
/* If the incoming queue isn't empty, then
|
||||
* read as much data as will fit in buf
|
||||
* */
|
||||
if (read_amt > 0)
|
||||
fifo_read(mic->sample_buffer, s, read_amt);
|
||||
SDL_UnlockAudioDevice(mic->device_id); /* Let the mic thread run again */
|
||||
ret = (int)read_amt;
|
||||
/* Read as much data as will fit in buf; the ring is SPSC so the
|
||||
* SDL capture thread can keep pushing while we pull. */
|
||||
ret = (int)retro_spsc_read(&mic->ring, s, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -437,14 +440,11 @@ static int sdl_microphone_read(void *driver_context, void *mic_context, void *sv
|
||||
bool signalled;
|
||||
#endif
|
||||
|
||||
SDL_LockAudioDevice(mic->device_id);
|
||||
/* Stop the SDL microphone thread from running */
|
||||
avail = FIFO_READ_AVAIL(mic->sample_buffer);
|
||||
avail = retro_spsc_read_avail(&mic->ring);
|
||||
|
||||
if (avail == 0)
|
||||
{ /* If the incoming sample queue is empty... */
|
||||
SDL_UnlockAudioDevice(mic->device_id);
|
||||
/* Let the SDL microphone thread run so it can
|
||||
/* Wait for the SDL microphone thread to
|
||||
* push some incoming samples */
|
||||
#ifdef HAVE_THREADS
|
||||
slock_lock(mic->lock);
|
||||
@@ -468,11 +468,9 @@ static int sdl_microphone_read(void *driver_context, void *mic_context, void *sv
|
||||
else
|
||||
{
|
||||
size_t read_amt = MIN(len - read, avail);
|
||||
fifo_read(mic->sample_buffer, s + read, read_amt);
|
||||
retro_spsc_read(&mic->ring, s + read, read_amt);
|
||||
/* Read as many samples as we have available without
|
||||
* underflowing the queue */
|
||||
SDL_UnlockAudioDevice(mic->device_id);
|
||||
/* Let the SDL microphone thread run again */
|
||||
read += read_amt;
|
||||
}
|
||||
}
|
||||
@@ -545,7 +543,15 @@ typedef struct sdl_audio
|
||||
* Audio from the core ultimately makes its way here,
|
||||
* the last stop before the driver plays it.
|
||||
*/
|
||||
fifo_buffer_t *speaker_buffer;
|
||||
/* Outgoing samples. Single producer (the core thread in
|
||||
* sdl_audio_write), single consumer (SDL's playback thread in
|
||||
* sdl_audio_playback_cb): a lock-free retro_spsc ring, so the
|
||||
* writer no longer has to SDL_LockAudioDevice() - i.e. stall the
|
||||
* playback callback - to push samples. speaker_ring_size is the
|
||||
* size asked for; see sdl_ring_room. */
|
||||
retro_spsc_t speaker_ring;
|
||||
size_t speaker_ring_size;
|
||||
bool speaker_ring_init;
|
||||
bool nonblock;
|
||||
bool is_paused;
|
||||
SDL_AudioSpec device_spec;
|
||||
@@ -556,9 +562,7 @@ typedef struct sdl_audio
|
||||
static void sdl_audio_playback_cb(void *data, Uint8 *stream, int len)
|
||||
{
|
||||
sdl_audio_t *sdl = (sdl_audio_t*)data;
|
||||
size_t avail = FIFO_READ_AVAIL(sdl->speaker_buffer);
|
||||
size_t _len = (len > (int)avail) ? avail : (size_t)len;
|
||||
fifo_read(sdl->speaker_buffer, stream, _len);
|
||||
size_t _len = retro_spsc_read(&sdl->speaker_ring, stream, (size_t)len);
|
||||
#ifdef HAVE_THREADS
|
||||
scond_signal(sdl->cond);
|
||||
#endif
|
||||
@@ -717,19 +721,12 @@ static void *sdl_audio_init(const char *device,
|
||||
(unsigned)sdl->device_spec.samples,
|
||||
(unsigned)(sdl->device_spec.samples * 1000 / (*new_rate)));
|
||||
|
||||
tmp = calloc(1, bufsize);
|
||||
sdl->speaker_buffer = fifo_new(bufsize);
|
||||
tmp = calloc(1, bufsize);
|
||||
sdl->speaker_ring_size = bufsize;
|
||||
sdl->speaker_ring_init = retro_spsc_init(&sdl->speaker_ring, bufsize);
|
||||
|
||||
/* NULL-check fifo_new: hot-path audio callbacks
|
||||
* (sdl_audio_playback_cb, sdl_audio_write) index into
|
||||
* sdl->speaker_buffer unconditionally via FIFO_WRITE_AVAIL /
|
||||
* FIFO_READ_AVAIL and fifo_read/fifo_write. fifo_* NULL-deref
|
||||
* on a NULL buffer. Bail via sdl_audio_free which already
|
||||
* handles partial init state (speaker_device guard at line
|
||||
* ~716, speaker_buffer guard at line ~721, HAVE_THREADS locks
|
||||
* nullable). 'tmp' is owned here on failure since we haven't
|
||||
* transferred it to the fifo yet. */
|
||||
if (!sdl->speaker_buffer)
|
||||
/* Bail on OOM (see the mic path above). */
|
||||
if (!sdl->speaker_ring_init)
|
||||
{
|
||||
free(tmp);
|
||||
sdl_audio_free(sdl);
|
||||
@@ -738,7 +735,7 @@ static void *sdl_audio_init(const char *device,
|
||||
|
||||
if (tmp)
|
||||
{
|
||||
fifo_write(sdl->speaker_buffer, tmp, bufsize);
|
||||
retro_spsc_write(&sdl->speaker_ring, tmp, bufsize);
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
@@ -757,13 +754,10 @@ static ssize_t sdl_audio_write(void *data, const void *s, size_t len)
|
||||
/* If we shouldn't wait for space in a full outgoing sample queue... */
|
||||
if (sdl->nonblock)
|
||||
{
|
||||
size_t avail, write_amt;
|
||||
SDL_LockAudioDevice(sdl->speaker_device); /* Stop the SDL speaker thread from running */
|
||||
avail = FIFO_WRITE_AVAIL(sdl->speaker_buffer);
|
||||
write_amt = (avail > len) ? len : avail; /* Enqueue as much data as we can */
|
||||
fifo_write(sdl->speaker_buffer, s, write_amt);
|
||||
SDL_UnlockAudioDevice(sdl->speaker_device); /* Let the speaker thread run again */
|
||||
_len = write_amt; /* If the queue was full...well, too bad. */
|
||||
size_t avail = sdl_ring_room(&sdl->speaker_ring, sdl->speaker_ring_size);
|
||||
size_t write_amt = (avail > len) ? len : avail; /* Enqueue as much data as we can */
|
||||
retro_spsc_write(&sdl->speaker_ring, s, write_amt);
|
||||
_len = write_amt; /* If the queue was full...well, too bad. */
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -775,22 +769,17 @@ static ssize_t sdl_audio_write(void *data, const void *s, size_t len)
|
||||
bool signalled;
|
||||
#endif
|
||||
|
||||
/* Stop the SDL speaker thread from running */
|
||||
SDL_LockAudioDevice(sdl->speaker_device);
|
||||
avail = FIFO_WRITE_AVAIL(sdl->speaker_buffer);
|
||||
avail = sdl_ring_room(&sdl->speaker_ring, sdl->speaker_ring_size);
|
||||
|
||||
/* If the outgoing sample queue is full... */
|
||||
if (avail == 0)
|
||||
{
|
||||
SDL_UnlockAudioDevice(sdl->speaker_device);
|
||||
/* Let the SDL speaker thread run so it can play the enqueued samples,
|
||||
/* Wait for the SDL speaker thread to play the enqueued samples,
|
||||
* which will free up space for us to write new ones. */
|
||||
#ifdef HAVE_THREADS
|
||||
slock_lock(sdl->lock);
|
||||
/* Let *only* the SDL speaker thread touch the outgoing sample queue.
|
||||
* Bounded: the wait is on sdl->lock while the queue is guarded by
|
||||
* SDL_LockAudioDevice, and sdl_audio_playback_cb signals without
|
||||
* holding sdl->lock, so a signal raised between the avail test and
|
||||
/* Bounded: sdl_audio_playback_cb signals without holding
|
||||
* sdl->lock, so a signal raised between the avail test and
|
||||
* this wait reaches no waiter. Normally the next callback covers
|
||||
* that. If the device has stopped calling back at all there is no
|
||||
* next one, and an untimed wait here parked the core's thread for
|
||||
@@ -810,9 +799,8 @@ static ssize_t sdl_audio_write(void *data, const void *s, size_t len)
|
||||
else
|
||||
{
|
||||
size_t write_amt = len - _len > avail ? avail : len - _len;
|
||||
fifo_write(sdl->speaker_buffer, (const char*)s + _len, write_amt);
|
||||
retro_spsc_write(&sdl->speaker_ring, (const char*)s + _len, write_amt);
|
||||
/* Enqueue as many samples as we have available without overflowing the queue */
|
||||
SDL_UnlockAudioDevice(sdl->speaker_device); /* Let the SDL speaker thread run again */
|
||||
_len += write_amt;
|
||||
}
|
||||
}
|
||||
@@ -863,8 +851,8 @@ static void sdl_audio_free(void *data)
|
||||
SDL_CloseAudioDevice(sdl->speaker_device);
|
||||
}
|
||||
|
||||
if (sdl->speaker_buffer)
|
||||
fifo_free(sdl->speaker_buffer);
|
||||
if (sdl->speaker_ring_init)
|
||||
retro_spsc_free(&sdl->speaker_ring);
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
slock_free(sdl->lock);
|
||||
@@ -893,20 +881,16 @@ static bool sdl_audio_use_float(void *data)
|
||||
/* TODO/FIXME - implement */
|
||||
static size_t sdl_audio_write_avail(void *data)
|
||||
{
|
||||
size_t avail;
|
||||
sdl_audio_t *sdl = (sdl_audio_t*)data;
|
||||
SDL_LockAudioDevice(sdl->speaker_device);
|
||||
avail = FIFO_WRITE_AVAIL(sdl->speaker_buffer);
|
||||
SDL_UnlockAudioDevice(sdl->speaker_device);
|
||||
return avail;
|
||||
return sdl_ring_room(&sdl->speaker_ring, sdl->speaker_ring_size);
|
||||
}
|
||||
|
||||
static size_t sdl_audio_buffer_size(void *data)
|
||||
{
|
||||
sdl_audio_t *sdl = (sdl_audio_t*)data;
|
||||
/* The fifo's capacity: fifo_new(len) keeps len + 1 slots and holds
|
||||
* len bytes, which is what write_avail() can reach. */
|
||||
return sdl->speaker_buffer->size - 1;
|
||||
/* The size asked for, which is what write_avail() can reach; the
|
||||
* ring's physical capacity may be larger (power of two). */
|
||||
return sdl->speaker_ring_size;
|
||||
}
|
||||
|
||||
/* Sleep on the condition the speaker thread signals after every pull
|
||||
@@ -922,8 +906,8 @@ static size_t sdl_audio_wait_writable(void *data, size_t len)
|
||||
* keeps calling back but never frees enough. */
|
||||
int laps = 8;
|
||||
|
||||
if (len > (sdl->speaker_buffer->size - 1) / 2)
|
||||
len = (sdl->speaker_buffer->size - 1) / 2;
|
||||
if (len > sdl->speaker_ring_size / 2)
|
||||
len = sdl->speaker_ring_size / 2;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
@@ -932,9 +916,7 @@ static size_t sdl_audio_wait_writable(void *data, size_t len)
|
||||
#endif
|
||||
if (laps-- < 0)
|
||||
return 0;
|
||||
SDL_LockAudioDevice(sdl->speaker_device);
|
||||
avail = FIFO_WRITE_AVAIL(sdl->speaker_buffer);
|
||||
SDL_UnlockAudioDevice(sdl->speaker_device);
|
||||
avail = sdl_ring_room(&sdl->speaker_ring, sdl->speaker_ring_size);
|
||||
if (avail >= len)
|
||||
return avail;
|
||||
#ifdef HAVE_THREADS
|
||||
|
||||
Reference in new issue
Block a user