Skip to content

Commit

Permalink
Never lock in audio player
Browse files Browse the repository at this point in the history
PR #4752 removed the need for locks except for corner cases. Now replace
the remaining lock sections by atomics.

Refs #4572 <#4572>
  • Loading branch information
rom1v committed May 27, 2024
1 parent 09e8c20 commit a5a3b51
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 48 deletions.
70 changes: 25 additions & 45 deletions app/src/audio_player.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ static void SDLCALL
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
struct sc_audio_player *ap = userdata;

// This callback is called with the lock used by SDL_LockAudioDevice()

assert(len_int > 0);
size_t len = len_int;
uint32_t count = TO_SAMPLES(len);
Expand Down Expand Up @@ -181,29 +179,19 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
if (written < samples) {
uint32_t remaining = samples - written;

// All samples that could be written without locking have been written,
// now we need to lock to drop/consume old samples
SDL_LockAudioDevice(ap->device);

// Retry with the lock
written += sc_audiobuf_write(&ap->buf,
swr_buf + TO_BYTES(written),
remaining);
if (written < samples) {
remaining = samples - written;
// Still insufficient, drop old samples to make space
skipped_samples = sc_audiobuf_read(&ap->buf, NULL, remaining);
assert(skipped_samples == remaining);

// Now there is enough space
uint32_t w = sc_audiobuf_write(&ap->buf,
swr_buf + TO_BYTES(written),
remaining);
assert(w == remaining);
(void) w;
}
assert(remaining <= cap);
skipped_samples = sc_audiobuf_truncate(&ap->buf, cap - remaining);

LOGW("Audio buffer full, %" PRIu32 " samples dropped", skipped_samples);

SDL_UnlockAudioDevice(ap->device);
// Now there is enough space
uint32_t w = sc_audiobuf_write(&ap->buf,
swr_buf + TO_BYTES(written),
remaining);
assert(w == remaining);
(void) w;

written = samples;
}

uint32_t underflow = 0;
Expand All @@ -225,30 +213,22 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,

uint32_t can_read = sc_audiobuf_can_read(&ap->buf);
if (can_read > max_buffered_samples) {
uint32_t skip_samples = 0;

SDL_LockAudioDevice(ap->device);
can_read = sc_audiobuf_can_read(&ap->buf);
if (can_read > max_buffered_samples) {
skip_samples = can_read - max_buffered_samples;
uint32_t r = sc_audiobuf_read(&ap->buf, NULL, skip_samples);
assert(r == skip_samples);
(void) r;
skipped_samples += skip_samples;
}
SDL_UnlockAudioDevice(ap->device);
uint32_t skipped = sc_audiobuf_truncate(&ap->buf, max_buffered_samples);
assert(skipped);

if (skip_samples) {
if (played) {
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
" samples", skip_samples);
if (played) {
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
" samples", skipped);
#ifndef SC_AUDIO_PLAYER_NDEBUG
} else {
LOGD("[Audio] Playback not started, skipping %" PRIu32
" samples", skip_samples);
} else {
LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples",
skipped);
#endif
}
}

skipped_samples += skipped;

can_read = sc_audiobuf_can_read(&ap->buf);
}

atomic_store_explicit(&ap->received, true, memory_order_relaxed);
Expand Down Expand Up @@ -408,7 +388,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
// Use a ring-buffer of the target buffering size plus 1 second between the
// producer and the consumer. It's too big on purpose, to guarantee that
// the producer and the consumer will be able to access it in parallel
// without locking.
// without dropping samples.
uint32_t audiobuf_samples = ap->target_buffering + ap->sample_rate;

size_t sample_size = ap->nb_channels * ap->out_bytes_per_sample;
Expand Down
29 changes: 26 additions & 3 deletions app/src/util/audiobuf.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {

uint8_t *to = to_;

// Only the reader thread can write tail without synchronization, so
// memory_order_relaxed is sufficient
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_relaxed);
// The tail cursor may be updated by the writer thread to drop samples
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);

// The head cursor is updated after the data is written to the array
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
Expand Down Expand Up @@ -110,3 +109,27 @@ sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,

return samples_count;
}

uint32_t
sc_audiobuf_truncate(struct sc_audiobuf *buf, uint32_t samples_limit) {
for (;;) {
// Only the writer thread can write head, so memory_order_relaxed is
// sufficient
uint32_t head = atomic_load_explicit(&buf->head, memory_order_relaxed);

// The tail cursor is updated after the data is consumed by the reader
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);

uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
if (can_read <= samples_limit) {
// Nothing to truncate
return 0;
}

uint32_t skip = can_read - samples_limit;
uint32_t new_tail = (tail + skip) % buf->alloc_size;
if (atomic_compare_exchange_weak(&buf->tail, &tail, new_tail)) {
return skip;
}
}
}
10 changes: 10 additions & 0 deletions app/src/util/audiobuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ uint32_t
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from,
uint32_t samples_count);

/**
* Drop old samples to keep at most sample_limit samples
*
* Must be called by the writer thread.
*
* \return the number of samples dropped
*/
uint32_t
sc_audiobuf_truncate(struct sc_audiobuf *buf, uint32_t samples_limit);

static inline uint32_t
sc_audiobuf_capacity(struct sc_audiobuf *buf) {
assert(buf->alloc_size);
Expand Down

0 comments on commit a5a3b51

Please sign in to comment.