From bd9dd52038070abf0a5c30f191d80551f4b95c73 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Sat, 23 Nov 2019 18:42:27 +0000 Subject: [PATCH 1/9] Add support for packed 24 bit LE format --- example/sio_microphone.c | 1 + example/sio_record.c | 1 + example/sio_sine.c | 52 +++++++++++++++++++++++++++++++++++++++- soundio/soundio.h | 1 + src/alsa.c | 5 +++- src/dummy.c | 19 ++++++++------- src/pulseaudio.c | 1 + src/soundio.c | 2 ++ test/overflow.c | 1 + 9 files changed, 72 insertions(+), 11 deletions(-) diff --git a/example/sio_microphone.c b/example/sio_microphone.c index 0fa8cca3..f0b73644 100644 --- a/example/sio_microphone.c +++ b/example/sio_microphone.c @@ -22,6 +22,7 @@ static enum SoundIoFormat prioritized_formats[] = { SoundIoFormatS32FE, SoundIoFormatS24NE, SoundIoFormatS24FE, + SoundIoFormatS24PLE, SoundIoFormatS16NE, SoundIoFormatS16FE, SoundIoFormatFloat64NE, diff --git a/example/sio_record.c b/example/sio_record.c index 61d584da..9dc8fa5c 100644 --- a/example/sio_record.c +++ b/example/sio_record.c @@ -24,6 +24,7 @@ static enum SoundIoFormat prioritized_formats[] = { SoundIoFormatS32NE, SoundIoFormatS32FE, SoundIoFormatS24NE, + SoundIoFormatS24PLE, SoundIoFormatS24FE, SoundIoFormatS16NE, SoundIoFormatS16FE, diff --git a/example/sio_sine.c b/example/sio_sine.c index cc70c671..6bf46d50 100644 --- a/example/sio_sine.c +++ b/example/sio_sine.c @@ -11,6 +11,7 @@ #include #include #include +#include #include static int usage(char *exe) { @@ -33,6 +34,49 @@ static void write_sample_s16ne(char *ptr, double sample) { *buf = val; } +/** + * Construct a signed 24 bit integer from three bytes into a int32_t. + */ +static int32_t construct_s24(uint8_t low, uint8_t mid, uint8_t high) +{ + return (int32_t)low | ((int32_t)mid << 8) | ((int32_t)high << 16) | + /* extend the sign bit */ + (high & 0x80 ? ~(int32_t)0xffffff : 0); +} + +/** + * Read a packed signed native-endian 24 bit integer. + */ +static int32_t read_s24(const uint8_t *src) +{ +#if defined(SOUNDIO_OS_BIG_ENDIAN) + return construct_s24(src[2], src[1], src[0]); +#elif defined(SOUNDIO_OS_LITTLE_ENDIAN) + return construct_s24(src[0], src[1], src[2]); +#endif +} + +static void write_sample_s24ne(char *ptr, double sample) { + const double range = (double)0xFFFFFF; + const double val = sample * range / 2.0; + const int32_t src0 = val; + const int32_t src = read_s24((const uint8_t *)&src0); + int32_t *dest = (int32_t *)ptr; + *dest = src; +} + +static void write_sample_s24ple(char *ptr, double sample) { + const double range = 0xFFFFFF; + const double val = sample * range / 2.0; + const int32_t src0 = val; + const uint8_t *src = (const uint8_t *)&src0; + uint8_t *dest = (uint8_t *)ptr; + + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; +} + static void write_sample_s32ne(char *ptr, double sample) { int32_t *buf = (int32_t *)ptr; double range = (double)INT32_MAX - (double)INT32_MIN; @@ -232,7 +276,13 @@ int main(int argc, char **argv) { } else if (soundio_device_supports_format(device, SoundIoFormatS16NE)) { outstream->format = SoundIoFormatS16NE; write_sample = write_sample_s16ne; - } else { + } else if (soundio_device_supports_format(device, SoundIoFormatS24NE)) { + outstream->format = SoundIoFormatS24NE; + write_sample = write_sample_s24ne; + } else if (soundio_device_supports_format(device, SoundIoFormatS24PLE)) { + outstream->format = SoundIoFormatS24PLE; + write_sample = write_sample_s24ple; + }else { fprintf(stderr, "No suitable device format available.\n"); return 1; } diff --git a/soundio/soundio.h b/soundio/soundio.h index 89109a31..415c1359 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -242,6 +242,7 @@ enum SoundIoFormat { SoundIoFormatU16BE, ///< Unsigned 16 bit Big Endian SoundIoFormatS24LE, ///< Signed 24 bit Little Endian using low three bytes in 32-bit word SoundIoFormatS24BE, ///< Signed 24 bit Big Endian using low three bytes in 32-bit word + SoundIoFormatS24PLE, ///< Signed 24 bit Little Endian packed into 3 bytes SoundIoFormatU24LE, ///< Unsigned 24 bit Little Endian using low three bytes in 32-bit word SoundIoFormatU24BE, ///< Unsigned 24 bit Big Endian using low three bytes in 32-bit word SoundIoFormatS32LE, ///< Signed 32 bit Little Endian diff --git a/src/alsa.c b/src/alsa.c index edbf51ba..750ef969 100644 --- a/src/alsa.c +++ b/src/alsa.c @@ -236,6 +236,7 @@ static snd_pcm_format_t to_alsa_fmt(enum SoundIoFormat fmt) { case SoundIoFormatS24BE: return SND_PCM_FORMAT_S24_BE; case SoundIoFormatU24LE: return SND_PCM_FORMAT_U24_LE; case SoundIoFormatU24BE: return SND_PCM_FORMAT_U24_BE; + case SoundIoFormatS24PLE: return SND_PCM_FORMAT_S24_3LE; case SoundIoFormatS32LE: return SND_PCM_FORMAT_S32_LE; case SoundIoFormatS32BE: return SND_PCM_FORMAT_S32_BE; case SoundIoFormatU32LE: return SND_PCM_FORMAT_U32_LE; @@ -350,6 +351,7 @@ static int probe_open_device(struct SoundIoDevice *device, snd_pcm_t *handle, in snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S24_BE); snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U24_LE); snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U24_BE); + snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S24_3LE); snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S32_LE); snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S32_BE); snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U32_LE); @@ -364,7 +366,7 @@ static int probe_open_device(struct SoundIoDevice *device, snd_pcm_t *handle, in if (!device->formats) { snd_pcm_hw_params_get_format_mask(hwparams, fmt_mask); - device->formats = ALLOCATE(enum SoundIoFormat, 18); + device->formats = ALLOCATE(enum SoundIoFormat, 19); if (!device->formats) return SoundIoErrorNoMem; @@ -379,6 +381,7 @@ static int probe_open_device(struct SoundIoDevice *device, snd_pcm_t *handle, in test_fmt_mask(device, fmt_mask, SoundIoFormatS24BE); test_fmt_mask(device, fmt_mask, SoundIoFormatU24LE); test_fmt_mask(device, fmt_mask, SoundIoFormatU24BE); + test_fmt_mask(device, fmt_mask, SoundIoFormatS24PLE); test_fmt_mask(device, fmt_mask, SoundIoFormatS32LE); test_fmt_mask(device, fmt_mask, SoundIoFormatS32BE); test_fmt_mask(device, fmt_mask, SoundIoFormatU32LE); diff --git a/src/dummy.c b/src/dummy.c index f1a38289..cfc26ecb 100644 --- a/src/dummy.c +++ b/src/dummy.c @@ -380,7 +380,7 @@ static int instream_get_latency_dummy(struct SoundIoPrivate *si, struct SoundIoI } static int set_all_device_formats(struct SoundIoDevice *device) { - device->format_count = 18; + device->format_count = 19; device->formats = ALLOCATE(enum SoundIoFormat, device->format_count); if (!device->formats) return SoundIoErrorNoMem; @@ -395,14 +395,15 @@ static int set_all_device_formats(struct SoundIoDevice *device) { device->formats[7] = SoundIoFormatS24FE; device->formats[8] = SoundIoFormatU24NE; device->formats[9] = SoundIoFormatU24FE; - device->formats[10] = SoundIoFormatFloat64NE; - device->formats[11] = SoundIoFormatFloat64FE; - device->formats[12] = SoundIoFormatS16NE; - device->formats[13] = SoundIoFormatS16FE; - device->formats[14] = SoundIoFormatU16NE; - device->formats[15] = SoundIoFormatU16FE; - device->formats[16] = SoundIoFormatS8; - device->formats[17] = SoundIoFormatU8; + device->formats[10] = SoundIoFormatS24PLE; + device->formats[11] = SoundIoFormatFloat64NE; + device->formats[12] = SoundIoFormatFloat64FE; + device->formats[13] = SoundIoFormatS16NE; + device->formats[14] = SoundIoFormatS16FE; + device->formats[15] = SoundIoFormatU16NE; + device->formats[16] = SoundIoFormatU16FE; + device->formats[11] = SoundIoFormatS8; + device->formats[18] = SoundIoFormatU8; return 0; } diff --git a/src/pulseaudio.c b/src/pulseaudio.c index 7fb09d56..ca5ae021 100644 --- a/src/pulseaudio.c +++ b/src/pulseaudio.c @@ -520,6 +520,7 @@ static pa_sample_format_t to_pulseaudio_format(enum SoundIoFormat format) { case SoundIoFormatS24BE: return PA_SAMPLE_S24_32BE; case SoundIoFormatS32LE: return PA_SAMPLE_S32LE; case SoundIoFormatS32BE: return PA_SAMPLE_S32BE; + case SoundIoFormatS24PLE: return PA_SAMPLE_S24LE; case SoundIoFormatFloat32LE: return PA_SAMPLE_FLOAT32LE; case SoundIoFormatFloat32BE: return PA_SAMPLE_FLOAT32BE; diff --git a/src/soundio.c b/src/soundio.c index 116ef50f..0046136c 100644 --- a/src/soundio.c +++ b/src/soundio.c @@ -105,6 +105,7 @@ int soundio_get_bytes_per_sample(enum SoundIoFormat format) { case SoundIoFormatU16BE: return 2; case SoundIoFormatS24LE: return 4; case SoundIoFormatS24BE: return 4; + case SoundIoFormatS24PLE: return 3; case SoundIoFormatU24LE: return 4; case SoundIoFormatU24BE: return 4; case SoundIoFormatS32LE: return 4; @@ -131,6 +132,7 @@ const char * soundio_format_string(enum SoundIoFormat format) { case SoundIoFormatU16BE: return "unsigned 16-bit LE"; case SoundIoFormatS24LE: return "signed 24-bit LE"; case SoundIoFormatS24BE: return "signed 24-bit BE"; + case SoundIoFormatS24PLE: return "signed 24-bit packed LE"; case SoundIoFormatU24LE: return "unsigned 24-bit LE"; case SoundIoFormatU24BE: return "unsigned 24-bit BE"; case SoundIoFormatS32LE: return "signed 32-bit LE"; diff --git a/test/overflow.c b/test/overflow.c index 43b6d227..47c6bc50 100644 --- a/test/overflow.c +++ b/test/overflow.c @@ -27,6 +27,7 @@ static enum SoundIoFormat prioritized_formats[] = { SoundIoFormatFloat64FE, SoundIoFormatU32NE, SoundIoFormatU32FE, + SoundIoFormatS24PLE, SoundIoFormatU24NE, SoundIoFormatU24FE, SoundIoFormatU16NE, From dcc96c2536a6b7bbad23cd6029d6a7a2b5a572d4 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Wed, 13 Sep 2017 11:10:35 +1000 Subject: [PATCH 2/9] Add raw device enumeration to Core Audio backend Add raw device enumeration, AudioDeviceIOProc-based output support, and 24 bit packed integer support to Core Audio backend --- CMakeLists.txt | 7 + src/coreaudio.c | 715 +++++++++++++++++++++++++++++++++++++++++++----- src/coreaudio.h | 7 + 3 files changed, 658 insertions(+), 71 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6541f1b4..dde9a4e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,12 +109,17 @@ if(ENABLE_COREAUDIO) find_path(AUDIOUNIT_INCLUDE_DIR NAMES AudioUnit.h) find_library(AUDIOUNIT_LIBRARY NAMES AudioUnit) include_directories(${AUDIOUNIT_INCLUDE_DIR}) + + find_path(AUDIOTOOLBOX_INCLUDE_DIR NAMES AudioToolbox/AudioFormat.h) + find_library(AUDIOTOOLBOX_LIBRARY NAMES AudioToolbox) + include_directories(${AUDIOTOOLBOX_INCLUDE_DIR}) else() set(STATUS_COREAUDIO "not found") set(SOUNDIO_HAVE_COREAUDIO false) set(COREAUDIO_LIBRARY "") set(COREFOUNDATION_LIBRARY "") set(AUDIOUNIT_LIBRARY "") + set(AUDIOTOOLBOX_LIBRARY "") endif() else() set(STATUS_COREAUDIO "disabled") @@ -122,6 +127,7 @@ else() set(COREAUDIO_LIBRARY "") set(COREFOUNDATION_LIBRARY "") set(AUDIOUNIT_LIBRARY "") + set(AUDIOTOOLBOX_LIBRARY "") endif() if(ENABLE_WASAPI) @@ -194,6 +200,7 @@ set(LIBSOUNDIO_LIBS ${COREAUDIO_LIBRARY} ${COREFOUNDATION_LIBRARY} ${AUDIOUNIT_LIBRARY} + ${AUDIOTOOLBOX_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ) diff --git a/src/coreaudio.c b/src/coreaudio.c index 5cee4fcd..bfecda95 100644 --- a/src/coreaudio.c +++ b/src/coreaudio.c @@ -10,6 +10,8 @@ #include +#include + static const int OUTPUT_ELEMENT = 0; static const int INPUT_ELEMENT = 1; @@ -88,7 +90,7 @@ static AudioObjectPropertyAddress device_listen_props[] = { kAudioDevicePropertyBufferFrameSizeRange, kAudioObjectPropertyScopeOutput, kAudioObjectPropertyElementMaster - }, + } }; static enum SoundIoDeviceAim aims[] = { @@ -381,6 +383,50 @@ static bool all_channels_invalid(const struct SoundIoChannelLayout *layout) { return true; } +static bool flag_in_format(uint32_t formatFlags, uint32_t flag) { + return ((formatFlags & flag) == flag); +} + +static enum SoundIoFormat from_ca_asbd(AudioStreamBasicDescription *desc) { + uint32_t formatFlags = desc->mFormatFlags; + if (flag_in_format(formatFlags, kAudioFormatFlagIsFloat)) { + if (desc->mBitsPerChannel == 32) { + return SoundIoFormatFloat32LE; + } + if (desc->mBitsPerChannel == 64) { + return SoundIoFormatFloat64LE; + } + } + if (flag_in_format(formatFlags, kAudioFormatFlagIsSignedInteger)) { + if (desc->mBitsPerChannel == 16) { + return SoundIoFormatS16LE; + } + if (desc->mBitsPerChannel == 24) { + if (flag_in_format(formatFlags, kAudioFormatFlagIsPacked)) { + return SoundIoFormatS24PLE; + } else { + return SoundIoFormatS24LE; + } + } + if (desc->mBitsPerChannel == 32) { + return SoundIoFormatS32LE; + } + } + return SoundIoFormatInvalid; +} + +static bool asbd_equal(AudioStreamBasicDescription *a, AudioStreamBasicDescription *b) +{ + return (a->mSampleRate == b->mSampleRate && + a->mFormatID == b->mFormatID && + a->mFormatFlags == b->mFormatFlags && + a->mBytesPerPacket == b->mBytesPerPacket && + a->mFramesPerPacket == b->mFramesPerPacket && + a->mBytesPerFrame == b->mBytesPerFrame && + a->mChannelsPerFrame == b->mChannelsPerFrame && + a->mBitsPerChannel == b->mBitsPerChannel); +} + struct RefreshDevices { struct SoundIoPrivate *si; struct SoundIoDevicesInfo *devices_info; @@ -390,7 +436,8 @@ struct RefreshDevices { char *device_name; int device_name_len; AudioBufferList *buffer_list; - struct SoundIoDevice *device; + struct SoundIoDevice *device_shared; + struct SoundIoDevice *device_raw; AudioChannelLayout *audio_channel_layout; char *device_uid; int device_uid_len; @@ -407,7 +454,8 @@ static void deinit_refresh_devices(struct RefreshDevices *rd) { CFRelease(rd->string_ref); free(rd->device_name); free(rd->buffer_list); - soundio_device_unref(rd->device); + soundio_device_unref(rd->device_shared); + soundio_device_unref(rd->device_raw); free(rd->audio_channel_layout); free(rd->device_uid); free(rd->avr_array); @@ -580,23 +628,46 @@ static int refresh_devices(struct SoundIoPrivate *si) { if (channel_count <= 0) continue; - struct SoundIoDevicePrivate *dev = ALLOCATE(struct SoundIoDevicePrivate, 1); - if (!dev) { + struct SoundIoDevicePrivate *dev_shared = ALLOCATE(struct SoundIoDevicePrivate, 1); + if (!dev_shared) { + deinit_refresh_devices(&rd); + return SoundIoErrorNoMem; + } + + struct SoundIoDeviceCoreAudio *dca_shared = &dev_shared->backend_data.coreaudio; + dca_shared->device_id = device_id; + assert(!rd.device_shared); + rd.device_shared = &dev_shared->pub; + rd.device_shared->ref_count = 1; + rd.device_shared->soundio = soundio; + rd.device_shared->is_raw = false; + rd.device_shared->aim = aim; + rd.device_shared->id = soundio_str_dupe(rd.device_uid, rd.device_uid_len); + rd.device_shared->name = soundio_str_dupe(rd.device_name, rd.device_name_len); + + if (!rd.device_shared->id || !rd.device_shared->name) { deinit_refresh_devices(&rd); return SoundIoErrorNoMem; } - struct SoundIoDeviceCoreAudio *dca = &dev->backend_data.coreaudio; - dca->device_id = device_id; - assert(!rd.device); - rd.device = &dev->pub; - rd.device->ref_count = 1; - rd.device->soundio = soundio; - rd.device->is_raw = false; - rd.device->aim = aim; - rd.device->id = soundio_str_dupe(rd.device_uid, rd.device_uid_len); - rd.device->name = soundio_str_dupe(rd.device_name, rd.device_name_len); - - if (!rd.device->id || !rd.device->name) { + + struct SoundIoDevicePrivate *dev_raw = ALLOCATE(struct SoundIoDevicePrivate, 1); + if (!dev_raw) { + deinit_refresh_devices(&rd); + return SoundIoErrorNoMem; + } + + struct SoundIoDeviceCoreAudio *dca_raw = &dev_raw->backend_data.coreaudio; + dca_raw->device_id = device_id; + assert(!rd.device_raw); + rd.device_raw = &dev_raw->pub; + rd.device_raw->ref_count = 1; + rd.device_raw->soundio = soundio; + rd.device_raw->is_raw = true; + rd.device_raw->aim = aim; + rd.device_raw->id = soundio_str_dupe(rd.device_uid, rd.device_uid_len); + rd.device_raw->name = soundio_str_dupe(rd.device_name, rd.device_name_len); + + if (!rd.device_raw->id || !rd.device_raw->name) { deinit_refresh_devices(&rd); return SoundIoErrorNoMem; } @@ -618,28 +689,41 @@ static int refresh_devices(struct SoundIoPrivate *si) { 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 ((err = from_coreaudio_layout(rd.audio_channel_layout, &rd.device_shared->current_layout))) { + rd.device_shared->current_layout.channel_count = channel_count; + } + if ((err = from_coreaudio_layout(rd.audio_channel_layout, &rd.device_raw->current_layout))) { + rd.device_raw->current_layout.channel_count = channel_count; } } - if (all_channels_invalid(&rd.device->current_layout)) { + if (all_channels_invalid(&rd.device_shared->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_shared->current_layout = *guessed_layout; } - rd.device->layout_count = 1; - rd.device->layouts = &rd.device->current_layout; + if (all_channels_invalid(&rd.device_raw->current_layout)) { + const struct SoundIoChannelLayout *guessed_layout = + soundio_channel_layout_get_default(channel_count); + if (guessed_layout) + rd.device_raw->current_layout = *guessed_layout; + } + + rd.device_shared->layout_count = 1; + rd.device_shared->layouts = &rd.device_shared->current_layout; + + rd.device_raw->layout_count = 1; + rd.device_raw->layouts = &rd.device_raw->current_layout; - rd.device->format_count = 4; - rd.device->formats = ALLOCATE(enum SoundIoFormat, rd.device->format_count); - if (!rd.device->formats) + rd.device_shared->format_count = 4; + rd.device_shared->formats = ALLOCATE(enum SoundIoFormat, rd.device_shared->format_count); + if (!rd.device_shared->formats) return SoundIoErrorNoMem; - rd.device->formats[0] = SoundIoFormatS16LE; - rd.device->formats[1] = SoundIoFormatS32LE; - rd.device->formats[2] = SoundIoFormatFloat32LE; - rd.device->formats[3] = SoundIoFormatFloat64LE; + rd.device_shared->formats[0] = SoundIoFormatS16LE; + rd.device_shared->formats[1] = SoundIoFormatS32LE; + rd.device_shared->formats[2] = SoundIoFormatFloat32LE; + rd.device_shared->formats[3] = SoundIoFormatFloat64LE; prop_address.mSelector = kAudioDevicePropertyNominalSampleRate; prop_address.mScope = aim_to_scope(aim); @@ -657,15 +741,15 @@ static int refresh_devices(struct SoundIoPrivate *si) { deinit_refresh_devices(&rd); return SoundIoErrorIncompatibleDevice; } - rd.device->sample_rate_current = (int)floored_value; + rd.device_shared->sample_rate_current = (int)floored_value; // If you try to open an input stream with anything but the current // nominal sample rate, AudioUnitRender returns an error. if (aim == SoundIoDeviceAimInput) { - rd.device->sample_rate_count = 1; - rd.device->sample_rates = &dev->prealloc_sample_rate_range; - rd.device->sample_rates[0].min = rd.device->sample_rate_current; - rd.device->sample_rates[0].max = rd.device->sample_rate_current; + rd.device_shared->sample_rate_count = 1; + rd.device_shared->sample_rates = &dev_shared->prealloc_sample_rate_range; + rd.device_shared->sample_rates[0].min = rd.device_shared->sample_rate_current; + rd.device_shared->sample_rates[0].max = rd.device_shared->sample_rate_current; } else { prop_address.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; prop_address.mScope = aim_to_scope(aim); @@ -692,14 +776,14 @@ static int refresh_devices(struct SoundIoPrivate *si) { } if (avr_array_len == 1) { - rd.device->sample_rate_count = 1; - rd.device->sample_rates = &dev->prealloc_sample_rate_range; - rd.device->sample_rates[0].min = ceil_dbl_to_int(rd.avr_array[0].mMinimum); - rd.device->sample_rates[0].max = (int)(rd.avr_array[0].mMaximum); + rd.device_shared->sample_rate_count = 1; + rd.device_shared->sample_rates = &dev_shared->prealloc_sample_rate_range; + rd.device_shared->sample_rates[0].min = ceil_dbl_to_int(rd.avr_array[0].mMinimum); + rd.device_shared->sample_rates[0].max = (int)(rd.avr_array[0].mMaximum); } else { - rd.device->sample_rate_count = avr_array_len; - rd.device->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, avr_array_len); - if (!rd.device->sample_rates) { + rd.device_shared->sample_rate_count = avr_array_len; + rd.device_shared->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, avr_array_len); + if (!rd.device_shared->sample_rates) { deinit_refresh_devices(&rd); return SoundIoErrorNoMem; } @@ -707,11 +791,146 @@ static int refresh_devices(struct SoundIoPrivate *si) { AudioValueRange *avr = &rd.avr_array[i]; int min_val = ceil_dbl_to_int(avr->mMinimum); int max_val = (int)(avr->mMaximum); - rd.device->sample_rates[i].min = min_val; - rd.device->sample_rates[i].max = max_val; + rd.device_shared->sample_rates[i].min = min_val; + rd.device_shared->sample_rates[i].max = max_val; + } + } + } + + prop_address.mSelector = kAudioDevicePropertyStreams; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + if ((os_err = AudioObjectGetPropertyDataSize(device_id, &prop_address, 0, NULL, + &io_size))) + { + deinit_refresh_devices(&rd); + return SoundIoErrorOpeningDevice; + } + + AudioStreamID *hardware_stream_ids = (AudioStreamID *)ALLOCATE_NONZERO(char, io_size); + + if ((os_err = AudioObjectGetPropertyData(device_id, &prop_address, 0, NULL, + &io_size, hardware_stream_ids))) + { + deinit_refresh_devices(&rd); + return SoundIoErrorOpeningDevice; + } + + int stream_count = io_size / sizeof(AudioStreamID); + + for (int i = 0; i < stream_count; i++) + { + AudioStreamID stream_id = hardware_stream_ids[i]; + + prop_address.mSelector = kAudioStreamPropertyDirection; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + + io_size = sizeof(uint32_t); + uint32_t direction = -1; + + if ((os_err = AudioObjectGetPropertyData(stream_id, &prop_address, 0, NULL, &io_size, &direction))) + { + deinit_refresh_devices(&rd); + return SoundIoErrorOpeningDevice; + } + + // @constant kAudioStreamPropertyDirection + // A UInt32 where a value of 0 means that this AudioStream is an output stream + // and a value of 1 means that it is an input stream. + if (aim == direction) { + continue; + } + + prop_address.mSelector = kAudioStreamPropertyAvailableVirtualFormats; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + if ((os_err = AudioObjectGetPropertyDataSize(stream_id, &prop_address, 0, NULL, &io_size))) + { + deinit_refresh_devices(&rd); + return SoundIoErrorOpeningDevice; + } + + AudioStreamRangedDescription *asrds = (AudioStreamRangedDescription *)ALLOCATE_NONZERO(char, io_size); + int asrd_count = io_size / sizeof(AudioStreamRangedDescription); + + if ((os_err = AudioObjectGetPropertyData(stream_id, &prop_address, 0, NULL, &io_size, asrds))) + { + deinit_refresh_devices(&rd); + return SoundIoErrorOpeningDevice; + } + + int unique_sample_rates_count = 0; + AudioValueRange *unique_sample_rates = ALLOCATE(AudioValueRange, asrd_count); + + int unique_formats_count = 0; + enum SoundIoFormat *unique_formats = ALLOCATE(enum SoundIoFormat, asrd_count); + + bool is_unique; + for (int j = 0; j < asrd_count; j++) + { + AudioStreamRangedDescription asrd = asrds[j]; + is_unique = true; + + for (int k = 0; k < unique_sample_rates_count; k++) { + if (unique_sample_rates[k].mMinimum == asrd.mSampleRateRange.mMinimum && + unique_sample_rates[k].mMaximum == asrd.mSampleRateRange.mMaximum) + { + is_unique = false; + break; + } + } + if (is_unique) + { + unique_sample_rates[unique_sample_rates_count++] = asrd.mSampleRateRange; + } + + enum SoundIoFormat asrd_format = from_ca_asbd(&asrd.mFormat); + is_unique = true; + + for (int k = 0; k < unique_formats_count; k++) { + if (unique_formats[k] == asrd_format) + { + is_unique = false; + break; + } + } + + if (is_unique) + { + unique_formats[unique_formats_count++] = asrd_format; + } + } + free(asrds); + + if (unique_sample_rates_count == 1) { + rd.device_raw->sample_rate_count = 1; + rd.device_raw->sample_rates = &dev_raw->prealloc_sample_rate_range; + rd.device_raw->sample_rates[0].min = ceil_dbl_to_int(unique_sample_rates[0].mMinimum); + rd.device_raw->sample_rates[0].max = (int)(unique_sample_rates[0].mMaximum); + } else { + rd.device_raw->sample_rate_count = unique_sample_rates_count; + rd.device_raw->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, unique_sample_rates_count); + if (!rd.device_raw->sample_rates) { + deinit_refresh_devices(&rd); + return SoundIoErrorNoMem; + } + for (int i = 0; i < unique_sample_rates_count; i += 1) { + AudioValueRange *avr = &unique_sample_rates[i]; + int min_val = ceil_dbl_to_int(avr->mMinimum); + int max_val = (int)(avr->mMaximum); + rd.device_raw->sample_rates[i].min = min_val; + rd.device_raw->sample_rates[i].max = max_val; } } + + rd.device_raw->format_count = unique_formats_count; + rd.device_raw->formats = unique_formats; } + free(hardware_stream_ids); prop_address.mSelector = kAudioDevicePropertyBufferFrameSize; prop_address.mScope = aim_to_scope(aim); @@ -724,8 +943,9 @@ static int refresh_devices(struct SoundIoPrivate *si) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } - double use_sample_rate = rd.device->sample_rate_current; - rd.device->software_latency_current = buffer_frame_size / use_sample_rate; + double use_sample_rate = rd.device_shared->sample_rate_current; + rd.device_shared->software_latency_current = buffer_frame_size / use_sample_rate; + rd.device_raw->software_latency_current = buffer_frame_size / use_sample_rate; prop_address.mSelector = kAudioDevicePropertyBufferFrameSizeRange; prop_address.mScope = aim_to_scope(aim); @@ -738,37 +958,47 @@ static int refresh_devices(struct SoundIoPrivate *si) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } - rd.device->software_latency_min = avr.mMinimum / use_sample_rate; - rd.device->software_latency_max = avr.mMaximum / use_sample_rate; + rd.device_shared->software_latency_min = avr.mMinimum / use_sample_rate; + rd.device_raw->software_latency_min = avr.mMinimum / use_sample_rate; + rd.device_shared->software_latency_max = avr.mMaximum / use_sample_rate; + rd.device_raw->software_latency_max = avr.mMaximum / use_sample_rate; prop_address.mSelector = kAudioDevicePropertyLatency; prop_address.mScope = aim_to_scope(aim); prop_address.mElement = kAudioObjectPropertyElementMaster; io_size = sizeof(UInt32); if ((os_err = AudioObjectGetPropertyData(device_id, &prop_address, 0, NULL, - &io_size, &dca->latency_frames))) + &io_size, &dca_shared->latency_frames))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } + dca_raw->latency_frames = dca_shared->latency_frames; struct SoundIoListDevicePtr *device_list; - if (rd.device->aim == SoundIoDeviceAimOutput) { + if (rd.device_shared->aim == SoundIoDeviceAimOutput) { device_list = &rd.devices_info->output_devices; if (device_id == default_output_id) rd.devices_info->default_output_index = device_list->length; } else { - assert(rd.device->aim == SoundIoDeviceAimInput); + assert(rd.device_shared->aim == SoundIoDeviceAimInput); device_list = &rd.devices_info->input_devices; if (device_id == default_input_id) rd.devices_info->default_input_index = device_list->length; } - if ((err = SoundIoListDevicePtr_append(device_list, rd.device))) { + if ((err = SoundIoListDevicePtr_append(device_list, rd.device_shared))) { + deinit_refresh_devices(&rd); + return err; + } + + if ((err = SoundIoListDevicePtr_append(device_list, rd.device_raw))) { deinit_refresh_devices(&rd); return err; } - rd.device = NULL; + + rd.device_shared = NULL; + rd.device_raw = NULL; } } @@ -875,6 +1105,36 @@ static void device_thread_run(void *arg) { } } +static OSStatus on_physical_format_changed(AudioObjectID in_object_id, UInt32 in_number_addresses, + const AudioObjectPropertyAddress in_addresses[], void *in_client_data) +{ + struct SoundIoOutStreamPrivate *os = (struct SoundIoOutStreamPrivate *)in_client_data; + //struct SoundIoOutStream *outstream = &os->pub; + struct SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio; + + + for (int i = 0; i < in_number_addresses; i++) + { + if (in_addresses[i].mSelector == kAudioStreamPropertyVirtualFormat) + { + // Hardware physical format has changed. + AudioStreamBasicDescription new_hardware_format; + UInt32 io_size = sizeof(AudioStreamBasicDescription); + + OSStatus os_err; + if ((os_err = AudioObjectGetPropertyData(osca->raw_stream_id, &in_addresses[i], 0, NULL, &io_size, &new_hardware_format))) { + return os_err; + } + + if (SOUNDIO_ATOMIC_LOAD(osca->output_format_match) == false) + { + SOUNDIO_ATOMIC_STORE(osca->output_format_match, asbd_equal(&new_hardware_format, &osca->hardware_format)); + } + } + } + return noErr; +} + static OSStatus on_outstream_device_overload(AudioObjectID in_object_id, UInt32 in_number_addresses, const AudioObjectPropertyAddress in_addresses[], void *in_client_data) { @@ -903,9 +1163,39 @@ static void outstream_destroy_ca(struct SoundIoPrivate *si, struct SoundIoOutStr AudioComponentInstanceDispose(osca->instance); osca->instance = NULL; } + + if (osca->io_proc_id) { + + prop_address.mSelector = kAudioStreamPropertyVirtualFormat; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + AudioObjectRemovePropertyListener(dca->device_id, &prop_address, on_physical_format_changed, os); + + AudioDeviceStop(dca->device_id, osca->io_proc_id); + AudioDeviceDestroyIOProcID(dca->device_id, osca->io_proc_id); + + uint32_t io_size = sizeof(AudioStreamBasicDescription); + + if (osca->revert_format) { + AudioObjectSetPropertyData(osca->raw_stream_id, &prop_address, 0, NULL, io_size, &osca->previous_hardware_format); + } + + osca->io_proc_id = NULL; + + // unhog device + prop_address.mElement = kAudioObjectPropertyElementMaster; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mSelector = kAudioDevicePropertyHogMode; + + io_size = sizeof(pid_t); + + pid_t hogmode_pid; + AudioObjectGetPropertyData(dca->device_id, &prop_address, 0, NULL, &io_size, &hogmode_pid); + } } -static OSStatus write_callback_ca(void *userdata, AudioUnitRenderActionFlags *io_action_flags, +static OSStatus write_callback_ca_shared(void *userdata, AudioUnitRenderActionFlags *io_action_flags, const AudioTimeStamp *in_time_stamp, UInt32 in_bus_number, UInt32 in_number_frames, AudioBufferList *io_data) { @@ -922,6 +1212,22 @@ static OSStatus write_callback_ca(void *userdata, AudioUnitRenderActionFlags *io return noErr; } +static OSStatus write_callback_ca_raw(AudioDeviceID inDevice, const AudioTimeStamp *inNow, const AudioBufferList *inInputData, const AudioTimeStamp *inInputTime, AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, void *inClientData) +{ + struct SoundIoOutStreamPrivate *os = (struct SoundIoOutStreamPrivate *) inClientData; + struct SoundIoOutStream *outstream = &os->pub; + struct SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio; + + osca->io_data = outOutputData; + osca->buffer_index = 0; + osca->frames_left = outOutputData->mBuffers[0].mDataByteSize / osca->hardware_format.mBytesPerFrame; + + outstream->write_callback(outstream, osca->frames_left, osca->frames_left); + + osca->io_data = NULL; + return noErr; +} + static int set_ca_desc(enum SoundIoFormat fmt, AudioStreamBasicDescription *desc) { switch (fmt) { case SoundIoFormatFloat32LE: @@ -940,17 +1246,255 @@ static int set_ca_desc(enum SoundIoFormat fmt, AudioStreamBasicDescription *desc desc->mFormatFlags = kAudioFormatFlagIsSignedInteger; desc->mBitsPerChannel = 16; break; - case SoundIoFormatS24LE: - desc->mFormatFlags = kAudioFormatFlagIsSignedInteger; - desc->mBitsPerChannel = 24; - break; + case SoundIoFormatS24LE: + desc->mFormatFlags = kAudioFormatFlagIsSignedInteger; + desc->mBitsPerChannel = 24; + break; + case SoundIoFormatS24PLE: + desc->mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + desc->mBitsPerChannel = 24; default: return SoundIoErrorIncompatibleDevice; } return 0; } -static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { +static int outstream_open_ca_raw(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) +{ + struct SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio; + struct SoundIoOutStream *outstream = &os->pub; + struct SoundIoDevice *device = outstream->device; + struct SoundIoDevicePrivate *dev = (struct SoundIoDevicePrivate *)device; + struct SoundIoDeviceCoreAudio *dca = &dev->backend_data.coreaudio; + + OSStatus os_err; + if ((os_err = AudioDeviceCreateIOProcID(dca->device_id, write_callback_ca_raw, outstream, &osca->io_proc_id))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + assert(osca->io_proc_id != NULL); + + if (outstream->software_latency == 0.0) + outstream->software_latency = device->software_latency_current; + + outstream->software_latency = soundio_double_clamp( + device->software_latency_min, + outstream->software_latency, + device->software_latency_max); + + AudioObjectPropertyAddress prop_address; + UInt32 io_size; + + // hog device + prop_address.mElement = kAudioObjectPropertyElementMaster; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mSelector = kAudioDevicePropertyHogMode; + + pid_t pid = getpid(); + pid_t hogmode_pid, current_pid = -1; + io_size = sizeof(pid_t); + + if ((os_err = AudioObjectGetPropertyData(dca->device_id, &prop_address, 0, NULL, &io_size, &hogmode_pid))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + if (hogmode_pid != pid) { + if (hogmode_pid != -1) { + // device is exclusively in use by another process + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + if ((os_err = AudioObjectSetPropertyData(dca->device_id, &prop_address, 0, NULL, io_size, &hogmode_pid))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + } + + if ((os_err = AudioObjectGetPropertyData(dca->device_id, &prop_address, 0, NULL, &io_size, ¤t_pid))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + if (current_pid != pid) { + // Could not hog device + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + // Get available formats + prop_address.mSelector = kAudioDevicePropertyStreams; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + if ((os_err = AudioObjectGetPropertyDataSize(dca->device_id, &prop_address, 0, NULL, &io_size))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + AudioStreamID *hardware_stream_ids = (AudioStreamID *)ALLOCATE(char, io_size); + + if ((os_err = AudioObjectGetPropertyData(dca->device_id, &prop_address, 0, NULL, &io_size, hardware_stream_ids))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + int stream_count = io_size / sizeof(AudioStreamID); + osca->raw_stream_id = -1; + + for (int i = 0; i < stream_count; i++) { + + if (osca->raw_stream_id != -1) { + break; + } + AudioStreamID stream_id = hardware_stream_ids[i]; + + prop_address.mSelector = kAudioStreamPropertyDirection; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + + io_size = sizeof(uint32_t); + uint32_t direction = -1; + + if ((os_err = AudioObjectGetPropertyData(stream_id, &prop_address, 0, NULL, &io_size, &direction))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + // @constant kAudioStreamPropertyDirection + // A UInt32 where a value of 0 means that this AudioStream is an output stream + // and a value of 1 means that it is an input stream. + if (device->aim == direction) { + continue; + } + + prop_address.mSelector = kAudioStreamPropertyAvailableVirtualFormats; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + if ((os_err = AudioObjectGetPropertyDataSize(stream_id, &prop_address, 0, NULL, &io_size))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + AudioStreamRangedDescription *asrds = (AudioStreamRangedDescription *)ALLOCATE(char, io_size); + int asrd_count = io_size / sizeof(AudioStreamRangedDescription); + + if ((os_err = AudioObjectGetPropertyData(stream_id, &prop_address, 0, NULL, &io_size, asrds))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + // Select best + for (int j = 0; j < asrd_count; j++) + { + AudioStreamRangedDescription asrd = asrds[j]; + enum SoundIoFormat asrd_format = from_ca_asbd(&asrd.mFormat); + if (asrd_format == outstream->format && asrd.mFormat.mSampleRate == outstream->sample_rate) { + osca->raw_stream_id = stream_id; + memcpy(&osca->hardware_format, &asrd.mFormat, sizeof(AudioStreamBasicDescription)); + break; + } + } + free(asrds); + } + free(hardware_stream_ids); + + if (osca->raw_stream_id == -1) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + // get current + prop_address.mSelector = kAudioStreamPropertyVirtualFormat; + prop_address.mScope = kAudioDevicePropertyScopeOutput; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + + io_size = sizeof(AudioStreamBasicDescription); + + if ((os_err = AudioObjectGetPropertyData(osca->raw_stream_id, &prop_address, 0, NULL, &io_size, &osca->previous_hardware_format))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + if (asbd_equal(&osca->previous_hardware_format, &osca->hardware_format)) + { + osca->revert_format = false; + SOUNDIO_ATOMIC_STORE(osca->output_format_match, true); + } else { + osca->revert_format = true; + SOUNDIO_ATOMIC_STORE(osca->output_format_match, false); + } + + // Listen to physical format changes + prop_address.mSelector = kAudioStreamPropertyVirtualFormat; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + AudioObjectAddPropertyListener(osca->raw_stream_id, &prop_address, on_physical_format_changed, os); + + // Attempt to change + if (osca->revert_format) + { + if ((os_err = AudioObjectSetPropertyData(osca->raw_stream_id, &prop_address, 0, NULL, io_size, &osca->hardware_format))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + // wait for hardware + struct timespec delay; + int second_timer = 0; + while (!SOUNDIO_ATOMIC_LOAD(osca->output_format_match) && second_timer < 100) { + + delay.tv_sec = 0; + delay.tv_nsec = 1E7L; /* 10ms in ns */ + + if (nanosleep(&delay, NULL)) { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + second_timer++; + } + + } + + if (!SOUNDIO_ATOMIC_LOAD(osca->output_format_match)) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + prop_address.mSelector = kAudioDeviceProcessorOverload; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = OUTPUT_ELEMENT; + if ((os_err = AudioObjectAddPropertyListener(dca->device_id, &prop_address, + on_outstream_device_overload, os))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + osca->hardware_latency = dca->latency_frames / (double)outstream->sample_rate; + + return 0; +} + +static int outstream_open_ca_shared(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { struct SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio; struct SoundIoOutStream *outstream = &os->pub; struct SoundIoDevice *device = outstream->device; @@ -1014,7 +1558,7 @@ static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamP return SoundIoErrorIncompatibleDevice; } - AURenderCallbackStruct render_callback = {write_callback_ca, os}; + AURenderCallbackStruct render_callback = {write_callback_ca_shared, os}; if ((os_err = AudioUnitSetProperty(osca->instance, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, OUTPUT_ELEMENT, &render_callback, sizeof(AURenderCallbackStruct)))) { @@ -1045,29 +1589,58 @@ static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamP return SoundIoErrorOpeningDevice; } - if ((os_err = AudioUnitGetParameter (osca->instance, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, &outstream->volume))) { - outstream_destroy_ca(si, os); - return SoundIoErrorOpeningDevice; - } + if ((os_err = AudioUnitGetParameter (osca->instance, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, &outstream->volume))) { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } osca->hardware_latency = dca->latency_frames / (double)outstream->sample_rate; return 0; } -static int outstream_pause_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) { +static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { + struct SoundIoOutStream *outstream = &os->pub; + struct SoundIoDevice *device = outstream->device; + if (device->is_raw) { + return outstream_open_ca_raw(si, os); + } else { + assert(!device->is_raw); + return outstream_open_ca_shared(si, os); + } +} + +static int outstream_pause_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) +{ struct SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio; + struct SoundIoOutStream *outstream = &os->pub; + struct SoundIoDevice *device = outstream->device; + struct SoundIoDevicePrivate *dev = (struct SoundIoDevicePrivate *)device; + struct SoundIoDeviceCoreAudio *dca = &dev->backend_data.coreaudio; + OSStatus os_err; - if (pause) { - if ((os_err = AudioOutputUnitStop(osca->instance))) { - return SoundIoErrorStreaming; + + if (device->is_raw) { + if (pause) { + if ((os_err = AudioDeviceStop(dca->device_id, osca->io_proc_id))) { + return SoundIoErrorStreaming; + } + } else { + if ((os_err = AudioDeviceStart(dca->device_id, osca->io_proc_id))) { + return SoundIoErrorStreaming; + } } } else { - if ((os_err = AudioOutputUnitStart(osca->instance))) { - return SoundIoErrorStreaming; + if (pause) { + if ((os_err = AudioOutputUnitStop(osca->instance))) { + return SoundIoErrorStreaming; + } + } else { + if ((os_err = AudioOutputUnitStart(osca->instance))) { + return SoundIoErrorStreaming; + } } } - return 0; } diff --git a/src/coreaudio.h b/src/coreaudio.h index 8515f21d..5acab633 100644 --- a/src/coreaudio.h +++ b/src/coreaudio.h @@ -47,6 +47,10 @@ struct SoundIoCoreAudio { struct SoundIoOutStreamCoreAudio { AudioComponentInstance instance; + AudioDeviceIOProcID io_proc_id; + AudioStreamID raw_stream_id; + AudioStreamBasicDescription hardware_format; + AudioStreamBasicDescription previous_hardware_format; AudioBufferList *io_data; int buffer_index; int frames_left; @@ -54,6 +58,9 @@ struct SoundIoOutStreamCoreAudio { double hardware_latency; float volume; struct SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; + struct SoundIoAtomicBool output_format_match; + bool revert_format; + bool raw_integer_mode_support; }; struct SoundIoInStreamCoreAudio { From e33ddbc42071868c412ca45652762f045777da37 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Sat, 23 Nov 2019 18:54:29 +0000 Subject: [PATCH 3/9] Add support for changing HW IO buffer size (required for 24 bit packed audio) --- src/coreaudio.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/coreaudio.c b/src/coreaudio.c index bfecda95..a7f6148f 100644 --- a/src/coreaudio.c +++ b/src/coreaudio.c @@ -15,6 +15,13 @@ static const int OUTPUT_ELEMENT = 0; static const int INPUT_ELEMENT = 1; +#define STREAM_FORMAT_MSG(sfm) \ +fprintf(stderr, "[%i][%4.4s][%i][%i][%i][%i][%i][%i]\n", \ +(UInt32)sfm.mSampleRate, (char *)&sfm.mFormatID, \ +sfm.mFormatFlags, sfm.mBytesPerPacket, \ +sfm.mFramesPerPacket, sfm.mBytesPerFrame, \ +sfm.mChannelsPerFrame, sfm.mBitsPerChannel) + static AudioObjectPropertyAddress device_listen_props[] = { { kAudioDevicePropertyDeviceHasChanged, @@ -870,9 +877,11 @@ static int refresh_devices(struct SoundIoPrivate *si) { enum SoundIoFormat *unique_formats = ALLOCATE(enum SoundIoFormat, asrd_count); bool is_unique; + fprintf(stderr, "%s\n", dev_raw->pub.name); for (int j = 0; j < asrd_count; j++) { AudioStreamRangedDescription asrd = asrds[j]; + STREAM_FORMAT_MSG(asrd.mFormat); is_unique = true; for (int k = 0; k < unique_sample_rates_count; k++) { @@ -1417,6 +1426,29 @@ static int outstream_open_ca_raw(struct SoundIoPrivate *si, struct SoundIoOutStr return SoundIoErrorOpeningDevice; } + // check buffer size is acceptable + prop_address.mSelector = kAudioDevicePropertyBufferFrameSize; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + io_size = sizeof(UInt32); + UInt32 buffer_frame_size; + if ((os_err = AudioObjectGetPropertyData(dca->device_id, &prop_address, 0, NULL, &io_size, &buffer_frame_size))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + + if (buffer_frame_size % osca->hardware_format.mBytesPerFrame != 0) { + buffer_frame_size = (osca->hardware_format.mBytesPerFrame == 24 ? 768 : 512); + + if ((os_err = AudioObjectSetPropertyData(dca->device_id, &prop_address, 0, NULL, io_size, &buffer_frame_size))) + { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; + } + } + // get current prop_address.mSelector = kAudioStreamPropertyVirtualFormat; prop_address.mScope = kAudioDevicePropertyScopeOutput; From dc068376c29feded5496f3fb113a13eb5e5abbe7 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Wed, 20 Sep 2017 20:19:33 +1000 Subject: [PATCH 4/9] [WIP] enumerate raw devices efficiently and identify devices supporting integer IO --- src/coreaudio.c | 394 +++++++++++++++++++++++++++++++----------------- 1 file changed, 253 insertions(+), 141 deletions(-) diff --git a/src/coreaudio.c b/src/coreaudio.c index a7f6148f..672956cf 100644 --- a/src/coreaudio.c +++ b/src/coreaudio.c @@ -15,13 +15,6 @@ static const int OUTPUT_ELEMENT = 0; static const int INPUT_ELEMENT = 1; -#define STREAM_FORMAT_MSG(sfm) \ -fprintf(stderr, "[%i][%4.4s][%i][%i][%i][%i][%i][%i]\n", \ -(UInt32)sfm.mSampleRate, (char *)&sfm.mFormatID, \ -sfm.mFormatFlags, sfm.mBytesPerPacket, \ -sfm.mFramesPerPacket, sfm.mBytesPerFrame, \ -sfm.mChannelsPerFrame, sfm.mBitsPerChannel) - static AudioObjectPropertyAddress device_listen_props[] = { { kAudioDevicePropertyDeviceHasChanged, @@ -390,7 +383,7 @@ static bool all_channels_invalid(const struct SoundIoChannelLayout *layout) { return true; } -static bool flag_in_format(uint32_t formatFlags, uint32_t flag) { +static bool flag_in_format(AudioFormatFlags formatFlags, AudioFormatFlags flag) { return ((formatFlags & flag) == flag); } @@ -468,6 +461,254 @@ static void deinit_refresh_devices(struct RefreshDevices *rd) { free(rd->avr_array); } +#define STREAM_FORMAT_MSG(id, sfm) \ +fprintf(stderr, "%i: [%i][%4.4s][%i][%i][%i][%i][%i][%i]\n", \ +id, (UInt32)sfm.mSampleRate, (char *)&sfm.mFormatID, \ +sfm.mFormatFlags, sfm.mBytesPerPacket, \ +sfm.mFramesPerPacket, sfm.mBytesPerFrame, \ +sfm.mChannelsPerFrame, sfm.mBitsPerChannel) + +static int get_hardware_streams_for_device(AudioDeviceID device_id, enum SoundIoDeviceAim aim, AudioStreamID **streams) { + AudioObjectPropertyAddress prop_address; + + prop_address.mSelector = kAudioDevicePropertyStreams; + prop_address.mScope = aim_to_scope(aim); + prop_address.mElement = kAudioObjectPropertyElementMaster; + + OSStatus os_err; + UInt32 io_size; + + if ((os_err = AudioObjectGetPropertyDataSize(device_id, &prop_address, 0, NULL, &io_size))) { + return -1; + } + + AudioStreamID *hardware_stream_ids = (AudioStreamID *)ALLOCATE_NONZERO(char, io_size); + + if ((os_err = AudioObjectGetPropertyData(device_id, &prop_address, 0, NULL, &io_size, hardware_stream_ids))) + { + free(hardware_stream_ids); + return -1; + } + *streams = hardware_stream_ids; + int stream_count = io_size / sizeof(AudioStreamID); + return stream_count; +} + +static int get_asrd_count_for_stream(AudioStreamID stream_id, bool virtual) { + AudioObjectPropertyAddress prop_address; + + prop_address.mSelector = virtual ? kAudioStreamPropertyAvailableVirtualFormats : kAudioStreamPropertyAvailablePhysicalFormats; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + OSStatus os_err; + UInt32 io_size; + + if ((os_err = AudioObjectGetPropertyDataSize(stream_id, &prop_address, 0, NULL, &io_size))) { + return -1; + } + int format_count = io_size / sizeof(AudioStreamRangedDescription); + return format_count; +} + +static int get_asrds_for_stream (AudioStreamID stream_id, int format_count, bool virtual, AudioStreamRangedDescription **asrds) { + + AudioObjectPropertyAddress prop_address; + + prop_address.mSelector = virtual ? kAudioStreamPropertyAvailableVirtualFormats : kAudioStreamPropertyAvailablePhysicalFormats; + prop_address.mScope = kAudioObjectPropertyScopeGlobal; + prop_address.mElement = kAudioObjectPropertyElementMaster; + + int asrd_count = format_count; + if (asrd_count <= 0) { + return 0; + } + + OSStatus os_err; + UInt32 io_size = format_count * sizeof(AudioStreamRangedDescription); + + if ((os_err = AudioObjectGetPropertyData(stream_id, &prop_address, 0, NULL, &io_size, *asrds))) + { + free(asrds); + return 0; + } + assert(asrd_count == io_size / sizeof(AudioStreamRangedDescription)); + return asrd_count; +} + +struct StreamFormat { + UInt32 stream_id; + AudioStreamRangedDescription asrd; + bool physical_integer_match; +}; + +static int get_stream_formats_for_device (AudioDeviceID device_id, enum SoundIoDeviceAim aim, struct StreamFormat **result) { + + AudioStreamID *stream_ids; + int stream_count = get_hardware_streams_for_device(device_id, aim, &stream_ids); + + AudioStreamID stream_id; + int *virtual_asrd_counts = (int *)ALLOCATE_NONZERO(int, stream_count); + int virtual_asrd_count = 0; + + for (int i = 0; i < stream_count; i++) { + stream_id = stream_ids[i]; + virtual_asrd_counts[i] = get_asrd_count_for_stream(stream_id, true); + virtual_asrd_count += virtual_asrd_counts[i]; + } + + int stream_format_count = 0; + struct StreamFormat *stream_formats = (struct StreamFormat *)ALLOCATE_NONZERO(struct StreamFormat, virtual_asrd_count); + + int stream_asrd_count; + struct StreamFormat *stream_format; + + for (int i = 0; i < stream_count; i++) { + stream_id = stream_ids[i]; + stream_asrd_count = virtual_asrd_counts[i]; + AudioStreamRangedDescription *virtual_asrds = (AudioStreamRangedDescription *)ALLOCATE_NONZERO(AudioStreamRangedDescription, stream_asrd_count); + + get_asrds_for_stream(stream_id, stream_asrd_count, true, &virtual_asrds); + + for (int k = 0; k < stream_asrd_count; k++) { + stream_format = &stream_formats[stream_format_count++]; + stream_format->stream_id = stream_id; + memcpy(&stream_format->asrd, &virtual_asrds[k], sizeof(AudioStreamRangedDescription)); + stream_format->physical_integer_match = false; + } + + free(virtual_asrds); + } + assert(stream_format_count == virtual_asrd_count); + + fprintf(stderr, "%i - virtual\n", device_id); + for (int i = 0; i < stream_format_count; i++) { + STREAM_FORMAT_MSG(stream_formats[i].stream_id, stream_formats[i].asrd.mFormat); + } + fprintf(stderr, "\n"); + + int *physical_asrd_counts = (int *)ALLOCATE_NONZERO(int, stream_count); + + for (int i = 0; i < stream_count; i++) { + stream_id = stream_ids[i]; + physical_asrd_counts[i] = get_asrd_count_for_stream(stream_id, false); + } + + fprintf(stderr, "%i - physical\n", device_id); + + AudioStreamRangedDescription *physical_asrd; + for (int i = 0; i < stream_count; i++) { + stream_id = stream_ids[i]; + stream_asrd_count = physical_asrd_counts[i]; + AudioStreamRangedDescription *physical_asrds = (AudioStreamRangedDescription *)ALLOCATE_NONZERO(AudioStreamRangedDescription, stream_asrd_count); + get_asrds_for_stream(stream_id, stream_asrd_count, false, &physical_asrds); + + for (int j = 0; j < stream_asrd_count; j++) { + physical_asrd = &physical_asrds[j]; + STREAM_FORMAT_MSG(stream_id, physical_asrd->mFormat); + + for (int k = 0; k < stream_format_count; k++) { + // check for integer match + stream_format = &stream_formats[k]; + if (asbd_equal(&stream_format->asrd.mFormat, &physical_asrd->mFormat) && + stream_format->asrd.mFormat.mFormatID == kAudioFormatLinearPCM && + flag_in_format(stream_format->asrd.mFormat.mFormatFlags, kAudioFormatFlagIsSignedInteger) && + !flag_in_format(stream_format->asrd.mFormat.mFormatFlags, kAudioFormatFlagIsFloat)) + { + stream_format->physical_integer_match = true; + } + } + } + free(physical_asrds); + } + fprintf(stderr, "\n"); + + fprintf(stderr, "%i - integer\n", device_id); + for (int i = 0; i < stream_format_count; i++) { + stream_format = &stream_formats[i]; + if (stream_format->physical_integer_match) { + STREAM_FORMAT_MSG(stream_format->stream_id, stream_format->asrd.mFormat); + } + } + fprintf(stderr, "\n"); + + + /* + + int unique_sample_rates_count = 0; + AudioValueRange *unique_sample_rates = ALLOCATE(AudioValueRange, asrd_count); + + int unique_formats_count = 0; + enum SoundIoFormat *unique_formats = ALLOCATE(enum SoundIoFormat, asrd_count); + + bool is_unique; + fprintf(stderr, "%s\n", dev_raw->pub.name); + for (int j = 0; j < asrd_count; j++) + { + AudioStreamRangedDescription asrd = asrds[j]; + STREAM_FORMAT_MSG(asrd.mFormat); + is_unique = true; + + for (int k = 0; k < unique_sample_rates_count; k++) { + if (unique_sample_rates[k].mMinimum == asrd.mSampleRateRange.mMinimum && + unique_sample_rates[k].mMaximum == asrd.mSampleRateRange.mMaximum) + { + is_unique = false; + break; + } + } + if (is_unique) + { + unique_sample_rates[unique_sample_rates_count++] = asrd.mSampleRateRange; + } + + enum SoundIoFormat asrd_format = from_ca_asbd(&asrd.mFormat); + is_unique = true; + + for (int k = 0; k < unique_formats_count; k++) { + if (unique_formats[k] == asrd_format) + { + is_unique = false; + break; + } + } + + if (is_unique) + { + unique_formats[unique_formats_count++] = asrd_format; + } + } + free(asrds); + + if (unique_sample_rates_count == 1) { + rd.device_raw->sample_rate_count = 1; + rd.device_raw->sample_rates = &dev_raw->prealloc_sample_rate_range; + rd.device_raw->sample_rates[0].min = ceil_dbl_to_int(unique_sample_rates[0].mMinimum); + rd.device_raw->sample_rates[0].max = (int)(unique_sample_rates[0].mMaximum); + } else { + rd.device_raw->sample_rate_count = unique_sample_rates_count; + rd.device_raw->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, unique_sample_rates_count); + if (!rd.device_raw->sample_rates) { + deinit_refresh_devices(&rd); + return SoundIoErrorNoMem; + } + for (int i = 0; i < unique_sample_rates_count; i += 1) { + AudioValueRange *avr = &unique_sample_rates[i]; + int min_val = ceil_dbl_to_int(avr->mMinimum); + int max_val = (int)(avr->mMaximum); + rd.device_raw->sample_rates[i].min = min_val; + rd.device_raw->sample_rates[i].max = max_val; + } + } + + rd.device_raw->format_count = unique_formats_count; + rd.device_raw->formats = unique_formats; + } + free(hardware_stream_ids); +*/ + return 0; +} + static int refresh_devices(struct SoundIoPrivate *si) { struct SoundIo *soundio = &si->pub; struct SoundIoCoreAudio *sica = &si->backend_data.coreaudio; @@ -804,142 +1045,13 @@ static int refresh_devices(struct SoundIoPrivate *si) { } } - prop_address.mSelector = kAudioDevicePropertyStreams; - prop_address.mScope = kAudioObjectPropertyScopeGlobal; - prop_address.mElement = kAudioObjectPropertyElementMaster; + // raw + struct StreamFormat *stream_formats; + UInt32 stream_count = get_stream_formats_for_device(device_id, aim, &stream_formats); - if ((os_err = AudioObjectGetPropertyDataSize(device_id, &prop_address, 0, NULL, - &io_size))) - { - deinit_refresh_devices(&rd); - return SoundIoErrorOpeningDevice; - } - - AudioStreamID *hardware_stream_ids = (AudioStreamID *)ALLOCATE_NONZERO(char, io_size); - - if ((os_err = AudioObjectGetPropertyData(device_id, &prop_address, 0, NULL, - &io_size, hardware_stream_ids))) - { - deinit_refresh_devices(&rd); - return SoundIoErrorOpeningDevice; - } - - int stream_count = io_size / sizeof(AudioStreamID); - - for (int i = 0; i < stream_count; i++) - { - AudioStreamID stream_id = hardware_stream_ids[i]; - - prop_address.mSelector = kAudioStreamPropertyDirection; - prop_address.mScope = kAudioObjectPropertyScopeGlobal; - prop_address.mElement = kAudioObjectPropertyElementMaster; - - - io_size = sizeof(uint32_t); - uint32_t direction = -1; - - if ((os_err = AudioObjectGetPropertyData(stream_id, &prop_address, 0, NULL, &io_size, &direction))) - { - deinit_refresh_devices(&rd); - return SoundIoErrorOpeningDevice; - } - - // @constant kAudioStreamPropertyDirection - // A UInt32 where a value of 0 means that this AudioStream is an output stream - // and a value of 1 means that it is an input stream. - if (aim == direction) { - continue; - } - - prop_address.mSelector = kAudioStreamPropertyAvailableVirtualFormats; - prop_address.mScope = kAudioObjectPropertyScopeGlobal; - prop_address.mElement = kAudioObjectPropertyElementMaster; - - if ((os_err = AudioObjectGetPropertyDataSize(stream_id, &prop_address, 0, NULL, &io_size))) - { - deinit_refresh_devices(&rd); - return SoundIoErrorOpeningDevice; - } - - AudioStreamRangedDescription *asrds = (AudioStreamRangedDescription *)ALLOCATE_NONZERO(char, io_size); - int asrd_count = io_size / sizeof(AudioStreamRangedDescription); - - if ((os_err = AudioObjectGetPropertyData(stream_id, &prop_address, 0, NULL, &io_size, asrds))) - { - deinit_refresh_devices(&rd); - return SoundIoErrorOpeningDevice; - } - - int unique_sample_rates_count = 0; - AudioValueRange *unique_sample_rates = ALLOCATE(AudioValueRange, asrd_count); - - int unique_formats_count = 0; - enum SoundIoFormat *unique_formats = ALLOCATE(enum SoundIoFormat, asrd_count); - - bool is_unique; - fprintf(stderr, "%s\n", dev_raw->pub.name); - for (int j = 0; j < asrd_count; j++) - { - AudioStreamRangedDescription asrd = asrds[j]; - STREAM_FORMAT_MSG(asrd.mFormat); - is_unique = true; - - for (int k = 0; k < unique_sample_rates_count; k++) { - if (unique_sample_rates[k].mMinimum == asrd.mSampleRateRange.mMinimum && - unique_sample_rates[k].mMaximum == asrd.mSampleRateRange.mMaximum) - { - is_unique = false; - break; - } - } - if (is_unique) - { - unique_sample_rates[unique_sample_rates_count++] = asrd.mSampleRateRange; - } - - enum SoundIoFormat asrd_format = from_ca_asbd(&asrd.mFormat); - is_unique = true; - - for (int k = 0; k < unique_formats_count; k++) { - if (unique_formats[k] == asrd_format) - { - is_unique = false; - break; - } - } - - if (is_unique) - { - unique_formats[unique_formats_count++] = asrd_format; - } - } - free(asrds); - - if (unique_sample_rates_count == 1) { - rd.device_raw->sample_rate_count = 1; - rd.device_raw->sample_rates = &dev_raw->prealloc_sample_rate_range; - rd.device_raw->sample_rates[0].min = ceil_dbl_to_int(unique_sample_rates[0].mMinimum); - rd.device_raw->sample_rates[0].max = (int)(unique_sample_rates[0].mMaximum); - } else { - rd.device_raw->sample_rate_count = unique_sample_rates_count; - rd.device_raw->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, unique_sample_rates_count); - if (!rd.device_raw->sample_rates) { - deinit_refresh_devices(&rd); - return SoundIoErrorNoMem; - } - for (int i = 0; i < unique_sample_rates_count; i += 1) { - AudioValueRange *avr = &unique_sample_rates[i]; - int min_val = ceil_dbl_to_int(avr->mMinimum); - int max_val = (int)(avr->mMaximum); - rd.device_raw->sample_rates[i].min = min_val; - rd.device_raw->sample_rates[i].max = max_val; - } - } + for (int i = 0; i < stream_count; i++) { - rd.device_raw->format_count = unique_formats_count; - rd.device_raw->formats = unique_formats; } - free(hardware_stream_ids); prop_address.mSelector = kAudioDevicePropertyBufferFrameSize; prop_address.mScope = aim_to_scope(aim); From d93c3fe28faef8313ef13f582c5f791d2296d50b Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Thu, 21 Sep 2017 21:29:55 +1000 Subject: [PATCH 5/9] Delete private API for Core Audio integer mode --- src/coreaudio.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreaudio.h b/src/coreaudio.h index 5acab633..82e29618 100644 --- a/src/coreaudio.h +++ b/src/coreaudio.h @@ -60,7 +60,6 @@ struct SoundIoOutStreamCoreAudio { struct SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; struct SoundIoAtomicBool output_format_match; bool revert_format; - bool raw_integer_mode_support; }; struct SoundIoInStreamCoreAudio { From 0bc5dff1cafa93597e8a7124151cb4e9cf26bb44 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Thu, 21 Sep 2017 21:30:26 +1000 Subject: [PATCH 6/9] Add support for enumerating Core Audio HAL virtual formats --- src/coreaudio.c | 159 +++++++++++++++++++++--------------------------- 1 file changed, 69 insertions(+), 90 deletions(-) diff --git a/src/coreaudio.c b/src/coreaudio.c index 672956cf..f1b03159 100644 --- a/src/coreaudio.c +++ b/src/coreaudio.c @@ -558,7 +558,7 @@ static int get_stream_formats_for_device (AudioDeviceID device_id, enum SoundIoD } int stream_format_count = 0; - struct StreamFormat *stream_formats = (struct StreamFormat *)ALLOCATE_NONZERO(struct StreamFormat, virtual_asrd_count); + struct StreamFormat *stream_formats = (struct StreamFormat *)ALLOCATE(struct StreamFormat, virtual_asrd_count); int stream_asrd_count; struct StreamFormat *stream_format; @@ -594,8 +594,6 @@ static int get_stream_formats_for_device (AudioDeviceID device_id, enum SoundIoD physical_asrd_counts[i] = get_asrd_count_for_stream(stream_id, false); } - fprintf(stderr, "%i - physical\n", device_id); - AudioStreamRangedDescription *physical_asrd; for (int i = 0; i < stream_count; i++) { stream_id = stream_ids[i]; @@ -621,92 +619,9 @@ static int get_stream_formats_for_device (AudioDeviceID device_id, enum SoundIoD } free(physical_asrds); } - fprintf(stderr, "\n"); - - fprintf(stderr, "%i - integer\n", device_id); - for (int i = 0; i < stream_format_count; i++) { - stream_format = &stream_formats[i]; - if (stream_format->physical_integer_match) { - STREAM_FORMAT_MSG(stream_format->stream_id, stream_format->asrd.mFormat); - } - } - fprintf(stderr, "\n"); - - - /* - - int unique_sample_rates_count = 0; - AudioValueRange *unique_sample_rates = ALLOCATE(AudioValueRange, asrd_count); - - int unique_formats_count = 0; - enum SoundIoFormat *unique_formats = ALLOCATE(enum SoundIoFormat, asrd_count); - - bool is_unique; - fprintf(stderr, "%s\n", dev_raw->pub.name); - for (int j = 0; j < asrd_count; j++) - { - AudioStreamRangedDescription asrd = asrds[j]; - STREAM_FORMAT_MSG(asrd.mFormat); - is_unique = true; - for (int k = 0; k < unique_sample_rates_count; k++) { - if (unique_sample_rates[k].mMinimum == asrd.mSampleRateRange.mMinimum && - unique_sample_rates[k].mMaximum == asrd.mSampleRateRange.mMaximum) - { - is_unique = false; - break; - } - } - if (is_unique) - { - unique_sample_rates[unique_sample_rates_count++] = asrd.mSampleRateRange; - } - - enum SoundIoFormat asrd_format = from_ca_asbd(&asrd.mFormat); - is_unique = true; - - for (int k = 0; k < unique_formats_count; k++) { - if (unique_formats[k] == asrd_format) - { - is_unique = false; - break; - } - } - - if (is_unique) - { - unique_formats[unique_formats_count++] = asrd_format; - } - } - free(asrds); - - if (unique_sample_rates_count == 1) { - rd.device_raw->sample_rate_count = 1; - rd.device_raw->sample_rates = &dev_raw->prealloc_sample_rate_range; - rd.device_raw->sample_rates[0].min = ceil_dbl_to_int(unique_sample_rates[0].mMinimum); - rd.device_raw->sample_rates[0].max = (int)(unique_sample_rates[0].mMaximum); - } else { - rd.device_raw->sample_rate_count = unique_sample_rates_count; - rd.device_raw->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, unique_sample_rates_count); - if (!rd.device_raw->sample_rates) { - deinit_refresh_devices(&rd); - return SoundIoErrorNoMem; - } - for (int i = 0; i < unique_sample_rates_count; i += 1) { - AudioValueRange *avr = &unique_sample_rates[i]; - int min_val = ceil_dbl_to_int(avr->mMinimum); - int max_val = (int)(avr->mMaximum); - rd.device_raw->sample_rates[i].min = min_val; - rd.device_raw->sample_rates[i].max = max_val; - } - } - - rd.device_raw->format_count = unique_formats_count; - rd.device_raw->formats = unique_formats; - } - free(hardware_stream_ids); -*/ - return 0; + *result = stream_formats; + return stream_format_count; } static int refresh_devices(struct SoundIoPrivate *si) { @@ -1047,11 +962,75 @@ static int refresh_devices(struct SoundIoPrivate *si) { // raw struct StreamFormat *stream_formats; - UInt32 stream_count = get_stream_formats_for_device(device_id, aim, &stream_formats); + UInt32 stream_format_count = get_stream_formats_for_device(device_id, aim, &stream_formats); + + int unique_sample_rates_count = 0; + AudioValueRange *unique_sample_rates = ALLOCATE(AudioValueRange, stream_format_count); - for (int i = 0; i < stream_count; i++) { + int unique_formats_count = 0; + enum SoundIoFormat *unique_formats = ALLOCATE(enum SoundIoFormat, stream_format_count); + fprintf(stderr, "%s\n", dev_raw->pub.name); + bool is_unique; + + AudioStreamRangedDescription asrd; + + for (int i = 0; i < stream_format_count; i++) { + + asrd = stream_formats[i].asrd; + STREAM_FORMAT_MSG(stream_formats[i].stream_id, asrd.mFormat); is_unique = true; + + for (int j = 0; j < unique_sample_rates_count; j++) { + if (unique_sample_rates[j].mMinimum == asrd.mSampleRateRange.mMinimum && + unique_sample_rates[j].mMaximum == asrd.mSampleRateRange.mMaximum) { + is_unique = false; + break; + } + } + if (is_unique) { + unique_sample_rates[unique_sample_rates_count++] = asrd.mSampleRateRange; + } + + enum SoundIoFormat asrd_format = from_ca_asbd(&asrd.mFormat); + is_unique = true; + + for (int j = 0; j < unique_formats_count; j++) { + if (unique_formats[j] == asrd_format) { + is_unique = false; + break; + } + } + + if (is_unique) { + unique_formats[unique_formats_count++] = asrd_format; + } + } + + free(stream_formats); + + if (unique_sample_rates_count == 1) { + rd.device_raw->sample_rate_count = 1; + rd.device_raw->sample_rates = &dev_raw->prealloc_sample_rate_range; + rd.device_raw->sample_rates[0].min = ceil_dbl_to_int(unique_sample_rates[0].mMinimum); + rd.device_raw->sample_rates[0].max = (int)(unique_sample_rates[0].mMaximum); + } else { + rd.device_raw->sample_rate_count = unique_sample_rates_count; + rd.device_raw->sample_rates = ALLOCATE(struct SoundIoSampleRateRange, unique_sample_rates_count); + if (!rd.device_raw->sample_rates) { + deinit_refresh_devices(&rd); + return SoundIoErrorNoMem; + } + + for (int i = 0; i < unique_sample_rates_count; i += 1) { + AudioValueRange *avr = &unique_sample_rates[i]; + int min_val = ceil_dbl_to_int(avr->mMinimum); + int max_val = (int)(avr->mMaximum); + rd.device_raw->sample_rates[i].min = min_val; + rd.device_raw->sample_rates[i].max = max_val; + } } + rd.device_raw->format_count = unique_formats_count; + rd.device_raw->formats = unique_formats; prop_address.mSelector = kAudioDevicePropertyBufferFrameSize; prop_address.mScope = aim_to_scope(aim); From 61e5c43fb2f783d268cf4abbf0849b1ed4bc9244 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Thu, 21 Sep 2017 21:31:11 +1000 Subject: [PATCH 7/9] Update documentation and add flag for Core Audio output streams which have a matching device output format (i.e. can perform bit-perfect IO) --- soundio/soundio.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/soundio/soundio.h b/soundio/soundio.h index 415c1359..edf89f6d 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -423,7 +423,9 @@ struct SoundIoDevice { /// provided by a software mixing service such as dmix or PulseAudio (see /// SoundIoDevice::is_raw). If it is a raw device, /// current_format is meaningless; - /// the device has no current format until you open it. On the other hand, + /// the device has no current format until you open it (with the + /// exclusion of Core Audio on macOS where the device will always + /// be set to a valid format). On the other hand, /// if it is a virtual device, current_format describes the /// destination sample format that your audio will be converted to. Or, /// if you're the lucky first application to open the device, you might @@ -585,6 +587,14 @@ struct SoundIoOutStream { /// stream. Defaults to `false`. bool non_terminal_hint; + /// On macOS, even in raw mode the hardware format may not always match the + /// virtual format that the device accepts IO in. If the virtual and physical + /// formats for this stream match (usually only true for integer formats) + /// this flag will be true. Otherwise the output is typically 32 bit float + /// converted to integer by the kernel device driver (NOT Core Audio). Integer + /// virtual formats (required for "bit-perfect" playback) are typically only supported + /// by external USB DACs. + bool physical_format_match; /// computed automatically when you call ::soundio_outstream_open int bytes_per_frame; From 2a5e6341df583fdb59ed2b0c381f12645986377f Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Thu, 21 Sep 2017 22:33:17 +1000 Subject: [PATCH 8/9] Remove debug logging and implement hardware stream volume support --- src/coreaudio.c | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/coreaudio.c b/src/coreaudio.c index f1b03159..1bbd8cce 100644 --- a/src/coreaudio.c +++ b/src/coreaudio.c @@ -581,12 +581,6 @@ static int get_stream_formats_for_device (AudioDeviceID device_id, enum SoundIoD } assert(stream_format_count == virtual_asrd_count); - fprintf(stderr, "%i - virtual\n", device_id); - for (int i = 0; i < stream_format_count; i++) { - STREAM_FORMAT_MSG(stream_formats[i].stream_id, stream_formats[i].asrd.mFormat); - } - fprintf(stderr, "\n"); - int *physical_asrd_counts = (int *)ALLOCATE_NONZERO(int, stream_count); for (int i = 0; i < stream_count; i++) { @@ -603,7 +597,6 @@ static int get_stream_formats_for_device (AudioDeviceID device_id, enum SoundIoD for (int j = 0; j < stream_asrd_count; j++) { physical_asrd = &physical_asrds[j]; - STREAM_FORMAT_MSG(stream_id, physical_asrd->mFormat); for (int k = 0; k < stream_format_count; k++) { // check for integer match @@ -970,7 +963,6 @@ static int refresh_devices(struct SoundIoPrivate *si) { int unique_formats_count = 0; enum SoundIoFormat *unique_formats = ALLOCATE(enum SoundIoFormat, stream_format_count); - fprintf(stderr, "%s\n", dev_raw->pub.name); bool is_unique; AudioStreamRangedDescription asrd; @@ -978,7 +970,7 @@ static int refresh_devices(struct SoundIoPrivate *si) { for (int i = 0; i < stream_format_count; i++) { asrd = stream_formats[i].asrd; - STREAM_FORMAT_MSG(stream_formats[i].stream_id, asrd.mFormat); is_unique = true; + is_unique = true; for (int j = 0; j < unique_sample_rates_count; j++) { if (unique_sample_rates[j].mMinimum == asrd.mSampleRateRange.mMinimum && @@ -1614,6 +1606,16 @@ static int outstream_open_ca_raw(struct SoundIoPrivate *si, struct SoundIoOutStr osca->hardware_latency = dca->latency_frames / (double)outstream->sample_rate; + prop_address.mSelector = kAudioDevicePropertyVolumeScalar; + prop_address.mScope = aim_to_scope(device->aim); + prop_address.mElement = kAudioObjectPropertyElementMaster; + + io_size = sizeof(float); + if ((os_err = AudioObjectGetPropertyData(dca->device_id, &prop_address, 0, NULL, &io_size, &outstream->volume))) + { + return SoundIoErrorIncompatibleDevice; + } + return 0; } @@ -1821,9 +1823,29 @@ static int outstream_set_volume_ca(struct SoundIoPrivate *si, struct SoundIoOutS struct SoundIoOutStream *outstream = &os->pub; OSStatus os_err; - if ((os_err = AudioUnitSetParameter (osca->instance, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, volume, 0))) { - return SoundIoErrorIncompatibleDevice; + + if (osca->instance) { + if ((os_err = AudioUnitSetParameter(osca->instance, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, volume, 0))) { + return SoundIoErrorIncompatibleDevice; + } + } + + else if (osca->io_proc_id) { + + struct SoundIoDevice *device = outstream->device; + struct SoundIoDevicePrivate *dev = (struct SoundIoDevicePrivate *)device; + struct SoundIoDeviceCoreAudio *dca = &dev->backend_data.coreaudio; + + AudioObjectPropertyAddress prop_address; + prop_address.mSelector = kAudioDevicePropertyVolumeScalar; + prop_address.mScope = aim_to_scope(device->aim); + prop_address.mElement = kAudioObjectPropertyElementMaster; + + if ((os_err = AudioObjectSetPropertyData(dca->device_id, &prop_address, 0, NULL, sizeof(float), &volume))) { + return SoundIoErrorIncompatibleDevice; + } } + outstream->volume = volume; return 0; } From cb87a790c49af25a1f14718f843a288b82cd3af0 Mon Sep 17 00:00:00 2001 From: Ryan Walklin Date: Fri, 22 Sep 2017 21:46:51 +1000 Subject: [PATCH 9/9] Destroy device if HW format changes during raw mode playback --- src/coreaudio.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/coreaudio.c b/src/coreaudio.c index 1bbd8cce..8fbfe50d 100644 --- a/src/coreaudio.c +++ b/src/coreaudio.c @@ -1197,12 +1197,16 @@ static void device_thread_run(void *arg) { } } +static void outstream_destroy_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os); + static OSStatus on_physical_format_changed(AudioObjectID in_object_id, UInt32 in_number_addresses, const AudioObjectPropertyAddress in_addresses[], void *in_client_data) { struct SoundIoOutStreamPrivate *os = (struct SoundIoOutStreamPrivate *)in_client_data; - //struct SoundIoOutStream *outstream = &os->pub; struct SoundIoOutStreamCoreAudio *osca = &os->backend_data.coreaudio; + struct SoundIoOutStream *outstream = &os->pub; + struct SoundIoDevice *device = outstream->device; + struct SoundIoPrivate *si = (struct SoundIoPrivate *)device->soundio; for (int i = 0; i < in_number_addresses; i++) @@ -1218,9 +1222,10 @@ static OSStatus on_physical_format_changed(AudioObjectID in_object_id, UInt32 in return os_err; } - if (SOUNDIO_ATOMIC_LOAD(osca->output_format_match) == false) - { - SOUNDIO_ATOMIC_STORE(osca->output_format_match, asbd_equal(&new_hardware_format, &osca->hardware_format)); + SOUNDIO_ATOMIC_STORE(osca->output_format_match, asbd_equal(&new_hardware_format, &osca->hardware_format)); + if (SOUNDIO_ATOMIC_LOAD(osca->output_format_match) == false) { + outstream_destroy_ca(si, os); + return SoundIoErrorOpeningDevice; } } }