Replace locks by atomics in audio player

The audio output thread only reads samples from the buffer, and most of
the time, the audio receiver thread only writes samples to the buffer.
In these cases, using atomics avoids lock contention.

There are still corner cases where the audio receiver thread needs to
"read" samples (and drop them), so lock only in these cases.
audio_player_atomic.15
Romain Vimont 6 months ago
parent 1ea9e8f693
commit 6a01c39aac

@ -34,8 +34,8 @@ src = [
'src/trait/frame_source.c',
'src/trait/packet_source.c',
'src/util/acksync.c',
'src/util/audiobuf.c',
'src/util/average.c',
'src/util/bytebuf.c',
'src/util/file.c',
'src/util/intmap.c',
'src/util/intr.c',
@ -212,9 +212,10 @@ if get_option('buildtype') == 'debug'
['test_binary', [
'tests/test_binary.c',
]],
['test_bytebuf', [
'tests/test_bytebuf.c',
'src/util/bytebuf.c',
['test_audiobuf', [
'tests/test_audiobuf.c',
'src/util/audiobuf.c',
'src/util/memory.c',
]],
['test_cli', [
'tests/test_cli.c',

@ -66,8 +66,7 @@ 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_AudioDeviceLock(), so
// the audiobuf is protected
// This callback is called with the lock used by SDL_LockAudioDevice()
assert(len_int > 0);
size_t len = len_int;
@ -77,8 +76,9 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
#endif
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
if (!ap->played) {
bool played = atomic_load_explicit(&ap->played, memory_order_relaxed);
if (!played) {
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
// Part of the buffering is handled by inserting initial silence. The
// remaining (margin) last samples will be handled by compensation.
uint32_t margin = 30 * ap->sample_rate / 1000; // 30ms
@ -93,10 +93,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
}
}
uint32_t read = MIN(buffered_samples, count);
if (read) {
sc_audiobuf_read(&ap->buf, stream, read);
}
uint32_t read = sc_audiobuf_read(&ap->buf, stream, count);
if (read < count) {
uint32_t silence = count - read;
@ -109,13 +106,16 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
silence);
memset(stream + TO_BYTES(read), 0, TO_BYTES(silence));
if (ap->received) {
bool received = atomic_load_explicit(&ap->received,
memory_order_relaxed);
if (received) {
// Inserting additional samples immediately increases buffering
ap->underflow += silence;
atomic_fetch_add_explicit(&ap->underflow, silence,
memory_order_relaxed);
}
}
ap->played = true;
atomic_store_explicit(&ap->played, true, memory_order_relaxed);
}
static uint8_t *
@ -162,123 +162,119 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
// swr_convert() returns the number of samples which would have been
// written if the buffer was big enough.
uint32_t samples_written = MIN(ret, dst_nb_samples);
uint32_t samples = MIN(ret, dst_nb_samples);
#ifndef SC_AUDIO_PLAYER_NDEBUG
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples_written);
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples);
#endif
// Since this function is the only writer, the current available space is
// at least the previous available space. In practice, it should almost
// always be possible to write without lock.
bool lockless_write = samples_written <= ap->previous_can_write;
if (lockless_write) {
sc_audiobuf_prepare_write(&ap->buf, swr_buf, samples_written);
uint32_t cap = sc_audiobuf_capacity(&ap->buf);
if (samples > cap) {
// Very very unlikely: a single resampled frame should never
// exceed the audio buffer size (or something is very wrong).
// Ignore the first bytes in swr_buf to avoid memory corruption anyway.
swr_buf += TO_BYTES(samples - cap);
samples = cap;
}
SDL_LockAudioDevice(ap->device);
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
if (lockless_write) {
sc_audiobuf_commit_write(&ap->buf, samples_written);
} else {
uint32_t can_write = sc_audiobuf_can_write(&ap->buf);
if (samples_written > can_write) {
// Entering this branch is very unlikely, the audio buffer is
// allocated with a size sufficient to store 1 second more than the
// target buffering. If this happens, though, we have to skip old
// samples.
uint32_t cap = sc_audiobuf_capacity(&ap->buf);
if (samples_written > cap) {
// Very very unlikely: a single resampled frame should never
// exceed the audio buffer size (or something is very wrong).
// Ignore the first bytes in swr_buf
swr_buf += TO_BYTES(samples_written - cap);
// This change in samples_written will impact the
// instant_compensation below
samples_written = cap;
}
assert(samples_written >= can_write);
if (samples_written > can_write) {
uint32_t skip_samples = samples_written - can_write;
assert(buffered_samples >= skip_samples);
sc_audiobuf_skip(&ap->buf, skip_samples);
buffered_samples -= skip_samples;
if (ap->played) {
// Dropping input samples instantly decreases buffering
ap->avg_buffering.avg -= skip_samples;
}
}
// It should remain exactly the expected size to write the new
// samples.
assert(sc_audiobuf_can_write(&ap->buf) == samples_written);
uint32_t skipped_samples = 0;
uint32_t written = sc_audiobuf_write(&ap->buf, swr_buf, samples);
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;
}
sc_audiobuf_write(&ap->buf, swr_buf, samples_written);
SDL_UnlockAudioDevice(ap->device);
}
buffered_samples += samples_written;
assert(buffered_samples == sc_audiobuf_can_read(&ap->buf));
// Read with lock held, to be used after unlocking
bool played = ap->played;
uint32_t underflow = ap->underflow;
uint32_t underflow = 0;
uint32_t max_buffered_samples;
bool played = atomic_load_explicit(&ap->played, memory_order_relaxed);
if (played) {
uint32_t max_buffered_samples = ap->target_buffering
+ 12 * ap->output_buffer
+ ap->target_buffering / 10;
if (buffered_samples > max_buffered_samples) {
uint32_t skip_samples = buffered_samples - max_buffered_samples;
sc_audiobuf_skip(&ap->buf, skip_samples);
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
" samples", skip_samples);
}
underflow = atomic_exchange_explicit(&ap->underflow, 0,
memory_order_relaxed);
// reset (the current value was copied to a local variable)
ap->underflow = 0;
max_buffered_samples = ap->target_buffering
+ 12 * ap->output_buffer
+ ap->target_buffering / 10;
} else {
// SDL playback not started yet, do not accumulate more than
// max_initial_buffering samples, this would cause unnecessary delay
// (and glitches to compensate) on start.
uint32_t max_initial_buffering = ap->target_buffering
+ 2 * ap->output_buffer;
if (buffered_samples > max_initial_buffering) {
uint32_t skip_samples = buffered_samples - max_initial_buffering;
sc_audiobuf_skip(&ap->buf, skip_samples);
max_buffered_samples = ap->target_buffering + 2 * ap->output_buffer;
}
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);
if (skip_samples) {
if (played) {
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
" samples", skip_samples);
#ifndef SC_AUDIO_PLAYER_NDEBUG
LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples",
skip_samples);
} else {
LOGD("[Audio] Playback not started, skipping %" PRIu32
" samples", skip_samples);
#endif
}
}
}
ap->previous_can_write = sc_audiobuf_can_write(&ap->buf);
ap->received = true;
SDL_UnlockAudioDevice(ap->device);
atomic_store_explicit(&ap->received, true, memory_order_relaxed);
if (played) {
// Number of samples added (or removed, if negative) for compensation
int32_t instant_compensation =
(int32_t) samples_written - frame->nb_samples;
int32_t instant_compensation = (int32_t) written - frame->nb_samples;
// Inserting silence instantly increases buffering
int32_t inserted_silence = (int32_t) underflow;
// Dropping input samples instantly decreases buffering
int32_t dropped = (int32_t) skipped_samples;
// The compensation must apply instantly, it must not be smoothed
ap->avg_buffering.avg += instant_compensation + inserted_silence;
ap->avg_buffering.avg +=
instant_compensation + inserted_silence - dropped;
// However, the buffering level must be smoothed
sc_average_push(&ap->avg_buffering, buffered_samples);
sc_average_push(&ap->avg_buffering, can_read);
#ifndef SC_AUDIO_PLAYER_NDEBUG
LOGD("[Audio] buffered_samples=%" PRIu32 " avg_buffering=%f",
buffered_samples, sc_average_get(&ap->avg_buffering));
LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f",
can_read, sc_average_get(&ap->avg_buffering));
#endif
ap->samples_since_resync += samples_written;
ap->samples_since_resync += written;
if (ap->samples_since_resync >= ap->sample_rate) {
// Recompute compensation every second
ap->samples_since_resync = 0;
@ -288,7 +284,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
if (abs(diff) < (int) ap->sample_rate / 1000) {
// Do not compensate for less than 1ms, the error is just noise
diff = 0;
} else if (diff < 0 && buffered_samples < ap->target_buffering) {
} else if (diff < 0 && can_read < ap->target_buffering) {
// Do not accelerate if the instant buffering level is below
// the average, this would increase underflow
diff = 0;
@ -300,8 +296,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
int abs_max_diff = distance / 50;
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
" compensation=%d", ap->target_buffering, avg,
buffered_samples, diff);
" compensation=%d", ap->target_buffering, avg, can_read, diff);
if (diff != ap->compensation) {
int ret = swr_set_compensation(swr_ctx, diff, distance);
@ -397,7 +392,7 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
// 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.
size_t audiobuf_samples = ap->target_buffering + ap->sample_rate;
uint32_t audiobuf_samples = ap->target_buffering + ap->sample_rate;
size_t sample_size = ap->nb_channels * ap->out_bytes_per_sample;
bool ok = sc_audiobuf_init(&ap->buf, sample_size, audiobuf_samples);
@ -413,16 +408,15 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
}
ap->swr_buf_alloc_size = initial_swr_buf_size;
ap->previous_can_write = sc_audiobuf_can_write(&ap->buf);
// Samples are produced and consumed by blocks, so the buffering must be
// smoothed to get a relatively stable value.
sc_average_init(&ap->avg_buffering, 32);
ap->samples_since_resync = 0;
ap->received = false;
ap->played = false;
ap->underflow = 0;
atomic_init(&ap->played, false);
atomic_init(&ap->received, false);
atomic_init(&ap->underflow, 0);
ap->compensation = 0;
// The thread calling open() is the thread calling push(), which fills the

