remove all calls to soundio_panic

Return codes are the way errors are communicated, not
crashing the entire program.
This commit is contained in:
Andrew Kelley 2015-07-30 00:46:13 -07:00
parent 5503072fc8
commit 12db5fd970
10 changed files with 211 additions and 125 deletions

View file

@ -245,7 +245,6 @@ view `coverage/index.html` in a browser.
0. implement WASAPI (Windows) backend, get examples working 0. implement WASAPI (Windows) backend, get examples working
0. implement ASIO (Windows) backend, get examples working 0. implement ASIO (Windows) backend, get examples working
0. Integrate into libgroove and test with Groove Basin 0. Integrate into libgroove and test with Groove Basin
0. Avoid calling `soundio_panic` in PulseAudio.
0. PulseAudio: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`. 0. PulseAudio: when prebuf gets set to 0 need to pass `PA_STREAM_START_CORKED`.
0. clear buffer maybe could take an argument to say how many frames to not clear 0. clear buffer maybe could take an argument to say how many frames to not clear
0. In ALSA do we need to wake up the poll when destroying the in or out stream? 0. In ALSA do we need to wake up the poll when destroying the in or out stream?
@ -257,6 +256,8 @@ view `coverage/index.html` in a browser.
callback be a callback that just provides silence. Once callback be a callback that just provides silence. Once
`soundio_outstream_start` is called, switch to the real callback, then call `soundio_outstream_start` is called, switch to the real callback, then call
`pa_stream_flush`, then uncork the stream. `pa_stream_flush`, then uncork the stream.
0. API: devices should reference to their "other" device when the same
hardware has input and output. This is important due to clock timing.
0. Detect PulseAudio server going offline and emit `on_backend_disconnect`. 0. Detect PulseAudio server going offline and emit `on_backend_disconnect`.
0. Instead fo open(), start(), pause(), open() starts it and it starts paused. 0. Instead fo open(), start(), pause(), open() starts it and it starts paused.
0. Create a test for underflow handling. It just makes a sine wave for 5 0. Create a test for underflow handling. It just makes a sine wave for 5

View file

@ -73,7 +73,8 @@ static snd_pcm_stream_t purpose_to_stream(SoundIoDevicePurpose purpose) {
case SoundIoDevicePurposeOutput: return SND_PCM_STREAM_PLAYBACK; case SoundIoDevicePurposeOutput: return SND_PCM_STREAM_PLAYBACK;
case SoundIoDevicePurposeInput: return SND_PCM_STREAM_CAPTURE; case SoundIoDevicePurposeInput: return SND_PCM_STREAM_CAPTURE;
} }
soundio_panic("invalid purpose"); assert(0); // Invalid purpose
return SND_PCM_STREAM_PLAYBACK;
} }
static SoundIoChannelId from_alsa_chmap_pos(unsigned int pos) { static SoundIoChannelId from_alsa_chmap_pos(unsigned int pos) {
@ -507,11 +508,10 @@ static int refresh_devices(SoundIoPrivate *si) {
if (strcmp(io, "Input") == 0) { if (strcmp(io, "Input") == 0) {
is_playback = false; is_playback = false;
is_capture = true; is_capture = true;
} else if (strcmp(io, "Output") == 0) { } else {
assert(strcmp(io, "Output") == 0);
is_playback = true; is_playback = true;
is_capture = false; is_capture = false;
} else {
soundio_panic("invalid io hint value");
} }
free(io); free(io);
} else { } else {
@ -706,6 +706,15 @@ static int refresh_devices(SoundIoPrivate *si) {
return 0; return 0;
} }
static void shutdown_backend(SoundIoPrivate *si, int err) {
SoundIo *soundio = &si->pub;
SoundIoAlsa *sia = &si->backend_data.alsa;
soundio_os_mutex_lock(sia->mutex);
sia->shutdown_err = err;
soundio->on_events_signal(soundio);
soundio_os_mutex_unlock(sia->mutex);
}
static void device_thread_run(void *arg) { static void device_thread_run(void *arg) {
SoundIoPrivate *si = (SoundIoPrivate *)arg; SoundIoPrivate *si = (SoundIoPrivate *)arg;
SoundIoAlsa *sia = &si->backend_data.alsa; SoundIoAlsa *sia = &si->backend_data.alsa;
@ -737,7 +746,9 @@ static void device_thread_run(void *arg) {
assert(errno != EFAULT); assert(errno != EFAULT);
assert(errno != EINVAL); assert(errno != EINVAL);
assert(errno == ENOMEM); assert(errno == ENOMEM);
soundio_panic("kernel ran out of polling memory"); // Kernel ran out of polling memory.
shutdown_backend(si, SoundIoErrorSystemResources);
return;
} }
if (poll_num <= 0) if (poll_num <= 0)
continue; continue;
@ -794,8 +805,10 @@ static void device_thread_run(void *arg) {
} }
} }
if (got_rescan_event) { if (got_rescan_event) {
if ((err = refresh_devices(si))) if ((err = refresh_devices(si))) {
soundio_panic("error refreshing devices: %s", soundio_strerror(err)); shutdown_backend(si, err);
return;
}
} }
} }
} }
@ -815,11 +828,15 @@ static void flush_events(SoundIoPrivate *si) {
block_until_have_devices(sia); block_until_have_devices(sia);
bool change = false; bool change = false;
bool cb_shutdown = false;
SoundIoDevicesInfo *old_devices_info = nullptr; SoundIoDevicesInfo *old_devices_info = nullptr;
soundio_os_mutex_lock(sia->mutex); soundio_os_mutex_lock(sia->mutex);
if (sia->ready_devices_info) { if (sia->shutdown_err && !sia->emitted_shutdown_cb) {
sia->emitted_shutdown_cb = true;
cb_shutdown = true;
} else if (sia->ready_devices_info) {
old_devices_info = si->safe_devices_info; old_devices_info = si->safe_devices_info;
si->safe_devices_info = sia->ready_devices_info; si->safe_devices_info = sia->ready_devices_info;
sia->ready_devices_info = nullptr; sia->ready_devices_info = nullptr;
@ -828,7 +845,9 @@ static void flush_events(SoundIoPrivate *si) {
soundio_os_mutex_unlock(sia->mutex); soundio_os_mutex_unlock(sia->mutex);
if (change) if (cb_shutdown)
soundio->on_backend_disconnect(soundio, sia->shutdown_err);
else if (change)
soundio->on_devices_change(soundio); soundio->on_devices_change(soundio);
soundio_destroy_devices_info(old_devices_info); soundio_destroy_devices_info(old_devices_info);

View file

@ -33,6 +33,9 @@ struct SoundIoAlsa {
// this one is ready to be read with flush_events. protected by mutex // this one is ready to be read with flush_events. protected by mutex
struct SoundIoDevicesInfo *ready_devices_info; struct SoundIoDevicesInfo *ready_devices_info;
int shutdown_err;
bool emitted_shutdown_cb;
}; };
struct SoundIoOutStreamAlsa { struct SoundIoOutStreamAlsa {

View file

@ -317,7 +317,7 @@ static void flush_events_jack(struct SoundIoPrivate *si) {
soundio_os_mutex_unlock(sij->mutex); soundio_os_mutex_unlock(sij->mutex);
if (cb_shutdown) { if (cb_shutdown) {
soundio->on_backend_disconnect(soundio); soundio->on_backend_disconnect(soundio, SoundIoErrorBackendDisconnected);
} else { } else {
if (!sij->refresh_devices_flag.test_and_set()) { if (!sij->refresh_devices_flag.test_and_set()) {
if ((err = refresh_devices(si))) { if ((err = refresh_devices(si))) {

View file

@ -165,16 +165,6 @@ static DWORD WINAPI run_win32_thread(LPVOID userdata) {
thread->run(thread->arg); thread->run(thread->arg);
return 0; return 0;
} }
static void win32_panic(const char *str) {
DWORD err = GetLastError();
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
soundio_panic(str, messageBuffer);
LocalFree(messageBuffer);
}
#else #else
static void assert_no_err(int err) { static void assert_no_err(int err) {
assert(!err); assert(!err);
@ -433,8 +423,8 @@ void soundio_os_cond_signal(struct SoundIoOsCond *cond,
if (kevent(kq_id, &kev, 1, NULL, 0, &timeout) == -1) { if (kevent(kq_id, &kev, 1, NULL, 0, &timeout) == -1) {
if (errno == EINTR) if (errno == EINTR)
return; return 0;
soundio_panic("kevent signal error: %s", strerror(errno)); assert(0); // kevent signal error
} }
#else #else
if (locked_mutex) { if (locked_mutex) {
@ -479,7 +469,7 @@ void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond,
if (kevent(kq_id, &kev, 1, &out_kev, 1, &timeout) == -1) { if (kevent(kq_id, &kev, 1, &out_kev, 1, &timeout) == -1) {
if (errno == EINTR) if (errno == EINTR)
return; return;
soundio_panic("kevent wait error: %s", strerror(errno)); assert(0); // kevent wait error
} }
#else #else
pthread_mutex_t *target_mutex; pthread_mutex_t *target_mutex;
@ -531,7 +521,7 @@ void soundio_os_cond_wait(struct SoundIoOsCond *cond,
if (kevent(kq_id, &kev, 1, &out_kev, 1, NULL) == -1) { if (kevent(kq_id, &kev, 1, &out_kev, 1, NULL) == -1) {
if (errno == EINTR) if (errno == EINTR)
return; return;
soundio_panic("kevent wait error: %s", strerror(errno)); assert(0); // kevent wait error
} }
#else #else
pthread_mutex_t *target_mutex; pthread_mutex_t *target_mutex;
@ -551,11 +541,11 @@ void soundio_os_cond_wait(struct SoundIoOsCond *cond,
#endif #endif
} }
static void internal_init(void) { static int internal_init(void) {
#if defined(SOUNDIO_OS_KQUEUE) #if defined(SOUNDIO_OS_KQUEUE)
kq_id = kqueue(); kq_id = kqueue();
if (kq_id == -1) if (kq_id == -1)
soundio_panic("unable to create kqueue: %s", strerror(errno)); return SoundIoErrorSystemResources;
next_notify_ident.store(1); next_notify_ident.store(1);
#endif #endif
#if defined(SOUNDIO_OS_WINDOWS) #if defined(SOUNDIO_OS_WINDOWS)
@ -563,42 +553,49 @@ static void internal_init(void) {
if (QueryPerformanceFrequency((LARGE_INTEGER*) &frequency)) { if (QueryPerformanceFrequency((LARGE_INTEGER*) &frequency)) {
win32_time_resolution = 1.0 / (double) frequency; win32_time_resolution = 1.0 / (double) frequency;
} else { } else {
win32_panic("unable to initialize high precision timer: %s"); return SoundIoErrorSystemResources;
} }
GetSystemInfo(&win32_system_info); GetSystemInfo(&win32_system_info);
page_size = win32_system_info.dwAllocationGranularity; page_size = win32_system_info.dwAllocationGranularity;
#else #else
page_size = getpagesize(); page_size = getpagesize();
#endif #endif
return 0;
} }
void soundio_os_init(void) { int soundio_os_init(void) {
int err;
#if defined(SOUNDIO_OS_WINDOWS) #if defined(SOUNDIO_OS_WINDOWS)
PVOID lpContext; PVOID lpContext;
BOOL pending; BOOL pending;
if (!InitOnceBeginInitialize(&win32_init_once, INIT_ONCE_ASYNC, &pending, &lpContext)) if (!InitOnceBeginInitialize(&win32_init_once, INIT_ONCE_ASYNC, &pending, &lpContext))
win32_panic("InitOnceBeginInitialize failed: %s"); return SoundIoErrorSystemResources;
if (!pending) if (!pending)
return; return 0;
internal_init(); if ((err = internal_init()))
return err;
if (!InitOnceComplete(&win32_init_once, INIT_ONCE_ASYNC, nullptr)) if (!InitOnceComplete(&win32_init_once, INIT_ONCE_ASYNC, nullptr))
win32_panic("InitOnceComplete failed: %s"); return SoundIoErrorSystemResources;
#else #else
if (initialized.load()) if (initialized.load())
return; return 0;
assert_no_err(pthread_mutex_lock(&init_mutex)); assert_no_err(pthread_mutex_lock(&init_mutex));
if (initialized.load()) { if (initialized.load()) {
assert_no_err(pthread_mutex_unlock(&init_mutex)); assert_no_err(pthread_mutex_unlock(&init_mutex));
return; return 0;
} }
initialized.store(true); initialized.store(true);
internal_init(); if ((err = internal_init()))
return err;
assert_no_err(pthread_mutex_unlock(&init_mutex)); assert_no_err(pthread_mutex_unlock(&init_mutex));
#endif #endif
return 0;
} }
int soundio_os_page_size(void) { int soundio_os_page_size(void) {

View file

@ -14,7 +14,7 @@
// safe to call from any thread(s) multiple times, but // safe to call from any thread(s) multiple times, but
// must be called at least once before calling any other os functions // must be called at least once before calling any other os functions
// soundio_create calls this function. // soundio_create calls this function.
void soundio_os_init(void); int soundio_os_init(void);
double soundio_os_get_time(void); double soundio_os_get_time(void);

View file

@ -22,15 +22,15 @@ static void subscribe_callback(pa_context *context,
pa_threaded_mainloop_signal(sipa->main_loop, 0); pa_threaded_mainloop_signal(sipa->main_loop, 0);
} }
static void subscribe_to_events(SoundIoPrivate *si) { static int subscribe_to_events(SoundIoPrivate *si) {
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
pa_subscription_mask_t events = (pa_subscription_mask_t)( pa_subscription_mask_t events = (pa_subscription_mask_t)(
PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SERVER); PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SERVER);
pa_operation *subscribe_op = pa_context_subscribe(sipa->pulse_context, pa_operation *subscribe_op = pa_context_subscribe(sipa->pulse_context, events, nullptr, si);
events, nullptr, si);
if (!subscribe_op) if (!subscribe_op)
soundio_panic("pa_context_subscribe failed: %s", pa_strerror(pa_context_errno(sipa->pulse_context))); return SoundIoErrorNoMem;
pa_operation_unref(subscribe_op); pa_operation_unref(subscribe_op);
return 0;
} }
static void context_state_callback(pa_context *context, void *userdata) { static void context_state_callback(pa_context *context, void *userdata) {
@ -47,8 +47,6 @@ static void context_state_callback(pa_context *context, void *userdata) {
case PA_CONTEXT_SETTING_NAME: // The client is passing its application name to the daemon. case PA_CONTEXT_SETTING_NAME: // The client is passing its application name to the daemon.
return; return;
case PA_CONTEXT_READY: // The connection is established, the context is ready to execute operations. case PA_CONTEXT_READY: // The connection is established, the context is ready to execute operations.
sipa->device_scan_queued = true;
subscribe_to_events(si);
sipa->ready_flag = true; sipa->ready_flag = true;
pa_threaded_mainloop_signal(sipa->main_loop, 0); pa_threaded_mainloop_signal(sipa->main_loop, 0);
return; return;
@ -56,16 +54,11 @@ static void context_state_callback(pa_context *context, void *userdata) {
pa_threaded_mainloop_signal(sipa->main_loop, 0); pa_threaded_mainloop_signal(sipa->main_loop, 0);
return; return;
case PA_CONTEXT_FAILED: // The connection failed or was disconnected. case PA_CONTEXT_FAILED: // The connection failed or was disconnected.
{ sipa->connection_err = SoundIoErrorInitAudioBackend;
int err_number = pa_context_errno(context); sipa->ready_flag = true;
if (err_number == PA_ERR_CONNECTIONREFUSED) { pa_threaded_mainloop_signal(sipa->main_loop, 0);
sipa->connection_refused = true;
} else {
soundio_panic("pulseaudio connect failure: %s", pa_strerror(pa_context_errno(context)));
}
return; return;
} }
}
} }
static void destroy_pa(SoundIoPrivate *si) { static void destroy_pa(SoundIoPrivate *si) {
@ -135,8 +128,7 @@ static SoundIoChannelId from_pulseaudio_channel_pos(pa_channel_position_t pos) {
case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return SoundIoChannelIdTopBackRight; case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return SoundIoChannelIdTopBackRight;
case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return SoundIoChannelIdTopBackCenter; case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return SoundIoChannelIdTopBackCenter;
default: default: return SoundIoChannelIdInvalid;
soundio_panic("cannot map pulseaudio channel to libsoundio");
} }
} }
@ -184,6 +176,8 @@ static int set_all_device_formats(SoundIoDevice *device) {
} }
static int perform_operation(SoundIoPrivate *si, pa_operation *op) { static int perform_operation(SoundIoPrivate *si, pa_operation *op) {
if (!op)
return SoundIoErrorNoMem;
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
for (;;) { for (;;) {
switch (pa_operation_get_state(op)) { switch (pa_operation_get_state(op)) {
@ -195,7 +189,7 @@ static int perform_operation(SoundIoPrivate *si, pa_operation *op) {
return 0; return 0;
case PA_OPERATION_CANCELLED: case PA_OPERATION_CANCELLED:
pa_operation_unref(op); pa_operation_unref(op);
return -1; return SoundIoErrorInterrupted;
} }
} }
} }
@ -211,6 +205,11 @@ static void finish_device_query(SoundIoPrivate *si) {
return; return;
} }
if (sipa->device_query_err) {
sipa->device_scan_queued.store(true);
return;
}
// based on the default sink name, figure out the default output index // 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 // if the name doesn't match just pick the first one. if there are no
// devices then we need to set it to -1. // devices then we need to set it to -1.
@ -255,18 +254,25 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in
if (eol) { if (eol) {
sipa->have_sink_list = true; sipa->have_sink_list = true;
finish_device_query(si); finish_device_query(si);
} else { } else if (!sipa->device_query_err) {
SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>(); SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!dev) if (!dev) {
soundio_panic("out of memory"); sipa->device_query_err = SoundIoErrorNoMem;
pa_threaded_mainloop_signal(sipa->main_loop, 0);
return;
}
SoundIoDevice *device = &dev->pub; SoundIoDevice *device = &dev->pub;
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;
device->name = strdup(info->name); device->name = strdup(info->name);
device->description = strdup(info->description); device->description = strdup(info->description);
if (!device->name || !device->description) if (!device->name || !device->description) {
soundio_panic("out of memory"); 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; device->sample_rate_current = info->sample_spec.rate;
// PulseAudio performs resampling, so any value is valid. Let's pick // PulseAudio performs resampling, so any value is valid. Let's pick
@ -277,13 +283,21 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in
device->current_format = from_pulseaudio_format(info->sample_spec); device->current_format = from_pulseaudio_format(info->sample_spec);
// PulseAudio performs sample format conversion, so any PulseAudio // PulseAudio performs sample format conversion, so any PulseAudio
// value is valid. // value is valid.
if ((err = set_all_device_formats(device))) if ((err = set_all_device_formats(device))) {
soundio_panic("out of memory"); 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); set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout);
// PulseAudio does channel layout remapping, so any channel layout is valid. // PulseAudio does channel layout remapping, so any channel layout is valid.
if ((err = set_all_device_channel_layouts(device))) if ((err = set_all_device_channel_layouts(device))) {
soundio_panic("out of memory"); soundio_device_unref(device);
sipa->device_query_err = SoundIoErrorNoMem;
pa_threaded_mainloop_signal(sipa->main_loop, 0);
return;
}
device->buffer_duration_min = 0.10; device->buffer_duration_min = 0.10;
device->buffer_duration_max = 4.0; device->buffer_duration_max = 4.0;
@ -292,8 +306,12 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in
device->purpose = SoundIoDevicePurposeOutput; device->purpose = SoundIoDevicePurposeOutput;
if (sipa->current_devices_info->output_devices.append(device)) if (sipa->current_devices_info->output_devices.append(device)) {
soundio_panic("out of memory"); 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); pa_threaded_mainloop_signal(sipa->main_loop, 0);
} }
@ -306,18 +324,25 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info
if (eol) { if (eol) {
sipa->have_source_list = true; sipa->have_source_list = true;
finish_device_query(si); finish_device_query(si);
} else { } else if (!sipa->device_query_err) {
SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>(); SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!dev) if (!dev) {
soundio_panic("out of memory"); sipa->device_query_err = SoundIoErrorNoMem;
pa_threaded_mainloop_signal(sipa->main_loop, 0);
return;
}
SoundIoDevice *device = &dev->pub; SoundIoDevice *device = &dev->pub;
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;
device->name = strdup(info->name); device->name = strdup(info->name);
device->description = strdup(info->description); device->description = strdup(info->description);
if (!device->name || !device->description) if (!device->name || !device->description) {
soundio_panic("out of memory"); 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; device->sample_rate_current = info->sample_spec.rate;
// PulseAudio performs resampling, so any value is valid. Let's pick // PulseAudio performs resampling, so any value is valid. Let's pick
@ -328,24 +353,35 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info
device->current_format = from_pulseaudio_format(info->sample_spec); device->current_format = from_pulseaudio_format(info->sample_spec);
// PulseAudio performs sample format conversion, so any PulseAudio // PulseAudio performs sample format conversion, so any PulseAudio
// value is valid. // value is valid.
if ((err = set_all_device_formats(device))) if ((err = set_all_device_formats(device))) {
soundio_panic("out of memory"); 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); set_from_pulseaudio_channel_map(info->channel_map, &device->current_layout);
// PulseAudio does channel layout remapping, so any channel layout is valid. // PulseAudio does channel layout remapping, so any channel layout is valid.
if ((err = set_all_device_channel_layouts(device))) if ((err = set_all_device_channel_layouts(device))) {
soundio_panic("out of memory"); soundio_device_unref(device);
sipa->device_query_err = SoundIoErrorNoMem;
pa_threaded_mainloop_signal(sipa->main_loop, 0);
return;
}
device->buffer_duration_min = 0.10; device->buffer_duration_min = 0.10;
device->buffer_duration_max = 4.0; device->buffer_duration_max = 4.0;
device->buffer_duration_current = -1.0;
// "period" is not a recognized concept in PulseAudio. // "period" is not a recognized concept in PulseAudio.
device->purpose = SoundIoDevicePurposeInput; device->purpose = SoundIoDevicePurposeInput;
if (sipa->current_devices_info->input_devices.append(device)) if (sipa->current_devices_info->input_devices.append(device)) {
soundio_panic("out of memory"); 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); pa_threaded_mainloop_signal(sipa->main_loop, 0);
} }
@ -361,15 +397,18 @@ static void server_info_callback(pa_context *pulse_context, const pa_server_info
sipa->default_sink_name = strdup(info->default_sink_name); sipa->default_sink_name = strdup(info->default_sink_name);
sipa->default_source_name = strdup(info->default_source_name); sipa->default_source_name = strdup(info->default_source_name);
if (!sipa->default_sink_name || !sipa->default_source_name) if (!sipa->default_sink_name || !sipa->default_source_name) {
soundio_panic("out of memory"); free(sipa->default_sink_name);
free(sipa->default_source_name);
sipa->device_query_err = SoundIoErrorNoMem;
}
sipa->have_default_sink = true; sipa->have_default_sink = true;
finish_device_query(si); finish_device_query(si);
pa_threaded_mainloop_signal(sipa->main_loop, 0); pa_threaded_mainloop_signal(sipa->main_loop, 0);
} }
static void scan_devices(SoundIoPrivate *si) { static int scan_devices(SoundIoPrivate *si) {
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
sipa->have_sink_list = false; sipa->have_sink_list = false;
@ -379,27 +418,32 @@ static void scan_devices(SoundIoPrivate *si) {
soundio_destroy_devices_info(sipa->current_devices_info); soundio_destroy_devices_info(sipa->current_devices_info);
sipa->current_devices_info = create<SoundIoDevicesInfo>(); sipa->current_devices_info = create<SoundIoDevicesInfo>();
if (!sipa->current_devices_info) if (!sipa->current_devices_info)
soundio_panic("out of memory"); return SoundIoErrorNoMem;
pa_threaded_mainloop_lock(sipa->main_loop); pa_threaded_mainloop_lock(sipa->main_loop);
pa_operation *list_sink_op = pa_context_get_sink_info_list(sipa->pulse_context, pa_operation *list_sink_op = pa_context_get_sink_info_list(sipa->pulse_context, sink_info_callback, si);
sink_info_callback, si); pa_operation *list_source_op = pa_context_get_source_info_list(sipa->pulse_context, source_info_callback, si);
pa_operation *list_source_op = pa_context_get_source_info_list(sipa->pulse_context, pa_operation *server_info_op = pa_context_get_server_info(sipa->pulse_context, server_info_callback, si);
source_info_callback, si);
pa_operation *server_info_op = pa_context_get_server_info(sipa->pulse_context,
server_info_callback, si);
if (perform_operation(si, list_sink_op)) int err;
soundio_panic("list sinks failed"); if ((err = perform_operation(si, list_sink_op))) {
if (perform_operation(si, list_source_op)) pa_threaded_mainloop_unlock(sipa->main_loop);
soundio_panic("list sources failed"); return err;
if (perform_operation(si, server_info_op)) }
soundio_panic("get server info failed"); 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_signal(sipa->main_loop, 0);
pa_threaded_mainloop_unlock(sipa->main_loop); pa_threaded_mainloop_unlock(sipa->main_loop);
return 0;
} }
static void block_until_have_devices(SoundIoPrivate *si) { static void block_until_have_devices(SoundIoPrivate *si) {
@ -426,13 +470,13 @@ static void block_until_ready(SoundIoPrivate *si) {
static void flush_events(SoundIoPrivate *si) { static void flush_events(SoundIoPrivate *si) {
SoundIo *soundio = &si->pub; SoundIo *soundio = &si->pub;
block_until_ready(si);
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
int err;
if (sipa->device_scan_queued) { if (sipa->device_scan_queued) {
if (!(err = scan_devices(si)))
sipa->device_scan_queued = false; sipa->device_scan_queued = false;
scan_devices(si);
} }
SoundIoDevicesInfo *old_devices_info = nullptr; SoundIoDevicesInfo *old_devices_info = nullptr;
@ -544,8 +588,7 @@ static pa_channel_map to_pulseaudio_channel_map(const SoundIoChannelLayout *chan
pa_channel_map channel_map; pa_channel_map channel_map;
channel_map.channels = channel_layout->channel_count; channel_map.channels = channel_layout->channel_count;
if ((unsigned)channel_layout->channel_count > PA_CHANNELS_MAX) assert((unsigned)channel_layout->channel_count <= PA_CHANNELS_MAX);
soundio_panic("channel layout greater than pulseaudio max channels");
for (int i = 0; i < channel_layout->channel_count; i += 1) for (int i = 0; i < channel_layout->channel_count; i += 1)
channel_map.map[i] = to_pulseaudio_channel_pos(channel_layout->channels[i]); channel_map.map[i] = to_pulseaudio_channel_pos(channel_layout->channels[i]);
@ -570,7 +613,7 @@ static void playback_stream_state_callback(pa_stream *stream, void *userdata) {
pa_threaded_mainloop_signal(sipa->main_loop, 0); pa_threaded_mainloop_signal(sipa->main_loop, 0);
break; break;
case PA_STREAM_FAILED: case PA_STREAM_FAILED:
soundio_panic("pulseaudio stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); outstream->error_callback(outstream, SoundIoErrorStreaming);
break; break;
} }
} }
@ -611,6 +654,11 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio; SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio;
SoundIoOutStream *outstream = &os->pub; SoundIoOutStream *outstream = &os->pub;
if (outstream->layout.channel_count > SOUNDIO_MAX_CHANNELS)
return SoundIoErrorInvalid;
if ((unsigned)outstream->layout.channel_count > PA_CHANNELS_MAX)
return SoundIoErrorIncompatibleBackend;
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
ospa->stream_ready = false; ospa->stream_ready = false;
@ -771,8 +819,7 @@ static void recording_stream_state_callback(pa_stream *stream, void *userdata) {
pa_threaded_mainloop_signal(sipa->main_loop, 0); pa_threaded_mainloop_signal(sipa->main_loop, 0);
break; break;
case PA_STREAM_FAILED: case PA_STREAM_FAILED:
soundio_panic("pulseaudio stream error: %s", instream->error_callback(instream, SoundIoErrorStreaming);
pa_strerror(pa_context_errno(pa_stream_get_context(stream))));
break; break;
} }
} }
@ -808,6 +855,11 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio; SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio;
SoundIoInStream *instream = &is->pub; SoundIoInStream *instream = &is->pub;
if (instream->layout.channel_count > SOUNDIO_MAX_CHANNELS)
return SoundIoErrorInvalid;
if ((unsigned)instream->layout.channel_count > PA_CHANNELS_MAX)
return SoundIoErrorIncompatibleBackend;
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
ispa->stream_ready = false; ispa->stream_ready = false;
@ -949,10 +1001,9 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) {
SoundIo *soundio = &si->pub; SoundIo *soundio = &si->pub;
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
sipa->connection_refused = false; sipa->device_scan_queued.store(false);
sipa->device_scan_queued = false; sipa->ready_flag.store(false);
sipa->ready_flag = false; sipa->have_devices_flag.store(false);
sipa->have_devices_flag = false;
sipa->main_loop = pa_threaded_mainloop_new(); sipa->main_loop = pa_threaded_mainloop_new();
if (!sipa->main_loop) { if (!sipa->main_loop) {
@ -983,16 +1034,24 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) {
return SoundIoErrorInitAudioBackend; return SoundIoErrorInitAudioBackend;
} }
if (sipa->connection_refused) {
destroy_pa(si);
return SoundIoErrorInitAudioBackend;
}
if (pa_threaded_mainloop_start(sipa->main_loop)) { if (pa_threaded_mainloop_start(sipa->main_loop)) {
destroy_pa(si); destroy_pa(si);
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
block_until_ready(si);
if (sipa->connection_err) {
destroy_pa(si);
return sipa->connection_err;
}
sipa->device_scan_queued.store(true);
if ((err = subscribe_to_events(si))) {
destroy_pa(si);
return err;
}
si->destroy = destroy_pa; si->destroy = destroy_pa;
si->flush_events = flush_events; si->flush_events = flush_events;
si->wait_events = wait_events; si->wait_events = wait_events;

View file

@ -20,7 +20,7 @@ struct SoundIoDevicePulseAudio {
}; };
struct SoundIoPulseAudio { struct SoundIoPulseAudio {
bool connection_refused; int connection_err;
pa_context *pulse_context; pa_context *pulse_context;
atomic_bool device_scan_queued; atomic_bool device_scan_queued;
@ -33,6 +33,7 @@ struct SoundIoPulseAudio {
// this one is ready to be read with flush_events. protected by mutex // this one is ready to be read with flush_events. protected by mutex
struct SoundIoDevicesInfo *ready_devices_info; struct SoundIoDevicesInfo *ready_devices_info;
int device_query_err;
bool have_sink_list; bool have_sink_list;
bool have_source_list; bool have_source_list;
bool have_default_sink; bool have_default_sink;

View file

@ -63,7 +63,7 @@ const char *soundio_strerror(int error) {
case SoundIoErrorInterrupted: return "interrupted; try again"; case SoundIoErrorInterrupted: return "interrupted; try again";
case SoundIoErrorUnderflow: return "buffer underflow"; case SoundIoErrorUnderflow: return "buffer underflow";
} }
soundio_panic("invalid error enum value: %d", error); return "(invalid error)";
} }
int soundio_get_bytes_per_sample(enum SoundIoFormat format) { int soundio_get_bytes_per_sample(enum SoundIoFormat format) {
@ -87,10 +87,9 @@ int soundio_get_bytes_per_sample(enum SoundIoFormat format) {
case SoundIoFormatFloat64LE: return 8; case SoundIoFormatFloat64LE: return 8;
case SoundIoFormatFloat64BE: return 8; case SoundIoFormatFloat64BE: return 8;
case SoundIoFormatInvalid: case SoundIoFormatInvalid: return -1;
soundio_panic("invalid sample format");
} }
soundio_panic("invalid sample format"); return -1;
} }
const char * soundio_format_string(enum SoundIoFormat format) { const char * soundio_format_string(enum SoundIoFormat format) {
@ -129,7 +128,7 @@ const char *soundio_backend_name(enum SoundIoBackend backend) {
case SoundIoBackendAlsa: return "ALSA"; case SoundIoBackendAlsa: return "ALSA";
case SoundIoBackendDummy: return "Dummy"; case SoundIoBackendDummy: return "Dummy";
} }
soundio_panic("invalid backend enum value: %d", (int)backend); return "(invalid backend)";
} }
void soundio_destroy(struct SoundIo *soundio) { void soundio_destroy(struct SoundIo *soundio) {
@ -143,16 +142,19 @@ void soundio_destroy(struct SoundIo *soundio) {
} }
static void do_nothing_cb(struct SoundIo *) { } static void do_nothing_cb(struct SoundIo *) { }
static void do_nothing_backend_disconnect_cb(struct SoundIo *, int err) { }
static void default_msg_callback(const char *msg) { } static void default_msg_callback(const char *msg) { }
struct SoundIo * soundio_create(void) { struct SoundIo * soundio_create(void) {
soundio_os_init(); int err;
if ((err = soundio_os_init()))
return nullptr;
struct SoundIoPrivate *si = create<SoundIoPrivate>(); struct SoundIoPrivate *si = create<SoundIoPrivate>();
if (!si) if (!si)
return NULL; return nullptr;
SoundIo *soundio = &si->pub; SoundIo *soundio = &si->pub;
soundio->on_devices_change = do_nothing_cb; soundio->on_devices_change = do_nothing_cb;
soundio->on_backend_disconnect = do_nothing_cb; soundio->on_backend_disconnect = do_nothing_backend_disconnect_cb;
soundio->on_events_signal = do_nothing_cb; soundio->on_events_signal = do_nothing_cb;
soundio->app_name = "SoundIo"; soundio->app_name = "SoundIo";
soundio->jack_info_callback = default_msg_callback; soundio->jack_info_callback = default_msg_callback;

View file

@ -213,7 +213,7 @@ struct SoundIo {
// and opening streams will always fail with // and opening streams will always fail with
// SoundIoErrorBackendDisconnected. This callback is only called during a // SoundIoErrorBackendDisconnected. This callback is only called during a
// call to soundio_flush_events or soundio_wait_events. // call to soundio_flush_events or soundio_wait_events.
void (*on_backend_disconnect)(struct SoundIo *); void (*on_backend_disconnect)(struct SoundIo *, int err);
// Optional callback. Called from an unknown thread that you should not use // Optional callback. Called from an unknown thread that you should not use
// to call any soundio functions. You may use this to signal a condition // to call any soundio functions. You may use this to signal a condition
// variable to wake up. Called when soundio_wait_events would be woken up. // variable to wake up. Called when soundio_wait_events would be woken up.
@ -287,9 +287,12 @@ struct SoundIoDevice {
int sample_rate_max; int sample_rate_max;
int sample_rate_current; int sample_rate_current;
// Buffer duration in seconds. If any values are unknown, they are set to // Buffer duration in seconds. If `buffer_duration_current` is unknown or
// 0.0. These values are unknown for PulseAudio. For JACK, buffer duration // irrelevant, it is set to 0.0.
// and period duration are the same. // PulseAudio allows any value and so reasonable min/max of 0.10 and 4.0
// are used. You may check that the current backend is PulseAudio and
// ignore these min/max values.
// For JACK, buffer duration and period duration are the same.
double buffer_duration_min; double buffer_duration_min;
double buffer_duration_max; double buffer_duration_max;
double buffer_duration_current; double buffer_duration_current;
@ -561,6 +564,7 @@ void soundio_sort_channel_layouts(struct SoundIoChannelLayout *layouts, int layo
// Sample Formats // Sample Formats
// Returns -1 on invalid format.
int soundio_get_bytes_per_sample(enum SoundIoFormat format); int soundio_get_bytes_per_sample(enum SoundIoFormat format);
static inline int soundio_get_bytes_per_frame(enum SoundIoFormat format, int channel_count) { static inline int soundio_get_bytes_per_frame(enum SoundIoFormat format, int channel_count) {