various code adjustments

* doc clarifications
 * examples compile with c99 not c11
 * fix pulseaudio on_backend_disconnected not firing only during
   flush events
 * make wait events more efficient
 * fix alsa devices race condition
 * fix backend disconnected code handling
 * add overflow test
 * fix on_events_signal not called at correct times
 * refactor pulseaudio device scanning
 * fix SoundIoErrorNoSuchDevice string value
This commit is contained in:
Andrew Kelley 2015-08-27 20:53:28 -07:00
parent dd49396429
commit 16437bd357
17 changed files with 709 additions and 405 deletions

View file

@ -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)

View file

@ -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

View file

@ -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);

View file

@ -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<SoundIoDevicesInfo>(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;

View file

@ -11,6 +11,7 @@
#include "soundio_private.h"
#include "os.h"
#include "atomics.hpp"
#include "list.hpp"
#include <alsa/asoundlib.h>
@ -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<SoundIoAlsaPendingFile> pending_files;
// this one is ready to be read with flush_events. protected by mutex
struct SoundIoDevicesInfo *ready_devices_info;

View file

@ -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;
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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;

View file

@ -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<SoundIoDevicePrivate>(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<SoundIoDevicePrivate>(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<SoundIoDevicesInfo>(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<SoundIoDevicePrivate>(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<SoundIoDevicePrivate>(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<SoundIoDevicesInfo>(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;

View file

@ -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;

View file

@ -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";

View file

@ -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();

View file

@ -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;

View file

@ -12,7 +12,6 @@
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdatomic.h>
#include <unistd.h>
__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);

228
test/overflow.c Normal file
View file

@ -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 <soundio/soundio.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
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;
}
}

View file

@ -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);
}