CoreAudio: obtain most device information

This commit is contained in:
Andrew Kelley 2015-08-01 22:10:43 -07:00
parent 6d185de82c
commit f77663399f
12 changed files with 528 additions and 108 deletions

View file

@ -242,6 +242,11 @@ view `coverage/index.html` in a browser.
## Roadmap ## Roadmap
0. implement CoreAudio (OSX) backend, get examples working 0. implement CoreAudio (OSX) backend, get examples working
0. Add some builtin channel layouts from
https://developer.apple.com/library/mac/documentation/MusicAudio/Reference/CoreAudioDataTypesRef/#//apple_ref/doc/constant_group/Audio_Channel_Layout_Tags
0. Add more PulseAudio channel ids
0. Make sure PulseAudio can handle refresh devices crashing before
block_until_have_devices
0. implement WASAPI (Windows) backend, get examples working 0. implement WASAPI (Windows) backend, get examples working
0. implement ASIO (Windows) backend, get examples working 0. implement ASIO (Windows) backend, get examples working
0. Integrate into libgroove and test with Groove Basin 0. Integrate into libgroove and test with Groove Basin
@ -268,6 +273,7 @@ view `coverage/index.html` in a browser.
0. -fvisibility=hidden and then explicitly export stuff, or 0. -fvisibility=hidden and then explicitly export stuff, or
explicitly make the unexported stuff private explicitly make the unexported stuff private
0. add len arguments to APIs that have char * 0. add len arguments to APIs that have char *
- replace strdup with soundio_str_dupe
0. Support PulseAudio proplist properties for main context and streams 0. Support PulseAudio proplist properties for main context and streams
0. Expose JACK options in `jack_client_open` 0. Expose JACK options in `jack_client_open`
0. custom allocator support 0. custom allocator support

View file

@ -147,6 +147,7 @@ int main(int argc, char **argv) {
soundio_wait_events(soundio); soundio_wait_events(soundio);
} }
} else { } else {
soundio_flush_events(soundio);
int err = list_devices(soundio); int err = list_devices(soundio);
soundio_destroy(soundio); soundio_destroy(soundio);
return err; return err;

View file

@ -193,6 +193,7 @@ int main(int argc, char **argv) {
if (err) if (err)
panic("error connecting: %s", soundio_strerror(err)); panic("error connecting: %s", soundio_strerror(err));
soundio_flush_events(soundio);
int default_out_device_index = soundio_default_output_device_index(soundio); int default_out_device_index = soundio_default_output_device_index(soundio);
if (default_out_device_index < 0) if (default_out_device_index < 0)

View file

@ -105,6 +105,8 @@ int main(int argc, char **argv) {
if (err) if (err)
panic("error connecting: %s", soundio_strerror(err)); panic("error connecting: %s", soundio_strerror(err));
soundio_flush_events(soundio);
int default_out_device_index = soundio_default_output_device_index(soundio); int default_out_device_index = soundio_default_output_device_index(soundio);
if (default_out_device_index < 0) if (default_out_device_index < 0)
panic("no output device found"); panic("no output device found");

View file

@ -70,9 +70,52 @@ enum SoundIoChannelId {
SoundIoChannelIdTopSideRight, SoundIoChannelIdTopSideRight,
SoundIoChannelIdLeftLfe, SoundIoChannelIdLeftLfe,
SoundIoChannelIdRightLfe, SoundIoChannelIdRightLfe,
SoundIoChannelIdLfe2,
SoundIoChannelIdBottomCenter, SoundIoChannelIdBottomCenter,
SoundIoChannelIdBottomLeftCenter, SoundIoChannelIdBottomLeftCenter,
SoundIoChannelIdBottomRightCenter, SoundIoChannelIdBottomRightCenter,
// Mid/side recording
SoundIoChannelIdMsMid,
SoundIoChannelIdMsSide,
// first order ambisonic channels
SoundIoChannelIdAmbisonicW,
SoundIoChannelIdAmbisonicX,
SoundIoChannelIdAmbisonicY,
SoundIoChannelIdAmbisonicZ,
// X-Y Recording
SoundIoChannelIdXyX,
SoundIoChannelIdXyY,
// Other
SoundIoChannelIdHeadphonesLeft,
SoundIoChannelIdHeadphonesRight,
SoundIoChannelIdClickTrack,
SoundIoChannelIdForeignLanguage,
SoundIoChannelIdHearingImpaired,
SoundIoChannelIdNarration,
SoundIoChannelIdHaptic,
SoundIoChannelIdDialogCentricMix,
SoundIoChannelIdAux,
SoundIoChannelIdAux0,
SoundIoChannelIdAux1,
SoundIoChannelIdAux2,
SoundIoChannelIdAux3,
SoundIoChannelIdAux4,
SoundIoChannelIdAux5,
SoundIoChannelIdAux6,
SoundIoChannelIdAux7,
SoundIoChannelIdAux8,
SoundIoChannelIdAux9,
SoundIoChannelIdAux10,
SoundIoChannelIdAux11,
SoundIoChannelIdAux12,
SoundIoChannelIdAux13,
SoundIoChannelIdAux14,
SoundIoChannelIdAux15,
}; };
enum SoundIoChannelLayoutId { enum SoundIoChannelLayoutId {
@ -214,7 +257,9 @@ struct SoundIo {
// when the JACK server shuts down. When this happens, listing devices // when the JACK server shuts down. When this happens, listing devices
// and opening streams will always fail with // and opening streams will always fail with
// SoundIoErrorBackendDisconnected. This callback is only called during a // SoundIoErrorBackendDisconnected. This callback is only called during a
// call to soundio_flush_events or soundio_wait_events. // call to `soundio_flush_events` or `soundio_wait_events`.
// If you do not supply a callback, the default will crash your program
// with an error message.
void (*on_backend_disconnect)(struct SoundIo *, int err); void (*on_backend_disconnect)(struct SoundIo *, int err);
// Optional callback. Called from an unknown thread that you should not use // 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 // to call any soundio functions. You may use this to signal a condition
@ -592,22 +637,37 @@ const char * soundio_format_string(enum SoundIoFormat format);
// Devices // Devices
// When you call `soundio_flush_events`, a snapshot of all device state is
// saved and these functions merely access the snapshot data. When you want
// to check for new devices, call `soundio_flush_events`. Or you can call
// `soundio_wait_events` to block until devices change. If an error occurs
// scanning devices in a background thread, `on_backend_disconnect` is called
// with the error code.
// Get the number of input devices.
// Returns -1 if you never called `soundio_flush_events`.
int soundio_input_device_count(struct SoundIo *soundio); int soundio_input_device_count(struct SoundIo *soundio);
// Get the number of output devices.
// Returns -1 if you never called `soundio_flush_events`.
int soundio_output_device_count(struct SoundIo *soundio); int soundio_output_device_count(struct SoundIo *soundio);
// Always returns a device. Call soundio_device_unref when done. // Always returns a device. Call soundio_device_unref when done.
// `index` must be 0 <= index < soundio_input_device_count // `index` must be 0 <= index < soundio_input_device_count
// Returns NULL if you never called `soundio_flush_events`.
struct SoundIoDevice *soundio_get_input_device(struct SoundIo *soundio, int index); struct SoundIoDevice *soundio_get_input_device(struct SoundIo *soundio, int index);
// Always returns a device. Call soundio_device_unref when done. // Always returns a device. Call soundio_device_unref when done.
// `index` must be 0 <= index < soundio_output_device_count // `index` must be 0 <= index < soundio_output_device_count
// Returns NULL if you never called `soundio_flush_events`.
struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int index); struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int index);
// returns the index of the default input device // returns the index of the default input device
// returns -1 if there are no devices. // returns -1 if there are no devices or if you never called
// `soundio_flush_events`.
int soundio_default_input_device_index(struct SoundIo *soundio); int soundio_default_input_device_index(struct SoundIo *soundio);
// returns the index of the default output device // returns the index of the default output device
// returns -1 if there are no devices. // returns -1 if there are no devices or if you never called
// `soundio_flush_events`.
int soundio_default_output_device_index(struct SoundIo *soundio); int soundio_default_output_device_index(struct SoundIo *soundio);
void soundio_device_ref(struct SoundIoDevice *device); void soundio_device_ref(struct SoundIoDevice *device);

View file

@ -805,10 +805,13 @@ static void device_thread_run(void *arg) {
} }
} }
if (got_rescan_event) { if (got_rescan_event) {
if ((err = refresh_devices(si))) { err = refresh_devices(si);
if (err)
shutdown_backend(si, err); shutdown_backend(si, err);
if (!sia->have_devices_flag.exchange(true))
soundio_os_cond_signal(sica->have_devices_cond, nullptr);
if (err)
return; return;
}
} }
} }
} }

