diff --git a/CMakeLists.txt b/CMakeLists.txt index 392a6ba..c49f63b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,7 +171,7 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror -pedantic") set(LIB_CFLAGS "-std=c++11 -fno-exceptions -fno-rtti -fvisibility=hidden -Wall -Werror=strict-prototypes -Werror=old-style-definition -Werror=missing-prototypes -Wno-c99-extensions") -set(EXAMPLE_CFLAGS "-std=c11 -Wall") +set(EXAMPLE_CFLAGS "-std=c99 -Wall") set(EXAMPLE_INCLUDES "${CMAKE_SOURCE_DIR}/src") set(TEST_CFLAGS "${LIB_CFLAGS} -fprofile-arcs -ftest-coverage") set(TEST_LDFLAGS "-fprofile-arcs -ftest-coverage") @@ -261,7 +261,6 @@ install(TARGETS sio_microphone DESTINATION ${CMAKE_INSTALL_BINDIR}) -enable_testing() add_executable(unit_tests ${TEST_SOURCES}) target_link_libraries(unit_tests LINK_PUBLIC ${CMAKE_THREAD_LIBS_INIT} @@ -279,7 +278,6 @@ set_target_properties(unit_tests PROPERTIES LINK_FLAGS ${TEST_LDFLAGS} ) include_directories(${TEST_INCLUDES}) -add_test(UnitTests unit_tests) add_executable(underflow test/underflow.c) set_target_properties(underflow PROPERTIES @@ -287,7 +285,6 @@ set_target_properties(underflow PROPERTIES COMPILE_FLAGS ${EXAMPLE_CFLAGS}) include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(underflow libsoundio_shared) -add_test(Underflow underflow) add_executable(backend_disconnect_recover test/backend_disconnect_recover.c) set_target_properties(backend_disconnect_recover PROPERTIES @@ -295,7 +292,13 @@ set_target_properties(backend_disconnect_recover PROPERTIES COMPILE_FLAGS ${EXAMPLE_CFLAGS}) include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(backend_disconnect_recover libsoundio_shared) -add_test(backend_disconnect_recover backend_disconnect_recover) + +add_executable(overflow test/overflow.c) +set_target_properties(overflow PROPERTIES + LINKER_LANGUAGE C + COMPILE_FLAGS ${EXAMPLE_CFLAGS}) +include_directories(${EXAMPLE_INCLUDES}) +target_link_libraries(overflow libsoundio_shared) diff --git a/README.md b/README.md index be9f398..3b526be 100644 --- a/README.md +++ b/README.md @@ -274,10 +274,7 @@ Then look at `html/index.html` in a browser. ## Roadmap - 0. Ability to "activate" a buffer-flexible outstream by jumping the gun and - causing `write_callback` to be called early. - - Use the same mechanism when destroying the outstream - 0. Create a test for input stream overflow handling. + 0. `sio_microphone` with ALSA backend in raw mode quickly causes unrecoverable streaming failure 0. Create a test for the latency / synchronization API. - Input is an audio file and some events indexed at particular frame - when listening the events should line up exactly with a beat or visual diff --git a/soundio/soundio.h b/soundio/soundio.h index e6799c0..dad4aea 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -505,13 +505,22 @@ struct SoundIoOutStream { /// you will call ::soundio_outstream_clear_buffer when you want to reduce /// the latency to 0. On systems that do not support clearing the buffer, /// this defaults to a reasonable lower latency value. + /// + /// On backends with high latencies (such as 2 seconds), `frame_count_min` + /// will be 0, meaning you don't have to fill the entire buffer. In this + /// case, the large buffer is there if you want it; you only have to fill + /// as much as you want. On backends like JACK, `frame_count_min` will be + /// equal to `frame_count_max` and if you don't fill that many frames, you + /// will get glitches. + /// /// If the device has unknown software latency min and max values, you may /// still set this, but you might not get the value you requested. /// For PulseAudio, if you set this value to non-default, it sets /// `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` and /// `tlength`. + /// /// For JACK, this value is always equal to - /// SoundIoDevice::software_latency_current` of the device. + /// SoundIoDevice::software_latency_current of the device. double software_latency; /// Defaults to NULL. Put whatever you want here. @@ -589,8 +598,8 @@ struct SoundIoInStream { /// still set this, but you might not get the value you requested. /// For PulseAudio, if you set this value to non-default, it sets /// `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`. - /// For JACK, this value is always equal to `software_latency_current` of - /// the device. + /// For JACK, this value is always equal to + /// SoundIoDevice::software_latency_current double software_latency; /// Defaults to NULL. Put whatever you want here. @@ -602,6 +611,11 @@ struct SoundIoInStream { /// `frame_count_min`, the frames will be dropped. `frame_count_max` is how /// many frames are available to read. void (*read_callback)(struct SoundIoInStream *, int frame_count_min, int frame_count_max); + /// This optional callback happens when the sound device buffer is full, + /// yet there is more captured audio to put in it. + /// This is never fired for PulseAudio. + /// This is called from the SoundIoInStream::read_callback thread context. + void (*overflow_callback)(struct SoundIoInStream *); /// Optional callback. `err` is always SoundIoErrorStreaming. /// SoundIoErrorStreaming is an unrecoverable error. The stream is in an /// invalid state and must be destroyed. @@ -680,9 +694,15 @@ SOUNDIO_EXPORT enum SoundIoBackend soundio_get_backend(struct SoundIo *soundio, /// Returns whether libsoundio was compiled with backend. SOUNDIO_EXPORT bool soundio_have_backend(enum SoundIoBackend backend); +/// Atomically update information for all connected devices. Note that calling +/// this function merely flips a pointer; the actual work of collecting device +/// information is done elsewhere. It is performant to call this function many +/// times per second. +/// /// When you call this, the SoundIo::on_devices_change and /// SoundIo::on_events_signal callbacks /// might be called. This is the only time those callbacks will be called. +/// /// This must be called from the same thread as the thread in which you call /// these functions: /// * ::soundio_input_device_count @@ -691,6 +711,10 @@ SOUNDIO_EXPORT bool soundio_have_backend(enum SoundIoBackend backend); /// * ::soundio_get_output_device /// * ::soundio_default_input_device_index /// * ::soundio_default_output_device_index +/// +/// Note that if you do not care about learning about updated devices, you +/// might call this function only once ever and never call +/// ::soundio_wait_events. SOUNDIO_EXPORT void soundio_flush_events(struct SoundIo *soundio); /// This function calls ::soundio_flush_events then blocks until another event @@ -719,6 +743,9 @@ SOUNDIO_EXPORT enum SoundIoChannelId soundio_parse_channel_id(const char *str, i /// Returns the number of builtin channel layouts. SOUNDIO_EXPORT int soundio_channel_layout_builtin_count(void); /// Returns a builtin channel layout. 0 <= `index` < ::soundio_channel_layout_builtin_count +/// +/// Although `index` is of type `int`, it should be a valid +/// #SoundIoChannelLayoutId enum value. SOUNDIO_EXPORT const struct SoundIoChannelLayout *soundio_channel_layout_get_builtin(int index); /// Get the default builtin channel layout for the given number of channels. @@ -1044,8 +1071,10 @@ SOUNDIO_EXPORT int soundio_instream_pause(struct SoundIoInStream *instream, bool // Ring Buffer struct SoundIoRingBuffer; /// `requested_capacity` in bytes. -/// See also ::soundio_ring_buffer_destroy /// Returns `NULL` if and only if memory could not be allocated. +/// Use ::soundio_ring_buffer_capacity to get the actual capacity, which might +/// be greater for alignment purposes. +/// See also ::soundio_ring_buffer_destroy SOUNDIO_EXPORT struct SoundIoRingBuffer *soundio_ring_buffer_create(struct SoundIo *soundio, int requested_capacity); SOUNDIO_EXPORT void soundio_ring_buffer_destroy(struct SoundIoRingBuffer *ring_buffer); diff --git a/src/alsa.cpp b/src/alsa.cpp index c1d31c9..e328347 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -42,6 +42,8 @@ static void destroy_alsa(SoundIoPrivate *si) { soundio_os_thread_destroy(sia->thread); } + sia->pending_files.deinit(); + if (sia->cond) soundio_os_cond_destroy(sia->cond); @@ -450,6 +452,12 @@ static int refresh_devices(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoAlsa *sia = &si->backend_data.alsa; + int err; + if ((err = snd_config_update_free_global()) < 0) + return SoundIoErrorSystemResources; + if ((err = snd_config_update()) < 0) + return SoundIoErrorSystemResources; + SoundIoDevicesInfo *devices_info = allocate(1); if (!devices_info) return SoundIoErrorNoMem; @@ -685,7 +693,7 @@ static int refresh_devices(SoundIoPrivate *si) { 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); + sia->have_devices_flag = true; soundio_os_cond_signal(sia->cond, sia->mutex); soundio->on_events_signal(soundio); soundio_os_mutex_unlock(sia->mutex); @@ -697,13 +705,28 @@ static void shutdown_backend(SoundIoPrivate *si, int err) { SoundIoAlsa *sia = &si->backend_data.alsa; soundio_os_mutex_lock(sia->mutex); sia->shutdown_err = err; + soundio_os_cond_signal(sia->cond, sia->mutex); soundio->on_events_signal(soundio); soundio_os_mutex_unlock(sia->mutex); } +static bool copy_str(char *dest, const char *src, int buf_len) { + for (;;) { + buf_len -= 1; + if (buf_len <= 0) + return false; + *dest = *src; + dest += 1; + src += 1; + if (!*src) + break; + } + *dest = '\0'; + return true; +} + static void device_thread_run(void *arg) { SoundIoPrivate *si = (SoundIoPrivate *)arg; - SoundIo *soundio = &si->pub; SoundIoAlsa *sia = &si->backend_data.alsa; // Some systems cannot read integer variables if they are not @@ -749,6 +772,12 @@ static void device_thread_run(void *arg) { assert(errno != EINVAL); assert(errno != EIO); assert(errno != EISDIR); + if (errno == EBADF || errno == EFAULT || errno == EINVAL || + errno == EIO || errno == EISDIR) + { + shutdown_backend(si, SoundIoErrorSystemResources); + return; + } } // catches EINTR and EAGAIN @@ -759,7 +788,7 @@ static void device_thread_run(void *arg) { 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))) + if (!((event->mask & IN_CLOSE_WRITE) || (event->mask & IN_DELETE) || (event->mask & IN_CREATE))) continue; if (event->mask & IN_ISDIR) continue; @@ -771,8 +800,37 @@ static void device_thread_run(void *arg) { { continue; } - got_rescan_event = true; - break; + if (event->mask & IN_CREATE) { + if ((err = sia->pending_files.add_one())) { + shutdown_backend(si, SoundIoErrorNoMem); + return; + } + SoundIoAlsaPendingFile *pending_file = &sia->pending_files.last(); + if (!copy_str(pending_file->name, event->name, SOUNDIO_MAX_ALSA_SND_FILE_LEN)) { + sia->pending_files.pop(); + } + continue; + } + if (sia->pending_files.length > 0) { + // At this point ignore IN_DELETE in favor of waiting until the files + // opened with IN_CREATE have their IN_CLOSE_WRITE event. + if (!(event->mask & IN_CLOSE_WRITE)) + continue; + for (int i = 0; i < sia->pending_files.length; i += 1) { + SoundIoAlsaPendingFile *pending_file = &sia->pending_files.at(i); + if (strcmp(pending_file->name, event->name) == 0) { + sia->pending_files.swap_remove(i); + if (sia->pending_files.length == 0) { + got_rescan_event = true; + } + break; + } + } + } else if (event->mask & IN_DELETE) { + // We are not waiting on created files to be closed, so when + // a delete happens we act on it. + got_rescan_event = true; + } } } } @@ -786,40 +844,29 @@ static void device_thread_run(void *arg) { assert(errno != EINVAL); assert(errno != EIO); assert(errno != EISDIR); + if (errno == EBADF || errno == EFAULT || errno == EINVAL || + errno == EIO || errno == EISDIR) + { + shutdown_backend(si, SoundIoErrorSystemResources); + return; + } } if (len <= 0) break; } } if (got_rescan_event) { - err = refresh_devices(si); - if (err) + if ((err = refresh_devices(si))) { shutdown_backend(si, err); - if (!sia->have_devices_flag.exchange(true)) { - soundio_os_mutex_lock(sia->mutex); - soundio_os_cond_signal(sia->cond, sia->mutex); - soundio->on_events_signal(soundio); - soundio_os_mutex_unlock(sia->mutex); - } - if (err) return; + } } } } -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(SoundIoPrivate *si) { +static void my_flush_events(SoundIoPrivate *si, bool wait) { SoundIo *soundio = &si->pub; SoundIoAlsa *sia = &si->backend_data.alsa; - block_until_have_devices(sia); bool change = false; bool cb_shutdown = false; @@ -827,6 +874,12 @@ static void flush_events(SoundIoPrivate *si) { soundio_os_mutex_lock(sia->mutex); + // block until have devices + while (wait || (!sia->have_devices_flag && !sia->shutdown_err)) { + soundio_os_cond_wait(sia->cond, sia->mutex); + wait = false; + } + if (sia->shutdown_err && !sia->emitted_shutdown_cb) { sia->emitted_shutdown_cb = true; cb_shutdown = true; @@ -847,15 +900,16 @@ static void flush_events(SoundIoPrivate *si) { soundio_destroy_devices_info(old_devices_info); } -static void wait_events(SoundIoPrivate *si) { - SoundIoAlsa *sia = &si->backend_data.alsa; - flush_events(si); - soundio_os_mutex_lock(sia->mutex); - soundio_os_cond_wait(sia->cond, sia->mutex); - soundio_os_mutex_unlock(sia->mutex); +static void flush_events_alsa(SoundIoPrivate *si) { + my_flush_events(si, false); } -static void wakeup(SoundIoPrivate *si) { +static void wait_events_alsa(SoundIoPrivate *si) { + my_flush_events(si, false); + my_flush_events(si, true); +} + +static void wakeup_alsa(SoundIoPrivate *si) { SoundIoAlsa *sia = &si->backend_data.alsa; soundio_os_mutex_lock(sia->mutex); soundio_os_cond_signal(sia->cond, sia->mutex); @@ -899,10 +953,12 @@ static int os_xrun_recovery(SoundIoOutStreamPrivate *os, int err) { } static int instream_xrun_recovery(SoundIoInStreamPrivate *is, int err) { + SoundIoInStream *instream = &is->pub; SoundIoInStreamAlsa *isa = &is->backend_data.alsa; - // TODO do something with this overflow if (err == -EPIPE) { err = snd_pcm_prepare(isa->handle); + if (err >= 0) + instream->overflow_callback(instream); } else if (err == -ESTRPIPE) { while ((err = snd_pcm_resume(isa->handle)) == -EAGAIN) { // wait until suspend flag is released @@ -910,6 +966,8 @@ static int instream_xrun_recovery(SoundIoInStreamPrivate *is, int err) { } if (err < 0) err = snd_pcm_prepare(isa->handle); + if (err >= 0) + instream->overflow_callback(instream); } return err; } @@ -1651,7 +1709,6 @@ int soundio_alsa_init(SoundIoPrivate *si) { 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(); @@ -1681,7 +1738,7 @@ int soundio_alsa_init(SoundIoPrivate *si) { } } - sia->notify_wd = inotify_add_watch(sia->notify_fd, "/dev/snd", IN_CREATE | IN_DELETE); + sia->notify_wd = inotify_add_watch(sia->notify_fd, "/dev/snd", IN_CREATE | IN_CLOSE_WRITE | IN_DELETE); if (sia->notify_wd == -1) { err = errno; assert(err != EACCES); @@ -1714,9 +1771,9 @@ int soundio_alsa_init(SoundIoPrivate *si) { } si->destroy = destroy_alsa; - si->flush_events = flush_events; - si->wait_events = wait_events; - si->wakeup = wakeup; + si->flush_events = flush_events_alsa; + si->wait_events = wait_events_alsa; + si->wakeup = wakeup_alsa; si->outstream_open = outstream_open_alsa; si->outstream_destroy = outstream_destroy_alsa; diff --git a/src/alsa.hpp b/src/alsa.hpp index de7f874..1156b7d 100644 --- a/src/alsa.hpp +++ b/src/alsa.hpp @@ -11,6 +11,7 @@ #include "soundio_private.h" #include "os.h" #include "atomics.hpp" +#include "list.hpp" #include @@ -18,6 +19,11 @@ int soundio_alsa_init(struct SoundIoPrivate *si); struct SoundIoDeviceAlsa { }; +#define SOUNDIO_MAX_ALSA_SND_FILE_LEN 16 +struct SoundIoAlsaPendingFile { + char name[SOUNDIO_MAX_ALSA_SND_FILE_LEN]; +}; + struct SoundIoAlsa { SoundIoOsMutex *mutex; SoundIoOsCond *cond; @@ -26,8 +32,9 @@ struct SoundIoAlsa { atomic_flag abort_flag; int notify_fd; int notify_wd; - atomic_bool have_devices_flag; + bool have_devices_flag; int notify_pipe_fd[2]; + SoundIoList pending_files; // this one is ready to be read with flush_events. protected by mutex struct SoundIoDevicesInfo *ready_devices_info; diff --git a/src/coreaudio.cpp b/src/coreaudio.cpp index 5f7abf7..0ceb39c 100644 --- a/src/coreaudio.cpp +++ b/src/coreaudio.cpp @@ -754,7 +754,6 @@ static int refresh_devices(struct SoundIoPrivate *si) { soundio_os_mutex_lock(sica->mutex); soundio_destroy_devices_info(sica->ready_devices_info); sica->ready_devices_info = rd.devices_info; - soundio->on_events_signal(soundio); soundio_os_mutex_unlock(sica->mutex); rd.devices_info = nullptr; @@ -769,21 +768,20 @@ static void shutdown_backend(SoundIoPrivate *si, int err) { SoundIoCoreAudio *sica = &si->backend_data.coreaudio; soundio_os_mutex_lock(sica->mutex); sica->shutdown_err = err; - soundio->on_events_signal(soundio); + sica->have_devices_flag.store(true); soundio_os_mutex_unlock(sica->mutex); -} - -static void block_until_have_devices(SoundIoCoreAudio *sica) { - if (sica->have_devices_flag.load()) - return; - while (!sica->have_devices_flag.load()) - soundio_os_cond_wait(sica->have_devices_cond, nullptr); + soundio_os_cond_signal(sica->cond, nullptr); + soundio_os_cond_signal(sica->have_devices_cond, nullptr); + soundio->on_events_signal(soundio); } static void flush_events_ca(struct SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoCoreAudio *sica = &si->backend_data.coreaudio; - block_until_have_devices(sica); + + // block until have devices + while (!sica->have_devices_flag.load()) + soundio_os_cond_wait(sica->have_devices_cond, nullptr); bool change = false; bool cb_shutdown = false; @@ -837,15 +835,14 @@ static void device_thread_run(void *arg) { } if (sica->device_scan_queued.exchange(false)) { err = refresh_devices(si); - if (err) + if (err) { shutdown_backend(si, err); - if (!sica->have_devices_flag.exchange(true)) { - soundio_os_cond_signal(sica->have_devices_cond, nullptr); - soundio->on_events_signal(soundio); - } - if (err) return; + } + if (!sica->have_devices_flag.exchange(true)) + soundio_os_cond_signal(sica->have_devices_cond, nullptr); soundio_os_cond_signal(sica->cond, nullptr); + soundio->on_events_signal(soundio); } soundio_os_cond_wait(sica->scan_devices_cond, nullptr); } @@ -1053,10 +1050,9 @@ static int outstream_clear_buffer_ca(struct SoundIoPrivate *si, struct SoundIoOu static OSStatus on_instream_device_overload(AudioObjectID in_object_id, UInt32 in_number_addresses, const AudioObjectPropertyAddress in_addresses[], void *in_client_data) { - //SoundIoInStreamPrivate *os = (SoundIoInStreamPrivate *)in_client_data; - //SoundIoInStream *instream = &os->pub; - fprintf(stderr, "TODO overflow\n"); - //instream->underflow_callback(instream); + SoundIoInStreamPrivate *os = (SoundIoInStreamPrivate *)in_client_data; + SoundIoInStream *instream = &os->pub; + instream->overflow_callback(instream); return noErr; } diff --git a/src/dummy.cpp b/src/dummy.cpp index 6f69c23..2b633c5 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -88,7 +88,7 @@ static void capture_thread_run(void *arg) { frames_consumed += write_count; if (frames_to_kill > free_frames) { - // TODO overflow callback + instream->overflow_callback(instream); frames_consumed = 0; start_time = soundio_os_get_time(); } @@ -109,7 +109,7 @@ static void destroy_dummy(SoundIoPrivate *si) { soundio_os_mutex_destroy(sid->mutex); } -static void flush_events(SoundIoPrivate *si) { +static void flush_events_dummy(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoDummy *sid = &si->backend_data.dummy; if (sid->devices_emitted) @@ -118,13 +118,13 @@ static void flush_events(SoundIoPrivate *si) { soundio->on_devices_change(soundio); } -static void wait_events(SoundIoPrivate *si) { +static void wait_events_dummy(SoundIoPrivate *si) { SoundIoDummy *sid = &si->backend_data.dummy; - flush_events(si); + flush_events_dummy(si); soundio_os_cond_wait(sid->cond, nullptr); } -static void wakeup(SoundIoPrivate *si) { +static void wakeup_dummy(SoundIoPrivate *si) { SoundIoDummy *sid = &si->backend_data.dummy; soundio_os_cond_signal(sid->cond, nullptr); } @@ -505,9 +505,9 @@ int soundio_dummy_init(SoundIoPrivate *si) { si->destroy = destroy_dummy; - si->flush_events = flush_events; - si->wait_events = wait_events; - si->wakeup = wakeup; + si->flush_events = flush_events_dummy; + si->wait_events = wait_events_dummy; + si->wakeup = wakeup_dummy; si->outstream_open = outstream_open_dummy; si->outstream_destroy = outstream_destroy_dummy; diff --git a/src/jack.cpp b/src/jack.cpp index 06283df..3fd45cc 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -291,7 +291,7 @@ static int refresh_devices(SoundIoPrivate *si) { return err; } -static void flush_events_jack(struct SoundIoPrivate *si) { +static void my_flush_events(struct SoundIoPrivate *si, bool wait) { SoundIo *soundio = &si->pub; SoundIoJack *sij = &si->backend_data.jack; int err; @@ -300,6 +300,9 @@ static void flush_events_jack(struct SoundIoPrivate *si) { soundio_os_mutex_lock(sij->mutex); + if (wait) + soundio_os_cond_wait(sij->cond, sij->mutex); + if (sij->is_shutdown && !sij->emitted_shutdown_cb) { sij->emitted_shutdown_cb = true; cb_shutdown = true; @@ -320,12 +323,13 @@ static void flush_events_jack(struct SoundIoPrivate *si) { } } +static void flush_events_jack(struct SoundIoPrivate *si) { + my_flush_events(si, false); +} + static void wait_events_jack(struct SoundIoPrivate *si) { - SoundIoJack *sij = &si->backend_data.jack; - flush_events_jack(si); - soundio_os_mutex_lock(sij->mutex); - soundio_os_cond_wait(sij->cond, sij->mutex); - soundio_os_mutex_unlock(sij->mutex); + my_flush_events(si, false); + my_flush_events(si, true); } static void wakeup_jack(struct SoundIoPrivate *si) { @@ -559,9 +563,9 @@ static void instream_destroy_jack(struct SoundIoPrivate *si, struct SoundIoInStr } static int instream_xrun_callback(void *arg) { -// SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)arg; -// SoundIoInStream *instream = &is->pub; -// TODO do something with this overflow + SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)arg; + SoundIoInStream *instream = &is->pub; + instream->overflow_callback(instream); return 0; } @@ -794,6 +798,7 @@ static void shutdown_callback(void *arg) { SoundIoJack *sij = &si->backend_data.jack; soundio_os_mutex_lock(sij->mutex); sij->is_shutdown = true; + soundio_os_cond_signal(sij->cond, sij->mutex); soundio->on_events_signal(soundio); soundio_os_mutex_unlock(sij->mutex); } diff --git a/src/list.hpp b/src/list.hpp index 31e0f27..0105ed1 100644 --- a/src/list.hpp +++ b/src/list.hpp @@ -83,6 +83,17 @@ struct SoundIoList { return 0; } + T swap_remove(int index) { + assert(index >= 0); + assert(index < length); + T last = pop(); + if (index == length) + return last; + T item = items[index]; + items[index] = last; + return item; + } + T * items; int length; int capacity; diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index 1d7add9..2cc8b26 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -17,9 +17,11 @@ static void subscribe_callback(pa_context *context, pa_subscription_event_type_t event_bits, uint32_t index, void *userdata) { SoundIoPrivate *si = (SoundIoPrivate *)userdata; + SoundIo *soundio = &si->pub; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; sipa->device_scan_queued = true; pa_threaded_mainloop_signal(sipa->main_loop, 0); + soundio->on_events_signal(soundio); } static int subscribe_to_events(SoundIoPrivate *si) { @@ -55,11 +57,14 @@ static void context_state_callback(pa_context *context, void *userdata) { pa_threaded_mainloop_signal(sipa->main_loop, 0); return; case PA_CONTEXT_FAILED: // The connection failed or was disconnected. - sipa->connection_err = SoundIoErrorInitAudioBackend; - if (sipa->ready_flag.exchange(true)) { - soundio->on_backend_disconnect(soundio, SoundIoErrorBackendDisconnected); + if (sipa->ready_flag) { + sipa->connection_err = SoundIoErrorBackendDisconnected; + } else { + sipa->connection_err = SoundIoErrorInitAudioBackend; + sipa->ready_flag = true; } pa_threaded_mainloop_signal(sipa->main_loop, 0); + soundio->on_events_signal(soundio); return; } } @@ -70,12 +75,12 @@ static void destroy_pa(SoundIoPrivate *si) { if (sipa->main_loop) pa_threaded_mainloop_stop(sipa->main_loop); - 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); + soundio_destroy_devices_info(sipa->current_devices_info); + soundio_destroy_devices_info(sipa->ready_devices_info); + if (sipa->main_loop) pa_threaded_mainloop_free(sipa->main_loop); @@ -214,24 +219,193 @@ static int perform_operation(SoundIoPrivate *si, pa_operation *op) { } } -static void finish_device_query(SoundIoPrivate *si) { +static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *info, int eol, void *userdata) { + SoundIoPrivate *si = (SoundIoPrivate *)userdata; SoundIo *soundio = &si->pub; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; + int err; + if (eol) { + pa_threaded_mainloop_signal(sipa->main_loop, 0); + return; + } + if (sipa->device_query_err) + return; - if (!sipa->have_sink_list || - !sipa->have_source_list || - !sipa->have_default_sink) - { + SoundIoDevicePrivate *dev = allocate(1); + if (!dev) { + sipa->device_query_err = SoundIoErrorNoMem; + return; + } + SoundIoDevice *device = &dev->pub; + + device->ref_count = 1; + device->soundio = soundio; + device->id = strdup(info->name); + device->name = strdup(info->description); + if (!device->id || !device->name) { + soundio_device_unref(device); + sipa->device_query_err = SoundIoErrorNoMem; return; } - if (sipa->device_query_err) { - pa_threaded_mainloop_signal(sipa->main_loop, 0); - soundio->on_events_signal(soundio); - soundio->on_backend_disconnect(soundio, sipa->device_query_err); + device->sample_rate_current = info->sample_spec.rate; + // PulseAudio performs resampling, so any value is valid. Let's pick + // some reasonable min and max values. + device->sample_rate_count = 1; + device->sample_rates = &dev->prealloc_sample_rate_range; + device->sample_rates[0].min = min(SOUNDIO_MIN_SAMPLE_RATE, device->sample_rate_current); + device->sample_rates[0].max = max(SOUNDIO_MAX_SAMPLE_RATE, device->sample_rate_current); + + device->current_format = from_pulseaudio_format(info->sample_spec); + // PulseAudio performs sample format conversion, so any PulseAudio + // value is valid. + if ((err = set_all_device_formats(device))) { + soundio_device_unref(device); + sipa->device_query_err = SoundIoErrorNoMem; return; } + set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout); + // PulseAudio does channel layout remapping, so any channel layout is valid. + if ((err = set_all_device_channel_layouts(device))) { + soundio_device_unref(device); + sipa->device_query_err = SoundIoErrorNoMem; + return; + } + + device->aim = SoundIoDeviceAimOutput; + + if (sipa->current_devices_info->output_devices.append(device)) { + soundio_device_unref(device); + sipa->device_query_err = SoundIoErrorNoMem; + return; + } +} + +static void source_info_callback(pa_context *pulse_context, const pa_source_info *info, int eol, void *userdata) { + SoundIoPrivate *si = (SoundIoPrivate *)userdata; + SoundIo *soundio = &si->pub; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; + int err; + + if (eol) { + pa_threaded_mainloop_signal(sipa->main_loop, 0); + return; + } + if (sipa->device_query_err) + return; + + SoundIoDevicePrivate *dev = allocate(1); + if (!dev) { + sipa->device_query_err = SoundIoErrorNoMem; + return; + } + SoundIoDevice *device = &dev->pub; + + device->ref_count = 1; + device->soundio = soundio; + device->id = strdup(info->name); + device->name = strdup(info->description); + if (!device->id || !device->name) { + soundio_device_unref(device); + sipa->device_query_err = SoundIoErrorNoMem; + return; + } + + device->sample_rate_current = info->sample_spec.rate; + // PulseAudio performs resampling, so any value is valid. Let's pick + // some reasonable min and max values. + device->sample_rate_count = 1; + device->sample_rates = &dev->prealloc_sample_rate_range; + device->sample_rates[0].min = min(8000, device->sample_rate_current); + device->sample_rates[0].max = max(5644800, device->sample_rate_current); + + device->current_format = from_pulseaudio_format(info->sample_spec); + // PulseAudio performs sample format conversion, so any PulseAudio + // value is valid. + if ((err = set_all_device_formats(device))) { + soundio_device_unref(device); + sipa->device_query_err = SoundIoErrorNoMem; + return; + } + + set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout); + // PulseAudio does channel layout remapping, so any channel layout is valid. + if ((err = set_all_device_channel_layouts(device))) { + soundio_device_unref(device); + sipa->device_query_err = SoundIoErrorNoMem; + return; + } + + device->aim = SoundIoDeviceAimInput; + + if (sipa->current_devices_info->input_devices.append(device)) { + soundio_device_unref(device); + sipa->device_query_err = SoundIoErrorNoMem; + return; + } +} + +static void server_info_callback(pa_context *pulse_context, const pa_server_info *info, void *userdata) { + SoundIoPrivate *si = (SoundIoPrivate *)userdata; + assert(si); + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; + + assert(!sipa->default_sink_name); + assert(!sipa->default_source_name); + + sipa->default_sink_name = strdup(info->default_sink_name); + sipa->default_source_name = strdup(info->default_source_name); + + if (!sipa->default_sink_name || !sipa->default_source_name) + sipa->device_query_err = SoundIoErrorNoMem; + + pa_threaded_mainloop_signal(sipa->main_loop, 0); +} + +// always called even when refresh_devices succeeds +static void cleanup_refresh_devices(SoundIoPrivate *si) { + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; + + soundio_destroy_devices_info(sipa->current_devices_info); + sipa->current_devices_info = nullptr; + + free(sipa->default_sink_name); + sipa->default_sink_name = nullptr; + + free(sipa->default_source_name); + sipa->default_source_name = nullptr; +} + +// call this while holding the main loop lock +static int refresh_devices(SoundIoPrivate *si) { + SoundIo *soundio = &si->pub; + SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; + + assert(!sipa->current_devices_info); + sipa->current_devices_info = allocate(1); + if (!sipa->current_devices_info) + return SoundIoErrorNoMem; + + pa_operation *list_sink_op = pa_context_get_sink_info_list(sipa->pulse_context, sink_info_callback, si); + pa_operation *list_source_op = pa_context_get_source_info_list(sipa->pulse_context, source_info_callback, si); + pa_operation *server_info_op = pa_context_get_server_info(sipa->pulse_context, server_info_callback, si); + + int err; + if ((err = perform_operation(si, list_sink_op))) { + return err; + } + if ((err = perform_operation(si, list_source_op))) { + return err; + } + if ((err = perform_operation(si, server_info_op))) { + return err; + } + + if (sipa->device_query_err) { + return sipa->device_query_err; + } + // based on the default sink name, figure out the default output index // if the name doesn't match just pick the first one. if there are no // devices then we need to set it to -1. @@ -262,246 +436,36 @@ static void finish_device_query(SoundIoPrivate *si) { 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; + sipa->current_devices_info = nullptr; pa_threaded_mainloop_signal(sipa->main_loop, 0); soundio->on_events_signal(soundio); -} - -static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *info, int eol, void *userdata) { - SoundIoPrivate *si = (SoundIoPrivate *)userdata; - SoundIo *soundio = &si->pub; - SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - int err; - if (eol) { - sipa->have_sink_list = true; - finish_device_query(si); - } else if (!sipa->device_query_err) { - SoundIoDevicePrivate *dev = allocate(1); - if (!dev) { - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - SoundIoDevice *device = &dev->pub; - - device->ref_count = 1; - device->soundio = soundio; - device->id = strdup(info->name); - device->name = strdup(info->description); - if (!device->id || !device->name) { - soundio_device_unref(device); - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - - device->sample_rate_current = info->sample_spec.rate; - // PulseAudio performs resampling, so any value is valid. Let's pick - // some reasonable min and max values. - device->sample_rate_count = 1; - device->sample_rates = &dev->prealloc_sample_rate_range; - device->sample_rates[0].min = min(SOUNDIO_MIN_SAMPLE_RATE, device->sample_rate_current); - device->sample_rates[0].max = max(SOUNDIO_MAX_SAMPLE_RATE, device->sample_rate_current); - - device->current_format = from_pulseaudio_format(info->sample_spec); - // PulseAudio performs sample format conversion, so any PulseAudio - // value is valid. - if ((err = set_all_device_formats(device))) { - soundio_device_unref(device); - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - - set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout); - // PulseAudio does channel layout remapping, so any channel layout is valid. - if ((err = set_all_device_channel_layouts(device))) { - soundio_device_unref(device); - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - - - device->aim = SoundIoDeviceAimOutput; - - if (sipa->current_devices_info->output_devices.append(device)) { - soundio_device_unref(device); - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - } - pa_threaded_mainloop_signal(sipa->main_loop, 0); -} - -static void source_info_callback(pa_context *pulse_context, const pa_source_info *info, int eol, void *userdata) { - SoundIoPrivate *si = (SoundIoPrivate *)userdata; - SoundIo *soundio = &si->pub; - SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - int err; - if (eol) { - sipa->have_source_list = true; - finish_device_query(si); - } else if (!sipa->device_query_err) { - SoundIoDevicePrivate *dev = allocate(1); - if (!dev) { - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - SoundIoDevice *device = &dev->pub; - - device->ref_count = 1; - device->soundio = soundio; - device->id = strdup(info->name); - device->name = strdup(info->description); - if (!device->id || !device->name) { - soundio_device_unref(device); - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - - device->sample_rate_current = info->sample_spec.rate; - // PulseAudio performs resampling, so any value is valid. Let's pick - // some reasonable min and max values. - device->sample_rate_count = 1; - device->sample_rates = &dev->prealloc_sample_rate_range; - device->sample_rates[0].min = min(8000, device->sample_rate_current); - device->sample_rates[0].max = max(5644800, device->sample_rate_current); - - device->current_format = from_pulseaudio_format(info->sample_spec); - // PulseAudio performs sample format conversion, so any PulseAudio - // value is valid. - if ((err = set_all_device_formats(device))) { - soundio_device_unref(device); - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - - set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout); - // PulseAudio does channel layout remapping, so any channel layout is valid. - if ((err = set_all_device_channel_layouts(device))) { - soundio_device_unref(device); - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - - device->aim = SoundIoDeviceAimInput; - - if (sipa->current_devices_info->input_devices.append(device)) { - soundio_device_unref(device); - sipa->device_query_err = SoundIoErrorNoMem; - pa_threaded_mainloop_signal(sipa->main_loop, 0); - return; - } - } - pa_threaded_mainloop_signal(sipa->main_loop, 0); -} - -static void server_info_callback(pa_context *pulse_context, const pa_server_info *info, void *userdata) { - SoundIoPrivate *si = (SoundIoPrivate *)userdata; - assert(si); - SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - - free(sipa->default_sink_name); - free(sipa->default_source_name); - - sipa->default_sink_name = strdup(info->default_sink_name); - sipa->default_source_name = strdup(info->default_source_name); - - if (!sipa->default_sink_name || !sipa->default_source_name) { - free(sipa->default_sink_name); - free(sipa->default_source_name); - sipa->device_query_err = SoundIoErrorNoMem; - } - - sipa->have_default_sink = true; - finish_device_query(si); - pa_threaded_mainloop_signal(sipa->main_loop, 0); -} - -static int scan_devices(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - - sipa->have_sink_list = false; - sipa->have_default_sink = false; - sipa->have_source_list = false; - - soundio_destroy_devices_info(sipa->current_devices_info); - sipa->current_devices_info = allocate(1); - if (!sipa->current_devices_info) - return SoundIoErrorNoMem; - - pa_threaded_mainloop_lock(sipa->main_loop); - - pa_operation *list_sink_op = pa_context_get_sink_info_list(sipa->pulse_context, sink_info_callback, si); - pa_operation *list_source_op = pa_context_get_source_info_list(sipa->pulse_context, source_info_callback, si); - pa_operation *server_info_op = pa_context_get_server_info(sipa->pulse_context, server_info_callback, si); - - int err; - if ((err = perform_operation(si, list_sink_op))) { - pa_threaded_mainloop_unlock(sipa->main_loop); - return err; - } - if ((err = perform_operation(si, list_source_op))) { - pa_threaded_mainloop_unlock(sipa->main_loop); - return err; - } - if ((err = perform_operation(si, server_info_op))) { - pa_threaded_mainloop_unlock(sipa->main_loop); - return err; - } - - pa_threaded_mainloop_signal(sipa->main_loop, 0); - pa_threaded_mainloop_unlock(sipa->main_loop); return 0; } -static void block_until_have_devices(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - if (sipa->have_devices_flag) - return; - pa_threaded_mainloop_lock(sipa->main_loop); - while (!sipa->have_devices_flag && !sipa->device_query_err) { - pa_threaded_mainloop_wait(sipa->main_loop); - } - pa_threaded_mainloop_unlock(sipa->main_loop); -} - -static void block_until_ready(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - if (sipa->ready_flag) - return; - pa_threaded_mainloop_lock(sipa->main_loop); - while (!sipa->ready_flag) { - pa_threaded_mainloop_wait(sipa->main_loop); - } - pa_threaded_mainloop_unlock(sipa->main_loop); -} - -static void flush_events(SoundIoPrivate *si) { +static void my_flush_events(SoundIoPrivate *si, bool wait) { SoundIo *soundio = &si->pub; - SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - int err; - if (sipa->device_scan_queued) { - if (!(err = scan_devices(si))) - sipa->device_scan_queued = false; - } - - SoundIoDevicesInfo *old_devices_info = nullptr; bool change = false; + bool cb_shutdown = false; + SoundIoDevicesInfo *old_devices_info = nullptr; pa_threaded_mainloop_lock(sipa->main_loop); - if (sipa->ready_devices_info) { + if (wait) + pa_threaded_mainloop_wait(sipa->main_loop); + + if (sipa->device_scan_queued && !sipa->connection_err) { + sipa->device_scan_queued = false; + sipa->connection_err = refresh_devices(si); + cleanup_refresh_devices(si); + } + + if (sipa->connection_err && !sipa->emitted_shutdown_cb) { + sipa->emitted_shutdown_cb = true; + cb_shutdown = true; + } else if (sipa->ready_devices_info) { old_devices_info = si->safe_devices_info; si->safe_devices_info = sipa->ready_devices_info; sipa->ready_devices_info = nullptr; @@ -510,20 +474,21 @@ static void flush_events(SoundIoPrivate *si) { pa_threaded_mainloop_unlock(sipa->main_loop); - if (change) + if (cb_shutdown) + soundio->on_backend_disconnect(soundio, sipa->connection_err); + else if (change) soundio->on_devices_change(soundio); soundio_destroy_devices_info(old_devices_info); - - block_until_have_devices(si); } -static void wait_events(SoundIoPrivate *si) { - SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - flush_events(si); - pa_threaded_mainloop_lock(sipa->main_loop); - pa_threaded_mainloop_wait(sipa->main_loop); - pa_threaded_mainloop_unlock(sipa->main_loop); +static void flush_events_pa(SoundIoPrivate *si) { + my_flush_events(si, false); +} + +static void wait_events_pa(SoundIoPrivate *si) { + my_flush_events(si, false); + my_flush_events(si, true); } static void wakeup(SoundIoPrivate *si) { @@ -658,6 +623,7 @@ static void outstream_destroy_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os pa_stream_set_write_callback(stream, nullptr, nullptr); pa_stream_set_state_callback(stream, nullptr, nullptr); pa_stream_set_underflow_callback(stream, nullptr, nullptr); + pa_stream_set_overflow_callback(stream, nullptr, nullptr); pa_stream_disconnect(stream); pa_stream_unref(stream); @@ -733,8 +699,6 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { size_t writable_size = pa_stream_writable_size(ospa->stream); outstream->software_latency = writable_size / bytes_per_second; - // TODO get the correct software_latency value - pa_threaded_mainloop_unlock(sipa->main_loop); return 0; @@ -759,6 +723,7 @@ static int outstream_start_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { pa_operation_unref(op); pa_stream_set_write_callback(ospa->stream, playback_stream_write_callback, os); pa_stream_set_underflow_callback(ospa->stream, playback_stream_underflow_callback, outstream); + pa_stream_set_overflow_callback(ospa->stream, playback_stream_underflow_callback, outstream); pa_threaded_mainloop_unlock(sipa->main_loop); @@ -913,7 +878,6 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { pa_stream_set_state_callback(stream, recording_stream_state_callback, is); pa_stream_set_read_callback(stream, recording_stream_read_callback, is); - // TODO handle overflow callback ispa->buffer_attr.maxlength = UINT32_MAX; ispa->buffer_attr.tlength = UINT32_MAX; @@ -1039,9 +1003,7 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - sipa->device_scan_queued.store(false); - sipa->ready_flag.store(false); - sipa->have_devices_flag.store(false); + sipa->device_scan_queued = true; sipa->main_loop = pa_threaded_mainloop_new(); if (!sipa->main_loop) { @@ -1077,22 +1039,29 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) { return SoundIoErrorNoMem; } - block_until_ready(si); + pa_threaded_mainloop_lock(sipa->main_loop); + + // block until ready + while (!sipa->ready_flag) + pa_threaded_mainloop_wait(sipa->main_loop); if (sipa->connection_err) { + pa_threaded_mainloop_unlock(sipa->main_loop); destroy_pa(si); return sipa->connection_err; } - sipa->device_scan_queued.store(true); if ((err = subscribe_to_events(si))) { + pa_threaded_mainloop_unlock(sipa->main_loop); destroy_pa(si); return err; } + pa_threaded_mainloop_unlock(sipa->main_loop); + si->destroy = destroy_pa; - si->flush_events = flush_events; - si->wait_events = wait_events; + si->flush_events = flush_events_pa; + si->wait_events = wait_events_pa; si->wakeup = wakeup; si->outstream_open = outstream_open_pa; diff --git a/src/pulseaudio.hpp b/src/pulseaudio.hpp index 46a72f2..7a71536 100644 --- a/src/pulseaudio.hpp +++ b/src/pulseaudio.hpp @@ -18,10 +18,12 @@ int soundio_pulseaudio_init(struct SoundIoPrivate *si); struct SoundIoDevicePulseAudio { }; struct SoundIoPulseAudio { + int device_query_err; int connection_err; + bool emitted_shutdown_cb; pa_context *pulse_context; - atomic_bool device_scan_queued; + bool device_scan_queued; // the one that we're working on building struct SoundIoDevicesInfo *current_devices_info; @@ -31,13 +33,7 @@ struct SoundIoPulseAudio { // this one is ready to be read with flush_events. protected by mutex struct SoundIoDevicesInfo *ready_devices_info; - int device_query_err; - bool have_sink_list; - bool have_source_list; - bool have_default_sink; - - atomic_bool ready_flag; - atomic_bool have_devices_flag; + bool ready_flag; pa_threaded_mainloop *main_loop; pa_proplist *props; diff --git a/src/soundio.cpp b/src/soundio.cpp index aadb090..9df0535 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -70,7 +70,7 @@ const char *soundio_strerror(int error) { case SoundIoErrorInitAudioBackend: return "unable to initialize audio backend"; case SoundIoErrorSystemResources: return "system resource not available"; case SoundIoErrorOpeningDevice: return "unable to open device"; - case SoundIoErrorNoSuchDevice: return "unable to open device"; + case SoundIoErrorNoSuchDevice: return "no such device"; case SoundIoErrorInvalid: return "invalid value"; case SoundIoErrorBackendUnavailable: return "backend unavailable"; case SoundIoErrorStreaming: return "unrecoverable streaming failure"; diff --git a/src/wasapi.cpp b/src/wasapi.cpp index 97ad9a7..ebceb4c 100644 --- a/src/wasapi.cpp +++ b/src/wasapi.cpp @@ -908,6 +908,8 @@ static int refresh_devices(SoundIoPrivate *si) { soundio_os_mutex_lock(siw->mutex); soundio_destroy_devices_info(siw->ready_devices_info); siw->ready_devices_info = rd.devices_info; + siw->have_devices_flag = true; + soundio_os_cond_signal(siw->cond, siw->mutex); soundio->on_events_signal(soundio); soundio_os_mutex_unlock(siw->mutex); @@ -923,13 +925,13 @@ static void shutdown_backend(SoundIoPrivate *si, int err) { SoundIoWasapi *siw = &si->backend_data.wasapi; soundio_os_mutex_lock(siw->mutex); siw->shutdown_err = err; + soundio_os_cond_signal(siw->cond, siw->mutex); soundio->on_events_signal(soundio); soundio_os_mutex_unlock(siw->mutex); } static void device_thread_run(void *arg) { SoundIoPrivate *si = (SoundIoPrivate *)arg; - SoundIo *soundio = &si->pub; SoundIoWasapi *siw = &si->backend_data.wasapi; int err; @@ -937,10 +939,6 @@ static void device_thread_run(void *arg) { CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&siw->device_enumerator); if (FAILED(hr)) { shutdown_backend(si, SoundIoErrorSystemResources); - if (!siw->have_devices_flag.exchange(true)) { - soundio_os_cond_signal(siw->cond, nullptr); - soundio->on_events_signal(soundio); - } return; } @@ -948,10 +946,6 @@ static void device_thread_run(void *arg) { siw->device_enumerator, &siw->device_events))) { shutdown_backend(si, SoundIoErrorSystemResources); - if (!siw->have_devices_flag.exchange(true)) { - soundio_os_cond_signal(siw->cond, nullptr); - soundio->on_events_signal(soundio); - } return; } @@ -960,15 +954,10 @@ static void device_thread_run(void *arg) { break; if (siw->device_scan_queued.exchange(false)) { err = refresh_devices(si); - if (err) + if (err) { shutdown_backend(si, err); - if (!siw->have_devices_flag.exchange(true)) { - soundio_os_cond_signal(siw->cond, nullptr); - soundio->on_events_signal(soundio); + return; } - if (err) - break; - soundio_os_cond_signal(siw->cond, nullptr); } soundio_os_cond_wait(siw->scan_devices_cond, nullptr); } @@ -978,17 +967,9 @@ static void device_thread_run(void *arg) { siw->device_enumerator = nullptr; } -static void block_until_have_devices(SoundIoWasapi *siw) { - if (siw->have_devices_flag.load()) - return; - while (!siw->have_devices_flag.load()) - soundio_os_cond_wait(siw->cond, nullptr); -} - -static void flush_events_wasapi(struct SoundIoPrivate *si) { +static void my_flush_events(struct SoundIoPrivate *si, bool wait) { SoundIo *soundio = &si->pub; SoundIoWasapi *siw = &si->backend_data.wasapi; - block_until_have_devices(siw); bool change = false; bool cb_shutdown = false; @@ -996,6 +977,12 @@ static void flush_events_wasapi(struct SoundIoPrivate *si) { soundio_os_mutex_lock(siw->mutex); + // block until have devices + while (wait || (!siw->have_devices_flag && !siw->shutdown_err)) { + soundio_os_cond_wait(siw->cond, siw->mutex); + wait = false; + } + if (siw->shutdown_err && !siw->emitted_shutdown_cb) { siw->emitted_shutdown_cb = true; cb_shutdown = true; @@ -1016,15 +1003,18 @@ static void flush_events_wasapi(struct SoundIoPrivate *si) { soundio_destroy_devices_info(old_devices_info); } +static void flush_events_wasapi(struct SoundIoPrivate *si) { + my_flush_events(si, false); +} + static void wait_events_wasapi(struct SoundIoPrivate *si) { - SoundIoWasapi *siw = &si->backend_data.wasapi; - flush_events_wasapi(si); - soundio_os_cond_wait(siw->cond, nullptr); + my_flush_events(si, false); + my_flush_events(si, true); } static void wakeup_wasapi(struct SoundIoPrivate *si) { SoundIoWasapi *siw = &si->backend_data.wasapi; - soundio_os_cond_signal(siw->cond, nullptr); + soundio_os_cond_signal(siw->cond, siw->mutex); } static void outstream_thread_deinit(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { @@ -1963,7 +1953,6 @@ int soundio_wasapi_init(SoundIoPrivate *si) { SoundIoWasapi *siw = &si->backend_data.wasapi; int err; - siw->have_devices_flag.store(false); siw->device_scan_queued.store(true); siw->abort_flag.test_and_set(); diff --git a/src/wasapi.hpp b/src/wasapi.hpp index aa0e931..17507ba 100644 --- a/src/wasapi.hpp +++ b/src/wasapi.hpp @@ -40,7 +40,7 @@ struct SoundIoWasapi { atomic_flag abort_flag; // this one is ready to be read with flush_events. protected by mutex struct SoundIoDevicesInfo *ready_devices_info; - atomic_bool have_devices_flag; + bool have_devices_flag; atomic_bool device_scan_queued; int shutdown_err; bool emitted_shutdown_cb; diff --git a/test/backend_disconnect_recover.c b/test/backend_disconnect_recover.c index e37507b..046a150 100644 --- a/test/backend_disconnect_recover.c +++ b/test/backend_disconnect_recover.c @@ -12,7 +12,6 @@ #include #include #include -#include #include __attribute__ ((cold)) @@ -28,18 +27,20 @@ static void panic(const char *format, ...) { } static int usage(char *exe) { - fprintf(stderr, "Usage: %s [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n", exe); + fprintf(stderr, "Usage: %s [options]\n" + "Options:\n" + " [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n" + " [--timeout seconds]\n", exe); return 1; } static enum SoundIoBackend backend = SoundIoBackendNone; -static atomic_bool severed = ATOMIC_VAR_INIT(false); +static bool severed = false; static void on_backend_disconnect(struct SoundIo *soundio, int err) { fprintf(stderr, "OK backend disconnected with '%s'.\n", soundio_strerror(err)); - atomic_store(&severed, true); - soundio_wakeup(soundio); + severed = true; } int main(int argc, char **argv) { @@ -93,7 +94,7 @@ int main(int argc, char **argv) { fprintf(stderr, "OK connected to %s. Now cause the backend to disconnect.\n", soundio_backend_name(soundio->current_backend)); - while (!atomic_load(&severed)) + while (!severed) soundio_wait_events(soundio); soundio_disconnect(soundio); diff --git a/test/overflow.c b/test/overflow.c new file mode 100644 index 0000000..e187c35 --- /dev/null +++ b/test/overflow.c @@ -0,0 +1,228 @@ +/* + * 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 +#include + +static enum SoundIoFormat prioritized_formats[] = { + SoundIoFormatFloat32NE, + SoundIoFormatFloat32FE, + SoundIoFormatS32NE, + SoundIoFormatS32FE, + SoundIoFormatS24NE, + SoundIoFormatS24FE, + SoundIoFormatS16NE, + SoundIoFormatS16FE, + SoundIoFormatFloat64NE, + SoundIoFormatFloat64FE, + SoundIoFormatU32NE, + SoundIoFormatU32FE, + SoundIoFormatU24NE, + SoundIoFormatU24FE, + SoundIoFormatU16NE, + SoundIoFormatU16FE, + SoundIoFormatS8, + SoundIoFormatU8, + SoundIoFormatInvalid, +}; + +__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 int usage(char *exe) { + fprintf(stderr, "Usage: %s [options]\n" + "Options:\n" + " [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n" + " [--device id]\n" + " [--raw]\n", exe); + return 1; +} + +static struct SoundIo *soundio = NULL; +static float seconds_offset = 0.0f; +static float seconds_end = 9.0f; +static bool caused_underflow = false; +static int overflow_count = 0; + +static void read_callback(struct SoundIoInStream *instream, int frame_count_min, int frame_count_max) { + struct SoundIoChannelArea *areas; + float float_sample_rate = instream->sample_rate; + float seconds_per_frame = 1.0f / float_sample_rate; + int err; + + if (!caused_underflow && seconds_offset >= 3.0f) { + fprintf(stderr, "OK sleeping...\n"); + caused_underflow = true; + sleep(3); + } + + if (seconds_offset >= seconds_end) { + soundio_wakeup(soundio); + return; + } + + int frames_left = frame_count_max; + + for (;;) { + int frame_count = frames_left; + + if ((err = soundio_instream_begin_read(instream, &areas, &frame_count))) + panic("begin read error: %s", soundio_strerror(err)); + + if (!frame_count) + break; + + seconds_offset += seconds_per_frame * frame_count; + + if ((err = soundio_instream_end_read(instream))) + panic("end read error: %s", soundio_strerror(err)); + + frames_left -= frame_count; + if (frames_left <= 0) + break; + } + + fprintf(stderr, "OK received %d frames\n", frame_count_max); +} + +static void overflow_callback(struct SoundIoInStream *instream) { + fprintf(stderr, "OK overflow %d\n", overflow_count++); +} + +int main(int argc, char **argv) { + char *exe = argv[0]; + enum SoundIoBackend backend = SoundIoBackendNone; + bool is_raw = false; + char *device_id = NULL; + for (int i = 1; i < argc; i += 1) { + char *arg = argv[i]; + if (arg[0] == '-' && arg[1] == '-') { + if (strcmp(arg, "--raw") == 0) { + is_raw = true; + } else if (++i >= argc) { + return usage(exe); + } else if (strcmp(arg, "--device") == 0) { + device_id = argv[i]; + } else if (strcmp(arg, "--backend") == 0) { + if (strcmp("dummy", argv[i]) == 0) { + backend = SoundIoBackendDummy; + } else if (strcmp("alsa", argv[i]) == 0) { + backend = SoundIoBackendAlsa; + } else if (strcmp("pulseaudio", argv[i]) == 0) { + backend = SoundIoBackendPulseAudio; + } else if (strcmp("jack", argv[i]) == 0) { + backend = SoundIoBackendJack; + } else if (strcmp("coreaudio", argv[i]) == 0) { + backend = SoundIoBackendCoreAudio; + } else if (strcmp("wasapi", argv[i]) == 0) { + backend = SoundIoBackendWasapi; + } else { + fprintf(stderr, "Invalid backend: %s\n", argv[i]); + return 1; + } + } else { + return usage(exe); + } + } else { + return usage(exe); + } + } + + fprintf(stderr, + "Records for 3 seconds, sleeps for 3 seconds, then you should see at least\n" + "one buffer overflow message, then records for 3 seconds.\n" + "PulseAudio is not expected to pass this test.\n"); + + if (!(soundio = soundio_create())) + panic("out of memory"); + + int err = (backend == SoundIoBackendNone) ? + soundio_connect(soundio) : soundio_connect_backend(soundio, backend); + + if (err) + panic("error connecting: %s", soundio_strerror(err)); + + soundio_flush_events(soundio); + + int selected_device_index = -1; + if (device_id) { + int device_count = soundio_input_device_count(soundio); + for (int i = 0; i < device_count; i += 1) { + struct SoundIoDevice *device = soundio_get_input_device(soundio, i); + if (strcmp(device->id, device_id) == 0 && device->is_raw == is_raw) { + selected_device_index = i; + break; + } + } + } else { + selected_device_index = soundio_default_input_device_index(soundio); + } + + if (selected_device_index < 0) { + fprintf(stderr, "input device not found\n"); + return 1; + } + + struct SoundIoDevice *device = soundio_get_input_device(soundio, selected_device_index); + if (!device) { + fprintf(stderr, "out of memory\n"); + return 1; + } + + fprintf(stderr, "Input device: %s\n", device->name); + + enum SoundIoFormat *fmt; + for (fmt = prioritized_formats; *fmt != SoundIoFormatInvalid; fmt += 1) { + if (soundio_device_supports_format(device, *fmt)) + break; + } + if (*fmt == SoundIoFormatInvalid) + panic("incompatible sample format"); + + struct SoundIoInStream *instream = soundio_instream_create(device); + instream->format = *fmt; + instream->read_callback = read_callback; + instream->overflow_callback = overflow_callback; + + if ((err = soundio_instream_open(instream))) + panic("unable to open device: %s", soundio_strerror(err)); + + fprintf(stderr, "OK format: %s\n", soundio_format_string(instream->format)); + + if ((err = soundio_instream_start(instream))) + panic("unable to start device: %s", soundio_strerror(err)); + + while (seconds_offset < seconds_end) + soundio_wait_events(soundio); + + soundio_instream_destroy(instream); + soundio_device_unref(device); + soundio_destroy(soundio); + + if (overflow_count > 0) { + fprintf(stderr, "OK test passed with %d overflow callbacks\n", overflow_count); + return 0; + } else { + fprintf(stderr, "FAIL no overflow callbacks received\n"); + return 1; + } +} diff --git a/test/underflow.c b/test/underflow.c index a074309..2ac0d15 100644 --- a/test/underflow.c +++ b/test/underflow.c @@ -27,7 +27,7 @@ static void panic(const char *format, ...) { } static int usage(char *exe) { - fprintf(stderr, "Usage: %s [--dummy] [--alsa] [--pulseaudio] [--jack]\n", exe); + fprintf(stderr, "Usage: %s [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n", exe); return 1; } @@ -98,14 +98,30 @@ int main(int argc, char **argv) { enum SoundIoBackend backend = SoundIoBackendNone; for (int i = 1; i < argc; i += 1) { char *arg = argv[i]; - if (strcmp("--dummy", arg) == 0) { - backend = SoundIoBackendDummy; - } else if (strcmp("--alsa", arg) == 0) { - backend = SoundIoBackendAlsa; - } else if (strcmp("--pulseaudio", arg) == 0) { - backend = SoundIoBackendPulseAudio; - } else if (strcmp("--jack", arg) == 0) { - backend = SoundIoBackendJack; + if (arg[0] == '-' && arg[1] == '-') { + i += 1; + if (i >= argc) { + return usage(exe); + } else if (strcmp(arg, "--backend") == 0) { + if (strcmp("-dummy", argv[i]) == 0) { + backend = SoundIoBackendDummy; + } else if (strcmp("alsa", argv[i]) == 0) { + backend = SoundIoBackendAlsa; + } else if (strcmp("pulseaudio", argv[i]) == 0) { + backend = SoundIoBackendPulseAudio; + } else if (strcmp("jack", argv[i]) == 0) { + backend = SoundIoBackendJack; + } else if (strcmp("coreaudio", argv[i]) == 0) { + backend = SoundIoBackendCoreAudio; + } else if (strcmp("wasapi", argv[i]) == 0) { + backend = SoundIoBackendWasapi; + } else { + fprintf(stderr, "Invalid backend: %s\n", argv[i]); + return 1; + } + } else { + return usage(exe); + } } else { return usage(exe); }