From 12db5fd970c8f28372621100a57bba5b85464ad2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 30 Jul 2015 00:46:13 -0700 Subject: [PATCH] remove all calls to soundio_panic Return codes are the way errors are communicated, not crashing the entire program. --- README.md | 3 +- src/alsa.cpp | 37 ++++++-- src/alsa.hpp | 3 + src/jack.cpp | 2 +- src/os.cpp | 47 +++++----- src/os.hpp | 2 +- src/pulseaudio.cpp | 209 +++++++++++++++++++++++++++++---------------- src/pulseaudio.hpp | 3 +- src/soundio.cpp | 18 ++-- src/soundio.h | 12 ++- 10 files changed, 211 insertions(+), 125 deletions(-) diff --git a/README.md b/README.md index 6c691b1..283d876 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,6 @@ view `coverage/index.html` in a browser. 0. implement WASAPI (Windows) backend, get examples working 0. implement ASIO (Windows) backend, get examples working 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. 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? @@ -257,6 +256,8 @@ view `coverage/index.html` in a browser. callback be a callback that just provides silence. Once `soundio_outstream_start` is called, switch to the real callback, then call `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. 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 diff --git a/src/alsa.cpp b/src/alsa.cpp index 02145c6..5b57190 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -73,7 +73,8 @@ static snd_pcm_stream_t purpose_to_stream(SoundIoDevicePurpose purpose) { case SoundIoDevicePurposeOutput: return SND_PCM_STREAM_PLAYBACK; 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) { @@ -507,11 +508,10 @@ static int refresh_devices(SoundIoPrivate *si) { if (strcmp(io, "Input") == 0) { is_playback = false; is_capture = true; - } else if (strcmp(io, "Output") == 0) { + } else { + assert(strcmp(io, "Output") == 0); is_playback = true; is_capture = false; - } else { - soundio_panic("invalid io hint value"); } free(io); } else { @@ -706,6 +706,15 @@ static int refresh_devices(SoundIoPrivate *si) { 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) { SoundIoPrivate *si = (SoundIoPrivate *)arg; SoundIoAlsa *sia = &si->backend_data.alsa; @@ -737,7 +746,9 @@ static void device_thread_run(void *arg) { assert(errno != EFAULT); assert(errno != EINVAL); 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) continue; @@ -794,8 +805,10 @@ static void device_thread_run(void *arg) { } } if (got_rescan_event) { - if ((err = refresh_devices(si))) - soundio_panic("error refreshing devices: %s", soundio_strerror(err)); + if ((err = refresh_devices(si))) { + shutdown_backend(si, err); + return; + } } } } @@ -815,11 +828,15 @@ static void flush_events(SoundIoPrivate *si) { block_until_have_devices(sia); bool change = false; + bool cb_shutdown = false; SoundIoDevicesInfo *old_devices_info = nullptr; 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; si->safe_devices_info = sia->ready_devices_info; sia->ready_devices_info = nullptr; @@ -828,7 +845,9 @@ static void flush_events(SoundIoPrivate *si) { 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_destroy_devices_info(old_devices_info); diff --git a/src/alsa.hpp b/src/alsa.hpp index b48d4c1..5a5903e 100644 --- a/src/alsa.hpp +++ b/src/alsa.hpp @@ -33,6 +33,9 @@ struct SoundIoAlsa { // this one is ready to be read with flush_events. protected by mutex struct SoundIoDevicesInfo *ready_devices_info; + + int shutdown_err; + bool emitted_shutdown_cb; }; struct SoundIoOutStreamAlsa { diff --git a/src/jack.cpp b/src/jack.cpp index fc3e186..01daed4 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -317,7 +317,7 @@ static void flush_events_jack(struct SoundIoPrivate *si) { soundio_os_mutex_unlock(sij->mutex); if (cb_shutdown) { - soundio->on_backend_disconnect(soundio); + soundio->on_backend_disconnect(soundio, SoundIoErrorBackendDisconnected); } else { if (!sij->refresh_devices_flag.test_and_set()) { if ((err = refresh_devices(si))) { diff --git a/src/os.cpp b/src/os.cpp index 8efc67c..a9fbc6c 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -165,16 +165,6 @@ static DWORD WINAPI run_win32_thread(LPVOID userdata) { thread->run(thread->arg); 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 static void assert_no_err(int 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 (errno == EINTR) - return; - soundio_panic("kevent signal error: %s", strerror(errno)); + return 0; + assert(0); // kevent signal error } #else 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 (errno == EINTR) return; - soundio_panic("kevent wait error: %s", strerror(errno)); + assert(0); // kevent wait error } #else 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 (errno == EINTR) return; - soundio_panic("kevent wait error: %s", strerror(errno)); + assert(0); // kevent wait error } #else pthread_mutex_t *target_mutex; @@ -551,11 +541,11 @@ void soundio_os_cond_wait(struct SoundIoOsCond *cond, #endif } -static void internal_init(void) { +static int internal_init(void) { #if defined(SOUNDIO_OS_KQUEUE) kq_id = kqueue(); if (kq_id == -1) - soundio_panic("unable to create kqueue: %s", strerror(errno)); + return SoundIoErrorSystemResources; next_notify_ident.store(1); #endif #if defined(SOUNDIO_OS_WINDOWS) @@ -563,42 +553,49 @@ static void internal_init(void) { if (QueryPerformanceFrequency((LARGE_INTEGER*) &frequency)) { win32_time_resolution = 1.0 / (double) frequency; } else { - win32_panic("unable to initialize high precision timer: %s"); + return SoundIoErrorSystemResources; } GetSystemInfo(&win32_system_info); page_size = win32_system_info.dwAllocationGranularity; #else page_size = getpagesize(); #endif + return 0; } -void soundio_os_init(void) { +int soundio_os_init(void) { + int err; #if defined(SOUNDIO_OS_WINDOWS) PVOID lpContext; BOOL pending; if (!InitOnceBeginInitialize(&win32_init_once, INIT_ONCE_ASYNC, &pending, &lpContext)) - win32_panic("InitOnceBeginInitialize failed: %s"); + return SoundIoErrorSystemResources; if (!pending) - return; + return 0; - internal_init(); + if ((err = internal_init())) + return err; if (!InitOnceComplete(&win32_init_once, INIT_ONCE_ASYNC, nullptr)) - win32_panic("InitOnceComplete failed: %s"); + return SoundIoErrorSystemResources; #else if (initialized.load()) - return; + return 0; + assert_no_err(pthread_mutex_lock(&init_mutex)); if (initialized.load()) { assert_no_err(pthread_mutex_unlock(&init_mutex)); - return; + return 0; } initialized.store(true); - internal_init(); + if ((err = internal_init())) + return err; assert_no_err(pthread_mutex_unlock(&init_mutex)); #endif + + return 0; } int soundio_os_page_size(void) { diff --git a/src/os.hpp b/src/os.hpp index 5cb5f18..86586e4 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -14,7 +14,7 @@ // safe to call from any thread(s) multiple times, but // must be called at least once before calling any other os functions // soundio_create calls this function. -void soundio_os_init(void); +int soundio_os_init(void); double soundio_os_get_time(void); diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index e993e93..79708ad 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -22,15 +22,15 @@ static void subscribe_callback(pa_context *context, 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; pa_subscription_mask_t events = (pa_subscription_mask_t)( PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SERVER); - pa_operation *subscribe_op = pa_context_subscribe(sipa->pulse_context, - events, nullptr, si); + pa_operation *subscribe_op = pa_context_subscribe(sipa->pulse_context, events, nullptr, si); 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); + return 0; } 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. return; 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; pa_threaded_mainloop_signal(sipa->main_loop, 0); return; @@ -56,15 +54,10 @@ 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. - { - int err_number = pa_context_errno(context); - if (err_number == PA_ERR_CONNECTIONREFUSED) { - sipa->connection_refused = true; - } else { - soundio_panic("pulseaudio connect failure: %s", pa_strerror(pa_context_errno(context))); - } - return; - } + sipa->connection_err = SoundIoErrorInitAudioBackend; + sipa->ready_flag = true; + pa_threaded_mainloop_signal(sipa->main_loop, 0); + return; } } @@ -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_CENTER: return SoundIoChannelIdTopBackCenter; - default: - soundio_panic("cannot map pulseaudio channel to libsoundio"); + default: return SoundIoChannelIdInvalid; } } @@ -184,6 +176,8 @@ static int set_all_device_formats(SoundIoDevice *device) { } static int perform_operation(SoundIoPrivate *si, pa_operation *op) { + if (!op) + return SoundIoErrorNoMem; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; for (;;) { switch (pa_operation_get_state(op)) { @@ -195,7 +189,7 @@ static int perform_operation(SoundIoPrivate *si, pa_operation *op) { return 0; case PA_OPERATION_CANCELLED: pa_operation_unref(op); - return -1; + return SoundIoErrorInterrupted; } } } @@ -211,6 +205,11 @@ static void finish_device_query(SoundIoPrivate *si) { 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 // if the name doesn't match just pick the first one. if there are no // 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) { sipa->have_sink_list = true; finish_device_query(si); - } else { + } else if (!sipa->device_query_err) { SoundIoDevicePrivate *dev = create(); - if (!dev) - soundio_panic("out of memory"); + 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->name = strdup(info->name); device->description = strdup(info->description); - if (!device->name || !device->description) - soundio_panic("out of memory"); + if (!device->name || !device->description) { + 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 @@ -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); // PulseAudio performs sample format conversion, so any PulseAudio // value is valid. - if ((err = set_all_device_formats(device))) - soundio_panic("out of memory"); + 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_panic("out of memory"); + 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->buffer_duration_min = 0.10; 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; - if (sipa->current_devices_info->output_devices.append(device)) - soundio_panic("out of memory"); + 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); } @@ -306,18 +324,25 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info if (eol) { sipa->have_source_list = true; finish_device_query(si); - } else { + } else if (!sipa->device_query_err) { SoundIoDevicePrivate *dev = create(); - if (!dev) - soundio_panic("out of memory"); + 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->name = strdup(info->name); device->description = strdup(info->description); - if (!device->name || !device->description) - soundio_panic("out of memory"); + if (!device->name || !device->description) { + 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 @@ -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); // PulseAudio performs sample format conversion, so any PulseAudio // value is valid. - if ((err = set_all_device_formats(device))) - soundio_panic("out of memory"); + 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_panic("out of memory"); + 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->buffer_duration_min = 0.10; device->buffer_duration_max = 4.0; - device->buffer_duration_current = -1.0; // "period" is not a recognized concept in PulseAudio. device->purpose = SoundIoDevicePurposeInput; - if (sipa->current_devices_info->input_devices.append(device)) - soundio_panic("out of memory"); + 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); } @@ -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_source_name = strdup(info->default_source_name); - if (!sipa->default_sink_name || !sipa->default_source_name) - soundio_panic("out of memory"); + 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 void scan_devices(SoundIoPrivate *si) { +static int scan_devices(SoundIoPrivate *si) { SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; sipa->have_sink_list = false; @@ -379,27 +418,32 @@ static void scan_devices(SoundIoPrivate *si) { soundio_destroy_devices_info(sipa->current_devices_info); sipa->current_devices_info = create(); if (!sipa->current_devices_info) - soundio_panic("out of memory"); + 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); + 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); - if (perform_operation(si, list_sink_op)) - soundio_panic("list sinks failed"); - if (perform_operation(si, list_source_op)) - soundio_panic("list sources failed"); - if (perform_operation(si, server_info_op)) - soundio_panic("get server info failed"); + 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) { @@ -426,13 +470,13 @@ static void block_until_ready(SoundIoPrivate *si) { static void flush_events(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; - block_until_ready(si); SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; + int err; if (sipa->device_scan_queued) { - sipa->device_scan_queued = false; - scan_devices(si); + if (!(err = scan_devices(si))) + sipa->device_scan_queued = false; } 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; channel_map.channels = channel_layout->channel_count; - if ((unsigned)channel_layout->channel_count > PA_CHANNELS_MAX) - soundio_panic("channel layout greater than pulseaudio max channels"); + assert((unsigned)channel_layout->channel_count <= PA_CHANNELS_MAX); for (int i = 0; i < channel_layout->channel_count; i += 1) 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); break; 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; } } @@ -611,6 +654,11 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { SoundIoOutStreamPulseAudio *ospa = &os->backend_data.pulseaudio; 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; 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); break; case PA_STREAM_FAILED: - soundio_panic("pulseaudio stream error: %s", - pa_strerror(pa_context_errno(pa_stream_get_context(stream)))); + instream->error_callback(instream, SoundIoErrorStreaming); break; } } @@ -808,6 +855,11 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoInStreamPulseAudio *ispa = &is->backend_data.pulseaudio; 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; ispa->stream_ready = false; @@ -949,10 +1001,9 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) { SoundIo *soundio = &si->pub; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; - sipa->connection_refused = false; - sipa->device_scan_queued = false; - sipa->ready_flag = false; - sipa->have_devices_flag = false; + sipa->device_scan_queued.store(false); + sipa->ready_flag.store(false); + sipa->have_devices_flag.store(false); sipa->main_loop = pa_threaded_mainloop_new(); if (!sipa->main_loop) { @@ -983,16 +1034,24 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) { return SoundIoErrorInitAudioBackend; } - if (sipa->connection_refused) { - destroy_pa(si); - return SoundIoErrorInitAudioBackend; - } - if (pa_threaded_mainloop_start(sipa->main_loop)) { destroy_pa(si); 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->flush_events = flush_events; si->wait_events = wait_events; diff --git a/src/pulseaudio.hpp b/src/pulseaudio.hpp index 03953fd..06952b7 100644 --- a/src/pulseaudio.hpp +++ b/src/pulseaudio.hpp @@ -20,7 +20,7 @@ struct SoundIoDevicePulseAudio { }; struct SoundIoPulseAudio { - bool connection_refused; + int connection_err; pa_context *pulse_context; atomic_bool device_scan_queued; @@ -33,6 +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; diff --git a/src/soundio.cpp b/src/soundio.cpp index 68394c8..e82bf0a 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -63,7 +63,7 @@ const char *soundio_strerror(int error) { case SoundIoErrorInterrupted: return "interrupted; try again"; 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) { @@ -87,10 +87,9 @@ int soundio_get_bytes_per_sample(enum SoundIoFormat format) { case SoundIoFormatFloat64LE: return 8; case SoundIoFormatFloat64BE: return 8; - case SoundIoFormatInvalid: - soundio_panic("invalid sample format"); + case SoundIoFormatInvalid: return -1; } - soundio_panic("invalid sample format"); + return -1; } 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 SoundIoBackendDummy: return "Dummy"; } - soundio_panic("invalid backend enum value: %d", (int)backend); + return "(invalid backend)"; } 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_backend_disconnect_cb(struct SoundIo *, int err) { } static void default_msg_callback(const char *msg) { } struct SoundIo * soundio_create(void) { - soundio_os_init(); + int err; + if ((err = soundio_os_init())) + return nullptr; struct SoundIoPrivate *si = create(); if (!si) - return NULL; + return nullptr; SoundIo *soundio = &si->pub; 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->app_name = "SoundIo"; soundio->jack_info_callback = default_msg_callback; diff --git a/src/soundio.h b/src/soundio.h index 41150be..50c520c 100644 --- a/src/soundio.h +++ b/src/soundio.h @@ -213,7 +213,7 @@ struct SoundIo { // and opening streams will always fail with // SoundIoErrorBackendDisconnected. This callback is only called during a // 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 // 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. @@ -287,9 +287,12 @@ struct SoundIoDevice { int sample_rate_max; int sample_rate_current; - // Buffer duration in seconds. If any values are unknown, they are set to - // 0.0. These values are unknown for PulseAudio. For JACK, buffer duration - // and period duration are the same. + // Buffer duration in seconds. If `buffer_duration_current` is unknown or + // irrelevant, it is set to 0.0. + // 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_max; double buffer_duration_current; @@ -561,6 +564,7 @@ void soundio_sort_channel_layouts(struct SoundIoChannelLayout *layouts, int layo // Sample Formats +// Returns -1 on invalid format. int soundio_get_bytes_per_sample(enum SoundIoFormat format); static inline int soundio_get_bytes_per_frame(enum SoundIoFormat format, int channel_count) {