@ -3,6 +3,7 @@
#include "common.h"
#include <stdatomic.h>
#include <stdbool.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
@ -32,13 +33,9 @@ struct sc_audio_player {
uint16_t output_buffer;
// Audio buffer to communicate between the receiver and the SDL audio
// callback (protected by SDL_AudioDeviceLock())
// callback
struct sc_audiobuf buf;
// The previous empty space in the buffer (only used by the receiver
// thread)
uint32_t previous_can_write;
// Resampler (only used from the receiver thread)
struct SwrContext *swr_ctx;
@ -47,7 +44,7 @@ struct sc_audio_player {
// The number of channels is the same for input and output
unsigned nb_channels;
// The number of bytes per sample for a single channel
unsigned out_bytes_per_sample;
size_t out_bytes_per_sample;
// Target buffer for resampling (only used by the receiver thread)
uint8_t *swr_buf;
@ -61,19 +58,16 @@ struct sc_audio_player {
uint32_t samples_since_resync;
// Number of silence samples inserted since the last received packet
// (protected by SDL_AudioDeviceLock())
uint32_t underflow;
atomic_uint_least32_t underflow;
// Current applied compensation value (only used by the receiver thread)
int compensation;
// Set to true the first time a sample is received (protected by
// SDL_AudioDeviceLock())
bool received;
// Set to true the first time a sample is received
atomic_bool received;
// Set to true the first time the SDL callback is called (protected by
// SDL_AudioDeviceLock())
bool played;
// Set to true the first time the SDL callback is called
atomic_bool played;
const struct sc_audio_player_callbacks *cbs;
void *cbs_userdata;

@ -0,0 +1,112 @@
#include "audiobuf.h"
#include <stdlib.h>
#include <string.h>
#include <util/log.h>
#include <util/memory.h>
bool
sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size,
uint32_t capacity) {
assert(sample_size);
assert(capacity);
// The actual capacity is (alloc_size - 1) so that head == tail is
// non-ambiguous
buf->alloc_size = capacity + 1;
buf->data = sc_allocarray(buf->alloc_size, sample_size);
if (!buf->data) {
LOG_OOM();
return false;
}
buf->sample_size = sample_size;
atomic_init(&buf->head, 0);
atomic_init(&buf->tail, 0);
return true;
}
void
sc_audiobuf_destroy(struct sc_audiobuf *buf) {
free(buf->data);
}
uint32_t
sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
assert(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 head cursor is updated after the data is written to the array
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
if (samples_count > can_read) {
samples_count = can_read;
}
if (to) {
uint32_t right_count = buf->alloc_size - tail;
if (right_count > samples_count) {
right_count = samples_count;
}
memcpy(to,
buf->data + (tail * buf->sample_size),
right_count * buf->sample_size);
if (samples_count > right_count) {
uint32_t left_count = samples_count - right_count;
memcpy(to + (right_count * buf->sample_size),
buf->data,
left_count * buf->sample_size);
}
}
uint32_t new_tail = (tail + samples_count) % buf->alloc_size;
atomic_store_explicit(&buf->tail, new_tail, memory_order_release);
return samples_count;
}
uint32_t
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
uint32_t samples_count) {
const uint8_t *from = from_;
// 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_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
if (samples_count > can_write) {
samples_count = can_write;
}
uint32_t right_count = buf->alloc_size - head;
if (right_count > samples_count) {
right_count = samples_count;
}
memcpy(buf->data + (head * buf->sample_size),
from,
right_count * buf->sample_size);
if (samples_count > right_count) {
uint32_t left_count = samples_count - right_count;
memcpy(buf->data,
from + (right_count * buf->sample_size),
left_count * buf->sample_size);
}
uint32_t new_head = (head + samples_count) % buf->alloc_size;
atomic_store_explicit(&buf->head, new_head, memory_order_release);
return samples_count;
}

@ -3,19 +3,25 @@
#include "common.h"
#include <assert.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include "util/bytebuf.h"
/**
* Wrapper around bytebuf to read and write samples
*
* Each sample takes sample_size bytes.
*/
struct sc_audiobuf {
struct sc_bytebuf buf;
uint8_t *data;
uint32_t alloc_size; // in samples
size_t sample_size;
atomic_uint_least32_t head; // writer cursor, in samples
atomic_uint_least32_t tail; // reader cursor, in samples
// empty: tail == head
// full: ((tail + 1) % alloc_size) == head
};
static inline uint32_t
@ -29,66 +35,31 @@ sc_audiobuf_to_bytes(struct sc_audiobuf *buf, uint32_t samples) {
return samples * buf->sample_size;
}
static inline bool
bool
sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size,
uint32_t capacity) {
buf->sample_size = sample_size;
return sc_bytebuf_init(&buf->buf, capacity * sample_size + 1);
}
static inline void
sc_audiobuf_read(struct sc_audiobuf *buf, uint8_t *to, uint32_t samples) {
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
sc_bytebuf_read(&buf->buf, to, bytes);
}
uint32_t capacity);
static inline void
sc_audiobuf_skip(struct sc_audiobuf *buf, uint32_t samples) {
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
sc_bytebuf_skip(&buf->buf, bytes);
}
void
sc_audiobuf_destroy(struct sc_audiobuf *buf);
static inline void
sc_audiobuf_write(struct sc_audiobuf *buf, const uint8_t *from,
uint32_t samples) {
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
sc_bytebuf_write(&buf->buf, from, bytes);
}
uint32_t
sc_audiobuf_read(struct sc_audiobuf *buf, void *to, uint32_t samples_count);
static inline void
sc_audiobuf_prepare_write(struct sc_audiobuf *buf, const uint8_t *from,
uint32_t samples) {
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
sc_bytebuf_prepare_write(&buf->buf, from, bytes);
}
static inline void
sc_audiobuf_commit_write(struct sc_audiobuf *buf, uint32_t samples) {
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
sc_bytebuf_commit_write(&buf->buf, bytes);
}
static inline uint32_t
sc_audiobuf_can_read(struct sc_audiobuf *buf) {
size_t bytes = sc_bytebuf_can_read(&buf->buf);
return sc_audiobuf_to_samples(buf, bytes);
}
static inline uint32_t
sc_audiobuf_can_write(struct sc_audiobuf *buf) {
size_t bytes = sc_bytebuf_can_write(&buf->buf);
return sc_audiobuf_to_samples(buf, bytes);
}
uint32_t
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from,
uint32_t samples_count);
static inline uint32_t
sc_audiobuf_capacity(struct sc_audiobuf *buf) {
size_t bytes = sc_bytebuf_capacity(&buf->buf);
return sc_audiobuf_to_samples(buf, bytes);
assert(buf->alloc_size);
return buf->alloc_size - 1;
}
static inline void
sc_audiobuf_destroy(struct sc_audiobuf *buf) {
sc_bytebuf_destroy(&buf->buf);
static inline uint32_t
sc_audiobuf_can_read(struct sc_audiobuf *buf) {
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
return (buf->alloc_size + head - tail) % buf->alloc_size;
}
#endif

@ -1,104 +0,0 @@
#include "bytebuf.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "util/log.h"
bool
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size) {
assert(alloc_size);
buf->data = malloc(alloc_size);
if (!buf->data) {
LOG_OOM();
return false;
}
buf->alloc_size = alloc_size;
buf->head = 0;
buf->tail = 0;
return true;
}
void
sc_bytebuf_destroy(struct sc_bytebuf *buf) {
free(buf->data);
}
void
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) {
assert(len);
assert(len <= sc_bytebuf_can_read(buf));
assert(buf->tail != buf->head); // the buffer could not be empty
size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size;
size_t right_len = right_limit - buf->tail;
if (len < right_len) {
right_len = len;
}
memcpy(to, buf->data + buf->tail, right_len);
if (len > right_len) {
memcpy(to + right_len, buf->data, len - right_len);
}
buf->tail = (buf->tail + len) % buf->alloc_size;
}
void
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) {
assert(len);
assert(len <= sc_bytebuf_can_read(buf));
assert(buf->tail != buf->head); // the buffer could not be empty
buf->tail = (buf->tail + len) % buf->alloc_size;
}
static inline void
sc_bytebuf_write_step0(struct sc_bytebuf *buf, const uint8_t *from,
size_t len) {
size_t right_len = buf->alloc_size - buf->head;
if (len < right_len) {
right_len = len;
}
memcpy(buf->data + buf->head, from, right_len);
if (len > right_len) {
memcpy(buf->data, from + right_len, len - right_len);
}
}
static inline void
sc_bytebuf_write_step1(struct sc_bytebuf *buf, size_t len) {
buf->head = (buf->head + len) % buf->alloc_size;
}
void
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) {
assert(len);
assert(len <= sc_bytebuf_can_write(buf));
sc_bytebuf_write_step0(buf, from, len);
sc_bytebuf_write_step1(buf, len);
}
void
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
size_t len) {
// *This function MUST NOT access buf->tail (even in assert()).*
// The purpose of this function is to allow a reader and a writer to access
// different parts of the buffer in parallel simultaneously. It is intended
// to be called without lock (only sc_bytebuf_commit_write() is intended to
// be called with lock held).
assert(len < buf->alloc_size - 1);
sc_bytebuf_write_step0(buf, from, len);
}
void
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) {
assert(len <= sc_bytebuf_can_write(buf));
sc_bytebuf_write_step1(buf, len);
}

