Fix kqueue-based cond and signal implementation

This commit is contained in:
Andrew Kelley 2015-08-01 13:00:46 -07:00
parent d5423ba8fd
commit 7371ce5146
7 changed files with 420 additions and 54 deletions

View file

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

View file

@ -31,6 +31,7 @@ enum SoundIoError {
SoundIoErrorBackendDisconnected,
SoundIoErrorInterrupted,
SoundIoErrorUnderflow,
SoundIoErrorEncodingString,
};
enum SoundIoChannelId {

View file

@ -16,9 +16,7 @@
int soundio_alsa_init(struct SoundIoPrivate *si);
struct SoundIoDeviceAlsa {
};
struct SoundIoDeviceAlsa { };
struct SoundIoAlsa {
SoundIoOsMutex *mutex;

View file

@ -1,83 +1,415 @@
#include "coreaudio.hpp"
#include "soundio.hpp"
static void destroy_ca(struct SoundIoPrivate *) {
#include <assert.h>
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<char>(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<SoundIoDevicesInfo>();
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<char>(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<char>(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;

View file

@ -9,13 +9,29 @@
#define SOUNDIO_COREAUDIO_HPP
#include "soundio/soundio.h"
#include "soundio/os.h"
#include "atomics.hpp"
#include <CoreAudio.h>
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 {

View file

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

View file

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