console audio: every wait for the device is bounded

Each of these drivers waited for its output thread, callback or
hardware position with no bound, and one that stopped - the port
closed, the renderer gone, the system back from sleep with the channel
dropped - held the calling thread there. Each wait is now timed where
the platform offers a timed form of the same primitive, and capped at
a fixed number of them; a blocking write then returns what went and
wait_writable() hands the pass back as no space coming from this call.

  psp        scond_wait -> scond_wait_timeout, both loops; the write
             loop also stops when the thread is no longer running
  ps3        sysLwCondWait(cond, 0) - 0 is no timeout - -> a timeout in
             microseconds, both loops; the write loop also stops when
             the port is not started or the thread is quitting
  wiiu       the write's OSYieldThread() spin, which a running voice
             whose callback had stopped never left, waits on the frame
             event with OSWaitEventWithTimeout(); wait_writable()'s
             OSWaitEvent() the same
  switch     compat_condvar_wait_timeout() added to the compat layer
             (condvarWaitTimeout on libnx, trn_condvar_wait's own
             timeout on libtransistor); switch_thread and the libnx
             audren thread driver use it in both loops, the latter
             also returning from a write while paused, when its render
             thread takes nothing
  ctr dsp    the write's 100 ms poll of the channel position is capped;
             the quit path returned true from a function returning
             ssize_t, and returns -1
  ctr csnd   the write's 1 ms poll of the play position is capped
  xenon360   the write's udelay(50) spin on the unplayed count is capped

Not changed, for want of a timed primitive: gx's write, a spin with no
sleep on the DMA state, and its wait_writable() on OSSleepThread();
the 3DS DSP driver's wait_writable() on LightEvent_Wait(); and the
non-threaded libnx audren driver's audrenWaitFrame(). Those need hands
on the platform.

None of this could be compiled here: no console SDK. Each change
reuses a primitive the tree already calls on that platform (rthreads
for psp and the Switch and Wii U timed waits), keeps declarations at
block starts, and every file balances; the platform workflows are the
compile check.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-09-02 19:27:28 +02:00
1 parent d6cd992321
commit d32605d3af
9 files changed
+172 -15

No files matched your search