@ -1,114 +0,0 @@
#ifndef SC_BYTEBUF_H
#define SC_BYTEBUF_H
#include "common.h"
#include <stdbool.h>
#include <stdint.h>
struct sc_bytebuf {
uint8_t *data;
// The actual capacity is (allocated - 1) so that head == tail is
// non-ambiguous
size_t alloc_size;
size_t head; // writter cursor
size_t tail; // reader cursor
// empty: tail == head
// full: ((tail + 1) % alloc_size) == head
};
bool
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size);
/**
* Copy from the bytebuf to a user-provided array
*
* The caller must check that len <= sc_bytebuf_read_available() (it is an
* error to attempt to read more bytes than available).
*
* This function is guaranteed not to write to buf->head.
*/
void
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len);
/**
* Drop len bytes from the buffer
*
* The caller must check that len <= sc_bytebuf_read_available() (it is an
* error to attempt to skip more bytes than available).
*
* This function is guaranteed not to write to buf->head.
*
* It is equivalent to call sc_bytebuf_read() to some array and discard the
* array (but this function is more efficient since there is no copy).
*/
void
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len);
/**
* Copy the user-provided array to the bytebuf
*
* The caller must check that len <= sc_bytebuf_write_available() (it is an
* error to write more bytes than the remaining available space).
*
* This function is guaranteed not to write to buf->tail.
*/
void
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len);
/**
* Copy the user-provided array to the bytebuf, but do not advance the cursor
*
* The caller must check that len <= sc_bytebuf_write_available() (it is an
* error to write more bytes than the remaining available space).
*
* After this function is called, the write must be committed with
* sc_bytebuf_commit_write().
*
* The purpose of this mechanism is to acquire a lock only to commit the write,
* but not to perform the actual copy.
*
* This function is guaranteed not to access buf->tail.
*/
void
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
size_t len);
/**
* Commit a prepared write
*/
void
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len);
/**
* Return the number of bytes which can be read
*
* It is an error to read more bytes than available.
*/
static inline size_t
sc_bytebuf_can_read(struct sc_bytebuf *buf) {
return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size;
}
/**
* Return the number of bytes which can be written
*
* It is an error to write more bytes than available.
*/
static inline size_t
sc_bytebuf_can_write(struct sc_bytebuf *buf) {
return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size;
}
/**
* Return the actual capacity of the buffer (can_read() + can_write())
*/
static inline size_t
sc_bytebuf_capacity(struct sc_bytebuf *buf) {
return buf->alloc_size - 1;
}
void
sc_bytebuf_destroy(struct sc_bytebuf *buf);
#endif

