Compare commits
8 Commits
master
...
audio_play
Author | SHA1 | Date | |
---|---|---|---|
|
4e35761037 | ||
|
d335683a8b | ||
|
91daa94aa4 | ||
|
863adc0ba8 | ||
|
79ac87cb6f | ||
|
d74aa24fd5 | ||
|
6a01c39aac | ||
|
1ea9e8f693 |
@ -34,8 +34,8 @@ src = [
|
|||||||
'src/trait/frame_source.c',
|
'src/trait/frame_source.c',
|
||||||
'src/trait/packet_source.c',
|
'src/trait/packet_source.c',
|
||||||
'src/util/acksync.c',
|
'src/util/acksync.c',
|
||||||
|
'src/util/audiobuf.c',
|
||||||
'src/util/average.c',
|
'src/util/average.c',
|
||||||
'src/util/bytebuf.c',
|
|
||||||
'src/util/file.c',
|
'src/util/file.c',
|
||||||
'src/util/intmap.c',
|
'src/util/intmap.c',
|
||||||
'src/util/intr.c',
|
'src/util/intr.c',
|
||||||
@ -212,9 +212,10 @@ if get_option('buildtype') == 'debug'
|
|||||||
['test_binary', [
|
['test_binary', [
|
||||||
'tests/test_binary.c',
|
'tests/test_binary.c',
|
||||||
]],
|
]],
|
||||||
['test_bytebuf', [
|
['test_audiobuf', [
|
||||||
'tests/test_bytebuf.c',
|
'tests/test_audiobuf.c',
|
||||||
'src/util/bytebuf.c',
|
'src/util/audiobuf.c',
|
||||||
|
'src/util/memory.c',
|
||||||
]],
|
]],
|
||||||
['test_cli', [
|
['test_cli', [
|
||||||
'tests/test_cli.c',
|
'tests/test_cli.c',
|
||||||
|
@ -66,8 +66,7 @@ static void SDLCALL
|
|||||||
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
||||||
struct sc_audio_player *ap = userdata;
|
struct sc_audio_player *ap = userdata;
|
||||||
|
|
||||||
// This callback is called with the lock used by SDL_AudioDeviceLock(), so
|
// This callback is called with the lock used by SDL_LockAudioDevice()
|
||||||
// the audiobuf is protected
|
|
||||||
|
|
||||||
assert(len_int > 0);
|
assert(len_int > 0);
|
||||||
size_t len = len_int;
|
size_t len = len_int;
|
||||||
@ -77,12 +76,12 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
|||||||
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
|
LOGD("[Audio] SDL callback requests %" PRIu32 " samples", count);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool played = atomic_load_explicit(&ap->played, memory_order_relaxed);
|
||||||
|
if (!played) {
|
||||||
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
|
uint32_t buffered_samples = sc_audiobuf_can_read(&ap->buf);
|
||||||
if (!ap->played) {
|
// Wait until the buffer is filled up to at least target_buffering
|
||||||
// Part of the buffering is handled by inserting initial silence. The
|
// before playing
|
||||||
// remaining (margin) last samples will be handled by compensation.
|
if (buffered_samples < ap->target_buffering) {
|
||||||
uint32_t margin = 30 * ap->sample_rate / 1000; // 30ms
|
|
||||||
if (buffered_samples + margin < ap->target_buffering) {
|
|
||||||
LOGV("[Audio] Inserting initial buffering silence: %" PRIu32
|
LOGV("[Audio] Inserting initial buffering silence: %" PRIu32
|
||||||
" samples", count);
|
" samples", count);
|
||||||
// Delay playback starting to reach the target buffering. Fill the
|
// Delay playback starting to reach the target buffering. Fill the
|
||||||
@ -93,10 +92,7 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t read = MIN(buffered_samples, count);
|
uint32_t read = sc_audiobuf_read(&ap->buf, stream, count);
|
||||||
if (read) {
|
|
||||||
sc_audiobuf_read(&ap->buf, stream, read);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (read < count) {
|
if (read < count) {
|
||||||
uint32_t silence = count - read;
|
uint32_t silence = count - read;
|
||||||
@ -109,13 +105,16 @@ sc_audio_player_sdl_callback(void *userdata, uint8_t *stream, int len_int) {
|
|||||||
silence);
|
silence);
|
||||||
memset(stream + TO_BYTES(read), 0, TO_BYTES(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
|
// 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 *
|
static uint8_t *
|
||||||
@ -162,146 +161,160 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
|
|
||||||
// swr_convert() returns the number of samples which would have been
|
// swr_convert() returns the number of samples which would have been
|
||||||
// written if the buffer was big enough.
|
// 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
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples_written);
|
LOGD("[Audio] %" PRIu32 " samples written to buffer", samples);
|
||||||
#endif
|
#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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
uint32_t cap = sc_audiobuf_capacity(&ap->buf);
|
||||||
if (samples_written > cap) {
|
if (samples > cap) {
|
||||||
// Very very unlikely: a single resampled frame should never
|
// Very very unlikely: a single resampled frame should never
|
||||||
// exceed the audio buffer size (or something is very wrong).
|
// exceed the audio buffer size (or something is very wrong).
|
||||||
// Ignore the first bytes in swr_buf
|
// Ignore the first bytes in swr_buf to avoid memory corruption anyway.
|
||||||
swr_buf += TO_BYTES(samples_written - cap);
|
swr_buf += TO_BYTES(samples - cap);
|
||||||
// This change in samples_written will impact the
|
samples = cap;
|
||||||
// instant_compensation below
|
|
||||||
samples_written = cap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(samples_written >= can_write);
|
uint32_t skipped_samples = 0;
|
||||||
if (samples_written > can_write) {
|
|
||||||
uint32_t skip_samples = samples_written - can_write;
|
uint32_t written = sc_audiobuf_write(&ap->buf, swr_buf, samples);
|
||||||
assert(buffered_samples >= skip_samples);
|
if (written < samples) {
|
||||||
sc_audiobuf_skip(&ap->buf, skip_samples);
|
uint32_t remaining = samples - written;
|
||||||
buffered_samples -= skip_samples;
|
|
||||||
if (ap->played) {
|
// All samples that could be written without locking have been written,
|
||||||
// Dropping input samples instantly decreases buffering
|
// now we need to lock to drop/consume old samples
|
||||||
ap->avg_buffering.avg -= skip_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// It should remain exactly the expected size to write the new
|
SDL_UnlockAudioDevice(ap->device);
|
||||||
// samples.
|
|
||||||
assert(sc_audiobuf_can_write(&ap->buf) == samples_written);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sc_audiobuf_write(&ap->buf, swr_buf, samples_written);
|
uint32_t underflow = 0;
|
||||||
}
|
uint32_t max_buffered_samples;
|
||||||
|
bool played = atomic_load_explicit(&ap->played, memory_order_relaxed);
|
||||||
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;
|
|
||||||
|
|
||||||
if (played) {
|
if (played) {
|
||||||
uint32_t max_buffered_samples = ap->target_buffering
|
underflow = atomic_exchange_explicit(&ap->underflow, 0,
|
||||||
|
memory_order_relaxed);
|
||||||
|
|
||||||
|
max_buffered_samples = ap->target_buffering
|
||||||
+ 12 * ap->output_buffer
|
+ 12 * ap->output_buffer
|
||||||
+ ap->target_buffering / 10;
|
+ 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset (the current value was copied to a local variable)
|
|
||||||
ap->underflow = 0;
|
|
||||||
} else {
|
} else {
|
||||||
// SDL playback not started yet, do not accumulate more than
|
// SDL playback not started yet, do not accumulate more than
|
||||||
// max_initial_buffering samples, this would cause unnecessary delay
|
// max_initial_buffering samples, this would cause unnecessary delay
|
||||||
// (and glitches to compensate) on start.
|
// (and glitches to compensate) on start.
|
||||||
uint32_t max_initial_buffering = ap->target_buffering
|
max_buffered_samples = ap->target_buffering + 2 * ap->output_buffer;
|
||||||
+ 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);
|
|
||||||
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
|
||||||
LOGD("[Audio] Playback not started, skipping %" PRIu32 " samples",
|
|
||||||
skip_samples);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ap->previous_can_write = sc_audiobuf_can_write(&ap->buf);
|
uint32_t can_read = sc_audiobuf_can_read(&ap->buf);
|
||||||
ap->received = true;
|
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);
|
SDL_UnlockAudioDevice(ap->device);
|
||||||
|
|
||||||
|
if (skip_samples) {
|
||||||
if (played) {
|
if (played) {
|
||||||
|
LOGD("[Audio] Buffering threshold exceeded, skipping %" PRIu32
|
||||||
|
" samples", skip_samples);
|
||||||
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
|
} else {
|
||||||
|
LOGD("[Audio] Playback not started, skipping %" PRIu32
|
||||||
|
" samples", skip_samples);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_store_explicit(&ap->received, true, memory_order_relaxed);
|
||||||
|
if (!played) {
|
||||||
|
// Nothing more to do
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Number of samples added (or removed, if negative) for compensation
|
// Number of samples added (or removed, if negative) for compensation
|
||||||
int32_t instant_compensation =
|
int32_t instant_compensation = (int32_t) written - frame->nb_samples;
|
||||||
(int32_t) samples_written - frame->nb_samples;
|
// Inserting silence instantly increases buffering
|
||||||
int32_t inserted_silence = (int32_t) underflow;
|
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
|
// 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;
|
||||||
|
if (ap->avg_buffering.avg < 0) {
|
||||||
|
// Since dropping samples instantly reduces buffering, the difference
|
||||||
|
// is applied immediately to the average value, assuming that the delay
|
||||||
|
// between the producer and the consumer will be caught up.
|
||||||
|
//
|
||||||
|
// However, when this assumption is not valid, the average buffering
|
||||||
|
// may decrease indefinitely. Prevent it to become negative to limit
|
||||||
|
// the consequences.
|
||||||
|
ap->avg_buffering.avg = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// However, the buffering level must be smoothed
|
// 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
|
#ifndef SC_AUDIO_PLAYER_NDEBUG
|
||||||
LOGD("[Audio] buffered_samples=%" PRIu32 " avg_buffering=%f",
|
LOGD("[Audio] can_read=%" PRIu32 " avg_buffering=%f",
|
||||||
buffered_samples, sc_average_get(&ap->avg_buffering));
|
can_read, sc_average_get(&ap->avg_buffering));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ap->samples_since_resync += samples_written;
|
ap->samples_since_resync += written;
|
||||||
if (ap->samples_since_resync >= ap->sample_rate) {
|
if (ap->samples_since_resync >= ap->sample_rate) {
|
||||||
// Recompute compensation every second
|
// Recompute compensation every second
|
||||||
ap->samples_since_resync = 0;
|
ap->samples_since_resync = 0;
|
||||||
|
|
||||||
float avg = sc_average_get(&ap->avg_buffering);
|
float avg = sc_average_get(&ap->avg_buffering);
|
||||||
int diff = ap->target_buffering - avg;
|
int diff = ap->target_buffering - avg;
|
||||||
if (abs(diff) < (int) ap->sample_rate / 1000) {
|
|
||||||
// Do not compensate for less than 1ms, the error is just noise
|
// Enable compensation when the difference exceeds +/- 4ms.
|
||||||
|
// Disable compensation when the difference is lower than +/- 1ms.
|
||||||
|
int threshold = ap->compensation != 0
|
||||||
|
? ap->sample_rate / 1000 /* 1ms */
|
||||||
|
: ap->sample_rate * 4 / 1000; /* 4ms */
|
||||||
|
|
||||||
|
if (abs(diff) < threshold) {
|
||||||
|
// Do not compensate for small values, the error is just noise
|
||||||
diff = 0;
|
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
|
// Do not accelerate if the instant buffering level is below the
|
||||||
// the average, this would increase underflow
|
// target, this would increase underflow
|
||||||
diff = 0;
|
diff = 0;
|
||||||
}
|
}
|
||||||
// Compensate the diff over 4 seconds (but will be recomputed after
|
// Compensate the diff over 4 seconds (but will be recomputed after 1
|
||||||
// 1 second)
|
// second)
|
||||||
int distance = 4 * ap->sample_rate;
|
int distance = 4 * ap->sample_rate;
|
||||||
// Limit compensation rate to 2%
|
// Limit compensation rate to 2%
|
||||||
int abs_max_diff = distance / 50;
|
int abs_max_diff = distance / 50;
|
||||||
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
|
diff = CLAMP(diff, -abs_max_diff, abs_max_diff);
|
||||||
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
LOGV("[Audio] Buffering: target=%" PRIu32 " avg=%f cur=%" PRIu32
|
||||||
" compensation=%d", ap->target_buffering, avg,
|
" compensation=%d", ap->target_buffering, avg, can_read, diff);
|
||||||
buffered_samples, diff);
|
|
||||||
|
|
||||||
if (diff != ap->compensation) {
|
if (diff != ap->compensation) {
|
||||||
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
int ret = swr_set_compensation(swr_ctx, diff, distance);
|
||||||
@ -313,7 +326,6 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -397,7 +409,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
|
// 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
|
// the producer and the consumer will be able to access it in parallel
|
||||||
// without locking.
|
// 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;
|
size_t sample_size = ap->nb_channels * ap->out_bytes_per_sample;
|
||||||
bool ok = sc_audiobuf_init(&ap->buf, sample_size, audiobuf_samples);
|
bool ok = sc_audiobuf_init(&ap->buf, sample_size, audiobuf_samples);
|
||||||
@ -413,16 +425,15 @@ sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
|
|||||||
}
|
}
|
||||||
ap->swr_buf_alloc_size = initial_swr_buf_size;
|
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
|
// Samples are produced and consumed by blocks, so the buffering must be
|
||||||
// smoothed to get a relatively stable value.
|
// smoothed to get a relatively stable value.
|
||||||
sc_average_init(&ap->avg_buffering, 32);
|
sc_average_init(&ap->avg_buffering, 128);
|
||||||
ap->samples_since_resync = 0;
|
ap->samples_since_resync = 0;
|
||||||
|
|
||||||
ap->received = false;
|
ap->received = false;
|
||||||
ap->played = false;
|
atomic_init(&ap->played, false);
|
||||||
ap->underflow = 0;
|
atomic_init(&ap->received, false);
|
||||||
|
atomic_init(&ap->underflow, 0);
|
||||||
ap->compensation = 0;
|
ap->compensation = 0;
|
||||||
|
|
||||||
// The thread calling open() is the thread calling push(), which fills the
|
// The thread calling open() is the thread calling push(), which fills the
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <libavformat/avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <libswresample/swresample.h>
|
#include <libswresample/swresample.h>
|
||||||
@ -32,13 +33,9 @@ struct sc_audio_player {
|
|||||||
uint16_t output_buffer;
|
uint16_t output_buffer;
|
||||||
|
|
||||||
// Audio buffer to communicate between the receiver and the SDL audio
|
// Audio buffer to communicate between the receiver and the SDL audio
|
||||||
// callback (protected by SDL_AudioDeviceLock())
|
// callback
|
||||||
struct sc_audiobuf buf;
|
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)
|
// Resampler (only used from the receiver thread)
|
||||||
struct SwrContext *swr_ctx;
|
struct SwrContext *swr_ctx;
|
||||||
|
|
||||||
@ -47,7 +44,7 @@ struct sc_audio_player {
|
|||||||
// The number of channels is the same for input and output
|
// The number of channels is the same for input and output
|
||||||
unsigned nb_channels;
|
unsigned nb_channels;
|
||||||
// The number of bytes per sample for a single channel
|
// 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)
|
// Target buffer for resampling (only used by the receiver thread)
|
||||||
uint8_t *swr_buf;
|
uint8_t *swr_buf;
|
||||||
@ -61,19 +58,16 @@ struct sc_audio_player {
|
|||||||
uint32_t samples_since_resync;
|
uint32_t samples_since_resync;
|
||||||
|
|
||||||
// Number of silence samples inserted since the last received packet
|
// Number of silence samples inserted since the last received packet
|
||||||
// (protected by SDL_AudioDeviceLock())
|
atomic_uint_least32_t underflow;
|
||||||
uint32_t underflow;
|
|
||||||
|
|
||||||
// Current applied compensation value (only used by the receiver thread)
|
// Current applied compensation value (only used by the receiver thread)
|
||||||
int compensation;
|
int compensation;
|
||||||
|
|
||||||
// Set to true the first time a sample is received (protected by
|
// Set to true the first time a sample is received
|
||||||
// SDL_AudioDeviceLock())
|
atomic_bool received;
|
||||||
bool received;
|
|
||||||
|
|
||||||
// Set to true the first time the SDL callback is called (protected by
|
// Set to true the first time the SDL callback is called
|
||||||
// SDL_AudioDeviceLock())
|
atomic_bool played;
|
||||||
bool played;
|
|
||||||
|
|
||||||
const struct sc_audio_player_callbacks *cbs;
|
const struct sc_audio_player_callbacks *cbs;
|
||||||
void *cbs_userdata;
|
void *cbs_userdata;
|
||||||
|
@ -1385,7 +1385,11 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
|
|||||||
static bool
|
static bool
|
||||||
parse_buffering_time(const char *s, sc_tick *tick) {
|
parse_buffering_time(const char *s, sc_tick *tick) {
|
||||||
long value;
|
long value;
|
||||||
bool ok = parse_integer_arg(s, &value, false, 0, 0x7FFFFFFF,
|
// In practice, buffering time should not exceed a few seconds.
|
||||||
|
// Limit it to some arbitrary value (1 hour) to prevent 32-bit overflow
|
||||||
|
// when multiplied by the audio sample size and the number of samples per
|
||||||
|
// millisecond.
|
||||||
|
bool ok = parse_integer_arg(s, &value, false, 0, 60 * 60 * 1000,
|
||||||
"buffering time");
|
"buffering time");
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
return false;
|
return false;
|
||||||
|
112
app/src/util/audiobuf.c
Normal file
112
app/src/util/audiobuf.c
Normal file
@ -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 "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "util/bytebuf.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around bytebuf to read and write samples
|
* Wrapper around bytebuf to read and write samples
|
||||||
*
|
*
|
||||||
* Each sample takes sample_size bytes.
|
* Each sample takes sample_size bytes.
|
||||||
*/
|
*/
|
||||||
struct sc_audiobuf {
|
struct sc_audiobuf {
|
||||||
struct sc_bytebuf buf;
|
uint8_t *data;
|
||||||
|
uint32_t alloc_size; // in samples
|
||||||
size_t sample_size;
|
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
|
static inline uint32_t
|
||||||
@ -29,66 +35,31 @@ sc_audiobuf_to_bytes(struct sc_audiobuf *buf, uint32_t samples) {
|
|||||||
return samples * buf->sample_size;
|
return samples * buf->sample_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
bool
|
||||||
sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size,
|
sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size,
|
||||||
uint32_t capacity) {
|
uint32_t capacity);
|
||||||
buf->sample_size = sample_size;
|
|
||||||
return sc_bytebuf_init(&buf->buf, capacity * sample_size + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
void
|
||||||
sc_audiobuf_read(struct sc_audiobuf *buf, uint8_t *to, uint32_t samples) {
|
sc_audiobuf_destroy(struct sc_audiobuf *buf);
|
||||||
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
|
|
||||||
sc_bytebuf_read(&buf->buf, to, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
uint32_t
|
||||||
sc_audiobuf_skip(struct sc_audiobuf *buf, uint32_t samples) {
|
sc_audiobuf_read(struct sc_audiobuf *buf, void *to, uint32_t samples_count);
|
||||||
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
|
|
||||||
sc_bytebuf_skip(&buf->buf, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
uint32_t
|
||||||
sc_audiobuf_write(struct sc_audiobuf *buf, const uint8_t *from,
|
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from,
|
||||||
uint32_t samples) {
|
uint32_t samples_count);
|
||||||
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
|
|
||||||
sc_bytebuf_write(&buf->buf, from, bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
static inline uint32_t
|
||||||
sc_audiobuf_prepare_write(struct sc_audiobuf *buf, const uint8_t *from,
|
sc_audiobuf_capacity(struct sc_audiobuf *buf) {
|
||||||
uint32_t samples) {
|
assert(buf->alloc_size);
|
||||||
size_t bytes = sc_audiobuf_to_bytes(buf, samples);
|
return buf->alloc_size - 1;
|
||||||
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
|
static inline uint32_t
|
||||||
sc_audiobuf_can_read(struct sc_audiobuf *buf) {
|
sc_audiobuf_can_read(struct sc_audiobuf *buf) {
|
||||||
size_t bytes = sc_bytebuf_can_read(&buf->buf);
|
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
|
||||||
return sc_audiobuf_to_samples(buf, bytes);
|
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
||||||
}
|
return (buf->alloc_size + head - tail) % buf->alloc_size;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_audiobuf_destroy(struct sc_audiobuf *buf) {
|
|
||||||
sc_bytebuf_destroy(&buf->buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#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
|
|
128
app/tests/test_audiobuf.c
Normal file
128
app/tests/test_audiobuf.c
Normal file
@ -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…
x
Reference in New Issue
Block a user