+10
View File
@@ -162,6 +162,10 @@ static void ctr_csnd_audio_free(void *data)
free(ctr);
}
/* How many 1 ms polls a blocking write waits for the play position to
* advance before giving up on it. */
#define CTR_CSND_AUDIO_WAIT_LAPS 2000
static ssize_t ctr_csnd_audio_write(void *data, const void *buf, size_t len)
{
unsigned int i;
@@ -180,11 +184,17 @@ static ssize_t ctr_csnd_audio_write(void *data, const void *buf, size_t len)
ctr->pos = (ctr->playpos + (CTR_CSND_AUDIO_COUNT >> 1)) & CTR_CSND_AUDIO_COUNT_MASK;
else
{
/* Poll the play position while CSND plays this out, capped: a
* position that has stopped advancing never satisfies the test
* below, and the write then returns having written nothing. */
int laps = CTR_CSND_AUDIO_WAIT_LAPS;
do
{
/* todo: compute the correct sleep period */
retro_sleep(1);
ctr_csnd_audio_update_playpos(ctr);
if (--laps < 0)
return 0;
} while (((ctr->playpos - ctr->pos) & CTR_CSND_AUDIO_COUNT_MASK) < (CTR_CSND_AUDIO_COUNT >> 1)
|| (((ctr->pos - ctr->playpos) & CTR_CSND_AUDIO_COUNT_MASK) < (CTR_CSND_AUDIO_COUNT >> 4)));
}
+13 -1
View File
@@ -110,6 +110,10 @@ static void ctr_dsp_audio_free(void *data)
ndspExit();
}
/* How many 100 ms polls a blocking write waits for the channel to
* advance before giving up on it. */
#define CTR_DSP_AUDIO_WAIT_LAPS 20
static ssize_t ctr_dsp_audio_write(void *data, const void *buf, size_t len)
{
u32 pos;
@@ -124,6 +128,12 @@ static ssize_t ctr_dsp_audio_write(void *data, const void *buf, size_t len)
ctr->pos = (sample_pos + (CTR_DSP_AUDIO_COUNT >> 1)) & CTR_DSP_AUDIO_COUNT_MASK;
else
{
/* Poll the channel position while the DSP plays this out. A
* channel that has stopped advancing - the DSP reset, the
* system back from sleep with the channel dropped - never
* satisfies the test below, so the poll is capped and the
* write then returns having written nothing. */
int laps = CTR_DSP_AUDIO_WAIT_LAPS;
do
{
svcSleepThread(100000);
@@ -133,8 +143,10 @@ static ssize_t ctr_dsp_audio_write(void *data, const void *buf, size_t len)
if (!aptMainLoop())
{
retroarch_main_quit();
return true;
return -1;
}
if (--laps < 0)
return 0;
sample_pos = ndspChnGetSamplePos(ctr->channel);
}while ( ((sample_pos - (ctr->pos + (len >>2))) & CTR_DSP_AUDIO_COUNT_MASK) > (CTR_DSP_AUDIO_COUNT >> 1)
+29 -3
View File
@@ -26,6 +26,12 @@
#define AUDIO_BLOCKS 8
#define AUDIO_CHANNELS 2
/* Bound on any wait for the audio thread to consume, in microseconds
* (sysLwCondWait's unit; 0 there means no timeout at all), and how many
* of them before the caller gets the pass back. */
#define PS3_AUDIO_WAIT_US 100000
#define PS3_AUDIO_WAIT_LAPS 8
typedef struct
{
fifo_buffer_t *buffer;
@@ -152,8 +158,22 @@ static ssize_t ps3_audio_write(void *data, const void *s, size_t len)
return 0;
}
while (FIFO_WRITE_AVAIL(aud->buffer) < len)
sysLwCondWait(&aud->cond, 0);
{
/* The audio thread signals each time it consumes a block. One
* that has stopped - the port closed, the thread quitting -
* signals nothing; the wait is timed and capped, and the write
* then returns having written nothing rather than holding the
* caller. */
int laps = PS3_AUDIO_WAIT_LAPS;
while (FIFO_WRITE_AVAIL(aud->buffer) < len)
{
if (!aud->started || aud->quit_thread)
return 0;
sysLwCondWait(&aud->cond, PS3_AUDIO_WAIT_US);
if (--laps < 0)
return 0;
}
}
sysLwMutexLock(&aud->lock, PS3_SYS_NO_TIMEOUT);
fifo_write(aud->buffer, s, len);
@@ -245,6 +265,7 @@ static size_t ps3_audio_wait_writable(void *data, size_t len)
{
ps3_audio_t *aud = data;
size_t avail;
int laps = PS3_AUDIO_WAIT_LAPS;
if (len > aud->buffer->size / 2)
len = aud->buffer->size / 2;
@@ -256,7 +277,12 @@ static size_t ps3_audio_wait_writable(void *data, size_t len)
avail = ps3_audio_write_avail(aud);
if (avail >= len)
return avail;
sysLwCondWait(&aud->cond, 0);
/* Timed and capped, as in the write: no room after this many
* waits is a thread that has stopped consuming, and the pass is
* handed back as no space coming from this call. */
sysLwCondWait(&aud->cond, PS3_AUDIO_WAIT_US);
if (--laps < 0)
return 0;
}
}
+36 -4
View File
@@ -67,6 +67,12 @@ typedef struct psp_audio
#define AUDIO_BUFFER_SIZE (1u<<13u)
#define AUDIO_BUFFER_SIZE_MASK (AUDIO_BUFFER_SIZE-1)
/* Bound on any wait for the audio thread to consume: one wait, and how
* many of them before the caller gets the pass back. The thread
* consumes a period every period while the device runs. */
#define PSP_AUDIO_WAIT_US 100000
#define PSP_AUDIO_WAIT_LAPS 8
/* Return port used */
static int psp_configure_audio(unsigned rate)
{
@@ -221,9 +227,25 @@ static ssize_t psp_audio_write(void *data, const void *s, size_t len)
}
slock_lock(psp->cond_lock);
while (AUDIO_BUFFER_SIZE - ((uint16_t)
(psp->write_pos - psp->read_pos) & AUDIO_BUFFER_SIZE_MASK) < len)
scond_wait(psp->cond, psp->cond_lock);
{
/* The audio thread signals every period it consumes. One that has
* stopped consuming - the device suspended, the thread gone -
* signals nothing; the wait is bounded and the write then returns
* having written nothing rather than holding the caller. */
int laps = PSP_AUDIO_WAIT_LAPS;
while (AUDIO_BUFFER_SIZE - ((uint16_t)
(psp->write_pos - psp->read_pos) & AUDIO_BUFFER_SIZE_MASK) < len)
{
if ( !scond_wait_timeout(psp->cond, psp->cond_lock,
PSP_AUDIO_WAIT_US)
|| --laps < 0
|| !psp->running)
{
slock_unlock(psp->cond_lock);
return 0;
}
}
}
slock_unlock(psp->cond_lock);
slock_lock(psp->fifo_lock);
@@ -321,6 +343,7 @@ static size_t psp_wait_writable(void *data, size_t len)
{
psp_audio_t* psp = (psp_audio_t*)data;
size_t avail;
int laps = PSP_AUDIO_WAIT_LAPS;
if (len > AUDIO_BUFFER_SIZE / 2)
len = AUDIO_BUFFER_SIZE / 2;
@@ -337,7 +360,16 @@ static size_t psp_wait_writable(void *data, size_t len)
(psp->write_pos - psp->read_pos) & AUDIO_BUFFER_SIZE_MASK);
if (avail >= len)
break;
scond_wait(psp->cond, psp->cond_lock);
/* Bounded per wait and overall: a thread that has stopped
* consuming hands the pass back as no space coming from this
* call. */
if ( !scond_wait_timeout(psp->cond, psp->cond_lock,
PSP_AUDIO_WAIT_US)
|| --laps < 0)
{
slock_unlock(psp->cond_lock);
return 0;
}
}
slock_unlock(psp->cond_lock);
return avail;
+6
View File
@@ -34,6 +34,9 @@ typedef CondVar compat_condvar;
condvarInit(condvar)
#define compat_condvar_wait(condvar, mutex) \
condvarWait(condvar, mutex)
/* Timed wait; ns is the bound in nanoseconds. */
#define compat_condvar_wait_timeout(condvar, mutex, ns) \
condvarWaitTimeout(condvar, mutex, ns)
#define compat_condvar_wake_all(condvar) \
condvarWakeAll(condvar)
@@ -80,6 +83,9 @@ typedef trn_condvar_t compat_condvar;
trn_condvar_create(condvar)
#define compat_condvar_wait(condvar, mutex) \
trn_condvar_wait(condvar, mutex, -1)
/* Timed wait; ns is the bound in nanoseconds. */
#define compat_condvar_wait_timeout(condvar, mutex, ns) \
trn_condvar_wait(condvar, mutex, (ns))
#define compat_condvar_wake_all(condvar) \
trn_condvar_signal(condvar, -1)
@@ -279,6 +279,7 @@ static size_t libnx_audren_thread_audio_wait_writable(void *data, size_t len)
{
size_t available;
libnx_audren_thread_t *aud = (libnx_audren_thread_t*)data;
int laps = LIBNX_AUDREN_WAIT_LAPS;
if (!aud)
return 0;
@@ -294,9 +295,14 @@ static size_t libnx_audren_thread_audio_wait_writable(void *data, size_t len)
mutexUnlock(&aud->fifo_lock);
if (available >= len)
return available;
/* Timed and capped: a renderer that has stopped hands the pass
* back as no space coming from this call. */
mutexLock(&aud->fifo_condlock);
condvarWait(&aud->fifo_condvar, &aud->fifo_condlock);
condvarWaitTimeout(&aud->fifo_condvar, &aud->fifo_condlock,
LIBNX_AUDREN_WAIT_NS);
mutexUnlock(&aud->fifo_condlock);
if (--laps < 0)
return 0;
}
}
@@ -310,6 +316,12 @@ static size_t libnx_audren_thread_audio_buffer_size(void *data)
return aud->buffer_size;
}
/* Bound on a wait for the render thread to take from the fifo: one
* wait, in nanoseconds (condvarWaitTimeout's unit), and how many before
* the caller gets the pass back. */
#define LIBNX_AUDREN_WAIT_NS 100000000ULL
#define LIBNX_AUDREN_WAIT_LAPS 8
static ssize_t libnx_audren_thread_audio_write(void *data,
const void *s, size_t len)
{
@@ -333,6 +345,8 @@ static ssize_t libnx_audren_thread_audio_write(void *data,
}
else
{
int laps = LIBNX_AUDREN_WAIT_LAPS;
_len = 0;
while (_len < len && aud->running)
{
@@ -347,10 +361,19 @@ static ssize_t libnx_audren_thread_audio_write(void *data,
}
else
{
/* The render thread signals each time it takes from the
* fifo. Paused it takes nothing, and a renderer that has
* stopped takes nothing either; the wait is timed and
* capped, and the write returns what went. */
mutexUnlock(&aud->fifo_lock);
if (aud->paused)
break;
mutexLock(&aud->fifo_condlock);
condvarWait(&aud->fifo_condvar, &aud->fifo_condlock);
condvarWaitTimeout(&aud->fifo_condvar, &aud->fifo_condlock,
LIBNX_AUDREN_WAIT_NS);
mutexUnlock(&aud->fifo_condlock);
if (--laps < 0)
break;
}
}
}
+23 -2
View File
@@ -337,6 +337,12 @@ static void switch_thread_audio_free(void *data)
swa = NULL;
}
/* Bound on a wait for the output thread to take from the fifo: one
* wait, in nanoseconds, and how many before the caller gets the pass
* back. */
#define SWITCH_AUDIO_WAIT_NS 100000000ULL
#define SWITCH_AUDIO_WAIT_LAPS 8
static ssize_t switch_thread_audio_write(void *data, const void *s, size_t len)
{
size_t avail, _len;
@@ -356,6 +362,8 @@ static ssize_t switch_thread_audio_write(void *data, const void *s, size_t len)
}
else
{
int laps = SWITCH_AUDIO_WAIT_LAPS;
_len = 0;
while (_len < len && swa->running)
{
@@ -363,11 +371,18 @@ static ssize_t switch_thread_audio_write(void *data, const void *s, size_t len)
avail = FIFO_WRITE_AVAIL(swa->fifo);
if (avail == 0)
{
/* The output thread signals each time it takes from the
* fifo. One that has stopped taking - the output closed,
* the thread stalled - signals nothing; the wait is timed
* and capped, and the write returns what went. */
compat_mutex_unlock(&swa->fifoLock);
compat_mutex_lock(&swa->condLock);
if (swa->running)
compat_condvar_wait(&swa->cond, &swa->condLock);
compat_condvar_wait_timeout(&swa->cond, &swa->condLock,
SWITCH_AUDIO_WAIT_NS);
compat_mutex_unlock(&swa->condLock);
if (--laps < 0)
break;
}
else
{
@@ -414,6 +429,7 @@ static size_t switch_thread_audio_wait_writable(void *data, size_t len)
{
size_t avail;
switch_thread_audio_t *swa = (switch_thread_audio_t *)data;
int laps = SWITCH_AUDIO_WAIT_LAPS;
if (!swa)
return 0;
@@ -429,10 +445,15 @@ static size_t switch_thread_audio_wait_writable(void *data, size_t len)
compat_mutex_unlock(&swa->fifoLock);
if (avail >= len)
return avail;
/* Timed and capped: an output thread that has stopped hands the
* pass back as no space coming from this call. */
compat_mutex_lock(&swa->condLock);
if (swa->running)
compat_condvar_wait(&swa->cond, &swa->condLock);
compat_condvar_wait_timeout(&swa->cond, &swa->condLock,
SWITCH_AUDIO_WAIT_NS);
compat_mutex_unlock(&swa->condLock);
if (--laps < 0)
return 0;
}
}
+21 -3
View File
@@ -48,6 +48,10 @@ typedef struct
#define AX_AUDIO_SAMPLE_MIN (AX_AUDIO_SAMPLE_COUNT * 3) /* 9ms */
#define AX_AUDIO_SAMPLE_LOAD (AX_AUDIO_SAMPLE_COUNT * 10)/* 30ms */
#define AX_AUDIO_MAX_FREE (AX_AUDIO_COUNT - (AX_AUDIO_SAMPLE_COUNT * 2))
/* Bound on a wait for the frame callback to free room: one wait, in
* microseconds, and how many before the caller gets the pass back. */
#define AX_AUDIO_WAIT_US 100000
#define AX_AUDIO_WAIT_LAPS 8
#define AX_AUDIO_RATE 48000
#if 0
@@ -224,11 +228,19 @@ static ssize_t ax_audio_write(void* data, const void* buf, size_t len)
}
else if (count_avail < count)
{
/* Sync, wait for free memory */
/* Sync, wait for free memory. The AX frame callback frees a
* frame's worth and signals frame_event each frame; wait on that
* rather than yield-spin, and bound it - a voice that reports
* running while its callback has stopped freed nothing, and the
* spin never ended. The write then takes what room there is. */
int laps = AX_AUDIO_WAIT_LAPS;
while (AXIsMultiVoiceRunning(ax->mvoice) && (count_avail < count))
{
OSYieldThread(); /* Gives threads with same priority time to run */
OSWaitEventWithTimeout(&ax->frame_event,
(OSTime)OSMicroseconds(AX_AUDIO_WAIT_US));
count_avail = (ax->written > AX_AUDIO_MAX_FREE ? 0 : (AX_AUDIO_MAX_FREE - ax->written));
if (--laps < 0)
break;
}
}
@@ -325,6 +337,7 @@ static size_t ax_audio_wait_writable(void* data, size_t len)
{
ax_audio_t* ax = (ax_audio_t*)data;
size_t avail;
int laps = AX_AUDIO_WAIT_LAPS;
if (len > AX_AUDIO_MAX_FREE / 2)
len = AX_AUDIO_MAX_FREE / 2;
@@ -337,7 +350,12 @@ static size_t ax_audio_wait_writable(void* data, size_t len)
? 0 : (AX_AUDIO_MAX_FREE - ax->written);
if (avail >= len)
return avail;
OSWaitEvent(&ax->frame_event);
/* Timed and capped: a callback that has stopped hands the pass
* back as no space coming from this call. */
OSWaitEventWithTimeout(&ax->frame_event,
(OSTime)OSMicroseconds(AX_AUDIO_WAIT_US));
if (--laps < 0)
return 0;
}
}
+9
View File
@@ -64,6 +64,10 @@ static INLINE uint32_t xenon360_bswap_32(uint32_t val)
((val >> 8) & 0xff00) | ((val << 8) & 0xff0000);
}
/* How many 50 us delays a blocking write waits for the queue to drain
* before giving up on it (about a second). */
#define XENON360_AUDIO_WAIT_LAPS 20000
static ssize_t xenon360_audio_write(void *data, const void *s, size_t len)
{
size_t _len = 0, i;
@@ -83,11 +87,16 @@ static ssize_t xenon360_audio_write(void *data, const void *s, size_t len)
}
else
{
/* Capped: a sound queue that stops draining never drops below the
* mark, and the write then returns having written nothing. */
int laps = XENON360_AUDIO_WAIT_LAPS;
while (xenon_sound_get_unplayed() >= MAX_BUFFER)
{
/* libxenon doesn't have proper
* synchronization primitives for this... */
udelay(50);
if (--laps < 0)
return 0;
}
xenon_sound_submit(xa->buffer, len);