@ -0,0 +1,128 @@
#include "common.h"
#include <assert.h>
#include <string.h>
#include "util/audiobuf.h"
static void test_audiobuf_simple(void) {
struct sc_audiobuf buf;
uint32_t data[20];
bool ok = sc_audiobuf_init(&buf, 4, 20);
assert(ok);
uint32_t samples[] = {1, 2, 3, 4, 5};
uint32_t w = sc_audiobuf_write(&buf, samples, 5);
assert(w == 5);
uint32_t r = sc_audiobuf_read(&buf, data, 4);
assert(r == 4);
assert(!memcmp(data, samples, 16));
uint32_t samples2[] = {6, 7, 8};
w = sc_audiobuf_write(&buf, samples2, 3);
assert(w == 3);
uint32_t single = 9;
w = sc_audiobuf_write(&buf, &single, 1);
assert(w == 1);
r = sc_audiobuf_read(&buf, &data[4], 8);
assert(r == 5);
uint32_t expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
assert(!memcmp(data, expected, 36));
sc_audiobuf_destroy(&buf);
}
static void test_audiobuf_boundaries(void) {
struct sc_audiobuf buf;
uint32_t data[20];
bool ok = sc_audiobuf_init(&buf, 4, 20);
assert(ok);
uint32_t samples[] = {1, 2, 3, 4, 5, 6};
uint32_t w = sc_audiobuf_write(&buf, samples, 6);
assert(w == 6);
w = sc_audiobuf_write(&buf, samples, 6);
assert(w == 6);
w = sc_audiobuf_write(&buf, samples, 6);
assert(w == 6);
uint32_t r = sc_audiobuf_read(&buf, data, 9);
assert(r == 9);
uint32_t expected[] = {1, 2, 3, 4, 5, 6, 1, 2, 3};
assert(!memcmp(data, expected, 36));
uint32_t samples2[] = {7, 8, 9, 10, 11};
w = sc_audiobuf_write(&buf, samples2, 5);
assert(w == 5);
uint32_t single = 12;
w = sc_audiobuf_write(&buf, &single, 1);
assert(w == 1);
w = sc_audiobuf_read(&buf, NULL, 3);
assert(w == 3);
assert(sc_audiobuf_can_read(&buf) == 12);
r = sc_audiobuf_read(&buf, data, 12);
assert(r == 12);
uint32_t expected2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
assert(!memcmp(data, expected2, 48));
sc_audiobuf_destroy(&buf);
}
static void test_audiobuf_partial_read_write(void) {
struct sc_audiobuf buf;
uint32_t data[15];
bool ok = sc_audiobuf_init(&buf, 4, 10);
assert(ok);
uint32_t samples[] = {1, 2, 3, 4, 5, 6};
uint32_t w = sc_audiobuf_write(&buf, samples, 6);
assert(w == 6);
w = sc_audiobuf_write(&buf, samples, 6);
assert(w == 4);
w = sc_audiobuf_write(&buf, samples, 6);
assert(w == 0);
uint32_t r = sc_audiobuf_read(&buf, data, 3);
assert(r == 3);
uint32_t expected[] = {1, 2, 3};
assert(!memcmp(data, expected, 12));
w = sc_audiobuf_write(&buf, samples, 6);
assert(w == 3);
r = sc_audiobuf_read(&buf, data, 15);
assert(r == 10);
uint32_t expected2[] = {4, 5, 6, 1, 2, 3, 4, 1, 2, 3};
assert(!memcmp(data, expected2, 12));
sc_audiobuf_destroy(&buf);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_audiobuf_simple();
test_audiobuf_boundaries();
test_audiobuf_partial_read_write();
return 0;
}