View file

@ -15,7 +15,7 @@ static OSStatus on_devices_changed(AudioObjectID in_object_id, UInt32 in_number_
SoundIoCoreAudio *sica = &si->backend_data.coreaudio; SoundIoCoreAudio *sica = &si->backend_data.coreaudio;
sica->device_scan_queued.store(true); sica->device_scan_queued.store(true);
soundio_os_cond_signal(sica->cond, nullptr); soundio_os_cond_signal(sica->scan_devices_cond, nullptr);
return noErr; return noErr;
} }
@ -27,7 +27,7 @@ static OSStatus on_service_restarted(AudioObjectID in_object_id, UInt32 in_numbe
SoundIoCoreAudio *sica = &si->backend_data.coreaudio; SoundIoCoreAudio *sica = &si->backend_data.coreaudio;
sica->service_restarted.store(true); sica->service_restarted.store(true);
soundio_os_cond_signal(sica->cond, nullptr); soundio_os_cond_signal(sica->scan_devices_cond, nullptr);
return noErr; return noErr;
} }
@ -52,7 +52,7 @@ static void destroy_ca(struct SoundIoPrivate *si) {
if (sica->thread) { if (sica->thread) {
sica->abort_flag.clear(); sica->abort_flag.clear();
soundio_os_cond_signal(sica->cond, nullptr); soundio_os_cond_signal(sica->scan_devices_cond, nullptr);
soundio_os_thread_destroy(sica->thread); soundio_os_thread_destroy(sica->thread);
} }
@ -62,6 +62,9 @@ static void destroy_ca(struct SoundIoPrivate *si) {
if (sica->have_devices_cond) if (sica->have_devices_cond)
soundio_os_cond_destroy(sica->have_devices_cond); soundio_os_cond_destroy(sica->have_devices_cond);
if (sica->scan_devices_cond)
soundio_os_cond_destroy(sica->scan_devices_cond);
if (sica->mutex) if (sica->mutex)
soundio_os_mutex_destroy(sica->mutex); soundio_os_mutex_destroy(sica->mutex);
@ -95,12 +98,6 @@ static int aim_to_scope(SoundIoDeviceAim aim) {
kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput; kAudioObjectPropertyScopeInput : kAudioObjectPropertyScopeOutput;
} }
// TODO // TODO
/*
@constant kAudioHardwarePropertyDefaultInputDevice
The AudioObjectID of the default input AudioDevice.
@constant kAudioHardwarePropertyDefaultOutputDevice
The AudioObjectID of the default output AudioDevice.
*/
/* /*
* *
@constant kAudioDevicePropertyDeviceHasChanged @constant kAudioDevicePropertyDeviceHasChanged
@ -110,31 +107,265 @@ static int aim_to_scope(SoundIoDeviceAim aim) {
be conveyed through other notifications. In response to this notification, be conveyed through other notifications. In response to this notification,
clients should re-evaluate everything they need to know about the device, clients should re-evaluate everything they need to know about the device,
particularly the layout and values of the controls. particularly the layout and values of the controls.
*/ */
/* /*
@constant kAudioDevicePropertyBufferFrameSize @constant kAudioDevicePropertyBufferFrameSize
A UInt32 whose value indicates the number of frames in the IO buffers. A UInt32 whose value indicates the number of frames in the IO buffers.
@constant kAudioDevicePropertyBufferFrameSizeRange @constant kAudioDevicePropertyBufferFrameSizeRange
An AudioValueRange indicating the minimum and maximum values, inclusive, for An AudioValueRange indicating the minimum and maximum values, inclusive, for
kAudioDevicePropertyBufferFrameSize. kAudioDevicePropertyBufferFrameSize.
*/ */
/* /*
@constant kAudioDevicePropertyStreamConfiguration @constant kAudioDevicePropertyLatency
This property returns the stream configuration of the device in an A UInt32 containing the number of frames of latency in the AudioDevice. Note
AudioBufferList (with the buffer pointers set to NULL) which describes the that input and output latency may differ. Further, the AudioDevice's
list of streams and the number of channels in each stream. This corresponds AudioStreams may have additional latency so they should be queried as well.
to what will be passed into the IOProc. If both the device and the stream say they have latency, then the total
*/ latency for the stream is the device latency summed with the stream latency.
*/
/*
@constant kAudioDevicePropertyNominalSampleRate
A Float64 that indicates the current nominal sample rate of the AudioDevice.
@constant kAudioDevicePropertyAvailableNominalSampleRates
An array of AudioValueRange structs that indicates the valid ranges for the
nominal sample rate of the AudioDevice.
*/
/*
@constant kAudioDevicePropertyIcon
A CFURLRef that indicates an image file that can be used to represent the
device visually. The caller is responsible for releasing the returned
CFObject.
*/
/*
@constant kAudioDevicePropertyPreferredChannelsForStereo
An array of two UInt32s, the first for the left channel, the second for the
right channel, that indicate the channel numbers to use for stereo IO on the
device. The value of this property can be different for input and output and
there are no restrictions on the channel numbers that can be used.
*/
static SoundIoChannelId from_channel_descr(const AudioChannelDescription *descr) {
switch (descr->mChannelLabel) {
default: return SoundIoChannelIdInvalid;
case kAudioChannelLabel_Left: return SoundIoChannelIdFrontLeft;
case kAudioChannelLabel_Right: return SoundIoChannelIdFrontRight;
case kAudioChannelLabel_Center: return SoundIoChannelIdFrontCenter;
case kAudioChannelLabel_LFEScreen: return SoundIoChannelIdLfe;
case kAudioChannelLabel_LeftSurround: return SoundIoChannelIdSideLeft;
case kAudioChannelLabel_RightSurround: return SoundIoChannelIdSideRight;
case kAudioChannelLabel_LeftCenter: return SoundIoChannelIdSideLeft;
case kAudioChannelLabel_RightCenter: return SoundIoChannelIdSideRight;
case kAudioChannelLabel_CenterSurround: return SoundIoChannelIdBackCenter;
case kAudioChannelLabel_LeftSurroundDirect: return SoundIoChannelIdSideLeft;
case kAudioChannelLabel_RightSurroundDirect: return SoundIoChannelIdSideRight;
case kAudioChannelLabel_TopCenterSurround: return SoundIoChannelIdTopCenter;
case kAudioChannelLabel_VerticalHeightLeft: return SoundIoChannelIdTopSideLeft;
case kAudioChannelLabel_VerticalHeightCenter: return SoundIoChannelIdTopCenter;
case kAudioChannelLabel_VerticalHeightRight: return SoundIoChannelIdTopSideRight;
case kAudioChannelLabel_TopBackLeft: return SoundIoChannelIdTopBackLeft;
case kAudioChannelLabel_TopBackCenter: return SoundIoChannelIdTopBackCenter;
case kAudioChannelLabel_TopBackRight: return SoundIoChannelIdTopBackRight;
case kAudioChannelLabel_RearSurroundLeft: return SoundIoChannelIdBackLeft;
case kAudioChannelLabel_RearSurroundRight: return SoundIoChannelIdBackRight;
case kAudioChannelLabel_LeftWide: return SoundIoChannelIdFrontLeftWide;
case kAudioChannelLabel_RightWide: return SoundIoChannelIdFrontRightWide;
case kAudioChannelLabel_LFE2: return SoundIoChannelIdLfe2;
case kAudioChannelLabel_LeftTotal: return SoundIoChannelIdFrontLeft;
case kAudioChannelLabel_RightTotal: return SoundIoChannelIdFrontRight;
case kAudioChannelLabel_HearingImpaired: return SoundIoChannelIdHearingImpaired;
case kAudioChannelLabel_Narration: return SoundIoChannelIdNarration;
case kAudioChannelLabel_Mono: return SoundIoChannelIdFrontCenter;
case kAudioChannelLabel_DialogCentricMix: return SoundIoChannelIdDialogCentricMix;
case kAudioChannelLabel_CenterSurroundDirect: return SoundIoChannelIdBackCenter;
case kAudioChannelLabel_Haptic: return SoundIoChannelIdHaptic;
case kAudioChannelLabel_Ambisonic_W: return SoundIoChannelIdAmbisonicW;
case kAudioChannelLabel_Ambisonic_X: return SoundIoChannelIdAmbisonicX;
case kAudioChannelLabel_Ambisonic_Y: return SoundIoChannelIdAmbisonicY;
case kAudioChannelLabel_Ambisonic_Z: return SoundIoChannelIdAmbisonicZ;
case kAudioChannelLabel_MS_Mid: return SoundIoChannelIdMsMid;
case kAudioChannelLabel_MS_Side: return SoundIoChannelIdMsSide;
case kAudioChannelLabel_XY_X: return SoundIoChannelIdXyX;
case kAudioChannelLabel_XY_Y: return SoundIoChannelIdXyY;
case kAudioChannelLabel_HeadphonesLeft: return SoundIoChannelIdHeadphonesLeft;
case kAudioChannelLabel_HeadphonesRight: return SoundIoChannelIdHeadphonesRight;
case kAudioChannelLabel_ClickTrack: return SoundIoChannelIdClickTrack;
case kAudioChannelLabel_ForeignLanguage: return SoundIoChannelIdForeignLanguage;
case kAudioChannelLabel_Discrete: return SoundIoChannelIdAux;
case kAudioChannelLabel_Discrete_0: return SoundIoChannelIdAux0;
case kAudioChannelLabel_Discrete_1: return SoundIoChannelIdAux1;
case kAudioChannelLabel_Discrete_2: return SoundIoChannelIdAux2;
case kAudioChannelLabel_Discrete_3: return SoundIoChannelIdAux3;
case kAudioChannelLabel_Discrete_4: return SoundIoChannelIdAux4;
case kAudioChannelLabel_Discrete_5: return SoundIoChannelIdAux5;
case kAudioChannelLabel_Discrete_6: return SoundIoChannelIdAux6;
case kAudioChannelLabel_Discrete_7: return SoundIoChannelIdAux7;
case kAudioChannelLabel_Discrete_8: return SoundIoChannelIdAux8;
case kAudioChannelLabel_Discrete_9: return SoundIoChannelIdAux9;
case kAudioChannelLabel_Discrete_10: return SoundIoChannelIdAux10;
case kAudioChannelLabel_Discrete_11: return SoundIoChannelIdAux11;
case kAudioChannelLabel_Discrete_12: return SoundIoChannelIdAux12;
case kAudioChannelLabel_Discrete_13: return SoundIoChannelIdAux13;
case kAudioChannelLabel_Discrete_14: return SoundIoChannelIdAux14;
case kAudioChannelLabel_Discrete_15: return SoundIoChannelIdAux15;
}
}
// See https://developer.apple.com/library/mac/documentation/MusicAudio/Reference/CoreAudioDataTypesRef/#//apple_ref/doc/constant_group/Audio_Channel_Layout_Tags
// Possible Errors:
// * SoundIoErrorIncompatibleDevice
static int from_coreaudio_layout(const AudioChannelLayout *ca_layout, SoundIoChannelLayout *layout) {
switch (ca_layout->mChannelLayoutTag) {
case kAudioChannelLayoutTag_UseChannelDescriptions:
{
layout->channel_count = ca_layout->mNumberChannelDescriptions;
for (int i = 0; i < layout->channel_count; i += 1) {
layout->channels[i] = from_channel_descr(&ca_layout->mChannelDescriptions[i]);
}
break;
}
case kAudioChannelLayoutTag_UseChannelBitmap:
soundio_panic("TODO how the f to parse this");
return SoundIoErrorIncompatibleDevice;
case kAudioChannelLayoutTag_Mono:
layout->channel_count = 1;
layout->channels[0] = SoundIoChannelIdFrontCenter;
break;
case kAudioChannelLayoutTag_Stereo:
case kAudioChannelLayoutTag_StereoHeadphones:
case kAudioChannelLayoutTag_MatrixStereo:
layout->channel_count = 2;
layout->channels[0] = SoundIoChannelIdFrontLeft;
layout->channels[1] = SoundIoChannelIdFrontRight;
break;
case kAudioChannelLayoutTag_XY:
layout->channel_count = 2;
layout->channels[0] = SoundIoChannelIdXyX;
layout->channels[1] = SoundIoChannelIdXyY;
break;
case kAudioChannelLayoutTag_Binaural:
layout->channel_count = 2;
layout->channels[0] = SoundIoChannelIdFrontLeft;
layout->channels[1] = SoundIoChannelIdFrontRight;
break;
case kAudioChannelLayoutTag_MidSide:
layout->channel_count = 2;
layout->channels[0] = SoundIoChannelIdMsMid;
layout->channels[1] = SoundIoChannelIdMsSide;
break;
case kAudioChannelLayoutTag_Ambisonic_B_Format:
layout->channel_count = 4;
layout->channels[0] = SoundIoChannelIdAmbisonicW;
layout->channels[1] = SoundIoChannelIdAmbisonicX;
layout->channels[2] = SoundIoChannelIdAmbisonicY;
layout->channels[3] = SoundIoChannelIdAmbisonicZ;
break;
case kAudioChannelLayoutTag_Quadraphonic:
layout->channel_count = 4;
layout->channels[0] = SoundIoChannelIdFrontLeft;
layout->channels[1] = SoundIoChannelIdFrontRight;
layout->channels[2] = SoundIoChannelIdBackLeft;
layout->channels[3] = SoundIoChannelIdBackRight;
break;
case kAudioChannelLayoutTag_Pentagonal:
layout->channel_count = 5;
layout->channels[0] = SoundIoChannelIdSideLeft;
layout->channels[1] = SoundIoChannelIdSideRight;
layout->channels[2] = SoundIoChannelIdBackLeft;
layout->channels[3] = SoundIoChannelIdBackRight;
layout->channels[4] = SoundIoChannelIdFrontCenter;
break;
case kAudioChannelLayoutTag_Hexagonal:
layout->channel_count = 6;
layout->channels[0] = SoundIoChannelIdFrontLeft;
layout->channels[1] = SoundIoChannelIdFrontRight;
layout->channels[2] = SoundIoChannelIdBackLeft;
layout->channels[3] = SoundIoChannelIdBackRight;
layout->channels[4] = SoundIoChannelIdFrontCenter;
layout->channels[5] = SoundIoChannelIdBackCenter;
break;
case kAudioChannelLayoutTag_Octagonal:
layout->channel_count = 8;
layout->channels[0] = SoundIoChannelIdFrontLeft;
layout->channels[1] = SoundIoChannelIdFrontRight;
layout->channels[2] = SoundIoChannelIdBackLeft;
layout->channels[3] = SoundIoChannelIdBackRight;
layout->channels[4] = SoundIoChannelIdFrontCenter;
layout->channels[5] = SoundIoChannelIdBackCenter;
layout->channels[6] = SoundIoChannelIdSideLeft;
layout->channels[7] = SoundIoChannelIdSideRight;
break;
case kAudioChannelLayoutTag_Cube:
layout->channel_count = 8;
layout->channels[0] = SoundIoChannelIdFrontLeft;
layout->channels[1] = SoundIoChannelIdFrontRight;
layout->channels[2] = SoundIoChannelIdBackLeft;
layout->channels[3] = SoundIoChannelIdBackRight;
layout->channels[4] = SoundIoChannelIdTopFrontLeft;
layout->channels[5] = SoundIoChannelIdTopFrontRight;
layout->channels[6] = SoundIoChannelIdTopBackLeft;
layout->channels[7] = SoundIoChannelIdTopBackRight;
break;
// TODO more hardcoded channel layouts
default:
return SoundIoErrorIncompatibleDevice;
}
soundio_channel_layout_detect_builtin(layout);
return 0;
}
static bool all_channels_invalid(const struct SoundIoChannelLayout *layout) {
for (int i = 0; i < layout->channel_count; i += 1) {
if (layout->channels[i] != SoundIoChannelIdInvalid)
return false;
}
return true;
}
struct RefreshDevices {
SoundIoDevicesInfo *devices_info;
int devices_size;
AudioObjectID *devices;
CFStringRef string_ref;
char *device_name;
int device_name_len;
AudioBufferList *buffer_list;
SoundIoDevice *device;
AudioChannelLayout *audio_channel_layout;
};
static void deinit_refresh_devices(RefreshDevices *rd) {
destroy(rd->devices_info);
deallocate((char*)rd->devices, rd->devices_size);
if (rd->string_ref)
CFRelease(rd->string_ref);
free(rd->device_name);
free(rd->buffer_list);
soundio_device_unref(rd->device);
free(rd->audio_channel_layout);
}
// TODO get the device UID which persists between unplug/plug
// TODO go through here and make sure all allocations are freed // TODO go through here and make sure all allocations are freed
static int refresh_devices(struct SoundIoPrivate *si) { static int refresh_devices(struct SoundIoPrivate *si) {
SoundIo *soundio = &si->pub; SoundIo *soundio = &si->pub;
SoundIoCoreAudio *sica = &si->backend_data.coreaudio; SoundIoCoreAudio *sica = &si->backend_data.coreaudio;
SoundIoDevicesInfo *devices_info = create<SoundIoDevicesInfo>(); UInt32 io_size;
if (!devices_info) { OSStatus os_err;
soundio_panic("out of mem"); int err;
RefreshDevices rd;
memset(&rd, 0, sizeof(RefreshDevices));
if (!(rd.devices_info = create<SoundIoDevicesInfo>())) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
} }
AudioObjectPropertyAddress prop_address = { AudioObjectPropertyAddress prop_address = {
@ -143,54 +374,76 @@ static int refresh_devices(struct SoundIoPrivate *si) {
kAudioObjectPropertyElementMaster kAudioObjectPropertyElementMaster
}; };
UInt32 the_data_size = 0; if ((os_err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
OSStatus err; &prop_address, 0, nullptr, &io_size)))
if ((err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
&prop_address, 0, nullptr, &the_data_size)))
{ {
soundio_panic("get prop data size"); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
} }
int devices_available = the_data_size / (UInt32)sizeof(AudioObjectID); AudioObjectID default_input_id;
AudioObjectID default_output_id;
if (devices_available < 1) { int device_count = io_size / (UInt32)sizeof(AudioObjectID);
soundio_panic("no devices"); if (device_count >= 1) {
rd.devices_size = io_size;
rd.devices = (AudioObjectID *)allocate<char>(rd.devices_size);
if (!rd.devices) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
if ((os_err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop_address, 0, nullptr,
&io_size, rd.devices)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
io_size = sizeof(AudioObjectID);
prop_address.mSelector = kAudioHardwarePropertyDefaultInputDevice;
if ((os_err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop_address,
0, nullptr, &io_size, &default_input_id)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
io_size = sizeof(AudioObjectID);
prop_address.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
if ((os_err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &prop_address,
0, nullptr, &io_size, &default_output_id)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
} }
AudioObjectID *devices = (AudioObjectID *)allocate<char>(the_data_size); for (int i = 0; i < device_count; i += 1) {
if (!devices) { AudioObjectID deviceID = rd.devices[i];
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; prop_address.mSelector = kAudioObjectPropertyName;
CFStringRef temp_string_ref = nullptr; prop_address.mScope = kAudioObjectPropertyScopeGlobal;
UInt32 io_size = sizeof(CFStringRef); prop_address.mElement = kAudioObjectPropertyElementMaster;
if ((err = AudioObjectGetPropertyData(deviceID, &prop_address, io_size = sizeof(CFStringRef);
0, nullptr, &io_size, &temp_string_ref))) if (rd.string_ref) {
CFRelease(rd.string_ref);
rd.string_ref = nullptr;
}
if ((os_err = AudioObjectGetPropertyData(deviceID, &prop_address,
0, nullptr, &io_size, &rd.string_ref)))
{ {
soundio_panic("error getting name"); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
} }
char *device_name; free(rd.device_name);
int device_name_len; rd.device_name = nullptr;
if ((err = from_cf_string(temp_string_ref, &device_name, &device_name_len))) { if ((err = from_cf_string(rd.string_ref, &rd.device_name, &rd.device_name_len))) {
soundio_panic("error decoding"); deinit_refresh_devices(&rd);
return err;
} }
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) { for (int aim_i = 0; aim_i < array_length(aims); aim_i += 1) {
@ -199,42 +452,119 @@ static int refresh_devices(struct SoundIoPrivate *si) {
io_size = 0; io_size = 0;
prop_address.mSelector = kAudioDevicePropertyStreamConfiguration; prop_address.mSelector = kAudioDevicePropertyStreamConfiguration;
prop_address.mScope = aim_to_scope(aim); prop_address.mScope = aim_to_scope(aim);
prop_address.mElement = 0; prop_address.mElement = kAudioObjectPropertyElementMaster;
if ((err = AudioObjectGetPropertyDataSize(deviceID, &prop_address, 0, nullptr, &io_size))) { if ((os_err = AudioObjectGetPropertyDataSize(deviceID, &prop_address, 0, nullptr, &io_size))) {
soundio_panic("input channel get prop data size"); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
} }
AudioBufferList *buffer_list = (AudioBufferList*)allocate<char>(io_size); free(rd.buffer_list);
if (!buffer_list) { rd.buffer_list = (AudioBufferList*)allocate_nonzero<char>(io_size);
soundio_panic("out of mem"); if (!rd.buffer_list) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
} }
if ((err = AudioObjectGetPropertyData(deviceID, &prop_address, 0, nullptr, if ((os_err = AudioObjectGetPropertyData(deviceID, &prop_address, 0, nullptr,
&io_size, buffer_list))) &io_size, rd.buffer_list)))
{ {
soundio_panic("get input channel data"); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
} }
int channel_count = 0; int channel_count = 0;
for (int i = 0; i < buffer_list->mNumberBuffers; i += 1) { for (int i = 0; i < rd.buffer_list->mNumberBuffers; i += 1) {
channel_count += buffer_list->mBuffers[i].mNumberChannels; channel_count += rd.buffer_list->mBuffers[i].mNumberChannels;
} }
deallocate((char*)buffer_list, io_size);
fprintf(stderr, "%d channel count: %d\n", aim_i, channel_count); if (channel_count <= 0)
continue;
SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
if (!dev) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
assert(!rd.device);
rd.device = &dev->pub;
rd.device->ref_count = 1;
rd.device->soundio = soundio;
rd.device->is_raw = false; // TODO
rd.device->aim = aim;
rd.device->id = soundio_alloc_sprintf(nullptr, "%ld", (long)deviceID);
rd.device->name = soundio_str_dupe(rd.device_name, rd.device_name_len);
rd.device->layout_count = 1;
rd.device->layouts = create<SoundIoChannelLayout>();
rd.device->format_count = 1;
rd.device->formats = create<SoundIoFormat>();
// TODO more props
if (!rd.device->id || !rd.device->name || !rd.device->layouts || !rd.device->formats) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
prop_address.mSelector = kAudioDevicePropertyPreferredChannelLayout;
prop_address.mScope = aim_to_scope(aim);
prop_address.mElement = kAudioObjectPropertyElementMaster;
if (!(os_err = AudioObjectGetPropertyDataSize(deviceID, &prop_address,
0, nullptr, &io_size)))
{
rd.audio_channel_layout = (AudioChannelLayout *)allocate<char>(io_size);
if (!rd.audio_channel_layout) {
deinit_refresh_devices(&rd);
return SoundIoErrorNoMem;
}
if ((os_err = AudioObjectGetPropertyData(deviceID, &prop_address, 0, nullptr,
&io_size, rd.audio_channel_layout)))
{
deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice;
}
if ((err = from_coreaudio_layout(rd.audio_channel_layout, &rd.device->current_layout))) {
rd.device->current_layout.channel_count = channel_count;
}
}
if (all_channels_invalid(&rd.device->current_layout)) {
const struct SoundIoChannelLayout *guessed_layout =
soundio_channel_layout_get_default(channel_count);
if (guessed_layout)
rd.device->current_layout = *guessed_layout;
}
rd.device->layouts[0] = rd.device->current_layout;
// in CoreAudio, format is always 32-bit native endian float
rd.device->formats[0] = SoundIoFormatFloat32NE;
SoundIoList<SoundIoDevice *> *device_list;
if (rd.device->aim == SoundIoDeviceAimOutput) {
device_list = &rd.devices_info->output_devices;
if (deviceID == default_output_id)
rd.devices_info->default_output_index = device_list->length;
} else {
assert(rd.device->aim == SoundIoDeviceAimInput);
device_list = &rd.devices_info->input_devices;
if (deviceID == default_input_id)
rd.devices_info->default_input_index = device_list->length;
}
if ((err = device_list->append(rd.device))) {
deinit_refresh_devices(&rd);
return err;
}
rd.device = nullptr;
} }
} }
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 = devices_info; sica->ready_devices_info = rd.devices_info;
soundio->on_events_signal(soundio); soundio->on_events_signal(soundio);
soundio_os_mutex_unlock(sica->mutex); soundio_os_mutex_unlock(sica->mutex);
if (!sica->have_devices_flag.exchange(true))
soundio_os_cond_signal(sica->have_devices_cond, nullptr);
rd.devices_info = nullptr;
deinit_refresh_devices(&rd);
return 0; return 0;
} }
@ -310,12 +640,16 @@ static void device_thread_run(void *arg) {
return; return;
} }
if (sica->device_scan_queued.exchange(false)) { if (sica->device_scan_queued.exchange(false)) {
if ((err = refresh_devices(si))) { err = refresh_devices(si);
if (err)
shutdown_backend(si, err); shutdown_backend(si, err);
if (!sica->have_devices_flag.exchange(true))
soundio_os_cond_signal(sica->have_devices_cond, nullptr);
if (err)
return; return;
} soundio_os_cond_signal(sica->cond, nullptr);
} }
soundio_os_cond_wait(sica->cond, nullptr); soundio_os_cond_wait(sica->scan_devices_cond, nullptr);
} }
} }
@ -409,6 +743,12 @@ int soundio_coreaudio_init(SoundIoPrivate *si) {
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
sica->scan_devices_cond = soundio_os_cond_create();
if (!sica->scan_devices_cond) {
destroy_ca(si);
return SoundIoErrorNoMem;
}
AudioObjectPropertyAddress prop_address = { AudioObjectPropertyAddress prop_address = {
kAudioHardwarePropertyDevices, kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeGlobal,

View file

@ -28,6 +28,7 @@ struct SoundIoCoreAudio {
struct SoundIoDevicesInfo *ready_devices_info; struct SoundIoDevicesInfo *ready_devices_info;
atomic_bool have_devices_flag; atomic_bool have_devices_flag;
SoundIoOsCond *have_devices_cond; SoundIoOsCond *have_devices_cond;
SoundIoOsCond *scan_devices_cond;
atomic_bool device_scan_queued; atomic_bool device_scan_queued;
atomic_bool service_restarted; atomic_bool service_restarted;

View file

@ -70,15 +70,6 @@ static SoundIoJackClient *find_or_create_client(SoundIoList<SoundIoJackClient> *
return client; return client;
} }
static char *dupe_str(const char *str, int str_len) {
char *out = allocate_nonzero<char>(str_len + 1);
if (!out)
return nullptr;
memcpy(out, str, str_len);
out[str_len] = 0;
return out;
}
static void destruct_device(SoundIoDevicePrivate *dp) { static void destruct_device(SoundIoDevicePrivate *dp) {
SoundIoDeviceJack *dj = &dp->backend_data.jack; SoundIoDeviceJack *dj = &dp->backend_data.jack;
for (int i = 0; i < dj->port_count; i += 1) { for (int i = 0; i < dj->port_count; i += 1) {
@ -194,7 +185,7 @@ static int refresh_devices_bare(SoundIoPrivate *si) {
device->soundio = soundio; device->soundio = soundio;
device->is_raw = false; device->is_raw = false;
device->aim = client->aim; device->aim = client->aim;
device->id = dupe_str(client->name, client->name_len); device->id = soundio_str_dupe(client->name, client->name_len);
device->name = allocate<char>(description_len); device->name = allocate<char>(description_len);
device->layout_count = 1; device->layout_count = 1;
device->layouts = create<SoundIoChannelLayout>(); device->layouts = create<SoundIoChannelLayout>();
@ -223,7 +214,7 @@ static int refresh_devices_bare(SoundIoPrivate *si) {
for (int port_index = 0; port_index < client->port_count; port_index += 1) { for (int port_index = 0; port_index < client->port_count; port_index += 1) {
SoundIoJackPort *port = &client->ports[port_index]; SoundIoJackPort *port = &client->ports[port_index];
SoundIoDeviceJackPort *djp = &dj->ports[port_index]; SoundIoDeviceJackPort *djp = &dj->ports[port_index];
djp->full_name = dupe_str(port->full_name, port->full_name_len); djp->full_name = soundio_str_dupe(port->full_name, port->full_name_len);
djp->full_name_len = port->full_name_len; djp->full_name_len = port->full_name_len;
djp->channel_id = port->channel_id; djp->channel_id = port->channel_id;

View file

@ -425,6 +425,8 @@ void soundio_os_cond_signal(struct SoundIoOsCond *cond,
if (kevent(cond->kq_id, &kev, 1, NULL, 0, &timeout) == -1) { if (kevent(cond->kq_id, &kev, 1, NULL, 0, &timeout) == -1) {
if (errno == EINTR) if (errno == EINTR)
return; return;
if (errno == ENOENT)
return;
assert(0); // kevent signal error assert(0); // kevent signal error
} }
#else #else

View file

@ -152,10 +152,13 @@ void soundio_destroy(struct SoundIo *soundio) {
} }
static void do_nothing_cb(struct 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) { } static void default_msg_callback(const char *msg) { }
struct SoundIo * soundio_create(void) { static void default_backend_disconnect_cb(struct SoundIo *, int err) {
soundio_panic("libsoundio: backend disconnected: %s", soundio_strerror(err));
}
struct SoundIo *soundio_create(void) {
int err; int err;
if ((err = soundio_os_init())) if ((err = soundio_os_init()))
return nullptr; return nullptr;
@ -164,7 +167,7 @@ struct SoundIo * soundio_create(void) {
return nullptr; return nullptr;
SoundIo *soundio = &si->pub; SoundIo *soundio = &si->pub;
soundio->on_devices_change = do_nothing_cb; soundio->on_devices_change = do_nothing_cb;
soundio->on_backend_disconnect = do_nothing_backend_disconnect_cb; soundio->on_backend_disconnect = default_backend_disconnect_cb;
soundio->on_events_signal = do_nothing_cb; soundio->on_events_signal = do_nothing_cb;
soundio->app_name = "SoundIo"; soundio->app_name = "SoundIo";
soundio->jack_info_callback = default_msg_callback; soundio->jack_info_callback = default_msg_callback;
@ -207,6 +210,7 @@ int soundio_connect_backend(SoundIo *soundio, SoundIoBackend backend) {
return err; return err;
} }
soundio->current_backend = backend; soundio->current_backend = backend;
return 0; return 0;
} }
@ -252,8 +256,7 @@ int soundio_input_device_count(struct SoundIo *soundio) {
assert(soundio->current_backend != SoundIoBackendNone); assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
if (!si->safe_devices_info) if (!si->safe_devices_info)
soundio_flush_events(soundio); return -1;
assert(si->safe_devices_info);
return si->safe_devices_info->input_devices.length; return si->safe_devices_info->input_devices.length;
} }
@ -261,8 +264,7 @@ int soundio_output_device_count(struct SoundIo *soundio) {
assert(soundio->current_backend != SoundIoBackendNone); assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
if (!si->safe_devices_info) if (!si->safe_devices_info)
soundio_flush_events(soundio); return -1;
assert(si->safe_devices_info);
return si->safe_devices_info->output_devices.length; return si->safe_devices_info->output_devices.length;
} }
@ -270,8 +272,7 @@ int soundio_default_input_device_index(struct SoundIo *soundio) {
assert(soundio->current_backend != SoundIoBackendNone); assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
if (!si->safe_devices_info) if (!si->safe_devices_info)
soundio_flush_events(soundio); return -1;
assert(si->safe_devices_info);
return si->safe_devices_info->default_input_index; return si->safe_devices_info->default_input_index;
} }
@ -279,28 +280,31 @@ int soundio_default_output_device_index(struct SoundIo *soundio) {
assert(soundio->current_backend != SoundIoBackendNone); assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
if (!si->safe_devices_info) if (!si->safe_devices_info)
soundio_flush_events(soundio); return -1;
assert(si->safe_devices_info);
return si->safe_devices_info->default_output_index; return si->safe_devices_info->default_output_index;
} }
struct SoundIoDevice *soundio_get_input_device(struct SoundIo *soundio, int index) { struct SoundIoDevice *soundio_get_input_device(struct SoundIo *soundio, int index) {
assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
assert(si->safe_devices_info); assert(soundio->current_backend != SoundIoBackendNone);
assert(index >= 0); assert(index >= 0);
assert(index < si->safe_devices_info->input_devices.length); assert(index < si->safe_devices_info->input_devices.length);
if (!si->safe_devices_info)
return nullptr;
SoundIoDevice *device = si->safe_devices_info->input_devices.at(index); SoundIoDevice *device = si->safe_devices_info->input_devices.at(index);
soundio_device_ref(device); soundio_device_ref(device);
return device; return device;
} }
struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int index) { struct SoundIoDevice *soundio_get_output_device(struct SoundIo *soundio, int index) {
assert(soundio->current_backend != SoundIoBackendNone);
SoundIoPrivate *si = (SoundIoPrivate *)soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio;
assert(si->safe_devices_info); assert(soundio->current_backend != SoundIoBackendNone);
assert(index >= 0); assert(index >= 0);
assert(index < si->safe_devices_info->output_devices.length); assert(index < si->safe_devices_info->output_devices.length);
if (!si->safe_devices_info)
return nullptr;
SoundIoDevice *device = si->safe_devices_info->output_devices.at(index); SoundIoDevice *device = si->safe_devices_info->output_devices.at(index);
soundio_device_ref(device); soundio_device_ref(device);
return device; return device;

View file

@ -88,6 +88,15 @@ void soundio_panic(const char *format, ...)
char *soundio_alloc_sprintf(int *len, const char *format, ...) char *soundio_alloc_sprintf(int *len, const char *format, ...)
__attribute__ ((format (printf, 2, 3))); __attribute__ ((format (printf, 2, 3)));
static inline char *soundio_str_dupe(const char *str, int str_len) {
char *out = allocate_nonzero<char>(str_len + 1);
if (!out)
return nullptr;
memcpy(out, str, str_len);
out[str_len] = 0;
return out;
}
static inline bool soundio_streql(const char *str1, int str1_len, const char *str2, int str2_len) { static inline bool soundio_streql(const char *str1, int str1_len, const char *str2, int str2_len) {
if (str1_len != str2_len) if (str1_len != str2_len)
return false; return false;