mirror of
https://github.com/Ryujinx/libsoundio.git
synced 2024-12-23 03:05:37 +00:00
CoreAudio: listen for changes to device properties
This commit is contained in:
parent
d00fe4db58
commit
28f73ba037
|
@ -3,6 +3,84 @@
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
static AudioObjectPropertyAddress device_listen_props[] = {
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyDeviceHasChanged,
|
||||||
|
kAudioObjectPropertyScopeGlobal,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioObjectPropertyName,
|
||||||
|
kAudioObjectPropertyScopeGlobal,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyDeviceUID,
|
||||||
|
kAudioObjectPropertyScopeGlobal,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyStreamConfiguration,
|
||||||
|
kAudioObjectPropertyScopeInput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyStreamConfiguration,
|
||||||
|
kAudioObjectPropertyScopeOutput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyPreferredChannelLayout,
|
||||||
|
kAudioObjectPropertyScopeInput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyPreferredChannelLayout,
|
||||||
|
kAudioObjectPropertyScopeOutput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyNominalSampleRate,
|
||||||
|
kAudioObjectPropertyScopeInput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyNominalSampleRate,
|
||||||
|
kAudioObjectPropertyScopeOutput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyAvailableNominalSampleRates,
|
||||||
|
kAudioObjectPropertyScopeInput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyAvailableNominalSampleRates,
|
||||||
|
kAudioObjectPropertyScopeOutput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyBufferFrameSize,
|
||||||
|
kAudioObjectPropertyScopeInput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyBufferFrameSize,
|
||||||
|
kAudioObjectPropertyScopeOutput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyBufferFrameSizeRange,
|
||||||
|
kAudioObjectPropertyScopeInput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kAudioDevicePropertyBufferFrameSizeRange,
|
||||||
|
kAudioObjectPropertyScopeOutput,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
static SoundIoDeviceAim aims[] = {
|
static SoundIoDeviceAim aims[] = {
|
||||||
SoundIoDeviceAimInput,
|
SoundIoDeviceAimInput,
|
||||||
SoundIoDeviceAimOutput,
|
SoundIoDeviceAimOutput,
|
||||||
|
@ -32,10 +110,22 @@ static OSStatus on_service_restarted(AudioObjectID in_object_id, UInt32 in_numbe
|
||||||
return noErr;
|
return noErr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void unsubscribe_device_listeners(SoundIoPrivate *si) {
|
||||||
|
SoundIoCoreAudio *sica = &si->backend_data.coreaudio;
|
||||||
|
|
||||||
|
for (int device_index = 0; device_index < sica->registered_listeners.length; device_index += 1) {
|
||||||
|
AudioDeviceID device_id = sica->registered_listeners.at(device_index);
|
||||||
|
for (int i = 0; i < array_length(device_listen_props); i += 1) {
|
||||||
|
AudioObjectRemovePropertyListener(device_id, &device_listen_props[i],
|
||||||
|
on_devices_changed, si);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sica->registered_listeners.clear();
|
||||||
|
}
|
||||||
|
|
||||||
static void destroy_ca(struct SoundIoPrivate *si) {
|
static void destroy_ca(struct SoundIoPrivate *si) {
|
||||||
SoundIoCoreAudio *sica = &si->backend_data.coreaudio;
|
SoundIoCoreAudio *sica = &si->backend_data.coreaudio;
|
||||||
|
|
||||||
int err;
|
|
||||||
AudioObjectPropertyAddress prop_address = {
|
AudioObjectPropertyAddress prop_address = {
|
||||||
kAudioHardwarePropertyDevices,
|
kAudioHardwarePropertyDevices,
|
||||||
kAudioObjectPropertyScopeGlobal,
|
kAudioObjectPropertyScopeGlobal,
|
||||||
|
@ -46,6 +136,9 @@ static void destroy_ca(struct SoundIoPrivate *si) {
|
||||||
prop_address.mSelector = kAudioHardwarePropertyServiceRestarted;
|
prop_address.mSelector = kAudioHardwarePropertyServiceRestarted;
|
||||||
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &prop_address, on_service_restarted, si);
|
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &prop_address, on_service_restarted, si);
|
||||||
|
|
||||||
|
unsubscribe_device_listeners(si);
|
||||||
|
sica->registered_listeners.deinit();
|
||||||
|
|
||||||
if (sica->thread) {
|
if (sica->thread) {
|
||||||
sica->abort_flag.clear();
|
sica->abort_flag.clear();
|
||||||
soundio_os_cond_signal(sica->scan_devices_cond, nullptr);
|
soundio_os_cond_signal(sica->scan_devices_cond, nullptr);
|
||||||
|
@ -93,21 +186,7 @@ static int aim_to_scope(SoundIoDeviceAim aim) {
|
||||||
return (aim == SoundIoDeviceAimInput) ?
|
return (aim == SoundIoDeviceAimInput) ?
|
||||||
kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput;
|
kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput;
|
||||||
}
|
}
|
||||||
// TODO subscribe to device property changes for every property we read and
|
|
||||||
// trigger a rescan if anything changes. test with changing the preferredchannellayout
|
|
||||||
// for the device
|
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
/*
|
|
||||||
*
|
|
||||||
@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 kAudioDevicePropertyLatency
|
@constant kAudioDevicePropertyLatency
|
||||||
A UInt32 containing the number of frames of latency in the AudioDevice. Note
|
A UInt32 containing the number of frames of latency in the AudioDevice. Note
|
||||||
|
@ -310,6 +389,7 @@ static bool all_channels_invalid(const struct SoundIoChannelLayout *layout) {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RefreshDevices {
|
struct RefreshDevices {
|
||||||
|
SoundIoPrivate *si;
|
||||||
SoundIoDevicesInfo *devices_info;
|
SoundIoDevicesInfo *devices_info;
|
||||||
int devices_size;
|
int devices_size;
|
||||||
AudioObjectID *devices;
|
AudioObjectID *devices;
|
||||||
|
@ -322,9 +402,12 @@ struct RefreshDevices {
|
||||||
char *device_uid;
|
char *device_uid;
|
||||||
int device_uid_len;
|
int device_uid_len;
|
||||||
AudioValueRange *avr_array;
|
AudioValueRange *avr_array;
|
||||||
|
bool ok;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void deinit_refresh_devices(RefreshDevices *rd) {
|
static void deinit_refresh_devices(RefreshDevices *rd) {
|
||||||
|
if (!rd->ok)
|
||||||
|
unsubscribe_device_listeners(rd->si);
|
||||||
destroy(rd->devices_info);
|
destroy(rd->devices_info);
|
||||||
deallocate((char*)rd->devices, rd->devices_size);
|
deallocate((char*)rd->devices, rd->devices_size);
|
||||||
if (rd->string_ref)
|
if (rd->string_ref)
|
||||||
|
@ -345,7 +428,10 @@ static int refresh_devices(struct SoundIoPrivate *si) {
|
||||||
OSStatus os_err;
|
OSStatus os_err;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
|
unsubscribe_device_listeners(si);
|
||||||
|
|
||||||
RefreshDevices rd = {0};
|
RefreshDevices rd = {0};
|
||||||
|
rd.si = si;
|
||||||
|
|
||||||
if (!(rd.devices_info = create<SoundIoDevicesInfo>())) {
|
if (!(rd.devices_info = create<SoundIoDevicesInfo>())) {
|
||||||
deinit_refresh_devices(&rd);
|
deinit_refresh_devices(&rd);
|
||||||
|
@ -407,6 +493,20 @@ static int refresh_devices(struct SoundIoPrivate *si) {
|
||||||
for (int device_i = 0; device_i < device_count; device_i += 1) {
|
for (int device_i = 0; device_i < device_count; device_i += 1) {
|
||||||
AudioObjectID device_id = rd.devices[device_i];
|
AudioObjectID device_id = rd.devices[device_i];
|
||||||
|
|
||||||
|
for (int i = 0; i < array_length(device_listen_props); i += 1) {
|
||||||
|
if ((err = sica->registered_listeners.add_one())) {
|
||||||
|
deinit_refresh_devices(&rd);
|
||||||
|
return SoundIoErrorOpeningDevice;
|
||||||
|
}
|
||||||
|
if ((os_err = AudioObjectAddPropertyListener(device_id, &device_listen_props[i],
|
||||||
|
on_devices_changed, si)))
|
||||||
|
{
|
||||||
|
deinit_refresh_devices(&rd);
|
||||||
|
return SoundIoErrorOpeningDevice;
|
||||||
|
}
|
||||||
|
sica->registered_listeners.last() = device_id;
|
||||||
|
}
|
||||||
|
|
||||||
prop_address.mSelector = kAudioObjectPropertyName;
|
prop_address.mSelector = kAudioObjectPropertyName;
|
||||||
prop_address.mScope = kAudioObjectPropertyScopeGlobal;
|
prop_address.mScope = kAudioObjectPropertyScopeGlobal;
|
||||||
prop_address.mElement = kAudioObjectPropertyElementMaster;
|
prop_address.mElement = kAudioObjectPropertyElementMaster;
|
||||||
|
@ -639,7 +739,6 @@ static int refresh_devices(struct SoundIoPrivate *si) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
soundio_os_mutex_lock(sica->mutex);
|
soundio_os_mutex_lock(sica->mutex);
|
||||||
soundio_destroy_devices_info(sica->ready_devices_info);
|
soundio_destroy_devices_info(sica->ready_devices_info);
|
||||||
sica->ready_devices_info = rd.devices_info;
|
sica->ready_devices_info = rd.devices_info;
|
||||||
|
@ -647,6 +746,7 @@ static int refresh_devices(struct SoundIoPrivate *si) {
|
||||||
soundio_os_mutex_unlock(sica->mutex);
|
soundio_os_mutex_unlock(sica->mutex);
|
||||||
|
|
||||||
rd.devices_info = nullptr;
|
rd.devices_info = nullptr;
|
||||||
|
rd.ok = true;
|
||||||
deinit_refresh_devices(&rd);
|
deinit_refresh_devices(&rd);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -742,7 +842,7 @@ static void device_thread_run(void *arg) {
|
||||||
static void outstream_destroy_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
|
static void outstream_destroy_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
|
||||||
SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio;
|
SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio;
|
||||||
if (osca->output_instance) {
|
if (osca->output_instance) {
|
||||||
AudioOutputUnitStop(osca->output_instance)
|
AudioOutputUnitStop(osca->output_instance);
|
||||||
AudioComponentInstanceDispose(osca->output_instance);
|
AudioComponentInstanceDispose(osca->output_instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -764,6 +864,7 @@ static OSStatus write_callback_ca(void *userdata, AudioUnitRenderActionFlags *io
|
||||||
}
|
}
|
||||||
|
|
||||||
static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
|
static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
|
||||||
|
// TODO: use outstream->buffer_duration
|
||||||
SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio;
|
SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio;
|
||||||
SoundIoOutStream *outstream = &os->pub;
|
SoundIoOutStream *outstream = &os->pub;
|
||||||
SoundIoDevice *device = outstream->device;
|
SoundIoDevice *device = outstream->device;
|
||||||
|
@ -829,6 +930,22 @@ static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamP
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int outstream_pause_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) {
|
||||||
|
SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio;
|
||||||
|
OSStatus os_err;
|
||||||
|
if (pause) {
|
||||||
|
if ((os_err = AudioOutputUnitStop(osca->output_instance))) {
|
||||||
|
return SoundIoErrorStreaming;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ((os_err = AudioOutputUnitStart(osca->output_instance))) {
|
||||||
|
return SoundIoErrorStreaming;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int outstream_start_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
|
static int outstream_start_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) {
|
||||||
return outstream_pause_ca(si, os, false);
|
return outstream_pause_ca(si, os, false);
|
||||||
}
|
}
|
||||||
|
@ -866,21 +983,6 @@ static int outstream_clear_buffer_ca(struct SoundIoPrivate *si, struct SoundIoOu
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int outstream_pause_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) {
|
|
||||||
SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio;
|
|
||||||
OSStatus os_err;
|
|
||||||
if (pause) {
|
|
||||||
if ((os_err = AudioOutputUnitStop(osca->output_instance))) {
|
|
||||||
return SoundIoErrorStreaming;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ((os_err = AudioOutputUnitStart(osca->output_instance))) {
|
|
||||||
return SoundIoErrorStreaming;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
|
static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is) {
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "soundio/soundio.h"
|
#include "soundio/soundio.h"
|
||||||
#include "soundio/os.h"
|
#include "soundio/os.h"
|
||||||
#include "atomics.hpp"
|
#include "atomics.hpp"
|
||||||
|
#include "list.hpp"
|
||||||
|
|
||||||
#include <CoreAudio/CoreAudio.h>
|
#include <CoreAudio/CoreAudio.h>
|
||||||
#include <AudioUnit/AudioUnit.h>
|
#include <AudioUnit/AudioUnit.h>
|
||||||
|
@ -32,6 +33,7 @@ struct SoundIoCoreAudio {
|
||||||
atomic_bool have_devices_flag;
|
atomic_bool have_devices_flag;
|
||||||
SoundIoOsCond *have_devices_cond;
|
SoundIoOsCond *have_devices_cond;
|
||||||
SoundIoOsCond *scan_devices_cond;
|
SoundIoOsCond *scan_devices_cond;
|
||||||
|
SoundIoList<AudioDeviceID> registered_listeners;
|
||||||
|
|
||||||
atomic_bool device_scan_queued;
|
atomic_bool device_scan_queued;
|
||||||
atomic_bool service_restarted;
|
atomic_bool service_restarted;
|
||||||
|
|
|
@ -15,12 +15,7 @@
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct SoundIoList {
|
struct SoundIoList {
|
||||||
SoundIoList() {
|
void deinit() {
|
||||||
length = 0;
|
|
||||||
capacity = 0;
|
|
||||||
items = nullptr;
|
|
||||||
}
|
|
||||||
~SoundIoList() {
|
|
||||||
deallocate(items, capacity);
|
deallocate(items, capacity);
|
||||||
}
|
}
|
||||||
int __attribute__((warn_unused_result)) append(T item) {
|
int __attribute__((warn_unused_result)) append(T item) {
|
||||||
|
|
|
@ -557,6 +557,9 @@ void soundio_destroy_devices_info(SoundIoDevicesInfo *devices_info) {
|
||||||
for (int i = 0; i < devices_info->output_devices.length; i += 1)
|
for (int i = 0; i < devices_info->output_devices.length; i += 1)
|
||||||
soundio_device_unref(devices_info->output_devices.at(i));
|
soundio_device_unref(devices_info->output_devices.at(i));
|
||||||
|
|
||||||
|
devices_info->input_devices.deinit();
|
||||||
|
devices_info->output_devices.deinit();
|
||||||
|
|
||||||
destroy(devices_info);
|
destroy(devices_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue