From a69ff6025c405225d804bfdbe387ad2259ed2a57 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Jul 2015 01:05:22 -0700 Subject: [PATCH] add and export a ring buffer implementation --- CMakeLists.txt | 11 +++- example/microphone.c | 97 +++++++++++++++++++++++++++++ src/atomics.hpp | 5 ++ src/dummy.cpp | 20 +++--- src/dummy_ring_buffer.cpp | 75 ----------------------- src/dummy_ring_buffer.hpp | 33 ---------- src/pulseaudio.cpp | 55 ++++++++++------- src/ring_buffer.cpp | 125 ++++++++++++++++++++++++++++++++++++++ src/ring_buffer.hpp | 23 +++++++ src/soundio.cpp | 51 ++++++++++++++++ src/soundio.h | 39 +++++++++--- test/unit_tests.cpp | 115 ++++++++++++++++++++++++++++++++++- 12 files changed, 499 insertions(+), 150 deletions(-) create mode 100644 example/microphone.c delete mode 100644 src/dummy_ring_buffer.cpp delete mode 100644 src/dummy_ring_buffer.hpp create mode 100644 src/ring_buffer.cpp create mode 100644 src/ring_buffer.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c7e341b..d27ab91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,8 +51,8 @@ set(LIBSOUNDIO_SOURCES "${CMAKE_SOURCE_DIR}/src/util.cpp" "${CMAKE_SOURCE_DIR}/src/os.cpp" "${CMAKE_SOURCE_DIR}/src/dummy.cpp" - "${CMAKE_SOURCE_DIR}/src/dummy_ring_buffer.cpp" "${CMAKE_SOURCE_DIR}/src/channel_layout.cpp" + "${CMAKE_SOURCE_DIR}/src/ring_buffer.cpp" ) set(CONFIGURE_OUT_FILE "${CMAKE_BINARY_DIR}/config.h") @@ -67,8 +67,8 @@ set(TEST_SOURCES "${CMAKE_SOURCE_DIR}/src/os.cpp" "${CMAKE_SOURCE_DIR}/src/soundio.cpp" "${CMAKE_SOURCE_DIR}/src/dummy.cpp" - "${CMAKE_SOURCE_DIR}/src/dummy_ring_buffer.cpp" "${CMAKE_SOURCE_DIR}/src/channel_layout.cpp" + "${CMAKE_SOURCE_DIR}/src/ring_buffer.cpp" ) if(SOUNDIO_HAVE_PULSEAUDIO) @@ -152,6 +152,13 @@ set_target_properties(list_devices PROPERTIES include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(list_devices libsoundio_shared) +add_executable(microphone example/microphone.c) +set_target_properties(microphone PROPERTIES + LINKER_LANGUAGE C + COMPILE_FLAGS ${EXAMPLE_CFLAGS}) +include_directories(${EXAMPLE_INCLUDES}) +target_link_libraries(microphone libsoundio_shared) + enable_testing() diff --git a/example/microphone.c b/example/microphone.c new file mode 100644 index 0000000..91b67f7 --- /dev/null +++ b/example/microphone.c @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include + +#include +#include +#include +#include +#include + +__attribute__ ((cold)) +__attribute__ ((noreturn)) +__attribute__ ((format (printf, 1, 2))) +static void panic(const char *format, ...) { + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + fprintf(stderr, "\n"); + va_end(ap); + abort(); +} + +static void read_callback(struct SoundIoInputDevice *input_device) { + fprintf(stderr, "read_callback\n"); +} + +static void write_callback(struct SoundIoOutputDevice *output_device, int requested_frame_count) { + fprintf(stderr, "write_callback\n"); +} + +static void underrun_callback(struct SoundIoOutputDevice *output_device) { + static int count = 0; + fprintf(stderr, "underrun %d\n", count++); + soundio_output_device_fill_with_silence(output_device); +} + +int main(int argc, char **argv) { + struct SoundIo *soundio = soundio_create(); + if (!soundio) + panic("out of memory"); + + int err; + if ((err = soundio_connect(soundio))) + panic("error connecting: %s", soundio_error_string(err)); + + int default_out_device_index = soundio_get_default_output_device_index(soundio); + if (default_out_device_index < 0) + panic("no output device found"); + + int default_in_device_index = soundio_get_default_output_device_index(soundio); + if (default_in_device_index < 0) + panic("no output device found"); + + struct SoundIoDevice *out_device = soundio_get_output_device(soundio, default_out_device_index); + if (!out_device) + panic("could not get output device: out of memory"); + + struct SoundIoDevice *in_device = soundio_get_input_device(soundio, default_in_device_index); + if (!in_device) + panic("could not get input device: out of memory"); + + fprintf(stderr, "Input device: %s: %s\n", + soundio_device_name(in_device), + soundio_device_description(in_device)); + fprintf(stderr, "Output device: %s: %s\n", + soundio_device_name(out_device), + soundio_device_description(out_device)); + + struct SoundIoInputDevice *input_device; + soundio_input_device_create(in_device, SoundIoSampleFormatFloat, 0.1, NULL, + read_callback, &input_device); + + struct SoundIoOutputDevice *output_device; + soundio_output_device_create(out_device, SoundIoSampleFormatFloat, 0.1, NULL, + write_callback, underrun_callback, &output_device); + + if ((err = soundio_input_device_start(input_device))) + panic("unable to start input device: %s", soundio_error_string(err)); + + if ((err = soundio_output_device_start(output_device))) + panic("unable to start output device: %s", soundio_error_string(err)); + + for (;;) + soundio_wait_events(soundio); + + soundio_output_device_destroy(output_device); + soundio_input_device_destroy(input_device); + soundio_device_unref(in_device); + soundio_device_unref(out_device); + soundio_destroy(soundio); + return 0; +} diff --git a/src/atomics.hpp b/src/atomics.hpp index 30338d3..edf0258 100644 --- a/src/atomics.hpp +++ b/src/atomics.hpp @@ -3,10 +3,15 @@ #include using std::atomic_flag; +using std::atomic_int; using std::atomic_long; using std::atomic_bool; using std::atomic_uintptr_t; +#if ATOMIC_INT_LOCK_FREE != 2 +#error "require atomic_int to be lock free" +#endif + #if ATOMIC_LONG_LOCK_FREE != 2 #error "require atomic_long to be lock free" #endif diff --git a/src/dummy.cpp b/src/dummy.cpp index 276787e..a4c66c3 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -7,9 +7,9 @@ #include "dummy.hpp" #include "soundio.hpp" -#include "dummy_ring_buffer.hpp" #include "os.hpp" #include "atomics.hpp" +#include "ring_buffer.hpp" #include #include @@ -20,7 +20,7 @@ struct SoundIoOutputDeviceDummy { atomic_flag abort_flag; int buffer_size; double period; - struct SoundIoDummyRingBuffer ring_buffer; + struct SoundIoRingBuffer ring_buffer; }; struct SoundIoInputDeviceDummy { @@ -49,12 +49,12 @@ static void playback_thread_run(void *arg) { double total_time = now - start_time; long total_frames = total_time / time_per_frame; int frames_to_kill = total_frames - frames_consumed; - int fill_count = soundio_dummy_ring_buffer_fill_count(&opd->ring_buffer); + int fill_count = soundio_ring_buffer_fill_count(&opd->ring_buffer); int frames_in_buffer = fill_count / output_device->bytes_per_frame; int read_count = min(frames_to_kill, frames_in_buffer); int frames_left = frames_to_kill - read_count; int byte_count = read_count * output_device->bytes_per_frame; - soundio_dummy_ring_buffer_advance_read_ptr(&opd->ring_buffer, byte_count); + soundio_ring_buffer_advance_read_ptr(&opd->ring_buffer, byte_count); frames_consumed += read_count; if (frames_left > 0) { @@ -128,7 +128,7 @@ static void output_device_destroy_dummy(SoundIo *soundio, soundio_os_cond_destroy(opd->cond); opd->cond = nullptr; - soundio_dummy_ring_buffer_deinit(&opd->ring_buffer); + soundio_ring_buffer_deinit(&opd->ring_buffer); destroy(opd); output_device->backend_data = nullptr; @@ -150,7 +150,7 @@ static int output_device_init_dummy(SoundIo *soundio, opd->buffer_size = output_device->bytes_per_frame * buffer_frame_count; opd->period = output_device->latency * 0.5; - soundio_dummy_ring_buffer_init(&opd->ring_buffer, opd->buffer_size); + soundio_ring_buffer_init(&opd->ring_buffer, opd->buffer_size); opd->cond = soundio_os_cond_create(); if (!opd->cond) { @@ -167,7 +167,7 @@ static int output_device_start_dummy(SoundIo *soundio, SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; soundio_output_device_fill_with_silence(output_device); - assert(soundio_dummy_ring_buffer_fill_count(&opd->ring_buffer) == opd->buffer_size); + assert(soundio_ring_buffer_fill_count(&opd->ring_buffer) == opd->buffer_size); opd->abort_flag.test_and_set(); int err; @@ -182,7 +182,7 @@ static int output_device_free_count_dummy(SoundIo *soundio, SoundIoOutputDevice *output_device) { SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; - int fill_count = soundio_dummy_ring_buffer_fill_count(&opd->ring_buffer); + int fill_count = soundio_ring_buffer_fill_count(&opd->ring_buffer); int bytes_free_count = opd->buffer_size - fill_count; return bytes_free_count / output_device->bytes_per_frame; } @@ -203,14 +203,14 @@ static void output_device_write_dummy(SoundIo *soundio, SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; assert(data == opd->ring_buffer.address); int byte_count = frame_count * output_device->bytes_per_frame; - soundio_dummy_ring_buffer_advance_write_ptr(&opd->ring_buffer, byte_count); + soundio_ring_buffer_advance_write_ptr(&opd->ring_buffer, byte_count); } static void output_device_clear_buffer_dummy(SoundIo *soundio, SoundIoOutputDevice *output_device) { SoundIoOutputDeviceDummy *opd = (SoundIoOutputDeviceDummy *)output_device->backend_data; - soundio_dummy_ring_buffer_clear(&opd->ring_buffer); + soundio_ring_buffer_clear(&opd->ring_buffer); } static int input_device_init_dummy(SoundIo *soundio, diff --git a/src/dummy_ring_buffer.cpp b/src/dummy_ring_buffer.cpp deleted file mode 100644 index f7e5511..0000000 --- a/src/dummy_ring_buffer.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "dummy_ring_buffer.hpp" -#include "soundio.hpp" -#include - -int soundio_dummy_ring_buffer_create(int requested_capacity, struct SoundIoDummyRingBuffer **out) { - *out = nullptr; - SoundIoDummyRingBuffer *rb = create(); - - if (!rb) { - soundio_dummy_ring_buffer_destroy(rb); - return SoundIoErrorNoMem; - } - - int err; - if ((err = soundio_dummy_ring_buffer_init(rb, requested_capacity))) { - soundio_dummy_ring_buffer_destroy(rb); - return SoundIoErrorNoMem; - } - - *out = rb; - return 0; -} - -int soundio_dummy_ring_buffer_init(struct SoundIoDummyRingBuffer *rb, int requested_capacity) { - // round size up to the nearest power of two - rb->capacity = powf(2, ceilf(log2(requested_capacity))); - - rb->address = allocate_nonzero(rb->capacity); - if (!rb->address) { - soundio_dummy_ring_buffer_deinit(rb); - return SoundIoErrorNoMem; - } - - return 0; -} - -void soundio_dummy_ring_buffer_destroy(struct SoundIoDummyRingBuffer *rb) { - if (!rb) - return; - - soundio_dummy_ring_buffer_deinit(rb); - - destroy(rb); -} - -void soundio_dummy_ring_buffer_deinit(struct SoundIoDummyRingBuffer *rb) { - deallocate(rb->address, rb->capacity); - rb->address = nullptr; -} - -void soundio_dummy_ring_buffer_clear(struct SoundIoDummyRingBuffer *rb) { - rb->write_offset.store(rb->read_offset.load()); -} - -int soundio_dummy_ring_buffer_free_count(struct SoundIoDummyRingBuffer *rb) { - return rb->capacity - soundio_dummy_ring_buffer_fill_count(rb); -} - -int soundio_dummy_ring_buffer_fill_count(struct SoundIoDummyRingBuffer *rb) { - int count = rb->write_offset - rb->read_offset; - assert(count >= 0); - assert(count <= rb->capacity); - return count; -} - -void soundio_dummy_ring_buffer_advance_write_ptr(struct SoundIoDummyRingBuffer *rb, int count) { - rb->write_offset += count; - assert(soundio_dummy_ring_buffer_fill_count(rb) >= 0); -} - -void soundio_dummy_ring_buffer_advance_read_ptr(struct SoundIoDummyRingBuffer *rb, int count) { - rb->read_offset += count; - assert(soundio_dummy_ring_buffer_fill_count(rb) >= 0); -} - diff --git a/src/dummy_ring_buffer.hpp b/src/dummy_ring_buffer.hpp deleted file mode 100644 index 830445c..0000000 --- a/src/dummy_ring_buffer.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef SOUNDIO_DUMMY_RING_BUFFER_HPP -#define SOUNDIO_DUMMY_RING_BUFFER_HPP - -#include "util.hpp" -#include "atomics.hpp" - -struct SoundIoDummyRingBuffer { - char *address; - long capacity; - atomic_long write_offset; - atomic_long read_offset; -}; - -int soundio_dummy_ring_buffer_create(int requested_capacity, struct SoundIoDummyRingBuffer **out); -void soundio_dummy_ring_buffer_destroy(struct SoundIoDummyRingBuffer *rb); -int soundio_dummy_ring_buffer_init(struct SoundIoDummyRingBuffer *rb, int requested_capacity); -void soundio_dummy_ring_buffer_deinit(struct SoundIoDummyRingBuffer *rb); - -// must be called by the writer -void soundio_dummy_ring_buffer_clear(struct SoundIoDummyRingBuffer *rb); - - -// how much is available, ready for writing -int soundio_dummy_ring_buffer_free_count(struct SoundIoDummyRingBuffer *rb); - -// how much of the buffer is used, ready for reading -int soundio_dummy_ring_buffer_fill_count(struct SoundIoDummyRingBuffer *rb); - -void soundio_dummy_ring_buffer_advance_write_ptr(struct SoundIoDummyRingBuffer *rb, int count); -void soundio_dummy_ring_buffer_advance_read_ptr(struct SoundIoDummyRingBuffer *rb, int count); - -#endif - diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index f5f8df4..b431b3e 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -720,7 +720,8 @@ static void recording_stream_state_callback(pa_stream *stream, void *userdata) { ord->stream_ready = true; break; case PA_STREAM_FAILED: - soundio_panic("pulseaudio stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); + soundio_panic("pulseaudio stream error: %s", + pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); break; } } @@ -730,10 +731,39 @@ static void recording_stream_read_callback(pa_stream *stream, size_t nbytes, voi input_device->read_callback(input_device); } -static int input_device_init_pa(SoundIo *soundio, +static void input_device_destroy_pa(SoundIo *soundio, SoundIoInputDevice *input_device) { SoundIoInputDevicePulseAudio *ord = (SoundIoInputDevicePulseAudio *)input_device->backend_data; + if (!ord) + return; + + SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)soundio->backend_data; + pa_stream *stream = ord->stream; + if (stream) { + pa_threaded_mainloop_lock(sipa->main_loop); + + pa_stream_set_state_callback(stream, nullptr, nullptr); + pa_stream_set_read_callback(stream, nullptr, nullptr); + pa_stream_disconnect(stream); + pa_stream_unref(stream); + + pa_threaded_mainloop_unlock(sipa->main_loop); + + ord->stream = nullptr; + } +} + +static int input_device_init_pa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + SoundIoInputDevicePulseAudio *ord = create(); + if (!ord) { + input_device_destroy_pa(soundio, input_device); + return SoundIoErrorNoMem; + } + input_device->backend_data = ord; + SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)soundio->backend_data; SoundIoDevice *device = input_device->device; ord->stream_ready = false; @@ -750,6 +780,7 @@ static int input_device_init_pa(SoundIo *soundio, ord->stream = pa_stream_new(sipa->pulse_context, "SoundIo", &sample_spec, &channel_map); if (!input_device) { pa_threaded_mainloop_unlock(sipa->main_loop); + input_device_destroy_pa(soundio, input_device); return SoundIoErrorNoMem; } @@ -773,26 +804,6 @@ static int input_device_init_pa(SoundIo *soundio, return 0; } -static void input_device_destroy_pa(SoundIo *soundio, - SoundIoInputDevice *input_device) -{ - SoundIoInputDevicePulseAudio *ord = (SoundIoInputDevicePulseAudio *)input_device->backend_data; - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)soundio->backend_data; - pa_stream *stream = ord->stream; - if (stream) { - pa_threaded_mainloop_lock(sipa->main_loop); - - pa_stream_set_state_callback(stream, nullptr, nullptr); - pa_stream_set_read_callback(stream, nullptr, nullptr); - pa_stream_disconnect(stream); - pa_stream_unref(stream); - - pa_threaded_mainloop_unlock(sipa->main_loop); - - ord->stream = nullptr; - } -} - static int input_device_start_pa(SoundIo *soundio, SoundIoInputDevice *input_device) { diff --git a/src/ring_buffer.cpp b/src/ring_buffer.cpp new file mode 100644 index 0000000..e83ab76 --- /dev/null +++ b/src/ring_buffer.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "ring_buffer.hpp" +#include "soundio.hpp" +#include "util.hpp" + +#include +#include +#include +#include +#include +#include + +struct SoundIoRingBuffer *soundio_ring_buffer_create(struct SoundIo *soundio, int requested_capacity) { + SoundIoRingBuffer *rb = create(); + + if (!rb) { + soundio_ring_buffer_destroy(rb); + return nullptr; + } + + if (soundio_ring_buffer_init(rb, requested_capacity)) { + soundio_ring_buffer_destroy(rb); + return nullptr; + } + + return rb; +} + +void soundio_ring_buffer_destroy(struct SoundIoRingBuffer *rb) { + if (!rb) + return; + + soundio_ring_buffer_deinit(rb); + + destroy(rb); +} + +int soundio_ring_buffer_capacity(struct SoundIoRingBuffer *rb) { + return rb->capacity; +} + +char *soundio_ring_buffer_write_ptr(struct SoundIoRingBuffer *rb) { + return rb->address + (rb->write_offset % rb->capacity); +} + +void soundio_ring_buffer_advance_write_ptr(struct SoundIoRingBuffer *rb, int count) { + rb->write_offset += count; + assert(soundio_ring_buffer_fill_count(rb) >= 0); +} + +char *soundio_ring_buffer_read_ptr(struct SoundIoRingBuffer *rb) { + return rb->address + (rb->read_offset % rb->capacity); +} + +void soundio_ring_buffer_advance_read_ptr(struct SoundIoRingBuffer *rb, int count) { + rb->read_offset += count; + assert(soundio_ring_buffer_fill_count(rb) >= 0); +} + +int soundio_ring_buffer_fill_count(struct SoundIoRingBuffer *rb) { + int count = rb->write_offset - rb->read_offset; + assert(count >= 0); + assert(count <= rb->capacity); + return count; +} + +int soundio_ring_buffer_free_count(struct SoundIoRingBuffer *rb) { + return rb->capacity - soundio_ring_buffer_fill_count(rb); +} + +void soundio_ring_buffer_clear(struct SoundIoRingBuffer *rb) { + return rb->write_offset.store(rb->read_offset.load()); +} + +int soundio_ring_buffer_init(struct SoundIoRingBuffer *rb, int requested_capacity) { + // round size up to the nearest power of two + int pow2_size = powf(2, ceilf(log2(requested_capacity))); + // at minimum must be page size + int page_size = getpagesize(); + rb->capacity = max(pow2_size, page_size); + + rb->write_offset = 0; + rb->read_offset = 0; + + char shm_path[] = "/dev/shm/ring-buffer-XXXXXX"; + int fd = mkstemp(shm_path); + if (fd < 0) + return SoundIoErrorSystemResources; + + if (unlink(shm_path)) + return SoundIoErrorSystemResources; + + if (ftruncate(fd, rb->capacity)) + return SoundIoErrorNoMem; + + rb->address = (char*)mmap(NULL, rb->capacity * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (rb->address == MAP_FAILED) + return SoundIoErrorNoMem; + + char *other_address = (char*)mmap(rb->address, rb->capacity, + PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, 0); + if (other_address != rb->address) + return SoundIoErrorNoMem; + + other_address = (char*)mmap(rb->address + rb->capacity, rb->capacity, + PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, 0); + if (other_address != rb->address + rb->capacity) + return SoundIoErrorNoMem; + + if (close(fd)) + return SoundIoErrorSystemResources; + + return 0; +} + +void soundio_ring_buffer_deinit(struct SoundIoRingBuffer *rb) { + if (munmap(rb->address, 2 * rb->capacity)) + soundio_panic("munmap failed: %s", strerror(errno)); +} diff --git a/src/ring_buffer.hpp b/src/ring_buffer.hpp new file mode 100644 index 0000000..96d1953 --- /dev/null +++ b/src/ring_buffer.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef SOUNDIO_RING_BUFFER_HPP +#define SOUNDIO_RING_BUFFER_HPP + +#include "atomics.hpp" + +struct SoundIoRingBuffer { + char *address; + long capacity; + atomic_long write_offset; + atomic_long read_offset; +}; + +int soundio_ring_buffer_init(struct SoundIoRingBuffer *rb, int requested_capacity); +void soundio_ring_buffer_deinit(struct SoundIoRingBuffer *rb); + +#endif diff --git a/src/soundio.cpp b/src/soundio.cpp index be77d71..7a108e2 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -336,3 +336,54 @@ int soundio_output_device_start(struct SoundIoOutputDevice *output_device) { SoundIo *soundio = output_device->device->soundio; return soundio->output_device_start(soundio, output_device); } + +int soundio_input_device_create(struct SoundIoDevice *device, + enum SoundIoSampleFormat sample_format, double latency, void *userdata, + void (*read_callback)(struct SoundIoInputDevice *), + struct SoundIoInputDevice **out_input_device) +{ + *out_input_device = nullptr; + + SoundIoInputDevice *sid = create(); + if (!sid) { + soundio_input_device_destroy(sid); + return SoundIoErrorNoMem; + } + + soundio_device_ref(device); + sid->device = device; + sid->userdata = userdata; + sid->read_callback = read_callback; + sid->sample_format = sample_format; + sid->latency = latency; + sid->bytes_per_frame = soundio_get_bytes_per_frame(sample_format, + device->channel_layout.channel_count); + + SoundIo *soundio = device->soundio; + int err = soundio->input_device_init(soundio, sid); + if (err) { + soundio_input_device_destroy(sid); + return err; + } + + *out_input_device = sid; + return 0; +} + +int soundio_input_device_start(struct SoundIoInputDevice *input_device) { + SoundIo *soundio = input_device->device->soundio; + return soundio->input_device_start(soundio, input_device); +} + +void soundio_input_device_destroy(struct SoundIoInputDevice *input_device) { + if (!input_device) + return; + + SoundIo *soundio = input_device->device->soundio; + + if (soundio->input_device_destroy) + soundio->input_device_destroy(soundio, input_device); + + soundio_device_unref(input_device->device); + destroy(input_device); +} diff --git a/src/soundio.h b/src/soundio.h index 520f681..a24d1cc 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -309,16 +309,41 @@ void soundio_output_device_clear_buffer(struct SoundIoOutputDevice *output_devic int soundio_input_device_create(struct SoundIoDevice *device, enum SoundIoSampleFormat sample_format, double latency, void *userdata, - void (*read_callback)(struct SoundIoOutputDevice *), - struct SoundIoOutputDevice **out_input_device); -void soundio_input_device_destroy(struct SoundIoOutputDevice *input_device); + void (*read_callback)(struct SoundIoInputDevice *), + struct SoundIoInputDevice **out_input_device); +void soundio_input_device_destroy(struct SoundIoInputDevice *input_device); -int soundio_input_device_start(struct SoundIoOutputDevice *input_device); -void soundio_input_device_peek(struct SoundIoOutputDevice *input_device, +int soundio_input_device_start(struct SoundIoInputDevice *input_device); +void soundio_input_device_peek(struct SoundIoInputDevice *input_device, const char **data, int *frame_count); -void soundio_input_device_drop(struct SoundIoOutputDevice *input_device); +void soundio_input_device_drop(struct SoundIoInputDevice *input_device); + +void soundio_input_device_clear_buffer(struct SoundIoInputDevice *input_device); + + +// Ring Buffer +struct SoundIoRingBuffer; +struct SoundIoRingBuffer *soundio_ring_buffer_create(struct SoundIo *soundio, int requested_capacity); +void soundio_ring_buffer_destroy(struct SoundIoRingBuffer *ring_buffer); +int soundio_ring_buffer_capacity(struct SoundIoRingBuffer *ring_buffer); + +// don't write more than capacity +char *soundio_ring_buffer_write_ptr(struct SoundIoRingBuffer *ring_buffer); +void soundio_ring_buffer_advance_write_ptr(struct SoundIoRingBuffer *ring_buffer, int count); + +// don't read more than capacity +char *soundio_ring_buffer_read_ptr(struct SoundIoRingBuffer *ring_buffer); +void soundio_ring_buffer_advance_read_ptr(struct SoundIoRingBuffer *ring_buffer, int count); + +// how much of the buffer is used, ready for reading +int soundio_ring_buffer_fill_count(struct SoundIoRingBuffer *ring_buffer); + +// how much is available, ready for writing +int soundio_ring_buffer_free_count(struct SoundIoRingBuffer *ring_buffer); + +// must be called by the writer +void soundio_ring_buffer_clear(struct SoundIoRingBuffer *ring_buffer); -void soundio_input_device_clear_buffer(struct SoundIoOutputDevice *input_device); #ifdef __cplusplus } diff --git a/test/unit_tests.cpp b/test/unit_tests.cpp index d0656bb..9574735 100644 --- a/test/unit_tests.cpp +++ b/test/unit_tests.cpp @@ -1,8 +1,9 @@ #undef NDEBUG -#include "soundio.h" +#include "soundio.hpp" #include "os.hpp" #include "util.hpp" +#include "atomics.hpp" #include #include @@ -43,6 +44,116 @@ static void test_create_output_device(void) { soundio_destroy(soundio); } + +static void test_ring_buffer_basic(void) { + struct SoundIo *soundio = soundio_create(); + assert(soundio); + SoundIoRingBuffer *rb = soundio_ring_buffer_create(soundio, 10); + assert(rb); + + assert(soundio_ring_buffer_capacity(rb) == 4096); + + char *write_ptr = soundio_ring_buffer_write_ptr(rb); + int amt = sprintf(write_ptr, "hello") + 1; + soundio_ring_buffer_advance_write_ptr(rb, amt); + + assert(soundio_ring_buffer_fill_count(rb) == amt); + assert(soundio_ring_buffer_free_count(rb) == 4096 - amt); + + char *read_ptr = soundio_ring_buffer_read_ptr(rb); + + assert(strcmp(read_ptr, "hello") == 0); + + soundio_ring_buffer_advance_read_ptr(rb, amt); + + assert(soundio_ring_buffer_fill_count(rb) == 0); + assert(soundio_ring_buffer_free_count(rb) == soundio_ring_buffer_capacity(rb)); + + soundio_ring_buffer_advance_write_ptr(rb, 4094); + soundio_ring_buffer_advance_read_ptr(rb, 4094); + amt = sprintf(soundio_ring_buffer_write_ptr(rb), "writing past the end") + 1; + soundio_ring_buffer_advance_write_ptr(rb, amt); + + assert(soundio_ring_buffer_fill_count(rb) == amt); + + assert(strcmp(soundio_ring_buffer_read_ptr(rb), "writing past the end") == 0); + + soundio_ring_buffer_advance_read_ptr(rb, amt); + + assert(soundio_ring_buffer_fill_count(rb) == 0); + assert(soundio_ring_buffer_free_count(rb) == soundio_ring_buffer_capacity(rb)); + soundio_ring_buffer_destroy(rb); + soundio_destroy(soundio); +} + +static SoundIoRingBuffer *rb = nullptr; +static const int rb_size = 3528; +static long expected_write_head; +static long expected_read_head; +static atomic_bool rb_done; +static atomic_int rb_write_it; +static atomic_int rb_read_it; + +// just for testing purposes; does not need to be high quality random +static double random_double(void) { + return ((double)rand() / (double)RAND_MAX); +} + +static void reader_thread_run(void *) { + while (!rb_done) { + rb_read_it += 1; + int fill_count = soundio_ring_buffer_fill_count(rb); + assert(fill_count >= 0); + assert(fill_count <= rb_size); + int amount_to_read = min((int)(random_double() * 2.0 * fill_count), fill_count); + soundio_ring_buffer_advance_read_ptr(rb, amount_to_read); + expected_read_head += amount_to_read; + } +} + +static void writer_thread_run(void *) { + while (!rb_done) { + rb_write_it += 1; + int fill_count = soundio_ring_buffer_fill_count(rb); + assert(fill_count >= 0); + assert(fill_count <= rb_size); + int free_count = rb_size - fill_count; + assert(free_count >= 0); + assert(free_count <= rb_size); + int value = min((int)(random_double() * 2.0 * free_count), free_count); + soundio_ring_buffer_advance_write_ptr(rb, value); + expected_write_head += value; + } +} + +static void test_ring_buffer_threaded(void) { + struct SoundIo *soundio = soundio_create(); + assert(soundio); + rb = soundio_ring_buffer_create(soundio, rb_size); + expected_write_head = 0; + expected_read_head = 0; + rb_read_it = 0; + rb_write_it = 0; + rb_done = false; + + SoundIoOsThread *reader_thread; + ok_or_panic(soundio_os_thread_create(reader_thread_run, nullptr, false, &reader_thread)); + + SoundIoOsThread *writer_thread; + ok_or_panic(soundio_os_thread_create(writer_thread_run, nullptr, false, &writer_thread)); + + while (rb_read_it < 100000 || rb_write_it < 100000) {} + rb_done = true; + + soundio_os_thread_destroy(reader_thread); + soundio_os_thread_destroy(writer_thread); + + int fill_count = soundio_ring_buffer_fill_count(rb); + int expected_fill_count = expected_write_head - expected_read_head; + assert(fill_count == expected_fill_count); + soundio_destroy(soundio); +} + struct Test { const char *name; void (*fn)(void); @@ -51,6 +162,8 @@ struct Test { static struct Test tests[] = { {"os_get_time", test_os_get_time}, {"create output device", test_create_output_device}, + {"ring buffer basic", test_ring_buffer_basic}, + {"ring buffer threaded", test_ring_buffer_threaded}, {NULL, NULL}, };