/* * Copyright © 2011 Mozilla Foundation * * This program is made available under an ISC-style license. See the * accompanying file LICENSE for details. */ #undef NDEBUG #include #include #include #include #include #include #if !TARGET_OS_IPHONE #include #include #include #include #endif #include #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" #include "cubeb_panner.h" #if !TARGET_OS_IPHONE #include "cubeb_osx_run_loop.h" #endif #include "cubeb_resampler.h" #include "cubeb_ring_array.h" #include "cubeb_utils.h" #include #include #if !defined(kCFCoreFoundationVersionNumber10_7) /* From CoreFoundation CFBase.h */ #define kCFCoreFoundationVersionNumber10_7 635.00 #endif #if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 #define AudioComponent Component #define AudioComponentDescription ComponentDescription #define AudioComponentFindNext FindNextComponent #define AudioComponentInstanceNew OpenAComponent #define AudioComponentInstanceDispose CloseComponent #endif #if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 typedef UInt32 AudioFormatFlags; #endif #define CUBEB_STREAM_MAX 8 #define AU_OUT_BUS 0 #define AU_IN_BUS 1 #define PRINT_ERROR_CODE(str, r) do { \ LOG("System call failed: %s (rv: %d)", str, r); \ } while(0) const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb"; /* Testing empirically, some headsets report a minimal latency that is very * low, but this does not work in practice. Lie and say the minimum is 256 * frames. */ const uint32_t SAFE_MIN_LATENCY_FRAMES = 256; const uint32_t SAFE_MAX_LATENCY_FRAMES = 512; void audiounit_stream_stop_internal(cubeb_stream * stm); void audiounit_stream_start_internal(cubeb_stream * stm); static void audiounit_close_stream(cubeb_stream *stm); static int audiounit_setup_stream(cubeb_stream *stm); extern cubeb_ops const audiounit_ops; struct cubeb { cubeb_ops const * ops; owned_critical_section mutex; std::atomic active_streams; uint32_t global_latency_frames = 0; int limit_streams; cubeb_device_collection_changed_callback collection_changed_callback; void * collection_changed_user_ptr; /* Differentiate input from output devices. */ cubeb_device_type collection_changed_devtype; uint32_t devtype_device_count; AudioObjectID * devtype_device_array; // The queue is asynchronously deallocated once all references to it are released dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL); }; class auto_array_wrapper { public: explicit auto_array_wrapper(auto_array * ar) : float_ar(ar) , short_ar(nullptr) {assert((float_ar && !short_ar) || (!float_ar && short_ar));} explicit auto_array_wrapper(auto_array * ar) : float_ar(nullptr) , short_ar(ar) {assert((float_ar && !short_ar) || (!float_ar && short_ar));} ~auto_array_wrapper() { auto_lock l(lock); assert((float_ar && !short_ar) || (!float_ar && short_ar)); delete float_ar; delete short_ar; } void push(void * elements, size_t length){ assert((float_ar && !short_ar) || (!float_ar && short_ar)); auto_lock l(lock); if (float_ar) return float_ar->push(static_cast(elements), length); return short_ar->push(static_cast(elements), length); } size_t length() { assert((float_ar && !short_ar) || (!float_ar && short_ar)); auto_lock l(lock); if (float_ar) return float_ar->length(); return short_ar->length(); } void push_silence(size_t length) { assert((float_ar && !short_ar) || (!float_ar && short_ar)); auto_lock l(lock); if (float_ar) return float_ar->push_silence(length); return short_ar->push_silence(length); } bool pop(void * elements, size_t length) { assert((float_ar && !short_ar) || (!float_ar && short_ar)); auto_lock l(lock); if (float_ar) return float_ar->pop(static_cast(elements), length); return short_ar->pop(static_cast(elements), length); } void * data() { assert((float_ar && !short_ar) || (!float_ar && short_ar)); auto_lock l(lock); if (float_ar) return float_ar->data(); return short_ar->data(); } void clear() { assert((float_ar && !short_ar) || (!float_ar && short_ar)); auto_lock l(lock); if (float_ar) { float_ar->clear(); } else { short_ar->clear(); } } private: auto_array * float_ar; auto_array * short_ar; owned_critical_section lock; }; struct cubeb_stream { cubeb * context; cubeb_data_callback data_callback; cubeb_state_callback state_callback; cubeb_device_changed_callback device_changed_callback; /* Stream creation parameters */ cubeb_stream_params input_stream_params; cubeb_stream_params output_stream_params; cubeb_devid input_device; bool is_default_input; cubeb_devid output_device; /* User pointer of data_callback */ void * user_ptr; /* Format descriptions */ AudioStreamBasicDescription input_desc; AudioStreamBasicDescription output_desc; /* I/O AudioUnits */ AudioUnit input_unit; AudioUnit output_unit; /* I/O device sample rate */ Float64 input_hw_rate; Float64 output_hw_rate; /* Expected I/O thread interleave, * calculated from I/O hw rate. */ int expected_output_callbacks_in_a_row; owned_critical_section mutex; /* Hold the input samples in every * input callback iteration */ auto_array_wrapper * input_linear_buffer; /* Frames on input buffer */ std::atomic input_buffer_frames; /* Frame counters */ uint64_t frames_played; uint64_t frames_queued; std::atomic frames_read; std::atomic shutdown; std::atomic draining; /* Latency requested by the user. */ uint32_t latency_frames; std::atomic current_latency_frames; uint64_t hw_latency_frames; std::atomic panning; cubeb_resampler * resampler; /* This is the number of output callback we got in a row. This is usually one, * but can be two when the input and output rate are different, and more when * a device has been plugged or unplugged, as there can be some time before * the device is ready. */ std::atomic output_callback_in_a_row; /* This is true if a device change callback is currently running. */ std::atomic switching_device; std::atomic buffer_size_change_state{ false }; }; bool has_input(cubeb_stream * stm) { return stm->input_stream_params.rate != 0; } bool has_output(cubeb_stream * stm) { return stm->output_stream_params.rate != 0; } #if TARGET_OS_IPHONE typedef UInt32 AudioDeviceID; typedef UInt32 AudioObjectID; #define AudioGetCurrentHostTime mach_absolute_time uint64_t AudioConvertHostTimeToNanos(uint64_t host_time) { static struct mach_timebase_info timebase_info; static bool initialized = false; if (!initialized) { mach_timebase_info(&timebase_info); initialized = true; } long double answer = host_time; if (timebase_info.numer != timebase_info.denom) { answer *= timebase_info.numer; answer /= timebase_info.denom; } return (uint64_t)answer; } #endif static int64_t audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) { if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) { return 0; } uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL; } static void audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames) { stm->mutex.assert_current_thread_owns(); assert(stm->context->active_streams == 1); stm->context->global_latency_frames = latency_frames; } static void audiounit_make_silent(AudioBuffer * ioData) { assert(ioData); assert(ioData->mData); memset(ioData->mData, 0, ioData->mDataByteSize); } static OSStatus audiounit_render_input(cubeb_stream * stm, AudioUnitRenderActionFlags * flags, AudioTimeStamp const * tstamp, UInt32 bus, UInt32 input_frames) { /* Create the AudioBufferList to store input. */ AudioBufferList input_buffer_list; input_buffer_list.mBuffers[0].mDataByteSize = stm->input_desc.mBytesPerFrame * input_frames; input_buffer_list.mBuffers[0].mData = nullptr; input_buffer_list.mBuffers[0].mNumberChannels = stm->input_desc.mChannelsPerFrame; input_buffer_list.mNumberBuffers = 1; /* Render input samples */ OSStatus r = AudioUnitRender(stm->input_unit, flags, tstamp, bus, input_frames, &input_buffer_list); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitRender", r); return r; } /* Copy input data in linear buffer. */ stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData, input_frames * stm->input_desc.mChannelsPerFrame); LOGV("(%p) input: buffers %d, size %d, channels %d, frames %d.", stm, input_buffer_list.mNumberBuffers, input_buffer_list.mBuffers[0].mDataByteSize, input_buffer_list.mBuffers[0].mNumberChannels, input_frames); /* Advance input frame counter. */ assert(input_frames > 0); stm->frames_read += input_frames; return noErr; } static OSStatus audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, AudioTimeStamp const * tstamp, UInt32 bus, UInt32 input_frames, AudioBufferList * /* bufs */) { cubeb_stream * stm = static_cast(user_ptr); long outframes; assert(stm->input_unit != NULL); assert(AU_IN_BUS == bus); if (stm->shutdown) { LOG("(%p) input shutdown", stm); return noErr; } // This happens when we're finally getting a new input callback after having // switched device, we can clear the input buffer now, only keeping the data // we just got. if (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row) { stm->input_linear_buffer->pop( nullptr, stm->input_linear_buffer->length() - input_frames * stm->input_stream_params.channels); } OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames); if (r != noErr) { return r; } // Full Duplex. We'll call data_callback in the AudioUnit output callback. if (stm->output_unit != NULL) { stm->output_callback_in_a_row = 0; return noErr; } /* Input only. Call the user callback through resampler. Resampler will deliver input buffer in the correct rate. */ assert(input_frames <= stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame); long total_input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; outframes = cubeb_resampler_fill(stm->resampler, stm->input_linear_buffer->data(), &total_input_frames, NULL, 0); // Reset input buffer stm->input_linear_buffer->clear(); if (outframes < 0 || outframes != input_frames) { stm->shutdown = true; return noErr; } return noErr; } static bool is_extra_input_needed(cubeb_stream * stm) { /* If the output callback came first and this is a duplex stream, we need to * fill in some additional silence in the resampler. * Otherwise, if we had more than expected callbacks in a row, or we're currently * switching, we add some silence as well to compensate for the fact that * we're lacking some input data. */ /* If resampling is taking place after every output callback * the input buffer expected to be empty. Any frame left over * from resampling is stored inside the resampler available to * be used in next iteration as needed. * BUT when noop_resampler is operating we have left over * frames since it does not store anything internally. */ return stm->frames_read == 0 || (stm->input_linear_buffer->length() == 0 && (stm->output_callback_in_a_row > stm->expected_output_callbacks_in_a_row || stm->switching_device)); } static OSStatus audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * /* flags */, AudioTimeStamp const * tstamp, UInt32 bus, UInt32 output_frames, AudioBufferList * outBufferList) { assert(AU_OUT_BUS == bus); assert(outBufferList->mNumberBuffers == 1); cubeb_stream * stm = static_cast(user_ptr); stm->output_callback_in_a_row++; LOGV("(%p) output: buffers %d, size %d, channels %d, frames %d.", stm, outBufferList->mNumberBuffers, outBufferList->mBuffers[0].mDataByteSize, outBufferList->mBuffers[0].mNumberChannels, output_frames); long outframes = 0, input_frames = 0; void * output_buffer = NULL, * input_buffer = NULL; if (stm->shutdown) { LOG("(%p) output shutdown.", stm); audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr; } stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm); if (stm->draining) { OSStatus r = AudioOutputUnitStop(stm->output_unit); assert(r == 0); if (stm->input_unit) { r = AudioOutputUnitStop(stm->input_unit); assert(r == 0); } stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr; } /* Get output buffer. */ output_buffer = outBufferList->mBuffers[0].mData; /* If Full duplex get also input buffer */ if (stm->input_unit != NULL) { if (is_extra_input_needed(stm)) { uint32_t min_input_frames_required = ceilf(stm->input_hw_rate / stm->output_hw_rate * stm->input_buffer_frames); stm->input_linear_buffer->push_silence(min_input_frames_required * stm->input_desc.mChannelsPerFrame); LOG("(%p) %s pushed %u frames of input silence.", stm, stm->frames_read == 0 ? "Input hasn't started," : stm->switching_device ? "Device switching," : "Drop out,", min_input_frames_required); } // The input buffer input_buffer = stm->input_linear_buffer->data(); // Number of input frames in the buffer input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; } /* Call user callback through resampler. */ outframes = cubeb_resampler_fill(stm->resampler, input_buffer, input_buffer ? &input_frames : NULL, output_buffer, output_frames); if (input_buffer) { stm->input_linear_buffer->pop(nullptr, input_frames * stm->input_desc.mChannelsPerFrame); } if (outframes < 0) { stm->shutdown = true; return noErr; } size_t outbpf = stm->output_desc.mBytesPerFrame; stm->draining = outframes < output_frames; stm->frames_played = stm->frames_queued; stm->frames_queued += outframes; AudioFormatFlags outaff = stm->output_desc.mFormatFlags; float panning = (stm->output_desc.mChannelsPerFrame == 2) ? stm->panning.load(std::memory_order_relaxed) : 0.0f; /* Post process output samples. */ if (stm->draining) { /* Clear missing frames (silence) */ memset((uint8_t*)output_buffer + outframes * outbpf, 0, (output_frames - outframes) * outbpf); } /* Pan stereo. */ if (panning != 0.0f) { if (outaff & kAudioFormatFlagIsFloat) { cubeb_pan_stereo_buffer_float((float*)output_buffer, outframes, panning); } else if (outaff & kAudioFormatFlagIsSignedInteger) { cubeb_pan_stereo_buffer_int((short*)output_buffer, outframes, panning); } } return noErr; } extern "C" { int audiounit_init(cubeb ** context, char const * /* context_name */) { cubeb * ctx; *context = NULL; ctx = (cubeb *)calloc(1, sizeof(cubeb)); assert(ctx); // Placement new to call the ctors of cubeb members. new (ctx) cubeb(); ctx->ops = &audiounit_ops; ctx->active_streams = 0; ctx->limit_streams = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber10_7; #if !TARGET_OS_IPHONE cubeb_set_coreaudio_notification_runloop(); #endif *context = ctx; return CUBEB_OK; } } static char const * audiounit_get_backend_id(cubeb * /* ctx */) { return "audiounit"; } #if !TARGET_OS_IPHONE static int audiounit_get_output_device_id(AudioDeviceID * device_id) { UInt32 size; OSStatus r; AudioObjectPropertyAddress output_device_address = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; size = sizeof(*device_id); r = AudioObjectGetPropertyData(kAudioObjectSystemObject, &output_device_address, 0, NULL, &size, device_id); if (r != noErr) { PRINT_ERROR_CODE("output_device_id", r); return CUBEB_ERROR; } return CUBEB_OK; } static int audiounit_get_input_device_id(AudioDeviceID * device_id) { UInt32 size; OSStatus r; AudioObjectPropertyAddress input_device_address = { kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; size = sizeof(*device_id); r = AudioObjectGetPropertyData(kAudioObjectSystemObject, &input_device_address, 0, NULL, &size, device_id); if (r != noErr) { return CUBEB_ERROR; } return CUBEB_OK; } static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm); static int audiounit_reinit_stream(cubeb_stream * stm) { auto_lock context_lock(stm->context->mutex); if (!stm->shutdown) { audiounit_stream_stop_internal(stm); } int r = audiounit_uninstall_device_changed_callback(stm); if (r != CUBEB_OK) { LOG("(%p) Could not uninstall the device changed callback", stm); } { auto_lock lock(stm->mutex); float volume = 0.0; int vol_rv = audiounit_stream_get_volume(stm, &volume); audiounit_close_stream(stm); if (audiounit_setup_stream(stm) != CUBEB_OK) { LOG("(%p) Stream reinit failed.", stm); return CUBEB_ERROR; } if (vol_rv == CUBEB_OK) { audiounit_stream_set_volume(stm, volume); } // Reset input frames to force new stream pre-buffer // silence if needed, check `is_extra_input_needed()` stm->frames_read = 0; // If the stream was running, start it again. if (!stm->shutdown) { audiounit_stream_start_internal(stm); } } return CUBEB_OK; } static OSStatus audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, const AudioObjectPropertyAddress * addresses, void * user) { cubeb_stream * stm = (cubeb_stream*) user; stm->switching_device = true; LOG("(%p) Audio device changed, %d events.", stm, address_count); for (UInt32 i = 0; i < address_count; i++) { switch(addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: { LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i); // Allow restart to choose the new default stm->output_device = nullptr; } break; case kAudioHardwarePropertyDefaultInputDevice: { LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultInputDevice", i); // Allow restart to choose the new default stm->input_device = nullptr; } break; case kAudioDevicePropertyDeviceIsAlive: { LOG("Event[%d] - mSelector == kAudioDevicePropertyDeviceIsAlive", i); // If this is the default input device ignore the event, // kAudioHardwarePropertyDefaultInputDevice will take care of the switch if (stm->is_default_input) { LOG("It's the default input device, ignore the event"); return noErr; } // Allow restart to choose the new default. Event register only for input. stm->input_device = nullptr; } break; case kAudioDevicePropertyDataSource: { LOG("Event[%d] - mSelector == kAudioHardwarePropertyDataSource", i); return noErr; } } } for (UInt32 i = 0; i < address_count; i++) { switch(addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: case kAudioHardwarePropertyDefaultInputDevice: case kAudioDevicePropertyDeviceIsAlive: /* fall through */ case kAudioDevicePropertyDataSource: { auto_lock lock(stm->mutex); if (stm->device_changed_callback) { stm->device_changed_callback(stm->user_ptr); } break; } } } // Use a new thread, through the queue, to avoid deadlock when calling // Get/SetProperties method from inside notify callback dispatch_async(stm->context->serial_queue, ^() { if (audiounit_reinit_stream(stm) != CUBEB_OK) { stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); LOG("(%p) Could not reopen the stream after switching.", stm); } stm->switching_device = false; }); return noErr; } OSStatus audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector, AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener) { AudioObjectPropertyAddress address = { selector, scope, kAudioObjectPropertyElementMaster }; return AudioObjectAddPropertyListener(id, &address, listener, stm); } OSStatus audiounit_remove_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector, AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener) { AudioObjectPropertyAddress address = { selector, scope, kAudioObjectPropertyElementMaster }; return AudioObjectRemovePropertyListener(id, &address, listener, stm); } static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type); static int audiounit_install_device_changed_callback(cubeb_stream * stm) { OSStatus r; if (stm->output_unit) { /* This event will notify us when the data source on the same device changes, * for example when the user plugs in a normal (non-usb) headset in the * headphone jack. */ AudioDeviceID output_dev_id; r = audiounit_get_output_device_id(&output_dev_id); if (r != noErr) { return CUBEB_ERROR; } r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); if (r != noErr) { PRINT_ERROR_CODE("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource", r); return CUBEB_ERROR; } } if (stm->input_unit) { /* This event will notify us when the data source on the input device changes. */ AudioDeviceID input_dev_id; r = audiounit_get_input_device_id(&input_dev_id); if (r != noErr) { return CUBEB_ERROR; } r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); if (r != noErr) { PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource", r); return CUBEB_ERROR; } /* Event to notify when the input is going away. */ AudioDeviceID dev = stm->input_device ? reinterpret_cast(stm->input_device) : audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { PRINT_ERROR_CODE("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive", r); return CUBEB_ERROR; } } return CUBEB_OK; } static int audiounit_install_system_changed_callback(cubeb_stream * stm) { OSStatus r; if (stm->output_unit) { /* This event will notify us when the default audio device changes, * for example when the user plugs in a USB headset and the system chooses it * automatically as the default, or when another device is chosen in the * dropdown list. */ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { LOG("AudioObjectAddPropertyListener/output/kAudioHardwarePropertyDefaultOutputDevice rv=%d", r); return CUBEB_ERROR; } } if (stm->input_unit) { /* This event will notify us when the default input device changes. */ r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r); return CUBEB_ERROR; } } return CUBEB_OK; } static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm) { OSStatus r; if (stm->output_unit) { AudioDeviceID output_dev_id; r = audiounit_get_output_device_id(&output_dev_id); if (r != noErr) { return CUBEB_ERROR; } r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); if (r != noErr) { return CUBEB_ERROR; } } if (stm->input_unit) { AudioDeviceID input_dev_id; r = audiounit_get_input_device_id(&input_dev_id); if (r != noErr) { return CUBEB_ERROR; } r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); if (r != noErr) { return CUBEB_ERROR; } } return CUBEB_OK; } static int audiounit_uninstall_system_changed_callback(cubeb_stream * stm) { OSStatus r; if (stm->output_unit) { r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { return CUBEB_ERROR; } } if (stm->input_unit) { r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { return CUBEB_ERROR; } } return CUBEB_OK; } /* Get the acceptable buffer size (in frames) that this device can work with. */ static int audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) { UInt32 size; OSStatus r; AudioDeviceID output_device_id; AudioObjectPropertyAddress output_device_buffer_size_range = { kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { LOG("Could not get default output device id."); return CUBEB_ERROR; } /* Get the buffer size range this device supports */ size = sizeof(*latency_range); r = AudioObjectGetPropertyData(output_device_id, &output_device_buffer_size_range, 0, NULL, &size, latency_range); if (r != noErr) { PRINT_ERROR_CODE("AudioObjectGetPropertyData/buffer size range", r); return CUBEB_ERROR; } return CUBEB_OK; } #endif /* !TARGET_OS_IPHONE */ static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type) { AudioObjectPropertyAddress adr = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; AudioDeviceID devid; UInt32 size; if (type == CUBEB_DEVICE_TYPE_OUTPUT) { adr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; } else if (type == CUBEB_DEVICE_TYPE_INPUT) { adr.mSelector = kAudioHardwarePropertyDefaultInputDevice; } else { return kAudioObjectUnknown; } size = sizeof(AudioDeviceID); if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devid) != noErr) { return kAudioObjectUnknown; } return devid; } int audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) { #if TARGET_OS_IPHONE //TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels] *max_channels = 2; #else UInt32 size; OSStatus r; AudioDeviceID output_device_id; AudioStreamBasicDescription stream_format; AudioObjectPropertyAddress stream_format_address = { kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; assert(ctx && max_channels); if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { return CUBEB_ERROR; } size = sizeof(stream_format); r = AudioObjectGetPropertyData(output_device_id, &stream_format_address, 0, NULL, &size, &stream_format); if (r != noErr) { PRINT_ERROR_CODE("AudioObjectPropertyAddress/StreamFormat", r); return CUBEB_ERROR; } *max_channels = stream_format.mChannelsPerFrame; #endif return CUBEB_OK; } static int audiounit_get_min_latency(cubeb * /* ctx */, cubeb_stream_params /* params */, uint32_t * latency_frames) { #if TARGET_OS_IPHONE //TODO: [[AVAudioSession sharedInstance] inputLatency] return CUBEB_ERROR_NOT_SUPPORTED; #else AudioValueRange latency_range; if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { LOG("Could not get acceptable latency range."); return CUBEB_ERROR; } *latency_frames = std::max(latency_range.mMinimum, SAFE_MIN_LATENCY_FRAMES); #endif return CUBEB_OK; } static int audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate) { #if TARGET_OS_IPHONE //TODO return CUBEB_ERROR_NOT_SUPPORTED; #else UInt32 size; OSStatus r; Float64 fsamplerate; AudioDeviceID output_device_id; AudioObjectPropertyAddress samplerate_address = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { return CUBEB_ERROR; } size = sizeof(fsamplerate); r = AudioObjectGetPropertyData(output_device_id, &samplerate_address, 0, NULL, &size, &fsamplerate); if (r != noErr) { return CUBEB_ERROR; } *rate = static_cast(fsamplerate); #endif return CUBEB_OK; } static OSStatus audiounit_remove_device_listener(cubeb * context); static void audiounit_destroy(cubeb * ctx) { // Disabling this assert for bug 1083664 -- we seem to leak a stream // assert(ctx->active_streams == 0); { auto_lock lock(ctx->mutex); /* Unregister the callback if necessary. */ if(ctx->collection_changed_callback) { audiounit_remove_device_listener(ctx); } } ctx->~cubeb(); free(ctx); } static void audiounit_stream_destroy(cubeb_stream * stm); static int audio_stream_desc_init(AudioStreamBasicDescription * ss, const cubeb_stream_params * stream_params) { switch (stream_params->format) { case CUBEB_SAMPLE_S16LE: ss->mBitsPerChannel = 16; ss->mFormatFlags = kAudioFormatFlagIsSignedInteger; break; case CUBEB_SAMPLE_S16BE: ss->mBitsPerChannel = 16; ss->mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian; break; case CUBEB_SAMPLE_FLOAT32LE: ss->mBitsPerChannel = 32; ss->mFormatFlags = kAudioFormatFlagIsFloat; break; case CUBEB_SAMPLE_FLOAT32BE: ss->mBitsPerChannel = 32; ss->mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsBigEndian; break; default: return CUBEB_ERROR_INVALID_FORMAT; } ss->mFormatID = kAudioFormatLinearPCM; ss->mFormatFlags |= kLinearPCMFormatFlagIsPacked; ss->mSampleRate = stream_params->rate; ss->mChannelsPerFrame = stream_params->channels; ss->mBytesPerFrame = (ss->mBitsPerChannel / 8) * ss->mChannelsPerFrame; ss->mFramesPerPacket = 1; ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket; ss->mReserved = 0; return CUBEB_OK; } static int audiounit_create_unit(AudioUnit * unit, bool is_input, const cubeb_stream_params * /* stream_params */, cubeb_devid device) { AudioComponentDescription desc; AudioComponent comp; UInt32 enable; AudioDeviceID devid; OSStatus rv; desc.componentType = kAudioUnitType_Output; #if TARGET_OS_IPHONE bool use_default_output = false; desc.componentSubType = kAudioUnitSubType_RemoteIO; #else // Use the DefaultOutputUnit for output when no device is specified // so we retain automatic output device switching when the default // changes. Once we have complete support for device notifications // and switching, we can use the AUHAL for everything. bool use_default_output = device == NULL && !is_input; if (use_default_output) { desc.componentSubType = kAudioUnitSubType_DefaultOutput; } else { desc.componentSubType = kAudioUnitSubType_HALOutput; } #endif desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; comp = AudioComponentFindNext(NULL, &desc); if (comp == NULL) { LOG("Could not find matching audio hardware."); return CUBEB_ERROR; } rv = AudioComponentInstanceNew(comp, unit); if (rv != noErr) { PRINT_ERROR_CODE("AudioComponentInstanceNew", rv); return CUBEB_ERROR; } if (!use_default_output) { enable = 1; rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO, is_input ? kAudioUnitScope_Input : kAudioUnitScope_Output, is_input ? AU_IN_BUS : AU_OUT_BUS, &enable, sizeof(UInt32)); if (rv != noErr) { PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv); return CUBEB_ERROR; } enable = 0; rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO, is_input ? kAudioUnitScope_Output : kAudioUnitScope_Input, is_input ? AU_OUT_BUS : AU_IN_BUS, &enable, sizeof(UInt32)); if (rv != noErr) { PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO", rv); return CUBEB_ERROR; } if (device == NULL) { assert(is_input); devid = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); } else { devid = reinterpret_cast(device); } rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, is_input ? AU_IN_BUS : AU_OUT_BUS, &devid, sizeof(AudioDeviceID)); if (rv != noErr) { PRINT_ERROR_CODE("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice", rv); return CUBEB_ERROR; } } return CUBEB_OK; } static int audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity) { if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) { stream->input_linear_buffer = new auto_array_wrapper( new auto_array(capacity * stream->input_buffer_frames * stream->input_desc.mChannelsPerFrame) ); } else { stream->input_linear_buffer = new auto_array_wrapper( new auto_array(capacity * stream->input_buffer_frames * stream->input_desc.mChannelsPerFrame) ); } if (!stream->input_linear_buffer) { return CUBEB_ERROR; } assert(stream->input_linear_buffer->length() == 0); // Pre-buffer silence if needed if (capacity != 1) { size_t silence_size = stream->input_buffer_frames * stream->input_desc.mChannelsPerFrame; stream->input_linear_buffer->push_silence(silence_size); assert(stream->input_linear_buffer->length() == silence_size); } return CUBEB_OK; } static void audiounit_destroy_input_linear_buffer(cubeb_stream * stream) { delete stream->input_linear_buffer; } static uint32_t audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) { // For the 1st stream set anything within safe min-max assert(stm->context->active_streams > 0); if (stm->context->active_streams == 1) { return std::max(std::min(latency_frames, SAFE_MAX_LATENCY_FRAMES), SAFE_MIN_LATENCY_FRAMES); } // If more than one stream operates in parallel // allow only lower values of latency int r; UInt32 output_buffer_size = 0; UInt32 size = sizeof(output_buffer_size); if (stm->output_unit) { r = AudioUnitGetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Output, AU_OUT_BUS, &output_buffer_size, &size); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); return 0; } output_buffer_size = std::max(std::min(output_buffer_size, SAFE_MAX_LATENCY_FRAMES), SAFE_MIN_LATENCY_FRAMES); } UInt32 input_buffer_size = 0; if (stm->input_unit) { r = AudioUnitGetProperty(stm->input_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Input, AU_IN_BUS, &input_buffer_size, &size); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); return 0; } input_buffer_size = std::max(std::min(input_buffer_size, SAFE_MAX_LATENCY_FRAMES), SAFE_MIN_LATENCY_FRAMES); } // Every following active streams can only set smaller latency UInt32 upper_latency_limit = 0; if (input_buffer_size != 0 && output_buffer_size != 0) { upper_latency_limit = std::min(input_buffer_size, output_buffer_size); } else if (input_buffer_size != 0) { upper_latency_limit = input_buffer_size; } else if (output_buffer_size != 0) { upper_latency_limit = output_buffer_size; } else { upper_latency_limit = SAFE_MAX_LATENCY_FRAMES; } return std::max(std::min(latency_frames, upper_latency_limit), SAFE_MIN_LATENCY_FRAMES); } /* * Change buffer size is prone to deadlock thus we change it * following the steps: * - register a listener for the buffer size property * - change the property * - wait until the listener is executed * - property has changed, remove the listener * */ static void buffer_size_changed_callback(void * inClientData, AudioUnit inUnit, AudioUnitPropertyID inPropertyID, AudioUnitScope inScope, AudioUnitElement inElement) { cubeb_stream * stm = (cubeb_stream *)inClientData; AudioUnit au = inUnit; AudioUnitScope au_scope = kAudioUnitScope_Input; AudioUnitElement au_element = inElement; const char * au_type = "output"; if (au == stm->input_unit) { au_scope = kAudioUnitScope_Output; au_type = "input"; } switch (inPropertyID) { case kAudioDevicePropertyBufferFrameSize: { if (inScope != au_scope) { break; } UInt32 new_buffer_size; UInt32 outSize = sizeof(UInt32); OSStatus r = AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope, au_element, &new_buffer_size, &outSize); if (r != noErr) { LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm); } else { LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm, au_type, new_buffer_size, inScope); } stm->buffer_size_change_state = true; break; } } } enum set_buffer_size_side { INPUT, OUTPUT, }; static int audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side) { AudioUnit au = stm->output_unit; AudioUnitScope au_scope = kAudioUnitScope_Input; AudioUnitElement au_element = AU_OUT_BUS; const char * au_type = "output"; if (set_side == INPUT) { au = stm->input_unit; au_scope = kAudioUnitScope_Output; au_element = AU_IN_BUS; au_type = "input"; } uint32_t buffer_frames = 0; UInt32 size = sizeof(buffer_frames); int r = AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope, au_element, &buffer_frames, &size); if (r != noErr) { if (set_side == INPUT) { PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); } else { PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); } return CUBEB_ERROR; } if (new_size_frames == buffer_frames) { LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames); return CUBEB_OK; } r = AudioUnitAddPropertyListener(au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback, stm); if (r != noErr) { if (set_side == INPUT) { PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); } else { PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); } return CUBEB_ERROR; } stm->buffer_size_change_state = false; r = AudioUnitSetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope, au_element, &new_size_frames, sizeof(new_size_frames)); if (r != noErr) { if (set_side == INPUT) { PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r); } else { PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r); } r = AudioUnitRemovePropertyListenerWithUserData(au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback, stm); if (r != noErr) { if (set_side == INPUT) { PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); } else { PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); } } return CUBEB_ERROR; } int count = 0; while (!stm->buffer_size_change_state && count++ < 30) { struct timespec req, rem; req.tv_sec = 0; req.tv_nsec = 100000000L; // 0.1 sec if (nanosleep(&req , &rem) < 0 ) { LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec); } LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count); } r = AudioUnitRemovePropertyListenerWithUserData(au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback, stm); if (r != noErr) { if (set_side == INPUT) { PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); } else { PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); } return CUBEB_ERROR; } if (!stm->buffer_size_change_state && count >= 30) { LOG("(%p) Error, did not get buffer size change callback ...", stm); return CUBEB_ERROR; } LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames); return CUBEB_OK; } static int audiounit_configure_input(cubeb_stream * stm) { int r = 0; UInt32 size; AURenderCallbackStruct aurcbs_in; LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.", stm, stm->input_stream_params.rate, stm->input_stream_params.channels, stm->input_stream_params.format, stm->latency_frames); /* Get input device sample rate. */ AudioStreamBasicDescription input_hw_desc; size = sizeof(AudioStreamBasicDescription); r = AudioUnitGetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, AU_IN_BUS, &input_hw_desc, &size); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } stm->input_hw_rate = input_hw_desc.mSampleRate; LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate); /* Set format description according to the input params. */ r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params); if (r != CUBEB_OK) { LOG("(%p) Setting format description for input failed.", stm); return r; } // Use latency to set buffer size stm->input_buffer_frames = stm->latency_frames; r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT); if (r != CUBEB_OK) { LOG("(%p) Error in change input buffer size.", stm); return CUBEB_ERROR; } AudioStreamBasicDescription src_desc = stm->input_desc; /* Input AudioUnit must be configured with device's sample rate. we will resample inside input callback. */ src_desc.mSampleRate = stm->input_hw_rate; r = AudioUnitSetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, AU_IN_BUS, &src_desc, sizeof(AudioStreamBasicDescription)); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } /* Frames per buffer in the input callback. */ r = AudioUnitSetProperty(stm->input_unit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, AU_IN_BUS, &stm->input_buffer_frames, sizeof(UInt32)); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); return CUBEB_ERROR; } // Input only capacity unsigned int array_capacity = 1; if (has_output(stm)) { // Full-duplex increase capacity array_capacity = 8; } if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) { return CUBEB_ERROR; } assert(stm->input_unit != NULL); aurcbs_in.inputProc = audiounit_input_callback; aurcbs_in.inputProcRefCon = stm; r = AudioUnitSetProperty(stm->input_unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_in, sizeof(aurcbs_in)); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); return CUBEB_ERROR; } LOG("(%p) Input audiounit init successfully.", stm); return CUBEB_OK; } static int audiounit_configure_output(cubeb_stream * stm) { int r; AURenderCallbackStruct aurcbs_out; UInt32 size; LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.", stm, stm->output_stream_params.rate, stm->output_stream_params.channels, stm->output_stream_params.format, stm->latency_frames); r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params); if (r != CUBEB_OK) { LOG("(%p) Could not initialize the audio stream description.", stm); return r; } /* Get output device sample rate. */ AudioStreamBasicDescription output_hw_desc; size = sizeof(AudioStreamBasicDescription); memset(&output_hw_desc, 0, size); r = AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, AU_OUT_BUS, &output_hw_desc, &size); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } stm->output_hw_rate = output_hw_desc.mSampleRate; LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate); r = AudioUnitSetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, AU_OUT_BUS, &stm->output_desc, sizeof(AudioStreamBasicDescription)); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT); if (r != CUBEB_OK) { LOG("(%p) Error in change output buffer size.", stm); return CUBEB_ERROR; } /* Frames per buffer in the input callback. */ r = AudioUnitSetProperty(stm->output_unit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, AU_OUT_BUS, &stm->latency_frames, sizeof(UInt32)); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r); return CUBEB_ERROR; } assert(stm->output_unit != NULL); aurcbs_out.inputProc = audiounit_output_callback; aurcbs_out.inputProcRefCon = stm; r = AudioUnitSetProperty(stm->output_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_out, sizeof(aurcbs_out)); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); return CUBEB_ERROR; } LOG("(%p) Output audiounit init successfully.", stm); return CUBEB_OK; } static int audiounit_setup_stream(cubeb_stream * stm) { stm->mutex.assert_current_thread_owns(); int r = 0; if (has_input(stm)) { r = audiounit_create_unit(&stm->input_unit, true, &stm->input_stream_params, stm->input_device); if (r != CUBEB_OK) { LOG("(%p) AudioUnit creation for input failed.", stm); return r; } } if (has_output(stm)) { r = audiounit_create_unit(&stm->output_unit, false, &stm->output_stream_params, stm->output_device); if (r != CUBEB_OK) { LOG("(%p) AudioUnit creation for output failed.", stm); return r; } } /* Latency cannot change if another stream is operating in parallel. In this case * latecy is set to the other stream value. */ if (stm->context->active_streams > 1) { LOG("(%p) More than one active stream, use global latency.", stm); stm->latency_frames = stm->context->global_latency_frames; } else { /* Silently clamp the latency down to the platform default, because we * synthetize the clock from the callbacks, and we want the clock to update * often. */ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames); assert(stm->latency_frames); // Ungly error check audiounit_set_global_latency(stm, stm->latency_frames); } /* Setup Input Stream! */ if (has_input(stm)) { r = audiounit_configure_input(stm); if (r != CUBEB_OK) { LOG("(%p) Configure audiounit input failed.", stm); return r; } } /* Setup Output Stream! */ if (has_output(stm)) { r = audiounit_configure_output(stm); if (r != CUBEB_OK) { LOG("(%p) Configure audiounit output failed.", stm); return r; } } // Setting the latency doesn't work well for USB headsets (eg. plantronics). // Keep the default latency for now. #if 0 buffer_size = latency; /* Get the range of latency this particular device can work with, and clamp * the requested latency to this acceptable range. */ #if !TARGET_OS_IPHONE if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { return CUBEB_ERROR; } if (buffer_size < (unsigned int) latency_range.mMinimum) { buffer_size = (unsigned int) latency_range.mMinimum; } else if (buffer_size > (unsigned int) latency_range.mMaximum) { buffer_size = (unsigned int) latency_range.mMaximum; } /** * Get the default buffer size. If our latency request is below the default, * set it. Otherwise, use the default latency. **/ size = sizeof(default_buffer_size); if (AudioUnitGetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Output, 0, &default_buffer_size, &size) != 0) { return CUBEB_ERROR; } if (buffer_size < default_buffer_size) { /* Set the maximum number of frame that the render callback will ask for, * effectively setting the latency of the stream. This is process-wide. */ if (AudioUnitSetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size)) != 0) { return CUBEB_ERROR; } } #else // TARGET_OS_IPHONE //TODO: [[AVAudioSession sharedInstance] inputLatency] // http://stackoverflow.com/questions/13157523/kaudiodevicepropertybufferframesize-replacement-for-ios #endif #endif /* We use a resampler because input AudioUnit operates * reliable only in the capture device sample rate. * Resampler will convert it to the user sample rate * and deliver it to the callback. */ uint32_t target_sample_rate; if (has_input(stm)) { target_sample_rate = stm->input_stream_params.rate; } else { assert(has_output(stm)); target_sample_rate = stm->output_stream_params.rate; } cubeb_stream_params input_unconverted_params; if (has_input(stm)) { input_unconverted_params = stm->input_stream_params; /* Use the rate of the input device. */ input_unconverted_params.rate = stm->input_hw_rate; } /* Create resampler. Output params are unchanged * because we do not need conversion on the output. */ stm->resampler = cubeb_resampler_create(stm, has_input(stm) ? &input_unconverted_params : NULL, has_output(stm) ? &stm->output_stream_params : NULL, target_sample_rate, stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP); if (!stm->resampler) { LOG("(%p) Could not create resampler.", stm); return CUBEB_ERROR; } if (stm->input_unit != NULL) { r = AudioUnitInitialize(stm->input_unit); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitInitialize/input", r); return CUBEB_ERROR; } } if (stm->output_unit != NULL) { r = AudioUnitInitialize(stm->output_unit); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitInitialize/output", r); return CUBEB_ERROR; } } if (stm->input_unit && stm->output_unit) { // According to the I/O hardware rate it is expected a specific pattern of callbacks // for example is input is 44100 and output is 48000 we expected no more than 2 // out callback in a row. stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate); } r = audiounit_install_device_changed_callback(stm); if (r != CUBEB_OK) { LOG("(%p) Could not install the device change callback.", stm); return r; } return CUBEB_OK; } static int audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * /* stream_name */, cubeb_devid input_device, cubeb_stream_params * input_stream_params, cubeb_devid output_device, cubeb_stream_params * output_stream_params, unsigned int latency_frames, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr) { cubeb_stream * stm; int r; assert(context); *stream = NULL; assert(latency_frames > 0); if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) { LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX); return CUBEB_ERROR; } stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream)); assert(stm); // Placement new to call the ctors of cubeb_stream members. new (stm) cubeb_stream(); /* These could be different in the future if we have both * full-duplex stream and different devices for input vs output. */ stm->context = context; stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; stm->latency_frames = latency_frames; stm->device_changed_callback = NULL; if (input_stream_params) { stm->input_stream_params = *input_stream_params; stm->input_device = input_device; stm->is_default_input = input_device == nullptr || (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == reinterpret_cast(input_device)); } if (output_stream_params) { stm->output_stream_params = *output_stream_params; stm->output_device = output_device; } /* Init data members where necessary */ stm->hw_latency_frames = UINT64_MAX; stm->switching_device = false; auto_lock context_lock(context->mutex); { // It's not critical to lock here, because no other thread has been started // yet, but it allows to assert that the lock has been taken in // `audiounit_setup_stream`. context->active_streams += 1; auto_lock lock(stm->mutex); r = audiounit_setup_stream(stm); } if (r != CUBEB_OK) { LOG("(%p) Could not setup the audiounit stream.", stm); audiounit_stream_destroy(stm); return r; } r = audiounit_install_system_changed_callback(stm); if (r != CUBEB_OK) { LOG("(%p) Could not install the device change callback.", stm); return r; } *stream = stm; LOG("Cubeb stream (%p) init successful.", stm); return CUBEB_OK; } static void audiounit_close_stream(cubeb_stream *stm) { stm->mutex.assert_current_thread_owns(); if (stm->input_unit) { AudioUnitUninitialize(stm->input_unit); AudioComponentInstanceDispose(stm->input_unit); } audiounit_destroy_input_linear_buffer(stm); if (stm->output_unit) { AudioUnitUninitialize(stm->output_unit); AudioComponentInstanceDispose(stm->output_unit); } cubeb_resampler_destroy(stm->resampler); } static void audiounit_stream_destroy(cubeb_stream * stm) { stm->shutdown = true; int r = audiounit_uninstall_system_changed_callback(stm); if (r != CUBEB_OK) { LOG("(%p) Could not uninstall the device changed callback", stm); } r = audiounit_uninstall_device_changed_callback(stm); if (r != CUBEB_OK) { LOG("(%p) Could not uninstall the device changed callback", stm); } auto_lock context_lock(stm->context->mutex); audiounit_stream_stop_internal(stm); // Execute close in serial queue to avoid collision // with reinit when un/plug devices dispatch_sync(stm->context->serial_queue, ^() { auto_lock lock(stm->mutex); audiounit_close_stream(stm); }); assert(stm->context->active_streams >= 1); stm->context->active_streams -= 1; LOG("Cubeb stream (%p) destroyed successful.", stm); stm->~cubeb_stream(); free(stm); } void audiounit_stream_start_internal(cubeb_stream * stm) { OSStatus r; if (stm->input_unit != NULL) { r = AudioOutputUnitStart(stm->input_unit); assert(r == 0); } if (stm->output_unit != NULL) { r = AudioOutputUnitStart(stm->output_unit); assert(r == 0); } } static int audiounit_stream_start(cubeb_stream * stm) { auto_lock context_lock(stm->context->mutex); stm->shutdown = false; stm->draining = false; audiounit_stream_start_internal(stm); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); LOG("Cubeb stream (%p) started successfully.", stm); return CUBEB_OK; } void audiounit_stream_stop_internal(cubeb_stream * stm) { OSStatus r; if (stm->input_unit != NULL) { r = AudioOutputUnitStop(stm->input_unit); assert(r == 0); } if (stm->output_unit != NULL) { r = AudioOutputUnitStop(stm->output_unit); assert(r == 0); } } static int audiounit_stream_stop(cubeb_stream * stm) { auto_lock context_lock(stm->context->mutex); stm->shutdown = true; audiounit_stream_stop_internal(stm); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); LOG("Cubeb stream (%p) stopped successfully.", stm); return CUBEB_OK; } static int audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) { auto_lock lock(stm->mutex); *position = stm->frames_played; return CUBEB_OK; } int audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) { #if TARGET_OS_IPHONE //TODO return CUBEB_ERROR_NOT_SUPPORTED; #else auto_lock lock(stm->mutex); if (stm->hw_latency_frames == UINT64_MAX) { UInt32 size; uint32_t device_latency_frames, device_safety_offset; double unit_latency_sec; AudioDeviceID output_device_id; OSStatus r; AudioObjectPropertyAddress latency_address = { kAudioDevicePropertyLatency, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; AudioObjectPropertyAddress safety_offset_address = { kAudioDevicePropertySafetyOffset, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; r = audiounit_get_output_device_id(&output_device_id); if (r != noErr) { return CUBEB_ERROR; } size = sizeof(unit_latency_sec); r = AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &unit_latency_sec, &size); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitGetProperty/kAudioUnitProperty_Latency", r); return CUBEB_ERROR; } size = sizeof(device_latency_frames); r = AudioObjectGetPropertyData(output_device_id, &latency_address, 0, NULL, &size, &device_latency_frames); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitGetPropertyData/latency_frames", r); return CUBEB_ERROR; } size = sizeof(device_safety_offset); r = AudioObjectGetPropertyData(output_device_id, &safety_offset_address, 0, NULL, &size, &device_safety_offset); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitGetPropertyData/safety_offset", r); return CUBEB_ERROR; } /* This part is fixed and depend on the stream parameter and the hardware. */ stm->hw_latency_frames = static_cast(unit_latency_sec * stm->output_desc.mSampleRate) + device_latency_frames + device_safety_offset; } *latency = stm->hw_latency_frames + stm->current_latency_frames; return CUBEB_OK; #endif } static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume) { assert(stm->output_unit); OSStatus r = AudioUnitGetParameter(stm->output_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, volume); if (r != noErr) { LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r); return CUBEB_ERROR; } return CUBEB_OK; } int audiounit_stream_set_volume(cubeb_stream * stm, float volume) { OSStatus r; r = AudioUnitSetParameter(stm->output_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, volume, 0); if (r != noErr) { PRINT_ERROR_CODE("AudioUnitSetParameter/kHALOutputParam_Volume", r); return CUBEB_ERROR; } return CUBEB_OK; } int audiounit_stream_set_panning(cubeb_stream * stm, float panning) { if (stm->output_desc.mChannelsPerFrame > 2) { return CUBEB_ERROR_INVALID_PARAMETER; } stm->panning.store(panning, std::memory_order_relaxed); return CUBEB_OK; } int audiounit_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) { #if TARGET_OS_IPHONE //TODO return CUBEB_ERROR_NOT_SUPPORTED; #else OSStatus r; UInt32 size; UInt32 data; char strdata[4]; AudioDeviceID output_device_id; AudioDeviceID input_device_id; AudioObjectPropertyAddress datasource_address = { kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, kAudioObjectPropertyElementMaster }; AudioObjectPropertyAddress datasource_address_input = { kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput, kAudioObjectPropertyElementMaster }; *device = NULL; if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { return CUBEB_ERROR; } *device = new cubeb_device; if (!*device) { return CUBEB_ERROR; } PodZero(*device, 1); size = sizeof(UInt32); /* This fails with some USB headset, so simply return an empty string. */ r = AudioObjectGetPropertyData(output_device_id, &datasource_address, 0, NULL, &size, &data); if (r != noErr) { size = 0; data = 0; } (*device)->output_name = new char[size + 1]; if (!(*device)->output_name) { return CUBEB_ERROR; } // Turn the four chars packed into a uint32 into a string strdata[0] = (char)(data >> 24); strdata[1] = (char)(data >> 16); strdata[2] = (char)(data >> 8); strdata[3] = (char)(data); memcpy((*device)->output_name, strdata, size); (*device)->output_name[size] = '\0'; if (audiounit_get_input_device_id(&input_device_id) != CUBEB_OK) { return CUBEB_ERROR; } size = sizeof(UInt32); r = AudioObjectGetPropertyData(input_device_id, &datasource_address_input, 0, NULL, &size, &data); if (r != noErr) { LOG("(%p) Error when getting device !", stm); size = 0; data = 0; } (*device)->input_name = new char[size + 1]; if (!(*device)->input_name) { return CUBEB_ERROR; } // Turn the four chars packed into a uint32 into a string strdata[0] = (char)(data >> 24); strdata[1] = (char)(data >> 16); strdata[2] = (char)(data >> 8); strdata[3] = (char)(data); memcpy((*device)->input_name, strdata, size); (*device)->input_name[size] = '\0'; return CUBEB_OK; #endif } int audiounit_stream_device_destroy(cubeb_stream * /* stream */, cubeb_device * device) { delete [] device->output_name; delete [] device->input_name; delete device; return CUBEB_OK; } int audiounit_stream_register_device_changed_callback(cubeb_stream * stream, cubeb_device_changed_callback device_changed_callback) { /* Note: second register without unregister first causes 'nope' error. * Current implementation requires unregister before register a new cb. */ assert(!stream->device_changed_callback); auto_lock lock(stream->mutex); stream->device_changed_callback = device_changed_callback; return CUBEB_OK; } static OSStatus audiounit_get_devices(AudioObjectID ** devices, uint32_t * count) { OSStatus ret; UInt32 size = 0; AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); if (ret != noErr) { return ret; } *count = static_cast(size / sizeof(AudioObjectID)); if (size >= sizeof(AudioObjectID)) { if (*devices != NULL) { delete [] (*devices); } *devices = new AudioObjectID[*count]; PodZero(*devices, *count); ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, (void *)*devices); if (ret != noErr) { delete [] (*devices); *devices = NULL; } } else { *devices = NULL; ret = -1; } return ret; } static char * audiounit_strref_to_cstr_utf8(CFStringRef strref) { CFIndex len, size; char * ret; if (strref == NULL) { return NULL; } len = CFStringGetLength(strref); size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8); ret = static_cast(malloc(size)); if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) { free(ret); ret = NULL; } return ret; } static uint32_t audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope) { AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster }; UInt32 size = 0; uint32_t i, ret = 0; adr.mSelector = kAudioDevicePropertyStreamConfiguration; if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr && size > 0) { AudioBufferList * list = static_cast(alloca(size)); if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) == noErr) { for (i = 0; i < list->mNumberBuffers; i++) ret += list->mBuffers[i].mNumberChannels; } } return ret; } static void audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope, uint32_t * min, uint32_t * max, uint32_t * def) { AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster }; adr.mSelector = kAudioDevicePropertyNominalSampleRate; if (AudioObjectHasProperty(devid, &adr)) { UInt32 size = sizeof(Float64); Float64 fvalue = 0.0; if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &fvalue) == noErr) { *def = fvalue; } } adr.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; UInt32 size = 0; AudioValueRange range; if (AudioObjectHasProperty(devid, &adr) && AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) { uint32_t i, count = size / sizeof(AudioValueRange); AudioValueRange * ranges = new AudioValueRange[count]; range.mMinimum = 9999999999.0; range.mMaximum = 0.0; if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges) == noErr) { for (i = 0; i < count; i++) { if (ranges[i].mMaximum > range.mMaximum) range.mMaximum = ranges[i].mMaximum; if (ranges[i].mMinimum < range.mMinimum) range.mMinimum = ranges[i].mMinimum; } } delete [] ranges; *max = static_cast(range.mMaximum); *min = static_cast(range.mMinimum); } else { *min = *max = 0; } } static UInt32 audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope) { AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster }; UInt32 size, dev, stream = 0, offset; AudioStreamID sid[1]; adr.mSelector = kAudioDevicePropertyLatency; size = sizeof(UInt32); if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &dev) != noErr) { dev = 0; } adr.mSelector = kAudioDevicePropertyStreams; size = sizeof(sid); if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, sid) == noErr) { adr.mSelector = kAudioStreamPropertyLatency; size = sizeof(UInt32); AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream); } adr.mSelector = kAudioDevicePropertySafetyOffset; size = sizeof(UInt32); if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &offset) != noErr) { offset = 0; } return dev + stream + offset; } static cubeb_device_info * audiounit_create_device_from_hwdev(AudioObjectID devid, cubeb_device_type type) { AudioObjectPropertyAddress adr = { 0, 0, kAudioObjectPropertyElementMaster }; UInt32 size, ch, latency; cubeb_device_info * ret; CFStringRef str = NULL; AudioValueRange range; if (type == CUBEB_DEVICE_TYPE_OUTPUT) { adr.mScope = kAudioDevicePropertyScopeOutput; } else if (type == CUBEB_DEVICE_TYPE_INPUT) { adr.mScope = kAudioDevicePropertyScopeInput; } else { return NULL; } ch = audiounit_get_channel_count(devid, adr.mScope); if (ch == 0) { return NULL; } ret = new cubeb_device_info; PodZero(ret, 1); size = sizeof(CFStringRef); adr.mSelector = kAudioDevicePropertyDeviceUID; if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { ret->device_id = audiounit_strref_to_cstr_utf8(str); ret->devid = (cubeb_devid)(size_t)devid; ret->group_id = strdup(ret->device_id); CFRelease(str); } size = sizeof(CFStringRef); adr.mSelector = kAudioObjectPropertyName; if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { UInt32 ds; size = sizeof(UInt32); adr.mSelector = kAudioDevicePropertyDataSource; if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds) == noErr) { CFStringRef dsname; AudioValueTranslation trl = { &ds, sizeof(ds), &dsname, sizeof(dsname) }; adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString; size = sizeof(AudioValueTranslation); // If there is a datasource for this device, use it instead of the device // name. if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl) == noErr) { CFRelease(str); str = dsname; } } ret->friendly_name = audiounit_strref_to_cstr_utf8(str); CFRelease(str); } size = sizeof(CFStringRef); adr.mSelector = kAudioObjectPropertyManufacturer; if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &str) == noErr && str != NULL) { ret->vendor_name = audiounit_strref_to_cstr_utf8(str); CFRelease(str); } ret->type = type; ret->state = CUBEB_DEVICE_STATE_ENABLED; ret->preferred = (devid == audiounit_get_default_device_id(type)) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; ret->max_channels = ch; ret->format = (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */ /* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */ ret->default_format = CUBEB_DEVICE_FMT_F32NE; audiounit_get_available_samplerate(devid, adr.mScope, &ret->min_rate, &ret->max_rate, &ret->default_rate); latency = audiounit_get_device_presentation_latency(devid, adr.mScope); adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange; size = sizeof(AudioValueRange); if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range) == noErr) { ret->latency_lo = latency + range.mMinimum; ret->latency_hi = latency + range.mMaximum; } else { ret->latency_lo = 10 * ret->default_rate / 1000; /* Default to 10ms */ ret->latency_hi = 100 * ret->default_rate / 1000; /* Default to 100ms */ } return ret; } static int audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type, cubeb_device_collection ** collection) { AudioObjectID * hwdevs = NULL; uint32_t i, hwdevcount = 0; OSStatus err; if ((err = audiounit_get_devices(&hwdevs, &hwdevcount)) != noErr) { return CUBEB_ERROR; } *collection = static_cast(malloc(sizeof(cubeb_device_collection) + sizeof(cubeb_device_info*) * (hwdevcount > 0 ? hwdevcount - 1 : 0))); (*collection)->count = 0; if (hwdevcount > 0) { cubeb_device_info * cur; if (type & CUBEB_DEVICE_TYPE_OUTPUT) { for (i = 0; i < hwdevcount; i++) { if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_OUTPUT)) != NULL) (*collection)->device[(*collection)->count++] = cur; } } if (type & CUBEB_DEVICE_TYPE_INPUT) { for (i = 0; i < hwdevcount; i++) { if ((cur = audiounit_create_device_from_hwdev(hwdevs[i], CUBEB_DEVICE_TYPE_INPUT)) != NULL) (*collection)->device[(*collection)->count++] = cur; } } } delete [] hwdevs; return CUBEB_OK; } /* qsort compare method. */ int compare_devid(const void * a, const void * b) { return (*(AudioObjectID*)a - *(AudioObjectID*)b); } static uint32_t audiounit_get_devices_of_type(cubeb_device_type devtype, AudioObjectID ** devid_array) { assert(devid_array == NULL || *devid_array == NULL); AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; UInt32 size = 0; OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); if (ret != noErr) { return 0; } /* Total number of input and output devices. */ uint32_t count = (uint32_t)(size / sizeof(AudioObjectID)); AudioObjectID devices[count]; ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devices); if (ret != noErr) { return 0; } /* Expected sorted but did not find anything in the docs. */ qsort(devices, count, sizeof(AudioObjectID), compare_devid); if (devtype == (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) { if (devid_array) { *devid_array = new AudioObjectID[count]; assert(*devid_array); memcpy(*devid_array, &devices, count * sizeof(AudioObjectID)); } return count; } AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT) ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; uint32_t dev_count = 0; AudioObjectID devices_in_scope[count]; for(uint32_t i = 0; i < count; ++i) { /* For device in the given scope channel must be > 0. */ if (audiounit_get_channel_count(devices[i], scope) > 0) { devices_in_scope[dev_count] = devices[i]; ++dev_count; } } if (devid_array && dev_count > 0) { *devid_array = new AudioObjectID[dev_count]; assert(*devid_array); memcpy(*devid_array, &devices_in_scope, dev_count * sizeof(AudioObjectID)); } return dev_count; } static uint32_t audiounit_equal_arrays(AudioObjectID * left, AudioObjectID * right, uint32_t size) { /* Expected sorted arrays. */ for (uint32_t i = 0; i < size; ++i) { if (left[i] != right[i]) { return 0; } } return 1; } static OSStatus audiounit_collection_changed_callback(AudioObjectID /* inObjectID */, UInt32 /* inNumberAddresses */, const AudioObjectPropertyAddress * /* inAddresses */, void * inClientData) { cubeb * context = static_cast(inClientData); auto_lock lock(context->mutex); if (context->collection_changed_callback == NULL) { /* Listener removed while waiting in mutex, abort. */ return noErr; } /* Differentiate input from output changes. */ if (context->collection_changed_devtype == CUBEB_DEVICE_TYPE_INPUT || context->collection_changed_devtype == CUBEB_DEVICE_TYPE_OUTPUT) { AudioObjectID * devices = NULL; uint32_t new_number_of_devices = audiounit_get_devices_of_type(context->collection_changed_devtype, &devices); /* When count is the same examine the devid for the case of coalescing. */ if (context->devtype_device_count == new_number_of_devices && audiounit_equal_arrays(devices, context->devtype_device_array, new_number_of_devices)) { /* Device changed for the other scope, ignore. */ delete [] devices; return noErr; } /* Device on desired scope changed, reset counter and array. */ context->devtype_device_count = new_number_of_devices; /* Free the old array before replace. */ delete [] context->devtype_device_array; context->devtype_device_array = devices; } context->collection_changed_callback(context, context->collection_changed_user_ptr); return noErr; } static OSStatus audiounit_add_device_listener(cubeb * context, cubeb_device_type devtype, cubeb_device_collection_changed_callback collection_changed_callback, void * user_ptr) { /* Note: second register without unregister first causes 'nope' error. * Current implementation requires unregister before register a new cb. */ assert(context->collection_changed_callback == NULL); AudioObjectPropertyAddress devAddr; devAddr.mSelector = kAudioHardwarePropertyDevices; devAddr.mScope = kAudioObjectPropertyScopeGlobal; devAddr.mElement = kAudioObjectPropertyElementMaster; OSStatus ret = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devAddr, audiounit_collection_changed_callback, context); if (ret == noErr) { /* Expected zero after unregister. */ assert(context->devtype_device_count == 0); assert(context->devtype_device_array == NULL); /* Listener works for input and output. * When requested one of them we need to differentiate. */ if (devtype == CUBEB_DEVICE_TYPE_INPUT || devtype == CUBEB_DEVICE_TYPE_OUTPUT) { /* Used to differentiate input from output device changes. */ context->devtype_device_count = audiounit_get_devices_of_type(devtype, &context->devtype_device_array); } context->collection_changed_devtype = devtype; context->collection_changed_callback = collection_changed_callback; context->collection_changed_user_ptr = user_ptr; } return ret; } static OSStatus audiounit_remove_device_listener(cubeb * context) { AudioObjectPropertyAddress devAddr; devAddr.mSelector = kAudioHardwarePropertyDevices; devAddr.mScope = kAudioObjectPropertyScopeGlobal; devAddr.mElement = kAudioObjectPropertyElementMaster; /* Note: unregister a non registered cb is not a problem, not checking. */ OSStatus ret = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devAddr, audiounit_collection_changed_callback, context); if (ret == noErr) { /* Reset all values. */ context->collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN; context->collection_changed_callback = NULL; context->collection_changed_user_ptr = NULL; context->devtype_device_count = 0; if (context->devtype_device_array) { delete [] context->devtype_device_array; context->devtype_device_array = NULL; } } return ret; } int audiounit_register_device_collection_changed(cubeb * context, cubeb_device_type devtype, cubeb_device_collection_changed_callback collection_changed_callback, void * user_ptr) { OSStatus ret; auto_lock lock(context->mutex); if (collection_changed_callback) { ret = audiounit_add_device_listener(context, devtype, collection_changed_callback, user_ptr); } else { ret = audiounit_remove_device_listener(context); } return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR; } cubeb_ops const audiounit_ops = { /*.init =*/ audiounit_init, /*.get_backend_id =*/ audiounit_get_backend_id, /*.get_max_channel_count =*/ audiounit_get_max_channel_count, /*.get_min_latency =*/ audiounit_get_min_latency, /*.get_preferred_sample_rate =*/ audiounit_get_preferred_sample_rate, /*.enumerate_devices =*/ audiounit_enumerate_devices, /*.destroy =*/ audiounit_destroy, /*.stream_init =*/ audiounit_stream_init, /*.stream_destroy =*/ audiounit_stream_destroy, /*.stream_start =*/ audiounit_stream_start, /*.stream_stop =*/ audiounit_stream_stop, /*.stream_get_position =*/ audiounit_stream_get_position, /*.stream_get_latency =*/ audiounit_stream_get_latency, /*.stream_set_volume =*/ audiounit_stream_set_volume, /*.stream_set_panning =*/ audiounit_stream_set_panning, /*.stream_get_current_device =*/ audiounit_stream_get_current_device, /*.stream_device_destroy =*/ audiounit_stream_device_destroy, /*.stream_register_device_changed_callback =*/ audiounit_stream_register_device_changed_callback, /*.register_device_collection_changed =*/ audiounit_register_device_collection_changed };