libsoundio/src/coreaudio.cpp

435 lines
14 KiB
C++
Raw Normal View History

2015-08-01 01:52:51 +00:00
#include "coreaudio.hpp"
#include "soundio.hpp"
#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;
2015-08-01 01:52:51 +00:00
}
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);
2015-08-01 01:52:51 +00:00
}
// 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;
2015-08-01 01:52:51 +00:00
}
static int aim_to_scope(SoundIoDeviceAim aim) {
return (aim == SoundIoDeviceAimInput) ?
kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput;
2015-08-01 01:52:51 +00:00
}
// 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");
}
2015-08-01 01:52:51 +00:00
if ((err = AudioObjectGetPropertyData(deviceID, &prop_address, 0, nullptr,
&io_size, buffer_list)))
{
soundio_panic("get input channel data");
}
2015-08-01 01:52:51 +00:00
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) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static void outstream_destroy_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static int outstream_start_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static int outstream_begin_write_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os,
2015-08-01 01:52:51 +00:00
SoundIoChannelArea **out_areas, int *frame_count)
{
soundio_panic("TODO");
}
static int outstream_end_write_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os,
int frame_count)
{
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static int outstream_clear_buffer_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static int outstream_pause_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static void instream_destroy_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static int instream_start_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static int instream_begin_read_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is,
2015-08-01 01:52:51 +00:00
SoundIoChannelArea **out_areas, int *frame_count)
{
soundio_panic("TODO");
}
static int instream_end_read_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
static int instream_pause_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is, bool pause) {
2015-08-01 01:52:51 +00:00
soundio_panic("TODO");
}
// Possible errors:
// * SoundIoErrorNoMem
2015-08-01 01:52:51 +00:00
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;
}
2015-08-01 01:52:51 +00:00
si->destroy = destroy_ca;
si->flush_events = flush_events_ca;
si->wait_events = wait_events_ca;
si->wakeup = wakeup_ca;
si->outstream_open = outstream_open_ca;
si->outstream_destroy = outstream_destroy_ca;
si->outstream_start = outstream_start_ca;
si->outstream_begin_write = outstream_begin_write_ca;
si->outstream_end_write = outstream_end_write_ca;
si->outstream_clear_buffer = outstream_clear_buffer_ca;
si->outstream_pause = outstream_pause_ca;
si->instream_open = instream_open_ca;
si->instream_destroy = instream_destroy_ca;
si->instream_start = instream_start_ca;
si->instream_begin_read = instream_begin_read_ca;
si->instream_end_read = instream_end_read_ca;
si->instream_pause = instream_pause_ca;
return 0;
}