mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-09 03:29:43 -10:00
microphone: the outgoing sample queue is a retro_spsc ring; the flush runs unlocked
The queue from the capture worker to the core's retro_microphone_read() was a fifo_buffer_t under fifo_lock, and the worker held that lock across the whole of microphone_driver_flush() - the device read, the dual-mono up-channel, the resampler and the write - so the core's read, which needs the same lock to take its frames, queued behind a slice's resampling every time the two met. On the frame-synchronous path (no worker) the same thread flushes and reads, so the lock was never the issue there. One producer, one consumer, either way: replace it with a retro_spsc_t. The worker's flush runs without the lock and its room check is a producer-side query; the core's read takes what is there without the lock. fifo_lock and fifo_cond stay for the two bounded waits and the signals, as they were. retro_spsc rounds capacity up to a power of two; outgoing_size keeps the size asked for, the flush's room is measured against it, and the "asked for more than the queue holds" check compares to it. audio_driver.o compiles through the real build rule; the eight audio samples that #include audio_driver.c build and pass under ASan+UBSan. There is no microphone harness and no capture device here, so the mic path itself is not run.
This commit is contained in:
1 parent
f79caf9168
commit
afacc8c8ee
2 files changed
+56
-34
No files matched your search
+43
-30
@@ -6352,10 +6352,10 @@ static void mic_driver_microphone_handle_free(retro_microphone_t *microphone, bo
|
||||
microphone->microphone_context = NULL;
|
||||
}
|
||||
|
||||
if (microphone->outgoing_samples)
|
||||
if (microphone->outgoing_init)
|
||||
{
|
||||
fifo_free(microphone->outgoing_samples);
|
||||
microphone->outgoing_samples = NULL;
|
||||
retro_spsc_free(µphone->outgoing_samples);
|
||||
microphone->outgoing_init = false;
|
||||
}
|
||||
|
||||
if (microphone->resampler && microphone->resampler->free && microphone->resampler_data)
|
||||
@@ -6537,8 +6537,10 @@ static bool mic_driver_open_mic_internal(retro_microphone_t* microphone)
|
||||
if (!mic_driver_allocate_frames(mic_st))
|
||||
goto error;
|
||||
|
||||
microphone->outgoing_samples = fifo_new(max_samples * sizeof(int16_t));
|
||||
if (!microphone->outgoing_samples)
|
||||
microphone->outgoing_size = max_samples * sizeof(int16_t);
|
||||
microphone->outgoing_init = retro_spsc_init(µphone->outgoing_samples,
|
||||
microphone->outgoing_size);
|
||||
if (!microphone->outgoing_init)
|
||||
goto error;
|
||||
|
||||
microphone->microphone_context = mic_driver->open_mic(driver_context,
|
||||
@@ -6783,6 +6785,15 @@ bool microphone_driver_get_mic_state(const retro_microphone_t *microphone)
|
||||
* @param[out] frames The buffer in which the core will receive microphone samples.
|
||||
* @param num_frames The size of \c frames, in samples.
|
||||
*/
|
||||
/* Room in the outgoing ring, in bytes, against the size asked for:
|
||||
* retro_spsc rounds capacity up to a power of two. */
|
||||
static size_t microphone_outgoing_room(const retro_microphone_t *microphone)
|
||||
{
|
||||
size_t room = retro_spsc_write_avail(µphone->outgoing_samples);
|
||||
size_t excess = microphone->outgoing_samples.capacity - microphone->outgoing_size;
|
||||
return room > excess ? room - excess : 0;
|
||||
}
|
||||
|
||||
static size_t microphone_driver_flush(
|
||||
microphone_driver_state_t *mic_st,
|
||||
retro_microphone_t *microphone,
|
||||
@@ -6840,16 +6851,16 @@ static size_t microphone_driver_flush(
|
||||
{ /* If the mic's native rate is practically the same as the requested one... */
|
||||
|
||||
/* ...then skip the resampler, since it'll produce (more or less) identical results. */
|
||||
frames_to_enqueue = MIN(FIFO_WRITE_AVAIL(microphone->outgoing_samples) / sizeof(int16_t), resampler_data.input_frames);
|
||||
frames_to_enqueue = MIN(microphone_outgoing_room(microphone) / sizeof(int16_t), resampler_data.input_frames);
|
||||
|
||||
/* If this mic provides floating-point samples... */
|
||||
if (sample_size == sizeof(float))
|
||||
{
|
||||
convert_float_to_s16(mic_st->final_frames, (const float*)mic_st->input_frames, resampler_data.input_frames);
|
||||
fifo_write(microphone->outgoing_samples, mic_st->final_frames, frames_to_enqueue * sizeof(int16_t));
|
||||
retro_spsc_write(µphone->outgoing_samples, mic_st->final_frames, frames_to_enqueue * sizeof(int16_t));
|
||||
}
|
||||
else
|
||||
fifo_write(microphone->outgoing_samples, mic_st->input_frames, frames_to_enqueue * sizeof(int16_t));
|
||||
retro_spsc_write(µphone->outgoing_samples, mic_st->input_frames, frames_to_enqueue * sizeof(int16_t));
|
||||
|
||||
return frames_to_enqueue;
|
||||
}
|
||||
@@ -6896,9 +6907,9 @@ static size_t microphone_driver_flush(
|
||||
mic_st->final_frames[n] = mic_st->resampled_frames_int16[n << 1];
|
||||
|
||||
frames_to_enqueue = MIN(
|
||||
FIFO_WRITE_AVAIL(microphone->outgoing_samples) / sizeof(int16_t),
|
||||
microphone_outgoing_room(microphone) / sizeof(int16_t),
|
||||
s16.output_frames);
|
||||
fifo_write(microphone->outgoing_samples, mic_st->final_frames,
|
||||
retro_spsc_write(µphone->outgoing_samples, mic_st->final_frames,
|
||||
frames_to_enqueue * sizeof(int16_t));
|
||||
return frames_to_enqueue;
|
||||
}
|
||||
@@ -6935,8 +6946,8 @@ static size_t microphone_driver_flush(
|
||||
/* Finally, we convert the audio back to 16-bit ints, as the mic interface requires. */
|
||||
convert_float_to_s16(mic_st->final_frames, mic_st->resampled_mono_frames, resampler_data.output_frames);
|
||||
|
||||
frames_to_enqueue = MIN(FIFO_WRITE_AVAIL(microphone->outgoing_samples) / sizeof(int16_t), resampler_data.output_frames);
|
||||
fifo_write(microphone->outgoing_samples, mic_st->final_frames, frames_to_enqueue * sizeof(int16_t));
|
||||
frames_to_enqueue = MIN(microphone_outgoing_room(microphone) / sizeof(int16_t), resampler_data.output_frames);
|
||||
retro_spsc_write(µphone->outgoing_samples, mic_st->final_frames, frames_to_enqueue * sizeof(int16_t));
|
||||
return frames_to_enqueue;
|
||||
}
|
||||
|
||||
@@ -6965,15 +6976,13 @@ static void microphone_driver_capture_thread(void *data)
|
||||
|
||||
if ( !mic_st->driver
|
||||
|| !mic_st->driver->wait_readable
|
||||
|| !microphone->outgoing_samples)
|
||||
|| !microphone->outgoing_init)
|
||||
break;
|
||||
|
||||
/* Do not read more than the fifo can take, or the flush would
|
||||
/* Do not read more than the ring can take, or the flush would
|
||||
* discard what it could not enqueue and the device would run
|
||||
* ahead of the core. */
|
||||
slock_lock(microphone->fifo_lock);
|
||||
room = FIFO_WRITE_AVAIL(microphone->outgoing_samples);
|
||||
slock_unlock(microphone->fifo_lock);
|
||||
* ahead of the core. Producer-side query, no lock needed. */
|
||||
room = microphone_outgoing_room(microphone);
|
||||
|
||||
sample_size = microphone->worker_sample_size;
|
||||
if (room < slice * sizeof(int16_t))
|
||||
@@ -6990,8 +6999,11 @@ static void microphone_driver_capture_thread(void *data)
|
||||
microphone->microphone_context, slice * sample_size))
|
||||
continue;
|
||||
|
||||
slock_lock(microphone->fifo_lock);
|
||||
/* The flush - device read, up-channel, resample, ring write -
|
||||
* runs without the lock: SPSC, this thread is the producer. The
|
||||
* core's read no longer waits behind the resampler. */
|
||||
microphone_driver_flush(mic_st, microphone, slice);
|
||||
slock_lock(microphone->fifo_lock);
|
||||
scond_signal(microphone->fifo_cond);
|
||||
slock_unlock(microphone->fifo_lock);
|
||||
}
|
||||
@@ -7066,8 +7078,8 @@ int microphone_driver_read(retro_microphone_t *microphone, int16_t* frames, size
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If the core asked for more frames than the FIFO can hold... */
|
||||
if (num_frames * sizeof(int16_t) > microphone->outgoing_samples->size - 1)
|
||||
/* If the core asked for more frames than the ring can hold... */
|
||||
if (num_frames * sizeof(int16_t) > microphone->outgoing_size)
|
||||
return -1;
|
||||
|
||||
retro_assert(mic_st->input_frames != NULL);
|
||||
@@ -7083,15 +7095,16 @@ int microphone_driver_read(retro_microphone_t *microphone, int16_t* frames, size
|
||||
size_t want = num_frames * sizeof(int16_t);
|
||||
size_t got;
|
||||
|
||||
slock_lock(microphone->fifo_lock);
|
||||
if (FIFO_READ_AVAIL(microphone->outgoing_samples) < want)
|
||||
if (retro_spsc_read_avail(µphone->outgoing_samples) < want)
|
||||
{
|
||||
slock_lock(microphone->fifo_lock);
|
||||
scond_wait_timeout(microphone->fifo_cond, microphone->fifo_lock,
|
||||
10000);
|
||||
got = FIFO_READ_AVAIL(microphone->outgoing_samples);
|
||||
if (got > want)
|
||||
got = want;
|
||||
if (got)
|
||||
fifo_read(microphone->outgoing_samples, frames, got);
|
||||
slock_unlock(microphone->fifo_lock);
|
||||
}
|
||||
/* Consumer side, no lock: SPSC. */
|
||||
got = retro_spsc_read(µphone->outgoing_samples, frames, want);
|
||||
slock_lock(microphone->fifo_lock);
|
||||
scond_signal(microphone->fifo_cond);
|
||||
slock_unlock(microphone->fifo_lock);
|
||||
|
||||
@@ -7103,7 +7116,7 @@ int microphone_driver_read(retro_microphone_t *microphone, int16_t* frames, size
|
||||
|
||||
{
|
||||
unsigned stall_count = 0;
|
||||
while (FIFO_READ_AVAIL(microphone->outgoing_samples) < num_frames * sizeof(int16_t))
|
||||
while (retro_spsc_read_avail(µphone->outgoing_samples) < num_frames * sizeof(int16_t))
|
||||
{ /* Until we can give the core the frames it asked for... */
|
||||
size_t frames_to_read = MIN(AUDIO_CHUNK_SIZE_NONBLOCKING, frames_remaining);
|
||||
size_t frames_read = microphone_driver_flush(
|
||||
@@ -7122,7 +7135,7 @@ int microphone_driver_read(retro_microphone_t *microphone, int16_t* frames, size
|
||||
} /* If the queue already has enough samples to give, the loop will be skipped */
|
||||
}
|
||||
|
||||
fifo_read(microphone->outgoing_samples, frames, num_frames * sizeof(int16_t));
|
||||
retro_spsc_read(µphone->outgoing_samples, frames, num_frames * sizeof(int16_t));
|
||||
return (int)num_frames;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <libretro.h>
|
||||
#include <audio/audio_resampler.h>
|
||||
#include <audio/sinc_resampler_int16.h>
|
||||
#include <queues/fifo_queue.h>
|
||||
#include <retro_spsc.h>
|
||||
|
||||
/**
|
||||
* Flags that indicate the current state of the microphone driver.
|
||||
@@ -148,9 +148,18 @@ struct retro_microphone
|
||||
int flags;
|
||||
|
||||
/**
|
||||
* Samples that will be sent to the core.
|
||||
* Samples that will be sent to the core. One producer (the capture
|
||||
* worker, or the core's own read on the frame-synchronous path) and
|
||||
* one consumer (retro_microphone_read), so a lock-free retro_spsc
|
||||
* ring; with the worker, fifo_lock covers only the waits, no longer
|
||||
* the flush - the read, up-channel and resample of a slice - which
|
||||
* the core's read used to queue behind. retro_spsc rounds capacity
|
||||
* up to a power of two; outgoing_size is the size asked for and the
|
||||
* producer never fills past it.
|
||||
*/
|
||||
fifo_buffer_t *outgoing_samples;
|
||||
retro_spsc_t outgoing_samples;
|
||||
size_t outgoing_size;
|
||||
bool outgoing_init;
|
||||
|
||||
/**
|
||||
* The requested microphone parameters,
|
||||
@@ -206,7 +215,7 @@ struct retro_microphone
|
||||
/* Threaded capture. When the driver offers wait_readable() and the
|
||||
* Threaded Pipeline setting is on, a worker owns the blocking read,
|
||||
* the dual-mono up-channel and the resampler, and the core's
|
||||
* retro_microphone_read() becomes a fifo read that never touches the
|
||||
* retro_microphone_read() becomes a ring read that never touches the
|
||||
* device. Without it, all three happen inside the core's call, on the
|
||||
* frame - which is what the threaded pipeline removed from the
|
||||
* playback path for the same reasons.
|
||||
|
||||
Reference in new issue
Block a user