From 7371ce5146576078019b86eb1d05bdbd7d9263ec Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 1 Aug 2015 13:00:46 -0700 Subject: [PATCH] Fix kqueue-based cond and signal implementation --- CMakeLists.txt | 7 + soundio/soundio.h | 1 + src/alsa.hpp | 4 +- src/coreaudio.cpp | 392 ++++++++++++++++++++++++++++++++++++++++++---- src/coreaudio.hpp | 20 ++- src/os.cpp | 37 +++-- src/soundio.cpp | 13 +- 7 files changed, 420 insertions(+), 54 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 08107fe..1ce68d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,10 +61,15 @@ if(COREAUDIO_FOUND) set(STATUS_COREAUDIO "OK") set(SOUNDIO_HAVE_COREAUDIO true) include_directories(${COREAUDIO_INCLUDE_DIR}) + + find_path(COREFOUNDATION_INCLUDE_DIR NAMES CoreFoundation.h) + find_library(COREFOUNDATION_LIBRARY NAMES CoreFoundation) + include_directories(${COREFOUNDATION_INCLUDE_DIR}) else() set(STATUS_COREAUDIO "not found") set(SOUNDIO_HAVE_COREAUDIO false) set(COREAUDIO_LIBRARY "") + set(COREFOUNDATION_LIBRARY "") endif() @@ -174,6 +179,7 @@ target_link_libraries(libsoundio_shared LINK_PUBLIC ${PULSEAUDIO_LIBRARY} ${ALSA_LIBRARIES} ${COREAUDIO_LIBRARY} + ${COREFOUNDATION_LIBRARY} m ${CMAKE_THREAD_LIBS_INIT} ) @@ -228,6 +234,7 @@ target_link_libraries(unit_tests LINK_PUBLIC ${PULSEAUDIO_LIBRARY} ${ALSA_LIBRARIES} ${COREAUDIO_LIBRARY} + ${COREFOUNDATION_LIBRARY} m ) set_target_properties(unit_tests PROPERTIES diff --git a/soundio/soundio.h b/soundio/soundio.h index e5d6e6a..238a4e2 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -31,6 +31,7 @@ enum SoundIoError { SoundIoErrorBackendDisconnected, SoundIoErrorInterrupted, SoundIoErrorUnderflow, + SoundIoErrorEncodingString, }; enum SoundIoChannelId { diff --git a/src/alsa.hpp b/src/alsa.hpp index a1d0dc9..2e6c0b7 100644 --- a/src/alsa.hpp +++ b/src/alsa.hpp @@ -16,9 +16,7 @@ int soundio_alsa_init(struct SoundIoPrivate *si); -struct SoundIoDeviceAlsa { - -}; +struct SoundIoDeviceAlsa { }; struct SoundIoAlsa { SoundIoOsMutex *mutex; diff --git a/src/coreaudio.cpp b/src/coreaudio.cpp index 36e5f46..6060cf6 100644 --- a/src/coreaudio.cpp +++ b/src/coreaudio.cpp @@ -1,83 +1,415 @@ #include "coreaudio.hpp" #include "soundio.hpp" -static void destroy_ca(struct SoundIoPrivate *) { +#include + +static SoundIoDeviceAim aims[] = { + SoundIoDeviceAimInput, + SoundIoDeviceAimOutput, +}; + +static OSStatus on_devices_changed(AudioObjectID in_object_id, UInt32 in_number_addresses, + const AudioObjectPropertyAddress in_addresses[], void *in_client_data) +{ + SoundIoPrivate *si = (SoundIoPrivate*)in_client_data; + SoundIoCoreAudio *sica = &si->backend_data.coreaudio; + + sica->device_scan_queued.store(true); + soundio_os_cond_signal(sica->cond, nullptr); + + return noErr; +} + +static void destroy_ca(struct SoundIoPrivate *si) { + SoundIoCoreAudio *sica = &si->backend_data.coreaudio; + + AudioObjectPropertyAddress prop_address = { + kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + int err = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &prop_address, + on_devices_changed, si); + assert(!err); + + if (sica->thread) { + sica->abort_flag.clear(); + soundio_os_cond_signal(sica->cond, nullptr); + soundio_os_thread_destroy(sica->thread); + } + + if (sica->cond) + soundio_os_cond_destroy(sica->cond); + + if (sica->have_devices_cond) + soundio_os_cond_destroy(sica->have_devices_cond); + + if (sica->mutex) + soundio_os_mutex_destroy(sica->mutex); + + soundio_destroy_devices_info(sica->ready_devices_info); +} + +// Possible errors: +// * SoundIoErrorNoMem +// * SoundIoErrorEncodingString +static int from_cf_string(CFStringRef string_ref, char **out_str, int *out_str_len) { + assert(string_ref); + + CFIndex length = CFStringGetLength(string_ref); + CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8); + char *buf = allocate_nonzero(max_size); + if (!buf) + return SoundIoErrorNoMem; + + if (!CFStringGetCString(string_ref, buf, max_size, kCFStringEncodingUTF8)) { + deallocate(buf, max_size); + return SoundIoErrorEncodingString; + } + + *out_str = buf; + *out_str_len = strlen(buf); + return 0; +} + +static int aim_to_scope(SoundIoDeviceAim aim) { + return (aim == SoundIoDeviceAimInput) ? + kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput; +} +// TODO +/* + @constant kAudioHardwarePropertyDefaultInputDevice + The AudioObjectID of the default input AudioDevice. + @constant kAudioHardwarePropertyDefaultOutputDevice + The AudioObjectID of the default output AudioDevice. + */ +/* + @constant kAudioHardwarePropertyServiceRestarted + A UInt32 whose value has no meaning. Rather, this property exists so that + clients can be informed when the service has been reset for some reason. + When a reset happens, any state the client has , such as cached data or + added listeners, must be re-established by the client. + */ + +/* + * + @constant kAudioDevicePropertyDeviceHasChanged + The type of this property is a UInt32, but its value has no meaning. This + property exists so that clients can listen to it and be told when the + configuration of the AudioDevice has changed in ways that cannot otherwise + be conveyed through other notifications. In response to this notification, + clients should re-evaluate everything they need to know about the device, + particularly the layout and values of the controls. + */ +/* + @constant kAudioDevicePropertyBufferFrameSize + A UInt32 whose value indicates the number of frames in the IO buffers. + @constant kAudioDevicePropertyBufferFrameSizeRange + An AudioValueRange indicating the minimum and maximum values, inclusive, for + kAudioDevicePropertyBufferFrameSize. + */ +/* + @constant kAudioDevicePropertyStreamConfiguration + This property returns the stream configuration of the device in an + AudioBufferList (with the buffer pointers set to NULL) which describes the + list of streams and the number of channels in each stream. This corresponds + to what will be passed into the IOProc. + */ + + +// TODO go through here and make sure all allocations are freed +static int refresh_devices(struct SoundIoPrivate *si) { + SoundIo *soundio = &si->pub; + SoundIoCoreAudio *sica = &si->backend_data.coreaudio; + + SoundIoDevicesInfo *devices_info = create(); + if (!devices_info) { + soundio_panic("out of mem"); + } + + AudioObjectPropertyAddress prop_address = { + kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + + UInt32 the_data_size = 0; + OSStatus err; + if ((err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, + &prop_address, 0, nullptr, &the_data_size))) + { + soundio_panic("get prop data size"); + } + + int devices_available = the_data_size / (UInt32)sizeof(AudioObjectID); + + if (devices_available < 1) { + soundio_panic("no devices"); + } + + AudioObjectID *devices = (AudioObjectID *)allocate(the_data_size); + if (!devices) { + soundio_panic("out of mem"); + } + + if ((err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop_address, 0, nullptr, + &the_data_size, devices))) + { + soundio_panic("get property data"); + } + + fprintf(stderr, "device count: %d\n", devices_available); + + for (int i = 0; i < devices_available; i += 1) { + AudioObjectID deviceID = devices[i]; + + prop_address.mSelector = kAudioObjectPropertyName; + CFStringRef temp_string_ref = nullptr; + UInt32 io_size = sizeof(CFStringRef); + if ((err = AudioObjectGetPropertyData(deviceID, &prop_address, + 0, nullptr, &io_size, &temp_string_ref))) + { + soundio_panic("error getting name"); + } + + char *device_name; + int device_name_len; + if ((err = from_cf_string(temp_string_ref, &device_name, &device_name_len))) { + soundio_panic("error decoding"); + } + fprintf(stderr, "device name: %s\n", device_name); + + CFRelease(temp_string_ref); + temp_string_ref = nullptr; + + + for (int aim_i = 0; aim_i < array_length(aims); aim_i += 1) { + SoundIoDeviceAim aim = aims[aim_i]; + + io_size = 0; + prop_address.mSelector = kAudioDevicePropertyStreamConfiguration; + prop_address.mScope = aim_to_scope(aim); + prop_address.mElement = 0; + if ((err = AudioObjectGetPropertyDataSize(deviceID, &prop_address, 0, nullptr, &io_size))) { + soundio_panic("input channel get prop data size"); + } + + AudioBufferList *buffer_list = (AudioBufferList*)allocate(io_size); + if (!buffer_list) { + soundio_panic("out of mem"); + } + + if ((err = AudioObjectGetPropertyData(deviceID, &prop_address, 0, nullptr, + &io_size, buffer_list))) + { + soundio_panic("get input channel data"); + } + + int channel_count = 0; + for (int i = 0; i < buffer_list->mNumberBuffers; i += 1) { + channel_count += buffer_list->mBuffers[i].mNumberChannels; + } + deallocate((char*)buffer_list, io_size); + + fprintf(stderr, "%d channel count: %d\n", aim_i, channel_count); + } + + } + + + soundio_os_mutex_lock(sica->mutex); + soundio_destroy_devices_info(sica->ready_devices_info); + sica->ready_devices_info = devices_info; + soundio->on_events_signal(soundio); + soundio_os_mutex_unlock(sica->mutex); + if (!sica->have_devices_flag.exchange(true)) + soundio_os_cond_signal(sica->have_devices_cond, nullptr); + + + return 0; +} + +static void shutdown_backend(SoundIoPrivate *si, int err) { + SoundIo *soundio = &si->pub; + SoundIoCoreAudio *sica = &si->backend_data.coreaudio; + soundio_os_mutex_lock(sica->mutex); + sica->shutdown_err = err; + soundio->on_events_signal(soundio); + 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); +} + +static void flush_events_ca(struct SoundIoPrivate *si) { + SoundIo *soundio = &si->pub; + SoundIoCoreAudio *sica = &si->backend_data.coreaudio; + block_until_have_devices(sica); + + bool change = false; + bool cb_shutdown = false; + SoundIoDevicesInfo *old_devices_info = nullptr; + + soundio_os_mutex_lock(sica->mutex); + + if (sica->shutdown_err && !sica->emitted_shutdown_cb) { + sica->emitted_shutdown_cb = true; + cb_shutdown = true; + } else if (sica->ready_devices_info) { + old_devices_info = si->safe_devices_info; + si->safe_devices_info = sica->ready_devices_info; + sica->ready_devices_info = nullptr; + change = true; + } + + soundio_os_mutex_unlock(sica->mutex); + + if (cb_shutdown) + soundio->on_backend_disconnect(soundio, sica->shutdown_err); + else if (change) + soundio->on_devices_change(soundio); + + soundio_destroy_devices_info(old_devices_info); +} + +static void wait_events_ca(struct SoundIoPrivate *si) { + SoundIoCoreAudio *sica = &si->backend_data.coreaudio; + flush_events_ca(si); + soundio_os_cond_wait(sica->cond, nullptr); +} + +static void wakeup_ca(struct SoundIoPrivate *si) { + SoundIoCoreAudio *sica = &si->backend_data.coreaudio; + soundio_os_cond_signal(sica->cond, nullptr); +} + +static void device_thread_run(void *arg) { + SoundIoPrivate *si = (SoundIoPrivate *)arg; + SoundIoCoreAudio *sica = &si->backend_data.coreaudio; + int err; + + for (;;) { + if (!sica->abort_flag.test_and_set()) + break; + if (sica->device_scan_queued.exchange(false)) { + if ((err = refresh_devices(si))) { + shutdown_backend(si, err); + return; + } + } + soundio_os_cond_wait(sica->cond, nullptr); + } +} + +static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { soundio_panic("TODO"); } -static void flush_events_ca(struct SoundIoPrivate *) { +static void outstream_destroy_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { soundio_panic("TODO"); } -static void wait_events_ca(struct SoundIoPrivate *) { +static int outstream_start_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { soundio_panic("TODO"); } -static void wakeup_ca(struct SoundIoPrivate *) { - soundio_panic("TODO"); -} - - -static int outstream_open_ca(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { - soundio_panic("TODO"); -} - -static void outstream_destroy_ca(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { - soundio_panic("TODO"); -} - -static int outstream_start_ca(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { - soundio_panic("TODO"); -} - -static int outstream_begin_write_ca(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, +static int outstream_begin_write_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, SoundIoChannelArea **out_areas, int *frame_count) { soundio_panic("TODO"); } -static int outstream_end_write_ca(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, int frame_count) { +static int outstream_end_write_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, + int frame_count) +{ soundio_panic("TODO"); } -static int outstream_clear_buffer_ca(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *) { +static int outstream_clear_buffer_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { soundio_panic("TODO"); } -static int outstream_pause_ca(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, bool pause) { +static int outstream_pause_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) { soundio_panic("TODO"); } -static int instream_open_ca(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { +static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) { soundio_panic("TODO"); } -static void instream_destroy_ca(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { +static void instream_destroy_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) { soundio_panic("TODO"); } -static int instream_start_ca(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { +static int instream_start_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) { soundio_panic("TODO"); } -static int instream_begin_read_ca(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, +static int instream_begin_read_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count) { soundio_panic("TODO"); } -static int instream_end_read_ca(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *) { +static int instream_end_read_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) { soundio_panic("TODO"); } -static int instream_pause_ca(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, bool pause) { +static int instream_pause_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is, bool pause) { soundio_panic("TODO"); } +// Possible errors: +// * SoundIoErrorNoMem int soundio_coreaudio_init(SoundIoPrivate *si) { + SoundIoCoreAudio *sica = &si->backend_data.coreaudio; + int err; + + sica->have_devices_flag.store(false); + sica->device_scan_queued.store(true); + sica->abort_flag.test_and_set(); + + sica->mutex = soundio_os_mutex_create(); + if (!sica->mutex) { + destroy_ca(si); + return SoundIoErrorNoMem; + } + + sica->cond = soundio_os_cond_create(); + if (!sica->cond) { + destroy_ca(si); + return SoundIoErrorNoMem; + } + + sica->have_devices_cond = soundio_os_cond_create(); + if (!sica->have_devices_cond) { + destroy_ca(si); + return SoundIoErrorNoMem; + } + + AudioObjectPropertyAddress prop_address = { + kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + if ((err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &prop_address, + on_devices_changed, si))) + { + soundio_panic("add prop listener"); + } + + if ((err = soundio_os_thread_create(device_thread_run, si, false, &sica->thread))) { + destroy_ca(si); + return err; + } + si->destroy = destroy_ca; si->flush_events = flush_events_ca; si->wait_events = wait_events_ca; diff --git a/src/coreaudio.hpp b/src/coreaudio.hpp index c06afcc..5ca92e0 100644 --- a/src/coreaudio.hpp +++ b/src/coreaudio.hpp @@ -9,13 +9,29 @@ #define SOUNDIO_COREAUDIO_HPP #include "soundio/soundio.h" +#include "soundio/os.h" +#include "atomics.hpp" + +#include int soundio_coreaudio_init(struct SoundIoPrivate *si); -struct SoundIoDeviceCoreAudio { -}; +struct SoundIoDeviceCoreAudio { }; struct SoundIoCoreAudio { + SoundIoOsMutex *mutex; + SoundIoOsCond *cond; + struct SoundIoOsThread *thread; + 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; + SoundIoOsCond *have_devices_cond; + + atomic_bool device_scan_queued; + int shutdown_err; + bool emitted_shutdown_cb; }; struct SoundIoOutStreamCoreAudioPort { diff --git a/src/os.cpp b/src/os.cpp index a231591..3eea942 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -98,10 +98,9 @@ struct SoundIoOsMutex { }; #if defined(SOUNDIO_OS_KQUEUE) -static int kq_id; -static atomic_uintptr_t next_notify_ident; +static const uintptr_t notify_ident = 1; struct SoundIoOsCond { - int notify_ident; + int kq_id; }; #elif defined(SOUNDIO_OS_WINDOWS) struct SoundIoOsCond { @@ -348,7 +347,9 @@ struct SoundIoOsCond * soundio_os_cond_create(void) { InitializeConditionVariable(&cond->id); InitializeCriticalSection(&cond->default_cs_id); #elif defined(SOUNDIO_OS_KQUEUE) - cond->notify_ident = next_notify_ident.fetch_add(1); + cond->kq_id = kqueue(); + if (cond->kq_id == -1) + return NULL; #else if (pthread_condattr_init(&cond->attr)) { soundio_os_cond_destroy(cond); @@ -417,11 +418,11 @@ void soundio_os_cond_signal(struct SoundIoOsCond *cond, struct timespec timeout = { 0, 0 }; memset(&kev, 0, sizeof(kev)); - kev.ident = cond->notify_ident; + kev.ident = notify_ident; kev.filter = EVFILT_USER; kev.fflags = NOTE_TRIGGER; - if (kevent(kq_id, &kev, 1, NULL, 0, &timeout) == -1) { + if (kevent(cond->kq_id, &kev, 1, NULL, 0, &timeout) == -1) { if (errno == EINTR) return; assert(0); // kevent signal error @@ -456,8 +457,11 @@ void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond, struct kevent kev; struct kevent out_kev; + if (locked_mutex) + assert_no_err(pthread_mutex_unlock(&locked_mutex->id)); + memset(&kev, 0, sizeof(kev)); - kev.ident = cond->notify_ident; + kev.ident = notify_ident; kev.filter = EVFILT_USER; kev.flags = EV_ADD | EV_CLEAR; @@ -466,11 +470,13 @@ void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond, timeout.tv_sec = 0; timeout.tv_nsec = (seconds * 1000000000L); - if (kevent(kq_id, &kev, 1, &out_kev, 1, &timeout) == -1) { + if (kevent(cond->kq_id, &kev, 1, &out_kev, 1, &timeout) == -1) { if (errno == EINTR) return; assert(0); // kevent wait error } + if (locked_mutex) + assert_no_err(pthread_mutex_lock(&locked_mutex->id)); #else pthread_mutex_t *target_mutex; if (locked_mutex) { @@ -513,16 +519,21 @@ void soundio_os_cond_wait(struct SoundIoOsCond *cond, struct kevent kev; struct kevent out_kev; + if (locked_mutex) + assert_no_err(pthread_mutex_unlock(&locked_mutex->id)); + memset(&kev, 0, sizeof(kev)); - kev.ident = cond->notify_ident; + kev.ident = notify_ident; kev.filter = EVFILT_USER; kev.flags = EV_ADD | EV_CLEAR; - if (kevent(kq_id, &kev, 1, &out_kev, 1, NULL) == -1) { + if (kevent(cond->kq_id, &kev, 1, &out_kev, 1, NULL) == -1) { if (errno == EINTR) return; assert(0); // kevent wait error } + if (locked_mutex) + assert_no_err(pthread_mutex_lock(&locked_mutex->id)); #else pthread_mutex_t *target_mutex; if (locked_mutex) { @@ -542,12 +553,6 @@ void soundio_os_cond_wait(struct SoundIoOsCond *cond, } static int internal_init(void) { -#if defined(SOUNDIO_OS_KQUEUE) - kq_id = kqueue(); - if (kq_id == -1) - return SoundIoErrorSystemResources; - next_notify_ident.store(1); -#endif #if defined(SOUNDIO_OS_WINDOWS) unsigned __int64 frequency; if (QueryPerformanceFrequency((LARGE_INTEGER*) &frequency)) { diff --git a/src/soundio.cpp b/src/soundio.cpp index d98ae2c..3c58b80 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -70,6 +70,7 @@ const char *soundio_strerror(int error) { case SoundIoErrorBackendDisconnected: return "backend disconnected"; case SoundIoErrorInterrupted: return "interrupted; try again"; case SoundIoErrorUnderflow: return "buffer underflow"; + case SoundIoErrorEncodingString: return "failed to encode string"; } return "(invalid error)"; } @@ -379,10 +380,14 @@ struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device) } int soundio_outstream_open(struct SoundIoOutStream *outstream) { + SoundIoDevice *device = outstream->device; + + if (device->aim != SoundIoDeviceAimOutput) + return SoundIoErrorInvalid; + if (outstream->format <= SoundIoFormatInvalid) return SoundIoErrorInvalid; - SoundIoDevice *device = outstream->device; if (device->probe_error) return device->probe_error; @@ -459,10 +464,12 @@ struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) { } int soundio_instream_open(struct SoundIoInStream *instream) { - if (instream->format <= SoundIoFormatInvalid) + SoundIoDevice *device = instream->device; + if (device->aim != SoundIoDeviceAimInput) return SoundIoErrorInvalid; - SoundIoDevice *device = instream->device; + if (instream->format <= SoundIoFormatInvalid) + return SoundIoErrorInvalid; if (device->probe_error) return device->probe_error;