diff --git a/CMakeLists.txt b/CMakeLists.txt index bc34eaa..b5ed2d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ set(LIBSOUNDIO_HEADERS ) set(TEST_SOURCES - "${CMAKE_SOURCE_DIR}/test/unit_tests.c" + "${CMAKE_SOURCE_DIR}/test/unit_tests.cpp" ) find_package(Threads) diff --git a/example/list_devices.c b/example/list_devices.c index 4d190a6..f542b2d 100644 --- a/example/list_devices.c +++ b/example/list_devices.c @@ -18,6 +18,35 @@ static int usage(char *exe) { return 1; } +static void print_channel_layout(const struct SoundIoChannelLayout *layout) { + if (layout->name) { + fprintf(stderr, "%s", layout->name); + } else { + fprintf(stderr, "%s", soundio_get_channel_name(layout->channels[0])); + for (int i = 1; i < layout->channel_count; i += 1) { + fprintf(stderr, ", %s", soundio_get_channel_name(layout->channels[i])); + } + } + +} + +static void print_device(struct SoundIoDevice *device, bool is_default) { + const char *purpose_str; + const char *default_str; + if (soundio_device_purpose(device) == SoundIoDevicePurposeOutput) { + purpose_str = "playback"; + default_str = is_default ? " (default)" : ""; + } else { + purpose_str = "recording"; + default_str = is_default ? " (default)" : ""; + } + const char *description = soundio_device_description(device); + int sample_rate = soundio_device_sample_rate(device); + fprintf(stderr, "%s device: ", purpose_str); + print_channel_layout(soundio_device_channel_layout(device)); + fprintf(stderr, " %d Hz %s%s\n", sample_rate, description, default_str); +} + static int list_devices(struct SoundIo *soundio) { int output_count = soundio_get_output_device_count(soundio); int input_count = soundio_get_input_device_count(soundio); @@ -27,21 +56,12 @@ static int list_devices(struct SoundIo *soundio) { for (int i = 0; i < input_count; i += 1) { struct SoundIoDevice *device = soundio_get_input_device(soundio, i); - const char *purpose_str = "input"; - const char *default_str = (i == default_input) ? " (default)" : ""; - const char *description = soundio_device_description(device); - int sample_rate = soundio_device_sample_rate(device); - fprintf(stderr, "%s device: %d Hz %s%s\n", purpose_str, sample_rate, description, default_str); + print_device(device, default_input == i); soundio_device_unref(device); } for (int i = 0; i < output_count; i += 1) { struct SoundIoDevice *device = soundio_get_output_device(soundio, i); - - const char *purpose_str = "output"; - const char *default_str = (i == default_output) ? " (default)" : ""; - const char *description = soundio_device_description(device); - int sample_rate = soundio_device_sample_rate(device); - fprintf(stderr, "%s device: %d Hz %s%s\n", purpose_str, sample_rate, description, default_str); + print_device(device, default_output == i); soundio_device_unref(device); } diff --git a/src/dummy.cpp b/src/dummy.cpp index fa53e53..5dd6ee3 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -7,9 +7,33 @@ #include "dummy.hpp" #include "soundio.hpp" +#include "dummy_ring_buffer.hpp" +#include "os.hpp" #include #include +#include +using std::atomic_flag; + +struct SoundIoOutputDeviceDummy { + struct SoundIoOsThread *thread; + struct SoundIoOsMutex *mutex; + struct SoundIoOsCond *cond; + atomic_flag abort_flag; + int buffer_size; + double period; + struct SoundIoDummyRingBuffer ring_buffer; +}; + +struct SoundIoInputDeviceDummy { + // TODO +}; + +struct SoundIoDummy { + SoundIoOsMutex *mutex; + SoundIoOsCond *cond; + bool devices_emitted; +}; static void playback_thread_run(void *arg) { SoundIoOutputDevice *output_device = (SoundIoOutputDevice *)arg; @@ -55,11 +79,49 @@ static void recording_thread_run(void *arg) { */ static void destroy_dummy(SoundIo *soundio) { - destroy(soundio->safe_devices_info); - soundio->safe_devices_info = nullptr; + SoundIoDummy *sid = (SoundIoDummy *)soundio->backend_data; + if (!sid) + return; + + if (sid->cond) + soundio_os_cond_destroy(sid->cond); + + if (sid->mutex) + soundio_os_mutex_destroy(sid->mutex); + + if (soundio->safe_devices_info) { + for (int i = 0; i < soundio->safe_devices_info->input_devices.length; i += 1) + soundio_device_unref(soundio->safe_devices_info->input_devices.at(i)); + for (int i = 0; i < soundio->safe_devices_info->output_devices.length; i += 1) + soundio_device_unref(soundio->safe_devices_info->output_devices.at(i)); + destroy(soundio->safe_devices_info); + soundio->safe_devices_info = nullptr; + } + } -static void flush_events(SoundIo *soundio) { } +static void flush_events(SoundIo *soundio) { + SoundIoDummy *sid = (SoundIoDummy *)soundio->backend_data; + if (sid->devices_emitted) + return; + sid->devices_emitted = true; + soundio->on_devices_change(soundio); +} + +static void wait_events(SoundIo *soundio) { + SoundIoDummy *sid = (SoundIoDummy *)soundio->backend_data; + flush_events(soundio); + soundio_os_mutex_lock(sid->mutex); + soundio_os_cond_wait(sid->cond, sid->mutex); + soundio_os_mutex_unlock(sid->mutex); +} + +static void wakeup(SoundIo *soundio) { + SoundIoDummy *sid = (SoundIoDummy *)soundio->backend_data; + soundio_os_mutex_lock(sid->mutex); + soundio_os_cond_signal(sid->cond); + soundio_os_mutex_unlock(sid->mutex); +} static void refresh_devices(SoundIo *soundio) { } @@ -201,6 +263,26 @@ static void input_device_clear_buffer_dummy(SoundIo *soundio, } int soundio_dummy_init(SoundIo *soundio) { + assert(!soundio->backend_data); + SoundIoDummy *sid = create(); + if (!sid) { + destroy_dummy(soundio); + return SoundIoErrorNoMem; + } + soundio->backend_data = sid; + + sid->mutex = soundio_os_mutex_create(); + if (!sid->mutex) { + destroy_dummy(soundio); + return SoundIoErrorNoMem; + } + + sid->cond = soundio_os_cond_create(); + if (!sid->cond) { + destroy_dummy(soundio); + return SoundIoErrorNoMem; + } + assert(!soundio->safe_devices_info); soundio->safe_devices_info = create(); if (!soundio->safe_devices_info) { @@ -214,8 +296,10 @@ int soundio_dummy_init(SoundIo *soundio) { // create output device { SoundIoDevice *device = create(); - if (!device) + if (!device) { + destroy_dummy(soundio); return SoundIoErrorNoMem; + } device->ref_count = 1; device->soundio = soundio; @@ -224,6 +308,7 @@ int soundio_dummy_init(SoundIo *soundio) { if (!device->name || !device->description) { free(device->name); free(device->description); + destroy_dummy(soundio); return SoundIoErrorNoMem; } device->channel_layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); @@ -234,6 +319,7 @@ int soundio_dummy_init(SoundIo *soundio) { if (soundio->safe_devices_info->output_devices.append(device)) { soundio_device_unref(device); + destroy_dummy(soundio); return SoundIoErrorNoMem; } } @@ -241,8 +327,10 @@ int soundio_dummy_init(SoundIo *soundio) { // create input device { SoundIoDevice *device = create(); - if (!device) + if (!device) { + destroy_dummy(soundio); return SoundIoErrorNoMem; + } device->ref_count = 1; device->soundio = soundio; @@ -251,6 +339,7 @@ int soundio_dummy_init(SoundIo *soundio) { if (!device->name || !device->description) { free(device->name); free(device->description); + destroy_dummy(soundio); return SoundIoErrorNoMem; } device->channel_layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdMono); @@ -261,6 +350,7 @@ int soundio_dummy_init(SoundIo *soundio) { if (soundio->safe_devices_info->input_devices.append(device)) { soundio_device_unref(device); + destroy_dummy(soundio); return SoundIoErrorNoMem; } } @@ -268,6 +358,8 @@ int soundio_dummy_init(SoundIo *soundio) { soundio->destroy = destroy_dummy; soundio->flush_events = flush_events; + soundio->wait_events = wait_events; + soundio->wakeup = wakeup; soundio->refresh_devices = refresh_devices; soundio->output_device_init = output_device_init_dummy; diff --git a/src/dummy.hpp b/src/dummy.hpp index 5777976..603832e 100644 --- a/src/dummy.hpp +++ b/src/dummy.hpp @@ -8,27 +8,6 @@ #ifndef SOUNDIO_DUMMY_HPP #define SOUNDIO_DUMMY_HPP -#include "os.hpp" -#include "dummy_ring_buffer.hpp" -#include -using std::atomic_flag; - -struct SoundIoOutputDeviceDummy { - struct SoundIoOsThread *thread; - struct SoundIoOsMutex *mutex; - struct SoundIoOsCond *cond; - atomic_flag abort_flag; - int buffer_size; - double period; - struct SoundIoDummyRingBuffer ring_buffer; -}; - -struct SoundIoInputDeviceDummy { - // TODO -}; - int soundio_dummy_init(struct SoundIo *soundio); #endif - - diff --git a/src/list.hpp b/src/list.hpp index 8c9ecfe..fc0da0d 100644 --- a/src/list.hpp +++ b/src/list.hpp @@ -70,49 +70,6 @@ struct SoundIoList { return 0; } - T swap_remove(int index) { - assert(index >= 0); - assert(index < length); - if (index == length - 1) - return pop(); - - T last = pop(); - T item = items[index]; - items[index] = last; - return item; - } - - void remove_range(int start, int end) { - assert(0 <= start); - assert(start <= end); - assert(end <= length); - int del_count = end - start; - for (int i = start; i < length - del_count; i += 1) { - items[i] = items[i + del_count]; - } - length -= del_count; - } - - int __attribute__((warn_unused_result)) insert_space(int pos, int size) { - int old_length = length; - assert(pos >= 0 && pos <= old_length); - int err = resize(old_length + size); - if (err) - return err; - - for (int i = old_length - 1; i >= pos; i -= 1) { - items[i + size] = items[i]; - } - - return 0; - } - - void fill(T value) { - for (int i = 0; i < length; i += 1) { - items[i] = value; - } - } - void clear() { length = 0; } @@ -131,10 +88,6 @@ struct SoundIoList { return 0; } - int allocated_size() const { - return capacity * sizeof(T); - } - T * items; int length; int capacity; diff --git a/src/os.cpp b/src/os.cpp index 76322f2..ebfef8e 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -249,3 +249,13 @@ void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond, assert(err != EINVAL); } } + +void soundio_os_cond_wait(struct SoundIoOsCond *cond, + struct SoundIoOsMutex *mutex) +{ + int err; + if ((err = pthread_cond_wait(&cond->id, &mutex->id))) { + assert(err != EPERM); + assert(err != EINVAL); + } +} diff --git a/src/os.hpp b/src/os.hpp index c2a7ecc..b221e11 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -35,5 +35,7 @@ void soundio_os_cond_broadcast(struct SoundIoOsCond *cond); void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond, struct SoundIoOsMutex *mutex, double seconds); +void soundio_os_cond_wait(struct SoundIoOsCond *cond, + struct SoundIoOsMutex *mutex); #endif diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index e6d060d..41f7d99 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -4,6 +4,48 @@ #include #include +#include +#include +using std::atomic_bool; + +struct SoundIoOutputDevicePulseAudio { + pa_stream *stream; + atomic_bool stream_ready; + pa_buffer_attr buffer_attr; +}; + +struct SoundIoInputDevicePulseAudio { + pa_stream *stream; + atomic_bool stream_ready; + pa_buffer_attr buffer_attr; +}; + +struct SoundIoPulseAudio { + bool connection_refused; + + pa_context *pulse_context; + atomic_bool device_scan_queued; + + // the one that we're working on building + struct SoundIoDevicesInfo *current_devices_info; + char * default_sink_name; + char * default_source_name; + + // this one is ready to be read with flush_events. protected by mutex + struct SoundIoDevicesInfo *ready_devices_info; + + bool have_sink_list; + bool have_source_list; + bool have_default_sink; + + atomic_bool ready_flag; + atomic_bool have_devices_flag; + + pa_threaded_mainloop *main_loop; + pa_proplist *props; +}; + + static void subscribe_callback(pa_context *context, pa_subscription_event_type_t event_bits, uint32_t index, void *userdata) { @@ -87,6 +129,9 @@ static void destroy_ready_devices_info(SoundIo *soundio) { static void destroy_pa(SoundIo *soundio) { SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + if (!ah) + return; + if (ah->main_loop) pa_threaded_mainloop_stop(ah->main_loop); @@ -104,6 +149,9 @@ static void destroy_pa(SoundIo *soundio) { free(ah->default_sink_name); free(ah->default_source_name); + + destroy(ah); + soundio->backend_data = nullptr; } static double usec_to_sec(pa_usec_t usec) { @@ -797,7 +845,13 @@ static void refresh_devices(SoundIo *soundio) { } int soundio_pulseaudio_init(SoundIo *soundio) { - SoundIoPulseAudio *ah = (SoundIoPulseAudio *)soundio->backend_data; + assert(!soundio->backend_data); + SoundIoPulseAudio *ah = create(); + if (!ah) { + destroy_pa(soundio); + return SoundIoErrorNoMem; + } + soundio->backend_data = ah; ah->connection_refused = false; ah->device_scan_queued = false; diff --git a/src/pulseaudio.hpp b/src/pulseaudio.hpp index 9ca84a2..f1480f3 100644 --- a/src/pulseaudio.hpp +++ b/src/pulseaudio.hpp @@ -8,47 +8,6 @@ #ifndef SOUNDIO_PULSEAUDIO_HPP #define SOUNDIO_PULSEAUDIO_HPP -#include -#include -using std::atomic_bool; - -struct SoundIoOutputDevicePulseAudio { - pa_stream *stream; - atomic_bool stream_ready; - pa_buffer_attr buffer_attr; -}; - -struct SoundIoInputDevicePulseAudio { - pa_stream *stream; - atomic_bool stream_ready; - pa_buffer_attr buffer_attr; -}; - -struct SoundIoPulseAudio { - bool connection_refused; - - pa_context *pulse_context; - atomic_bool device_scan_queued; - - // the one that we're working on building - struct SoundIoDevicesInfo *current_devices_info; - char * default_sink_name; - char * default_source_name; - - // this one is ready to be read with flush_events. protected by mutex - struct SoundIoDevicesInfo *ready_devices_info; - - bool have_sink_list; - bool have_source_list; - bool have_default_sink; - - atomic_bool ready_flag; - atomic_bool have_devices_flag; - - pa_threaded_mainloop *main_loop; - pa_proplist *props; -}; - int soundio_pulseaudio_init(struct SoundIo *soundio); #endif diff --git a/src/soundio.cpp b/src/soundio.cpp index e89be11..b8b6bf5 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -10,6 +10,7 @@ #include "dummy.hpp" #include "pulseaudio.hpp" +#include #include const char *soundio_error_string(int error) { @@ -138,3 +139,85 @@ struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int ind soundio_device_ref(device); return device; } + +void soundio_device_ref(struct SoundIoDevice *device); +void soundio_device_unref(struct SoundIoDevice *device); + +// the name is the identifier for the device. UTF-8 encoded +const char *soundio_device_name(const struct SoundIoDevice *device) { + return device->name; +} + +// UTF-8 encoded +const char *soundio_device_description(const struct SoundIoDevice *device) { + return device->description; +} + +enum SoundIoDevicePurpose soundio_device_purpose(const struct SoundIoDevice *device) { + return device->purpose; +} + +const struct SoundIoChannelLayout *soundio_device_channel_layout(const struct SoundIoDevice *device) { + return &device->channel_layout; +} + +int soundio_device_sample_rate(const struct SoundIoDevice *device) { + return device->default_sample_rate; +} + +void soundio_device_unref(struct SoundIoDevice *device) { + if (!device) + return; + + device->ref_count -= 1; + assert(device->ref_count >= 0); + + if (device->ref_count == 0) { + free(device->name); + free(device->description); + destroy(device); + } +} + +void soundio_device_ref(struct SoundIoDevice *device) { + device->ref_count += 1; +} + +void soundio_wait_events(struct SoundIo *soundio) { + soundio->wait_events(soundio); +} + +void soundio_wakeup(struct SoundIo *soundio) { + soundio->wakeup(soundio); +} + +void soundio_output_device_fill_with_silence(struct SoundIoOutputDevice *output_device) { + char *buffer; + int requested_frame_count = soundio_output_device_free_count(output_device); + while (requested_frame_count > 0) { + int frame_count = requested_frame_count; + soundio_output_device_begin_write(output_device, &buffer, &frame_count); + memset(buffer, 0, frame_count * output_device->bytes_per_frame); + soundio_output_device_write(output_device, buffer, frame_count); + requested_frame_count -= frame_count; + } +} + +int soundio_output_device_free_count(struct SoundIoOutputDevice *output_device) { + SoundIo *soundio = output_device->device->soundio; + return soundio->output_device_free_count(soundio, output_device); +} + +void soundio_output_device_begin_write(struct SoundIoOutputDevice *output_device, + char **data, int *frame_count) +{ + SoundIo *soundio = output_device->device->soundio; + soundio->output_device_begin_write(soundio, output_device, data, frame_count); +} + +void soundio_output_device_write(struct SoundIoOutputDevice *output_device, + char *data, int frame_count) +{ + SoundIo *soundio = output_device->device->soundio; + soundio->output_device_write(soundio, output_device, data, frame_count); +} diff --git a/src/soundio.h b/src/soundio.h index 4eadb99..001e071 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -156,6 +156,8 @@ struct SoundIo { void (*destroy)(struct SoundIo *); void (*flush_events)(struct SoundIo *); + void (*wait_events)(struct SoundIo *); + void (*wakeup)(struct SoundIo *); void (*refresh_devices)(struct SoundIo *); int (*output_device_init)(struct SoundIo *, struct SoundIoOutputDevice *); diff --git a/test/unit_tests.c b/test/unit_tests.cpp similarity index 100% rename from test/unit_tests.c rename to test/unit_tests.cpp