diff --git a/CMakeLists.txt b/CMakeLists.txt index 39b732c..8081ef7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,14 @@ if(SOUNDIO_HAVE_PULSEAUDIO) "${CMAKE_SOURCE_DIR}/src/pulseaudio.cpp" ) endif() +if(SOUNDIO_HAVE_ALSA) + set(LIBSOUNDIO_SOURCES ${LIBSOUNDIO_SOURCES} + "${CMAKE_SOURCE_DIR}/src/alsa.cpp" + ) + set(TEST_SOURCES ${TEST_SOURCES} + "${CMAKE_SOURCE_DIR}/src/alsa.cpp" + ) +endif() # GTFO, -lstdc++ !! diff --git a/README.md b/README.md index 394d949..5cc7613 100644 --- a/README.md +++ b/README.md @@ -115,21 +115,28 @@ view `coverage/index.html` in a browser. ## Roadmap + 0. implement ALSA (Linux) backend, get examples working 0. pipe record to playback example working with dummy linux, osx, windows 0. pipe record to playback example working with pulseaudio linux 0. implement CoreAudio (OSX) backend, get examples working 0. implement DirectSound (Windows) backend, get examples working - 0. implement ALSA (Linux) backend, get examples working 0. implement JACK backend, get examples working 0. Avoid calling `panic` in PulseAudio. 0. implement ASIO (Windows) backend, get examples working 0. clean up API and improve documentation + - make sure every function which can return an error documents which errors + it can return + - consider doing the public/private struct thing and make `backend_data` a + union instead of a `void *` 0. use a documentation generator and host the docs somewhere 0. -fvisibility=hidden and then explicitly export stuff 0. Integrate into libgroove and test with Groove Basin 0. Consider testing on FreeBSD 0. look at microphone example and determine if fewer memcpys can be done with the audio data + - pulseaudio has peek() drop() which sucks, but what if libsoundio lets you + specify how much to peek() and if you don't peek all of it, save the + unused to a buffer for you. ## Planned Uses for libsoundio diff --git a/example/microphone.c b/example/microphone.c index e27172f..5a4214c 100644 --- a/example/microphone.c +++ b/example/microphone.c @@ -52,7 +52,7 @@ int main(int argc, char **argv) { if (default_out_device_index < 0) panic("no output device found"); - int default_in_device_index = soundio_get_default_output_device_index(soundio); + int default_in_device_index = soundio_get_default_input_device_index(soundio); if (default_in_device_index < 0) panic("no output device found"); @@ -64,12 +64,8 @@ int main(int argc, char **argv) { 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)); + fprintf(stderr, "Input device: %s\n", soundio_device_description(in_device)); + fprintf(stderr, "Output device: %s\n", soundio_device_description(out_device)); const struct SoundIoChannelLayout *in_layout = soundio_device_channel_layout(in_device); const struct SoundIoChannelLayout *out_layout = soundio_device_channel_layout(out_device); diff --git a/src/alsa.cpp b/src/alsa.cpp new file mode 100644 index 0000000..e0c4b10 --- /dev/null +++ b/src/alsa.cpp @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#include "alsa.hpp" +#include "soundio.hpp" +#include "os.hpp" +#include "atomics.hpp" + +#include +#include + +static snd_pcm_stream_t stream_types[] = {SND_PCM_STREAM_PLAYBACK, SND_PCM_STREAM_CAPTURE}; + +struct SoundIoAlsa { + SoundIoOsMutex *mutex; + SoundIoOsCond *cond; + + struct SoundIoOsThread *thread; + atomic_flag abort_flag; + int notify_fd; + int notify_wd; + atomic_bool have_devices_flag; + int notify_pipe_fd[2]; + + // this one is ready to be read with flush_events. protected by mutex + struct SoundIoDevicesInfo *ready_devices_info; +}; + +static void wakeup_device_poll(SoundIoAlsa *sia) { + ssize_t amt = write(sia->notify_pipe_fd[1], "a", 1); + if (amt == -1) { + assert(errno != EBADF); + assert(errno != EIO); + assert(errno != ENOSPC); + assert(errno != EPERM); + assert(errno != EPIPE); + } +} + +static void destroy_alsa(SoundIo *soundio) { + SoundIoAlsa *sia = (SoundIoAlsa *)soundio->backend_data; + if (!sia) + return; + + if (sia->thread) { + sia->abort_flag.clear(); + wakeup_device_poll(sia); + soundio_os_thread_destroy(sia->thread); + } + + if (sia->cond) + soundio_os_cond_destroy(sia->cond); + + if (sia->mutex) + soundio_os_mutex_destroy(sia->mutex); + + soundio_destroy_devices_info(sia->ready_devices_info); + + + + close(sia->notify_pipe_fd[0]); + close(sia->notify_pipe_fd[1]); + close(sia->notify_fd); + + destroy(sia); + soundio->backend_data = nullptr; +} + +static int refresh_devices(SoundIo *soundio) { + SoundIoAlsa *sia = (SoundIoAlsa *)soundio->backend_data; + int card_index = -1; + + if (snd_card_next(&card_index) < 0) + return SoundIoErrorSystemResources; + + snd_ctl_card_info_t *card_info; + snd_ctl_card_info_alloca(&card_info); + + snd_pcm_info_t *pcm_info; + snd_pcm_info_alloca(&pcm_info); + + SoundIoDevicesInfo *devices_info = create(); + if (!devices_info) + return SoundIoErrorNoMem; + + while (card_index >= 0) { + int err; + snd_ctl_t *handle; + char name[32]; + sprintf(name, "hw:%d", card_index); + if ((err = snd_ctl_open(&handle, name, 0)) < 0) { + if (err == -ENOENT) { + break; + } else { + destroy(devices_info); + return SoundIoErrorOpeningDevice; + } + } + + if ((err = snd_ctl_card_info(handle, card_info)) < 0) { + snd_ctl_close(handle); + destroy(devices_info); + return SoundIoErrorSystemResources; + } + const char *card_name = snd_ctl_card_info_get_name(card_info); + + int device_index = -1; + for (;;) { + if (snd_ctl_pcm_next_device(handle, &device_index) < 0) { + snd_ctl_close(handle); + destroy(devices_info); + return SoundIoErrorSystemResources; + } + if (device_index < 0) + break; + + snd_pcm_info_set_device(pcm_info, device_index); + snd_pcm_info_set_subdevice(pcm_info, 0); + + for (int stream_type_i = 0; stream_type_i < array_length(stream_types); stream_type_i += 1) { + snd_pcm_stream_t stream = stream_types[stream_type_i]; + snd_pcm_info_set_stream(pcm_info, stream); + + if ((err = snd_ctl_pcm_info(handle, pcm_info)) < 0) { + if (err == -ENOENT) { + continue; + } else { + snd_ctl_close(handle); + destroy(devices_info); + return SoundIoErrorSystemResources; + } + } + + const char *device_name = snd_pcm_info_get_name(pcm_info); + + SoundIoDevice *device = create(); + if (!device) { + snd_ctl_close(handle); + destroy(devices_info); + return SoundIoErrorNoMem; + } + device->ref_count = 1; + device->soundio = soundio; + device->name = soundio_alloc_sprintf(nullptr, "hw:%d,%d,%d", card_index, device_index, 0); + device->description = soundio_alloc_sprintf(nullptr, "%s %s", card_name, device_name); + + if (!device->name || !device->description) { + soundio_device_unref(device); + snd_ctl_close(handle); + destroy(devices_info); + return SoundIoErrorNoMem; + } + + // TODO: device->channel_layout + // TODO: device->default_sample_format + // TODO: device->default_latency + // TODO: device->default_sample_rate + + SoundIoList *device_list; + if (stream == SND_PCM_STREAM_PLAYBACK) { + device->purpose = SoundIoDevicePurposeOutput; + device_list = &devices_info->output_devices; + } else { + assert(stream == SND_PCM_STREAM_CAPTURE); + device->purpose = SoundIoDevicePurposeInput; + device_list = &devices_info->input_devices; + } + + if (device_list->append(device)) { + soundio_device_unref(device); + destroy(devices_info); + return SoundIoErrorNoMem; + } + } + } + snd_ctl_close(handle); + if (snd_card_next(&card_index) < 0) { + destroy(devices_info); + return SoundIoErrorSystemResources; + } + } + + soundio_os_mutex_lock(sia->mutex); + soundio_destroy_devices_info(sia->ready_devices_info); + sia->ready_devices_info = devices_info; + sia->have_devices_flag.store(true); + soundio_os_cond_signal(sia->cond, sia->mutex); + soundio_os_mutex_unlock(sia->mutex); + return 0; +} + +static void device_thread_run(void *arg) { + SoundIo *soundio = (SoundIo *)arg; + SoundIoAlsa *sia = (SoundIoAlsa *)soundio->backend_data; + + // Some systems cannot read integer variables if they are not + // properly aligned. On other systems, incorrect alignment may + // decrease performance. Hence, the buffer used for reading from + // the inotify file descriptor should have the same alignment as + // struct inotify_event. + char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event)))); + const struct inotify_event *event; + + struct pollfd fds[2]; + fds[0].fd = sia->notify_fd; + fds[0].events = POLLIN; + + fds[1].fd = sia->notify_pipe_fd[0]; + fds[1].events = POLLIN; + + int err; + for (;;) { + int poll_num = poll(fds, 2, -1); + if (!sia->abort_flag.test_and_set()) + break; + if (poll_num == -1) { + if (errno == EINTR) + continue; + assert(errno != EFAULT); + assert(errno != EFAULT); + assert(errno != EINVAL); + assert(errno == ENOMEM); + soundio_panic("kernel ran out of polling memory"); + } + if (poll_num <= 0) + continue; + bool got_rescan_event = false; + if (fds[0].revents & POLLIN) { + for (;;) { + ssize_t len = read(sia->notify_fd, buf, sizeof(buf)); + if (len == -1) { + assert(errno != EBADF); + assert(errno != EFAULT); + assert(errno != EINVAL); + assert(errno != EIO); + assert(errno != EISDIR); + } + + // catches EINTR and EAGAIN + if (len <= 0) + break; + + // loop over all events in the buffer + for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) { + event = (const struct inotify_event *) ptr; + + if (!((event->mask & IN_CREATE) || (event->mask & IN_DELETE))) + continue; + if (event->mask & IN_ISDIR) + continue; + if (!event->len || event->len < 8) + continue; + if (event->name[0] != 'p' || + event->name[1] != 'c' || + event->name[2] != 'm') + { + continue; + } + got_rescan_event = true; + break; + } + } + } + if (fds[1].revents & POLLIN) { + got_rescan_event = true; + for (;;) { + ssize_t len = read(sia->notify_pipe_fd[0], buf, sizeof(buf)); + if (len == -1) { + assert(errno != EBADF); + assert(errno != EFAULT); + assert(errno != EINVAL); + assert(errno != EIO); + assert(errno != EISDIR); + } + if (len <= 0) + break; + } + } + if (got_rescan_event) { + if ((err = refresh_devices(soundio))) + soundio_panic("error refreshing devices: %s", soundio_error_string(err)); + } + } +} + +static void block_until_have_devices(SoundIoAlsa *sia) { + if (sia->have_devices_flag.load()) + return; + soundio_os_mutex_lock(sia->mutex); + while (!sia->have_devices_flag.load()) + soundio_os_cond_wait(sia->cond, sia->mutex); + soundio_os_mutex_unlock(sia->mutex); +} + +static void flush_events(SoundIo *soundio) { + SoundIoAlsa *sia = (SoundIoAlsa *)soundio->backend_data; + block_until_have_devices(sia); + + bool change = false; + SoundIoDevicesInfo *old_devices_info = nullptr; + + soundio_os_mutex_lock(sia->mutex); + + if (sia->ready_devices_info) { + old_devices_info = soundio->safe_devices_info; + soundio->safe_devices_info = sia->ready_devices_info; + sia->ready_devices_info = nullptr; + change = true; + } + + soundio_os_mutex_unlock(sia->mutex); + + if (change) + soundio->on_devices_change(soundio); + + soundio_destroy_devices_info(old_devices_info); +} + +static void wait_events(SoundIo *soundio) { + SoundIoAlsa *sia = (SoundIoAlsa *)soundio->backend_data; + flush_events(soundio); + soundio_os_mutex_lock(sia->mutex); + soundio_os_cond_wait(sia->cond, sia->mutex); + soundio_os_mutex_unlock(sia->mutex); +} + +static void wakeup(SoundIo *soundio) { + SoundIoAlsa *sia = (SoundIoAlsa *)soundio->backend_data; + soundio_os_mutex_lock(sia->mutex); + soundio_os_cond_signal(sia->cond, sia->mutex); + soundio_os_mutex_unlock(sia->mutex); +} + +static void output_device_destroy_alsa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + soundio_panic("TODO"); +} + +static int output_device_init_alsa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + soundio_panic("TODO"); +} + +static int output_device_start_alsa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + soundio_panic("TODO"); +} + +static int output_device_free_count_alsa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + soundio_panic("TODO"); +} + +static void output_device_begin_write_alsa(SoundIo *soundio, + SoundIoOutputDevice *output_device, char **data, int *frame_count) +{ + soundio_panic("TODO"); +} + +static void output_device_write_alsa(SoundIo *soundio, + SoundIoOutputDevice *output_device, char *data, int frame_count) +{ + soundio_panic("TODO"); +} + +static void output_device_clear_buffer_alsa(SoundIo *soundio, + SoundIoOutputDevice *output_device) +{ + soundio_panic("TODO"); +} + +static int input_device_init_alsa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + soundio_panic("TODO"); +} + +static void input_device_destroy_alsa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + soundio_panic("TODO"); +} + +static int input_device_start_alsa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + soundio_panic("TODO"); +} + +static void input_device_peek_alsa(SoundIo *soundio, + SoundIoInputDevice *input_device, const char **data, int *frame_count) +{ + soundio_panic("TODO"); +} + +static void input_device_drop_alsa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + soundio_panic("TODO"); +} + +static void input_device_clear_buffer_alsa(SoundIo *soundio, + SoundIoInputDevice *input_device) +{ + soundio_panic("TODO"); +} + +int soundio_alsa_init(SoundIo *soundio) { + int err; + + assert(!soundio->backend_data); + SoundIoAlsa *sia = create(); + if (!sia) { + destroy_alsa(soundio); + return SoundIoErrorNoMem; + } + soundio->backend_data = sia; + sia->notify_fd = -1; + sia->notify_wd = -1; + sia->have_devices_flag.store(false); + sia->abort_flag.test_and_set(); + + sia->mutex = soundio_os_mutex_create(); + if (!sia->mutex) { + destroy_alsa(soundio); + return SoundIoErrorNoMem; + } + + sia->cond = soundio_os_cond_create(); + if (!sia->cond) { + destroy_alsa(soundio); + return SoundIoErrorNoMem; + } + + + // set up inotify to watch /dev/snd for devices added or removed + sia->notify_fd = inotify_init1(IN_NONBLOCK); + if (sia->notify_fd == -1) { + err = errno; + assert(err != EINVAL); + destroy_alsa(soundio); + if (err == EMFILE || err == ENFILE) { + return SoundIoErrorSystemResources; + } else { + assert(err == ENOMEM); + return SoundIoErrorNoMem; + } + } + + sia->notify_wd = inotify_add_watch(sia->notify_fd, "/dev/snd", IN_CREATE | IN_DELETE); + if (sia->notify_wd == -1) { + err = errno; + assert(err != EACCES); + assert(err != EBADF); + assert(err != EFAULT); + assert(err != EINVAL); + assert(err != ENAMETOOLONG); + assert(err != ENOENT); + destroy_alsa(soundio); + if (err == ENOSPC) { + return SoundIoErrorSystemResources; + } else { + assert(err == ENOMEM); + return SoundIoErrorNoMem; + } + } + + if (pipe2(sia->notify_pipe_fd, O_NONBLOCK)) { + assert(errno != EFAULT); + assert(errno != EINVAL); + assert(errno == EMFILE || errno == ENFILE); + return SoundIoErrorSystemResources; + } + + wakeup_device_poll(sia); + + if ((err = soundio_os_thread_create(device_thread_run, soundio, false, &sia->thread))) { + destroy_alsa(soundio); + return err; + } + + soundio->destroy = destroy_alsa; + soundio->flush_events = flush_events; + soundio->wait_events = wait_events; + soundio->wakeup = wakeup; + + soundio->output_device_init = output_device_init_alsa; + soundio->output_device_destroy = output_device_destroy_alsa; + soundio->output_device_start = output_device_start_alsa; + soundio->output_device_free_count = output_device_free_count_alsa; + soundio->output_device_begin_write = output_device_begin_write_alsa; + soundio->output_device_write = output_device_write_alsa; + soundio->output_device_clear_buffer = output_device_clear_buffer_alsa; + + soundio->input_device_init = input_device_init_alsa; + soundio->input_device_destroy = input_device_destroy_alsa; + soundio->input_device_start = input_device_start_alsa; + soundio->input_device_peek = input_device_peek_alsa; + soundio->input_device_drop = input_device_drop_alsa; + soundio->input_device_clear_buffer = input_device_clear_buffer_alsa; + + return 0; +} diff --git a/src/alsa.hpp b/src/alsa.hpp new file mode 100644 index 0000000..680321f --- /dev/null +++ b/src/alsa.hpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + +#ifndef SOUNDIO_ALSA_HPP +#define SOUNDIO_ALSA_HPP + +int soundio_alsa_init(struct SoundIo *soundio); + +#endif + diff --git a/src/dummy.cpp b/src/dummy.cpp index a4c66c3..94804e0 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -108,8 +108,6 @@ static void wakeup(SoundIo *soundio) { soundio_os_cond_signal(sid->cond, nullptr); } -static void refresh_devices(SoundIo *soundio) { } - static void output_device_destroy_dummy(SoundIo *soundio, SoundIoOutputDevice *output_device) { @@ -216,39 +214,37 @@ static void output_device_clear_buffer_dummy(SoundIo *soundio, static int input_device_init_dummy(SoundIo *soundio, SoundIoInputDevice *input_device) { - // TODO - return 0; + soundio_panic("TODO"); } static void input_device_destroy_dummy(SoundIo *soundio, SoundIoInputDevice *input_device) { - // TODO + soundio_panic("TODO"); } static int input_device_start_dummy(SoundIo *soundio, SoundIoInputDevice *input_device) { - // TODO - return 0; + soundio_panic("TODO"); } static void input_device_peek_dummy(SoundIo *soundio, SoundIoInputDevice *input_device, const char **data, int *frame_count) { - // TODO + soundio_panic("TODO"); } static void input_device_drop_dummy(SoundIo *soundio, SoundIoInputDevice *input_device) { - // TODO + soundio_panic("TODO"); } static void input_device_clear_buffer_dummy(SoundIo *soundio, SoundIoInputDevice *input_device) { - // TODO + soundio_panic("TODO"); } int soundio_dummy_init(SoundIo *soundio) { @@ -326,8 +322,7 @@ int soundio_dummy_init(SoundIo *soundio) { device->name = strdup("dummy-in"); device->description = strdup("Dummy input device"); if (!device->name || !device->description) { - free(device->name); - free(device->description); + soundio_device_unref(device); destroy_dummy(soundio); return SoundIoErrorNoMem; } @@ -349,7 +344,6 @@ int soundio_dummy_init(SoundIo *soundio) { 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; soundio->output_device_destroy = output_device_destroy_dummy; diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index b431b3e..67e865a 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -1,3 +1,10 @@ +/* + * Copyright (c) 2015 Andrew Kelley + * + * This file is part of libsoundio, which is MIT licensed. + * See http://opensource.org/licenses/MIT + */ + #include "pulseaudio.hpp" #include "soundio.hpp" #include "atomics.hpp" @@ -100,32 +107,6 @@ static void context_state_callback(pa_context *context, void *userdata) { } } -static void destroy_current_devices_info(SoundIo *soundio) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)soundio->backend_data; - if (sipa->current_devices_info) { - for (int i = 0; i < sipa->current_devices_info->input_devices.length; i += 1) - soundio_device_unref(sipa->current_devices_info->input_devices.at(i)); - for (int i = 0; i < sipa->current_devices_info->output_devices.length; i += 1) - soundio_device_unref(sipa->current_devices_info->output_devices.at(i)); - - destroy(sipa->current_devices_info); - sipa->current_devices_info = nullptr; - } -} - -static void destroy_ready_devices_info(SoundIo *soundio) { - SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)soundio->backend_data; - if (sipa->ready_devices_info) { - for (int i = 0; i < sipa->ready_devices_info->input_devices.length; i += 1) - soundio_device_unref(sipa->ready_devices_info->input_devices.at(i)); - for (int i = 0; i < sipa->ready_devices_info->output_devices.length; i += 1) - soundio_device_unref(sipa->ready_devices_info->output_devices.at(i)); - destroy(sipa->ready_devices_info); - sipa->ready_devices_info = nullptr; - } -} - - static void destroy_pa(SoundIo *soundio) { SoundIoPulseAudio *sipa = (SoundIoPulseAudio *)soundio->backend_data; if (!sipa) @@ -134,8 +115,8 @@ static void destroy_pa(SoundIo *soundio) { if (sipa->main_loop) pa_threaded_mainloop_stop(sipa->main_loop); - destroy_current_devices_info(soundio); - destroy_ready_devices_info(soundio); + soundio_destroy_devices_info(sipa->current_devices_info); + soundio_destroy_devices_info(sipa->ready_devices_info); pa_context_disconnect(sipa->pulse_context); pa_context_unref(sipa->pulse_context); @@ -259,7 +240,7 @@ static void finish_device_query(SoundIo *soundio) { } } - destroy_ready_devices_info(soundio); + soundio_destroy_devices_info(sipa->ready_devices_info); sipa->ready_devices_info = sipa->current_devices_info; sipa->current_devices_info = NULL; sipa->have_devices_flag = true; @@ -351,7 +332,7 @@ static void scan_devices(SoundIo *soundio) { sipa->have_default_sink = false; sipa->have_source_list = false; - destroy_current_devices_info(soundio); + soundio_destroy_devices_info(sipa->current_devices_info); sipa->current_devices_info = create(); if (!sipa->current_devices_info) soundio_panic("out of memory"); @@ -426,13 +407,7 @@ static void flush_events(SoundIo *soundio) { if (change) soundio->on_devices_change(soundio); - if (old_devices_info) { - for (int i = 0; i < old_devices_info->input_devices.length; i += 1) - soundio_device_unref(old_devices_info->input_devices.at(i)); - for (int i = 0; i < old_devices_info->output_devices.length; i += 1) - soundio_device_unref(old_devices_info->output_devices.at(i)); - destroy(old_devices_info); - } + soundio_destroy_devices_info(old_devices_info); block_until_have_devices(soundio); } @@ -876,12 +851,6 @@ static void input_device_clear_buffer_pa(SoundIo *soundio, pa_threaded_mainloop_unlock(sipa->main_loop); } -static void refresh_devices(SoundIo *soundio) { - block_until_ready(soundio); - soundio_flush_events(soundio); - block_until_have_devices(soundio); -} - int soundio_pulseaudio_init(SoundIo *soundio) { assert(!soundio->backend_data); SoundIoPulseAudio *sipa = create(); @@ -942,7 +911,6 @@ int soundio_pulseaudio_init(SoundIo *soundio) { soundio->destroy = destroy_pa; soundio->flush_events = flush_events; - soundio->refresh_devices = refresh_devices; soundio->wait_events = wait_events; soundio->wakeup = wakeup; diff --git a/src/soundio.cpp b/src/soundio.cpp index 7a108e2..e2cbdbf 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -15,6 +15,10 @@ #include "pulseaudio.hpp" #endif +#ifdef SOUNDIO_HAVE_ALSA +#include "alsa.hpp" +#endif + #include #include @@ -61,6 +65,7 @@ const char *soundio_backend_name(enum SoundIoBackend backend) { switch (backend) { case SoundIoBackendNone: return "(none)"; case SoundIoBackendPulseAudio: return "PulseAudio"; + case SoundIoBackendAlsa: return "ALSA"; case SoundIoBackendDummy: return "Dummy"; } soundio_panic("invalid backend enum value: %d", (int)backend); @@ -104,6 +109,17 @@ int soundio_connect(struct SoundIo *soundio) { } #endif +#ifdef SOUNDIO_HAVE_ALSA + soundio->current_backend = SoundIoBackendAlsa; + err = soundio_alsa_init(soundio); + if (!err) + return 0; + if (err != SoundIoErrorInitAudioBackend) { + soundio_disconnect(soundio); + return err; + } +#endif + soundio->current_backend = SoundIoBackendDummy; err = soundio_dummy_init(soundio); if (err) { @@ -121,20 +137,13 @@ void soundio_disconnect(struct SoundIo *soundio) { soundio->current_backend = SoundIoBackendNone; - 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; - } + soundio_destroy_devices_info(soundio->safe_devices_info); + soundio->safe_devices_info = nullptr; soundio->destroy = nullptr; soundio->flush_events = nullptr; soundio->wait_events = nullptr; soundio->wakeup = nullptr; - soundio->refresh_devices = nullptr; soundio->output_device_init = nullptr; soundio->output_device_destroy = nullptr; @@ -200,9 +209,6 @@ struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int ind 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; @@ -387,3 +393,15 @@ void soundio_input_device_destroy(struct SoundIoInputDevice *input_device) { soundio_device_unref(input_device->device); destroy(input_device); } + +void soundio_destroy_devices_info(SoundIoDevicesInfo *devices_info) { + if (!devices_info) + return; + + for (int i = 0; i < devices_info->input_devices.length; i += 1) + soundio_device_unref(devices_info->input_devices.at(i)); + for (int i = 0; i < devices_info->output_devices.length; i += 1) + soundio_device_unref(devices_info->output_devices.at(i)); + + destroy(devices_info); +} diff --git a/src/soundio.h b/src/soundio.h index b0a43c0..4606fb1 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -90,6 +90,7 @@ enum SoundIoChannelLayoutId { enum SoundIoBackend { SoundIoBackendNone, SoundIoBackendPulseAudio, + SoundIoBackendAlsa, SoundIoBackendDummy, }; @@ -160,7 +161,6 @@ 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 *); void (*output_device_destroy)(struct SoundIo *, struct SoundIoOutputDevice *); diff --git a/src/soundio.hpp b/src/soundio.hpp index a398d90..4081720 100644 --- a/src/soundio.hpp +++ b/src/soundio.hpp @@ -19,4 +19,6 @@ struct SoundIoDevicesInfo { int default_input_index; }; +void soundio_destroy_devices_info(struct SoundIoDevicesInfo *devices_info); + #endif diff --git a/src/util.cpp b/src/util.cpp index 4fd34cf..4dbdab0 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -19,3 +19,27 @@ void soundio_panic(const char *format, ...) { va_end(ap); abort(); } + +char *soundio_alloc_sprintf(int *len, const char *format, ...) { + va_list ap, ap2; + va_start(ap, format); + va_copy(ap2, ap); + + int len1 = vsnprintf(nullptr, 0, format, ap); + assert(len1 >= 0); + + size_t required_size = len1 + 1; + char *mem = allocate(required_size); + if (!mem) + return nullptr; + + int len2 = vsnprintf(mem, required_size, format, ap2); + assert(len2 == len1); + + va_end(ap2); + va_end(ap); + + if (len) + *len = len1; + return mem; +} diff --git a/src/util.hpp b/src/util.hpp index 20b8ea5..8c06fc2 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -84,6 +84,10 @@ void soundio_panic(const char *format, ...) __attribute__ ((noreturn)) __attribute__ ((format (printf, 1, 2))); +char *soundio_alloc_sprintf(int *len, const char *format, ...) + __attribute__ ((format (printf, 2, 3))); + + template constexpr long array_length(const T (&)[n]) { return n; diff --git a/test/valgrind.supp b/test/valgrind.supp new file mode 100644 index 0000000..fde83b6 --- /dev/null +++ b/test/valgrind.supp @@ -0,0 +1,96 @@ +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + obj:*/libasound.so.2.0.0 + fun:snd_config_hook_load +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + ... + obj:*/libasound.so.2.0.0 + fun:snd_config_hook_load +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + obj:*/libasound.so.2.0.0 + fun:snd_config_update_r +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + ... + obj:*/libasound.so.2.0.0 + fun:snd_config_update_r +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 + obj:*/libasound.so.2.0.0 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + obj:*/libasound.so.2.0.0 + fun:snd_config_load +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + obj:*/libasound.so.2.0.0 + fun:parse_array_defs +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:snd1_dlobj_cache_get +}