@ -1,126 +0,0 @@
#include "common.h"
#include <assert.h>
#include <string.h>
#include "util/bytebuf.h"
static void test_bytebuf_simple(void) {
struct sc_bytebuf buf;
uint8_t data[20];
bool ok = sc_bytebuf_init(&buf, 20);
assert(ok);
sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1);
assert(sc_bytebuf_can_read(&buf) == 5);
sc_bytebuf_read(&buf, data, 4);
assert(!strncmp((char *) data, "hell", 4));
sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1);
assert(sc_bytebuf_can_read(&buf) == 7);
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
assert(sc_bytebuf_can_read(&buf) == 8);
sc_bytebuf_read(&buf, &data[4], 8);
assert(sc_bytebuf_can_read(&buf) == 0);
data[12] = '\0';
assert(!strcmp((char *) data, "hello world!"));
assert(sc_bytebuf_can_read(&buf) == 0);
sc_bytebuf_destroy(&buf);
}
static void test_bytebuf_boundaries(void) {
struct sc_bytebuf buf;
uint8_t data[20];
bool ok = sc_bytebuf_init(&buf, 20);
assert(ok);
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
assert(sc_bytebuf_can_read(&buf) == 6);
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
assert(sc_bytebuf_can_read(&buf) == 12);
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
assert(sc_bytebuf_can_read(&buf) == 18);
sc_bytebuf_read(&buf, data, 9);
assert(!strncmp((char *) data, "hello hel", 9));
assert(sc_bytebuf_can_read(&buf) == 9);
sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
assert(sc_bytebuf_can_read(&buf) == 14);
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
assert(sc_bytebuf_can_read(&buf) == 15);
sc_bytebuf_skip(&buf, 3);
assert(sc_bytebuf_can_read(&buf) == 12);
sc_bytebuf_read(&buf, data, 12);
data[12] = '\0';
assert(!strcmp((char *) data, "hello world!"));
assert(sc_bytebuf_can_read(&buf) == 0);
sc_bytebuf_destroy(&buf);
}
static void test_bytebuf_two_steps_write(void) {
struct sc_bytebuf buf;
uint8_t data[20];
bool ok = sc_bytebuf_init(&buf, 20);
assert(ok);
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
assert(sc_bytebuf_can_read(&buf) == 6);
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
assert(sc_bytebuf_can_read(&buf) == 12);
sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
assert(sc_bytebuf_can_read(&buf) == 12); // write not committed yet
sc_bytebuf_read(&buf, data, 9);
assert(!strncmp((char *) data, "hello hel", 3));
assert(sc_bytebuf_can_read(&buf) == 3);
sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1);
assert(sc_bytebuf_can_read(&buf) == 9);
sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
assert(sc_bytebuf_can_read(&buf) == 9); // write not committed yet
sc_bytebuf_commit_write(&buf, sizeof("world") - 1);
assert(sc_bytebuf_can_read(&buf) == 14);
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
assert(sc_bytebuf_can_read(&buf) == 15);
sc_bytebuf_skip(&buf, 3);
assert(sc_bytebuf_can_read(&buf) == 12);
sc_bytebuf_read(&buf, data, 12);
data[12] = '\0';
assert(!strcmp((char *) data, "hello world!"));
assert(sc_bytebuf_can_read(&buf) == 0);
sc_bytebuf_destroy(&buf);
}
int main(int argc, char *argv[]) {
(void) argc;
(void) argv;
test_bytebuf_simple();
test_bytebuf_boundaries();
test_bytebuf_two_steps_write();
return 0;
}
Loading…
Cancel
Save