diff options
Diffstat (limited to 'media/libcubeb/src/cubeb_audiounit.cpp')
-rw-r--r-- | media/libcubeb/src/cubeb_audiounit.cpp | 3642 |
1 files changed, 1353 insertions, 2289 deletions
diff --git a/media/libcubeb/src/cubeb_audiounit.cpp b/media/libcubeb/src/cubeb_audiounit.cpp index 02cd134697..9483c2795e 100644 --- a/media/libcubeb/src/cubeb_audiounit.cpp +++ b/media/libcubeb/src/cubeb_audiounit.cpp @@ -6,387 +6,236 @@ */ #undef NDEBUG -#include <AudioUnit/AudioUnit.h> #include <TargetConditionals.h> #include <assert.h> #include <mach/mach_time.h> #include <pthread.h> #include <stdlib.h> +#include <AudioUnit/AudioUnit.h> #if !TARGET_OS_IPHONE #include <AvailabilityMacros.h> #include <CoreAudio/AudioHardware.h> #include <CoreAudio/HostTime.h> #include <CoreFoundation/CoreFoundation.h> #endif -#include "cubeb-internal.h" -#include "cubeb/cubeb.h" -#include "cubeb_mixer.h" -#include <AudioToolbox/AudioToolbox.h> #include <CoreAudio/CoreAudioTypes.h> +#include <AudioToolbox/AudioToolbox.h> +#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 <algorithm> #include <atomic> -#include <set> -#include <string> -#include <sys/time.h> -#include <vector> -using namespace std; +#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; +typedef UInt32 AudioFormatFlags; #endif -#define AU_OUT_BUS 0 -#define AU_IN_BUS 1 +#define CUBEB_STREAM_MAX 8 -const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb"; -const char * PRIVATE_AGGREGATE_DEVICE_NAME = "CubebAggregateDevice"; +#define AU_OUT_BUS 0 +#define AU_IN_BUS 1 -#ifdef ALOGV -#undef ALOGV -#endif -#define ALOGV(msg, ...) \ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \ - ^{ \ - LOGV(msg, ##__VA_ARGS__); \ - }) - -#ifdef ALOG -#undef ALOG -#endif -#define ALOG(msg, ...) \ - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \ - ^{ \ - LOG(msg, ##__VA_ARGS__); \ - }) +#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 = 128; +const uint32_t SAFE_MIN_LATENCY_FRAMES = 256; const uint32_t SAFE_MAX_LATENCY_FRAMES = 512; -const AudioObjectPropertyAddress DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS = { - kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - -const AudioObjectPropertyAddress DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS = { - kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - -const AudioObjectPropertyAddress DEVICE_IS_ALIVE_PROPERTY_ADDRESS = { - kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - -const AudioObjectPropertyAddress DEVICES_PROPERTY_ADDRESS = { - kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - -const AudioObjectPropertyAddress INPUT_DATA_SOURCE_PROPERTY_ADDRESS = { - kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput, - kAudioObjectPropertyElementMaster}; - -const AudioObjectPropertyAddress OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS = { - kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster}; - -typedef uint32_t device_flags_value; - -enum device_flags { - DEV_UNKNOWN = 0x00, /* Unknown */ - DEV_INPUT = 0x01, /* Record device like mic */ - DEV_OUTPUT = 0x02, /* Playback device like speakers */ - DEV_SYSTEM_DEFAULT = 0x04, /* System default device */ - DEV_SELECTED_DEFAULT = - 0x08, /* User selected to use the system default device */ -}; - -void -audiounit_stream_stop_internal(cubeb_stream * stm); -static int -audiounit_stream_start_internal(cubeb_stream * stm); -static void -audiounit_close_stream(cubeb_stream * stm); -static int -audiounit_setup_stream(cubeb_stream * stm); -static vector<AudioObjectID> -audiounit_get_devices_of_type(cubeb_device_type devtype); -static UInt32 -audiounit_get_device_presentation_latency(AudioObjectID devid, - AudioObjectPropertyScope scope); - -#if !TARGET_OS_IPHONE -static AudioObjectID -audiounit_get_default_device_id(cubeb_device_type type); -static int -audiounit_uninstall_device_changed_callback(cubeb_stream * stm); -static int -audiounit_uninstall_system_changed_callback(cubeb_stream * stm); -static void -audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags); -#endif +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 = &audiounit_ops; + cubeb_ops const * ops; owned_critical_section mutex; - int active_streams = 0; + std::atomic<int> active_streams; uint32_t global_latency_frames = 0; - cubeb_device_collection_changed_callback input_collection_changed_callback = - nullptr; - void * input_collection_changed_user_ptr = nullptr; - cubeb_device_collection_changed_callback output_collection_changed_callback = - nullptr; - void * output_collection_changed_user_ptr = nullptr; - // Store list of devices to detect changes - vector<AudioObjectID> input_device_array; - vector<AudioObjectID> output_device_array; - // The queue should be released when it’s no longer needed. - dispatch_queue_t serial_queue = - dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL); - // Current used channel layout - atomic<cubeb_channel_layout> layout{CUBEB_LAYOUT_UNDEFINED}; - uint32_t channels = 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); }; -static unique_ptr<AudioChannelLayout, decltype(&free)> -make_sized_audio_channel_layout(size_t sz) +class auto_array_wrapper { - assert(sz >= sizeof(AudioChannelLayout)); - AudioChannelLayout * acl = - reinterpret_cast<AudioChannelLayout *>(calloc(1, sz)); - assert(acl); // Assert the allocation works. - return unique_ptr<AudioChannelLayout, decltype(&free)>(acl, free); -} +public: + explicit auto_array_wrapper(auto_array<float> * ar) + : float_ar(ar) + , short_ar(nullptr) + {assert((float_ar && !short_ar) || (!float_ar && short_ar));} -enum class io_side { - INPUT, - OUTPUT, -}; + explicit auto_array_wrapper(auto_array<short> * ar) + : float_ar(nullptr) + , short_ar(ar) + {assert((float_ar && !short_ar) || (!float_ar && short_ar));} -static char const * -to_string(io_side side) -{ - switch (side) { - case io_side::INPUT: - return "input"; - case io_side::OUTPUT: - return "output"; + ~auto_array_wrapper() { + auto_lock l(lock); + assert((float_ar && !short_ar) || (!float_ar && short_ar)); + delete float_ar; + delete short_ar; } -} -struct device_info { - AudioDeviceID id = kAudioObjectUnknown; - device_flags_value flags = DEV_UNKNOWN; -}; + 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<float*>(elements), length); + return short_ar->push(static_cast<short*>(elements), length); + } -struct property_listener { - AudioDeviceID device_id; - const AudioObjectPropertyAddress * property_address; - AudioObjectPropertyListenerProc callback; - cubeb_stream * stream; + 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(); + } - property_listener(AudioDeviceID id, - const AudioObjectPropertyAddress * address, - AudioObjectPropertyListenerProc proc, cubeb_stream * stm) - : device_id(id), property_address(address), callback(proc), stream(stm) - { + 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<float*>(elements), length); + return short_ar->pop(static_cast<short*>(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> * float_ar; + auto_array<short> * short_ar; + owned_critical_section lock; }; struct cubeb_stream { - explicit cubeb_stream(cubeb * context); - - /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; - void * user_ptr = nullptr; - /**/ - - cubeb_data_callback data_callback = nullptr; - cubeb_state_callback state_callback = nullptr; - cubeb_device_changed_callback device_changed_callback = nullptr; - owned_critical_section device_changed_callback_lock; + 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_SAMPLE_FLOAT32NE, 0, 0, - CUBEB_LAYOUT_UNDEFINED, - CUBEB_STREAM_PREF_NONE}; - cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0, - CUBEB_LAYOUT_UNDEFINED, - CUBEB_STREAM_PREF_NONE}; - device_info input_device; - device_info output_device; + 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 = nullptr; - AudioUnit output_unit = nullptr; + AudioUnit input_unit; + AudioUnit output_unit; /* I/O device sample rate */ - Float64 input_hw_rate = 0; - Float64 output_hw_rate = 0; + 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 = 0; + int expected_output_callbacks_in_a_row; owned_critical_section mutex; - // Hold the input samples in every input callback iteration. - // Only accessed on input/output callback thread and during initial configure. - unique_ptr<auto_array_wrapper> input_linear_buffer; + /* Hold the input samples in every + * input callback iteration */ + auto_array_wrapper * input_linear_buffer; + /* Frames on input buffer */ + std::atomic<uint32_t> input_buffer_frames; /* Frame counters */ - atomic<uint64_t> frames_played{0}; - uint64_t frames_queued = 0; - // How many frames got read from the input since the stream started (includes - // padded silence) - atomic<int64_t> frames_read{0}; - // How many frames got written to the output device since the stream started - atomic<int64_t> frames_written{0}; - atomic<bool> shutdown{true}; - atomic<bool> draining{false}; - atomic<bool> reinit_pending{false}; - atomic<bool> destroy_pending{false}; + uint64_t frames_played; + uint64_t frames_queued; + std::atomic<int64_t> frames_read; + std::atomic<bool> shutdown; + std::atomic<bool> draining; /* Latency requested by the user. */ - uint32_t latency_frames = 0; - atomic<uint32_t> current_latency_frames{0}; - atomic<uint32_t> total_output_latency_frames{0}; - unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler; + uint32_t latency_frames; + std::atomic<uint64_t> current_latency_frames; + uint64_t hw_latency_frames; + std::atomic<float> 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<int> output_callback_in_a_row; /* This is true if a device change callback is currently running. */ - atomic<bool> switching_device{false}; - atomic<bool> buffer_size_change_state{false}; - AudioDeviceID aggregate_device_id = - kAudioObjectUnknown; // the aggregate device id - AudioObjectID plugin_id = - kAudioObjectUnknown; // used to create aggregate device - /* Mixer interface */ - unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> mixer; - /* Buffer where remixing/resampling will occur when upmixing is required */ - /* Only accessed from callback thread */ - unique_ptr<uint8_t[]> temp_buffer; - size_t temp_buffer_size = 0; // size in bytes. - /* Listeners indicating what system events are monitored. */ - unique_ptr<property_listener> default_input_listener; - unique_ptr<property_listener> default_output_listener; - unique_ptr<property_listener> input_alive_listener; - unique_ptr<property_listener> input_source_listener; - unique_ptr<property_listener> output_source_listener; + std::atomic<bool> switching_device; + std::atomic<bool> buffer_size_change_state{ false }; }; -bool -has_input(cubeb_stream * stm) +bool has_input(cubeb_stream * stm) { return stm->input_stream_params.rate != 0; } -bool -has_output(cubeb_stream * stm) +bool has_output(cubeb_stream * stm) { return stm->output_stream_params.rate != 0; } -cubeb_channel -channel_label_to_cubeb_channel(UInt32 label) -{ - switch (label) { - case kAudioChannelLabel_Left: - return CHANNEL_FRONT_LEFT; - case kAudioChannelLabel_Right: - return CHANNEL_FRONT_RIGHT; - case kAudioChannelLabel_Center: - return CHANNEL_FRONT_CENTER; - case kAudioChannelLabel_LFEScreen: - return CHANNEL_LOW_FREQUENCY; - case kAudioChannelLabel_LeftSurround: - return CHANNEL_BACK_LEFT; - case kAudioChannelLabel_RightSurround: - return CHANNEL_BACK_RIGHT; - case kAudioChannelLabel_LeftCenter: - return CHANNEL_FRONT_LEFT_OF_CENTER; - case kAudioChannelLabel_RightCenter: - return CHANNEL_FRONT_RIGHT_OF_CENTER; - case kAudioChannelLabel_CenterSurround: - return CHANNEL_BACK_CENTER; - case kAudioChannelLabel_LeftSurroundDirect: - return CHANNEL_SIDE_LEFT; - case kAudioChannelLabel_RightSurroundDirect: - return CHANNEL_SIDE_RIGHT; - case kAudioChannelLabel_TopCenterSurround: - return CHANNEL_TOP_CENTER; - case kAudioChannelLabel_VerticalHeightLeft: - return CHANNEL_TOP_FRONT_LEFT; - case kAudioChannelLabel_VerticalHeightCenter: - return CHANNEL_TOP_FRONT_CENTER; - case kAudioChannelLabel_VerticalHeightRight: - return CHANNEL_TOP_FRONT_RIGHT; - case kAudioChannelLabel_TopBackLeft: - return CHANNEL_TOP_BACK_LEFT; - case kAudioChannelLabel_TopBackCenter: - return CHANNEL_TOP_BACK_CENTER; - case kAudioChannelLabel_TopBackRight: - return CHANNEL_TOP_BACK_RIGHT; - default: - return CHANNEL_UNKNOWN; - } -} - -AudioChannelLabel -cubeb_channel_to_channel_label(cubeb_channel channel) -{ - switch (channel) { - case CHANNEL_FRONT_LEFT: - return kAudioChannelLabel_Left; - case CHANNEL_FRONT_RIGHT: - return kAudioChannelLabel_Right; - case CHANNEL_FRONT_CENTER: - return kAudioChannelLabel_Center; - case CHANNEL_LOW_FREQUENCY: - return kAudioChannelLabel_LFEScreen; - case CHANNEL_BACK_LEFT: - return kAudioChannelLabel_LeftSurround; - case CHANNEL_BACK_RIGHT: - return kAudioChannelLabel_RightSurround; - case CHANNEL_FRONT_LEFT_OF_CENTER: - return kAudioChannelLabel_LeftCenter; - case CHANNEL_FRONT_RIGHT_OF_CENTER: - return kAudioChannelLabel_RightCenter; - case CHANNEL_BACK_CENTER: - return kAudioChannelLabel_CenterSurround; - case CHANNEL_SIDE_LEFT: - return kAudioChannelLabel_LeftSurroundDirect; - case CHANNEL_SIDE_RIGHT: - return kAudioChannelLabel_RightSurroundDirect; - case CHANNEL_TOP_CENTER: - return kAudioChannelLabel_TopCenterSurround; - case CHANNEL_TOP_FRONT_LEFT: - return kAudioChannelLabel_VerticalHeightLeft; - case CHANNEL_TOP_FRONT_CENTER: - return kAudioChannelLabel_VerticalHeightCenter; - case CHANNEL_TOP_FRONT_RIGHT: - return kAudioChannelLabel_VerticalHeightRight; - case CHANNEL_TOP_BACK_LEFT: - return kAudioChannelLabel_TopBackLeft; - case CHANNEL_TOP_BACK_CENTER: - return kAudioChannelLabel_TopBackCenter; - case CHANNEL_TOP_BACK_RIGHT: - return kAudioChannelLabel_TopBackRight; - default: - return kAudioChannelLabel_Unknown; - } -} - #if TARGET_OS_IPHONE typedef UInt32 AudioDeviceID; typedef UInt32 AudioObjectID; #define AudioGetCurrentHostTime mach_absolute_time -#endif - uint64_t -ConvertHostTimeToNanos(uint64_t host_time) +AudioConvertHostTimeToNanos(uint64_t host_time) { static struct mach_timebase_info timebase_info; static bool initialized = false; @@ -402,34 +251,27 @@ ConvertHostTimeToNanos(uint64_t host_time) } return (uint64_t)answer; } +#endif -static void -audiounit_increment_active_streams(cubeb * ctx) +static int64_t +audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) { - ctx->mutex.assert_current_thread_owns(); - ctx->active_streams += 1; -} + if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) { + return 0; + } -static void -audiounit_decrement_active_streams(cubeb * ctx) -{ - ctx->mutex.assert_current_thread_owns(); - ctx->active_streams -= 1; -} + uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); + uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); -static int -audiounit_active_streams(cubeb * ctx) -{ - ctx->mutex.assert_current_thread_owns(); - return ctx->active_streams; + return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL; } static void -audiounit_set_global_latency(cubeb * ctx, uint32_t latency_frames) +audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames) { - ctx->mutex.assert_current_thread_owns(); - assert(audiounit_active_streams(ctx) == 1); - ctx->global_latency_frames = latency_frames; + stm->mutex.assert_current_thread_owns(); + assert(stm->context->active_streams == 1); + stm->context->global_latency_frames = latency_frames; } static void @@ -441,8 +283,10 @@ audiounit_make_silent(AudioBuffer * ioData) } static OSStatus -audiounit_render_input(cubeb_stream * stm, AudioUnitRenderActionFlags * flags, - AudioTimeStamp const * tstamp, UInt32 bus, +audiounit_render_input(cubeb_stream * stm, + AudioUnitRenderActionFlags * flags, + AudioTimeStamp const * tstamp, + UInt32 bus, UInt32 input_frames) { /* Create the AudioBufferList to store input. */ @@ -450,76 +294,66 @@ audiounit_render_input(cubeb_stream * stm, AudioUnitRenderActionFlags * flags, 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.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); + OSStatus r = AudioUnitRender(stm->input_unit, + flags, + tstamp, + bus, + input_frames, + &input_buffer_list); if (r != noErr) { - LOG("AudioUnitRender rv=%d", r); - if (r != kAudioUnitErr_CannotDoInCurrentContext) { - return r; - } - if (stm->output_unit) { - // kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT - // headset and the profile is changed from A2DP to HFP/HSP. The previous - // output device is no longer valid and must be reset. - audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT); - } - // For now state that no error occurred and feed silence, stream will be - // resumed once reinit has completed. - ALOGV("(%p) input: reinit pending feeding silence instead", stm); - stm->input_linear_buffer->push_silence(input_frames * - stm->input_desc.mChannelsPerFrame); - } else { - /* Copy input data in linear buffer. */ - stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData, - input_frames * - stm->input_desc.mChannelsPerFrame); + 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; - ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, " - "total frames %lu.", - stm, (unsigned int)input_buffer_list.mNumberBuffers, - (unsigned int)input_buffer_list.mBuffers[0].mDataByteSize, - (unsigned int)input_buffer_list.mBuffers[0].mNumberChannels, - (unsigned int)input_frames, - stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame); - return noErr; } static OSStatus -audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, - AudioTimeStamp const * tstamp, UInt32 bus, - UInt32 input_frames, AudioBufferList * /* bufs */) +audiounit_input_callback(void * user_ptr, + AudioUnitRenderActionFlags * flags, + AudioTimeStamp const * tstamp, + UInt32 bus, + UInt32 input_frames, + AudioBufferList * /* bufs */) { cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr); + long outframes; assert(stm->input_unit != NULL); assert(AU_IN_BUS == bus); if (stm->shutdown) { - ALOG("(%p) input shutdown", stm); + LOG("(%p) input shutdown", stm); return noErr; } - if (stm->draining) { - OSStatus r = AudioOutputUnitStop(stm->input_unit); - assert(r == 0); - // Only fire state callback in input-only stream. For duplex stream, - // the state callback will be fired in output callback. - if (stm->output_unit == NULL) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - } - 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); @@ -529,199 +363,147 @@ audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, // 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; - long outframes = cubeb_resampler_fill(stm->resampler.get(), - stm->input_linear_buffer->data(), - &total_input_frames, NULL, 0); - stm->draining = outframes < total_input_frames; - + 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 void -audiounit_mix_output_buffer(cubeb_stream * stm, size_t output_frames, - void * input_buffer, size_t input_buffer_size, - void * output_buffer, size_t output_buffer_size) +static bool +is_extra_input_needed(cubeb_stream * stm) { - assert(input_buffer_size >= - cubeb_sample_size(stm->output_stream_params.format) * - stm->output_stream_params.channels * output_frames); - assert(output_buffer_size >= stm->output_desc.mBytesPerFrame * output_frames); + /* 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. */ - int r = cubeb_mixer_mix(stm->mixer.get(), output_frames, input_buffer, - input_buffer_size, output_buffer, output_buffer_size); - if (r != 0) { - LOG("Remix error = %d", r); - } -} - -// Return how many input frames (sampled at input_hw_rate) are needed to provide -// output_frames (sampled at output_stream_params.rate) -static int64_t -minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames) -{ - if (stm->input_hw_rate == stm->output_stream_params.rate) { - // Fast path. - return output_frames; - } - return ceil(stm->input_hw_rate * output_frames / - stm->output_stream_params.rate); + /* 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) + AudioTimeStamp const * tstamp, + UInt32 bus, + UInt32 output_frames, + AudioBufferList * outBufferList) { assert(AU_OUT_BUS == bus); assert(outBufferList->mNumberBuffers == 1); cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr); - uint64_t now = ConvertHostTimeToNanos(mach_absolute_time()); - uint64_t audio_output_time = ConvertHostTimeToNanos(tstamp->mHostTime); - uint64_t output_latency_ns = audio_output_time - now; - - const int ns2s = 1e9; - // The total output latency is the timestamp difference + the stream latency + - // the hardware latency. - stm->total_output_latency_frames = - output_latency_ns * stm->output_hw_rate / ns2s + - stm->current_latency_frames; - - ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input " - "frames %lu.", - stm, (unsigned int)outBufferList->mNumberBuffers, - (unsigned int)outBufferList->mBuffers[0].mDataByteSize, - (unsigned int)outBufferList->mBuffers[0].mNumberChannels, - (unsigned int)output_frames, - has_input(stm) ? stm->input_linear_buffer->length() / - stm->input_desc.mChannelsPerFrame - : 0); - - long input_frames = 0; - void *output_buffer = NULL, *input_buffer = NULL; + 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) { - ALOG("(%p) output shutdown.", stm); + 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. */ - if (stm->mixer) { - // If remixing needs to occur, we can't directly work in our final - // destination buffer as data may be overwritten or too small to start with. - size_t size_needed = output_frames * stm->output_stream_params.channels * - cubeb_sample_size(stm->output_stream_params.format); - if (stm->temp_buffer_size < size_needed) { - stm->temp_buffer.reset(new uint8_t[size_needed]); - stm->temp_buffer_size = size_needed; - } - output_buffer = stm->temp_buffer.get(); - } else { - output_buffer = outBufferList->mBuffers[0].mData; - } - - stm->frames_written += output_frames; - + output_buffer = outBufferList->mBuffers[0].mData; /* If Full duplex get also input buffer */ if (stm->input_unit != NULL) { - /* 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. */ - uint32_t input_frames_needed = - minimum_resampling_input_frames(stm, stm->frames_written); - long missing_frames = input_frames_needed - stm->frames_read; - if (missing_frames > 0) { - stm->input_linear_buffer->push_silence(missing_frames * - stm->input_desc.mChannelsPerFrame); - stm->frames_read = input_frames_needed; - - ALOG("(%p) %s pushed %ld frames of input silence.", stm, - stm->frames_read == 0 ? "Input hasn't started," - : stm->switching_device ? "Device switching," - : "Drop out,", - missing_frames); - } + 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. It will change to actually used - // frames inside fill - input_frames = - stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; + // Number of input frames in the buffer + input_frames = stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame; } /* Call user callback through resampler. */ - long outframes = cubeb_resampler_fill(stm->resampler.get(), input_buffer, - input_buffer ? &input_frames : NULL, - output_buffer, output_frames); + outframes = cubeb_resampler_fill(stm->resampler, + input_buffer, + input_buffer ? &input_frames : NULL, + output_buffer, + output_frames); if (input_buffer) { - // Pop from the buffer the frames used by the the resampler. - stm->input_linear_buffer->pop(input_frames * - stm->input_desc.mChannelsPerFrame); + stm->input_linear_buffer->pop(nullptr, input_frames * stm->input_desc.mChannelsPerFrame); } - if (outframes < 0 || outframes > output_frames) { + if (outframes < 0) { stm->shutdown = true; - 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_ERROR); - audiounit_make_silent(&outBufferList->mBuffers[0]); return noErr; } - stm->draining = (UInt32)outframes < output_frames; + 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) */ - size_t channels = stm->output_stream_params.channels; - size_t missing_samples = (output_frames - outframes) * channels; - size_t size_sample = cubeb_sample_size(stm->output_stream_params.format); - /* number of bytes that have been filled with valid audio by the callback. - */ - size_t audio_byte_count = outframes * channels * size_sample; - PodZero((uint8_t *)output_buffer + audio_byte_count, - missing_samples * size_sample); + memset((uint8_t*)output_buffer + outframes * outbpf, 0, (output_frames - outframes) * outbpf); } - - /* Mixing */ - if (stm->mixer) { - audiounit_mix_output_buffer(stm, output_frames, output_buffer, - stm->temp_buffer_size, - outBufferList->mBuffers[0].mData, - outBufferList->mBuffers[0].mDataByteSize); + /* 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; } @@ -729,11 +511,25 @@ 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 = new cubeb; + *context = ctx; return CUBEB_OK; } @@ -746,338 +542,255 @@ audiounit_get_backend_id(cubeb * /* ctx */) } #if !TARGET_OS_IPHONE - -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_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side) +audiounit_get_output_device_id(AudioDeviceID * device_id) { - assert(stm); + UInt32 size; + OSStatus r; + AudioObjectPropertyAddress output_device_address = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; - device_info * info = nullptr; - cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN; + size = sizeof(*device_id); - if (side == io_side::INPUT) { - info = &stm->input_device; - type = CUBEB_DEVICE_TYPE_INPUT; - } else if (side == io_side::OUTPUT) { - info = &stm->output_device; - type = CUBEB_DEVICE_TYPE_OUTPUT; + r = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &output_device_address, + 0, + NULL, + &size, + device_id); + if (r != noErr) { + PRINT_ERROR_CODE("output_device_id", r); + return CUBEB_ERROR; } - memset(info, 0, sizeof(device_info)); - info->id = id; - if (side == io_side::INPUT) { - info->flags |= DEV_INPUT; - } else if (side == io_side::OUTPUT) { - info->flags |= DEV_OUTPUT; - } + return CUBEB_OK; +} - AudioDeviceID default_device_id = audiounit_get_default_device_id(type); - if (default_device_id == kAudioObjectUnknown) { - return CUBEB_ERROR; - } - if (id == kAudioObjectUnknown) { - info->id = default_device_id; - info->flags |= DEV_SELECTED_DEFAULT; - } +static int +audiounit_get_input_device_id(AudioDeviceID * device_id) +{ + UInt32 size; + OSStatus r; + AudioObjectPropertyAddress input_device_address = { + kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; - if (info->id == default_device_id) { - info->flags |= DEV_SYSTEM_DEFAULT; - } + size = sizeof(*device_id); - assert(info->id); - assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) || - !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT); + 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, device_flags_value flags) +audiounit_reinit_stream(cubeb_stream * stm) { auto_lock context_lock(stm->context->mutex); - assert((flags & DEV_INPUT && stm->input_unit) || - (flags & DEV_OUTPUT && stm->output_unit)); 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 all device change listeners.", stm); + LOG("(%p) Could not uninstall the device changed callback", stm); } { auto_lock lock(stm->mutex); float volume = 0.0; - int vol_rv = CUBEB_ERROR; - if (stm->output_unit) { - vol_rv = audiounit_stream_get_volume(stm, &volume); - } + int vol_rv = audiounit_stream_get_volume(stm, &volume); audiounit_close_stream(stm); - /* Reinit occurs in one of the following case: - * - When the device is not alive any more - * - When the default system device change. - * - The bluetooth device changed from A2DP to/from HFP/HSP profile - * We first attempt to re-use the same device id, should that fail we will - * default to the (potentially new) default device. */ - AudioDeviceID input_device = - flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown; - if (flags & DEV_INPUT) { - r = audiounit_set_device_info(stm, input_device, io_side::INPUT); - if (r != CUBEB_OK) { - LOG("(%p) Set input device info failed. This can happen when last " - "media device is unplugged", - stm); - return CUBEB_ERROR; - } - } - - /* Always use the default output on reinit. This is not correct in every - * case but it is sufficient for Firefox and prevent reinit from reporting - * failures. It will change soon when reinit mechanism will be updated. */ - r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT); - if (r != CUBEB_OK) { - LOG("(%p) Set output device info failed. This can happen when last media " - "device is unplugged", - stm); - return CUBEB_ERROR; - } - if (audiounit_setup_stream(stm) != CUBEB_OK) { LOG("(%p) Stream reinit failed.", stm); - if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) { - // Attempt to re-use the same device-id failed, so attempt again with - // default input device. - audiounit_close_stream(stm); - if (audiounit_set_device_info(stm, kAudioObjectUnknown, - io_side::INPUT) != CUBEB_OK || - audiounit_setup_stream(stm) != CUBEB_OK) { - LOG("(%p) Second stream reinit failed.", stm); - return CUBEB_ERROR; - } - } + 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) { - r = audiounit_stream_start_internal(stm); - if (r != CUBEB_OK) { - return CUBEB_ERROR; - } + audiounit_stream_start_internal(stm); } } return CUBEB_OK; } -static void -audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags) -{ - if (std::atomic_exchange(&stm->reinit_pending, true)) { - // A reinit task is already pending, nothing more to do. - ALOG("(%p) re-init stream task already pending, cancelling request", stm); - return; - } - - // 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 (stm->destroy_pending) { - ALOG("(%p) stream pending destroy, cancelling reinit task", stm); - return; - } - - if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) { - if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) { - LOG("(%p) Could not uninstall system changed callback", stm); - } - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - LOG("(%p) Could not reopen the stream after switching.", stm); - } - stm->switching_device = false; - stm->reinit_pending = false; - }); -} - -static char const * -event_addr_to_string(AudioObjectPropertySelector selector) -{ - switch (selector) { - case kAudioHardwarePropertyDefaultOutputDevice: - return "kAudioHardwarePropertyDefaultOutputDevice"; - case kAudioHardwarePropertyDefaultInputDevice: - return "kAudioHardwarePropertyDefaultInputDevice"; - case kAudioDevicePropertyDeviceIsAlive: - return "kAudioDevicePropertyDeviceIsAlive"; - case kAudioDevicePropertyDataSource: - return "kAudioDevicePropertyDataSource"; - default: - return "Unknown"; - } -} - static OSStatus -audiounit_property_listener_callback( - AudioObjectID id, UInt32 address_count, - const AudioObjectPropertyAddress * addresses, void * user) -{ - cubeb_stream * stm = (cubeb_stream *)user; - if (stm->switching_device) { - LOG("Switching is already taking place. Skip Event %s for id=%d", - event_addr_to_string(addresses[0].mSelector), id); - return noErr; - } +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, %u events.", stm, - (unsigned int)address_count); + 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[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice " - "for id=%d", - (unsigned int)i, id); - } break; - case kAudioHardwarePropertyDefaultInputDevice: { - LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice " - "for id=%d", - (unsigned int)i, id); - } break; - case kAudioDevicePropertyDeviceIsAlive: { - LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for " - "id=%d", - (unsigned int)i, id); - // If this is the default input device ignore the event, - // kAudioHardwarePropertyDefaultInputDevice will take care of the switch - if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) { - LOG("It's the default input device, ignore the event"); - stm->switching_device = false; - return noErr; - } - } break; - case kAudioDevicePropertyDataSource: { - LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d", - (unsigned int)i, id); - } break; - default: - LOG("Event[%u] - mSelector == Unexpected Event id %d, return", - (unsigned int)i, addresses[i].mSelector); - stm->switching_device = false; - return noErr; + 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; + } } } - // Allow restart to choose the new default - device_flags_value switch_side = DEV_UNKNOWN; - if (has_input(stm)) { - switch_side |= DEV_INPUT; - } - if (has_output(stm)) { - switch_side |= DEV_OUTPUT; - } - for (UInt32 i = 0; i < address_count; i++) { - switch (addresses[i].mSelector) { + switch(addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: case kAudioHardwarePropertyDefaultInputDevice: case kAudioDevicePropertyDeviceIsAlive: /* fall through */ case kAudioDevicePropertyDataSource: { - auto_lock dev_cb_lock(stm->device_changed_callback_lock); - if (stm->device_changed_callback) { - stm->device_changed_callback(stm->user_ptr); + auto_lock lock(stm->mutex); + if (stm->device_changed_callback) { + stm->device_changed_callback(stm->user_ptr); + } + break; } - break; - } } } - audiounit_reinit_stream_async(stm, switch_side); + // 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(const property_listener * listener) +audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector, + AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener) { - assert(listener); - return AudioObjectAddPropertyListener(listener->device_id, - listener->property_address, - listener->callback, listener->stream); + AudioObjectPropertyAddress address = { + selector, + scope, + kAudioObjectPropertyElementMaster + }; + + return AudioObjectAddPropertyListener(id, &address, listener, stm); } OSStatus -audiounit_remove_listener(const property_listener * listener) +audiounit_remove_listener(cubeb_stream * stm, AudioDeviceID id, + AudioObjectPropertySelector selector, + AudioObjectPropertyScope scope, + AudioObjectPropertyListenerProc listener) { - assert(listener); - return AudioObjectRemovePropertyListener( - listener->device_id, listener->property_address, listener->callback, - listener->stream); + 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 rv; - int r = CUBEB_OK; + 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. */ - stm->output_source_listener.reset(new property_listener( - stm->output_device.id, &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - rv = audiounit_add_listener(stm->output_source_listener.get()); - if (rv != noErr) { - stm->output_source_listener.reset(); - LOG("AudioObjectAddPropertyListener/output/" - "kAudioDevicePropertyDataSource rv=%d, device id=%d", - rv, stm->output_device.id); - r = CUBEB_ERROR; + /* 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. */ - stm->input_source_listener.reset(new property_listener( - stm->input_device.id, &INPUT_DATA_SOURCE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - rv = audiounit_add_listener(stm->input_source_listener.get()); - if (rv != noErr) { - stm->input_source_listener.reset(); - LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource " - "rv=%d, device id=%d", - rv, stm->input_device.id); - r = CUBEB_ERROR; + /* 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. */ - stm->input_alive_listener.reset(new property_listener( - stm->input_device.id, &DEVICE_IS_ALIVE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - rv = audiounit_add_listener(stm->input_alive_listener.get()); - if (rv != noErr) { - stm->input_alive_listener.reset(); - LOG("AudioObjectAddPropertyListener/input/" - "kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d", - rv, stm->input_device.id); - r = CUBEB_ERROR; + AudioDeviceID dev = stm->input_device ? reinterpret_cast<intptr_t>(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 r; + return CUBEB_OK; } static int @@ -1087,33 +800,23 @@ audiounit_install_system_changed_callback(cubeb_stream * stm) 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 + * 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. */ - stm->default_output_listener.reset(new property_listener( - kAudioObjectSystemObject, &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - r = audiounit_add_listener(stm->default_output_listener.get()); + r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { - stm->default_output_listener.reset(); - LOG("AudioObjectAddPropertyListener/output/" - "kAudioHardwarePropertyDefaultOutputDevice rv=%d", - r); + 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. */ - stm->default_input_listener.reset(new property_listener( - kAudioObjectSystemObject, &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS, - &audiounit_property_listener_callback, stm)); - r = audiounit_add_listener(stm->default_input_listener.get()); + r = audiounit_add_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { - stm->default_input_listener.reset(); - LOG("AudioObjectAddPropertyListener/input/" - "kAudioHardwarePropertyDefaultInputDevice rv=%d", - r); + LOG("AudioObjectAddPropertyListener/input/kAudioHardwarePropertyDefaultInputDevice rv=%d", r); return CUBEB_ERROR; } } @@ -1124,44 +827,36 @@ audiounit_install_system_changed_callback(cubeb_stream * stm) static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm) { - OSStatus rv; - // Failing to uninstall listeners is not a fatal error. - int r = CUBEB_OK; + OSStatus r; - if (stm->output_source_listener) { - rv = audiounit_remove_listener(stm->output_source_listener.get()); - if (rv != noErr) { - LOG("AudioObjectRemovePropertyListener/output/" - "kAudioDevicePropertyDataSource rv=%d, device id=%d", - rv, stm->output_device.id); - r = CUBEB_ERROR; + if (stm->output_unit) { + AudioDeviceID output_dev_id; + r = audiounit_get_output_device_id(&output_dev_id); + if (r != noErr) { + return CUBEB_ERROR; } - stm->output_source_listener.reset(); - } - if (stm->input_source_listener) { - rv = audiounit_remove_listener(stm->input_source_listener.get()); - if (rv != noErr) { - LOG("AudioObjectRemovePropertyListener/input/" - "kAudioDevicePropertyDataSource rv=%d, device id=%d", - rv, stm->input_device.id); - r = CUBEB_ERROR; + r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); + if (r != noErr) { + return CUBEB_ERROR; } - stm->input_source_listener.reset(); } - if (stm->input_alive_listener) { - rv = audiounit_remove_listener(stm->input_alive_listener.get()); - if (rv != noErr) { - LOG("AudioObjectRemovePropertyListener/input/" - "kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d", - rv, stm->input_device.id); - r = 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; } - stm->input_alive_listener.reset(); - } - return r; + 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 @@ -1169,20 +864,20 @@ audiounit_uninstall_system_changed_callback(cubeb_stream * stm) { OSStatus r; - if (stm->default_output_listener) { - r = audiounit_remove_listener(stm->default_output_listener.get()); + if (stm->output_unit) { + r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { return CUBEB_ERROR; } - stm->default_output_listener.reset(); } - if (stm->default_input_listener) { - r = audiounit_remove_listener(stm->default_input_listener.get()); + if (stm->input_unit) { + r = audiounit_remove_listener(stm, kAudioObjectSystemObject, kAudioHardwarePropertyDefaultInputDevice, + kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); if (r != noErr) { return CUBEB_ERROR; } - stm->default_input_listener.reset(); } return CUBEB_OK; } @@ -1195,11 +890,12 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) OSStatus r; AudioDeviceID output_device_id; AudioObjectPropertyAddress output_device_buffer_size_range = { - kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster}; + kAudioDevicePropertyBufferFrameSizeRange, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; - output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); - if (output_device_id == kAudioObjectUnknown) { + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { LOG("Could not get default output device id."); return CUBEB_ERROR; } @@ -1208,10 +904,13 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) size = sizeof(*latency_range); r = AudioObjectGetPropertyData(output_device_id, - &output_device_buffer_size_range, 0, NULL, - &size, latency_range); + &output_device_buffer_size_range, + 0, + NULL, + &size, + latency_range); if (r != noErr) { - LOG("AudioObjectGetPropertyData/buffer size range rv=%d", r); + PRINT_ERROR_CODE("AudioObjectGetPropertyData/buffer size range", r); return CUBEB_ERROR; } @@ -1222,19 +921,20 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type) { - const AudioObjectPropertyAddress * adr; + AudioObjectPropertyAddress adr = { 0, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + AudioDeviceID devid; + UInt32 size; + if (type == CUBEB_DEVICE_TYPE_OUTPUT) { - adr = &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS; + adr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; } else if (type == CUBEB_DEVICE_TYPE_INPUT) { - adr = &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS; + adr.mSelector = kAudioHardwarePropertyDefaultInputDevice; } else { return kAudioObjectUnknown; } - AudioDeviceID devid; - UInt32 size = sizeof(AudioDeviceID); - if (AudioObjectGetPropertyData(kAudioObjectSystemObject, adr, 0, NULL, &size, - &devid) != noErr) { + size = sizeof(AudioDeviceID); + if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, &devid) != noErr) { return kAudioObjectUnknown; } @@ -1245,7 +945,7 @@ int audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) { #if TARGET_OS_IPHONE - // TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels] + //TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels] *max_channels = 2; #else UInt32 size; @@ -1253,22 +953,27 @@ audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) AudioDeviceID output_device_id; AudioStreamBasicDescription stream_format; AudioObjectPropertyAddress stream_format_address = { - kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput, - kAudioObjectPropertyElementMaster}; + kAudioDevicePropertyStreamFormat, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; assert(ctx && max_channels); - output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); - if (output_device_id == kAudioObjectUnknown) { + 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); + r = AudioObjectGetPropertyData(output_device_id, + &stream_format_address, + 0, + NULL, + &size, + &stream_format); if (r != noErr) { - LOG("AudioObjectPropertyAddress/StreamFormat rv=%d", r); + PRINT_ERROR_CODE("AudioObjectPropertyAddress/StreamFormat", r); return CUBEB_ERROR; } @@ -1278,11 +983,12 @@ audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) } static int -audiounit_get_min_latency(cubeb * /* ctx */, cubeb_stream_params /* params */, +audiounit_get_min_latency(cubeb * /* ctx */, + cubeb_stream_params /* params */, uint32_t * latency_frames) { #if TARGET_OS_IPHONE - // TODO: [[AVAudioSession sharedInstance] inputLatency] + //TODO: [[AVAudioSession sharedInstance] inputLatency] return CUBEB_ERROR_NOT_SUPPORTED; #else AudioValueRange latency_range; @@ -1291,8 +997,8 @@ audiounit_get_min_latency(cubeb * /* ctx */, cubeb_stream_params /* params */, return CUBEB_ERROR; } - *latency_frames = - max<uint32_t>(latency_range.mMinimum, SAFE_MIN_LATENCY_FRAMES); + *latency_frames = std::max<uint32_t>(latency_range.mMinimum, + SAFE_MIN_LATENCY_FRAMES); #endif return CUBEB_OK; @@ -1302,7 +1008,7 @@ static int audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate) { #if TARGET_OS_IPHONE - // TODO + //TODO return CUBEB_ERROR_NOT_SUPPORTED; #else UInt32 size; @@ -1310,17 +1016,22 @@ audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate) Float64 fsamplerate; AudioDeviceID output_device_id; AudioObjectPropertyAddress samplerate_address = { - kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; + kAudioDevicePropertyNominalSampleRate, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; - output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); - if (output_device_id == kAudioObjectUnknown) { + 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); + r = AudioObjectGetPropertyData(output_device_id, + &samplerate_address, + 0, + NULL, + &size, + &fsamplerate); if (r != noErr) { return CUBEB_ERROR; @@ -1331,133 +1042,27 @@ audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate) return CUBEB_OK; } -static cubeb_channel_layout -audiounit_convert_channel_layout(AudioChannelLayout * layout) -{ - // When having one or two channel, force mono or stereo. Some devices (namely, - // Bose QC35, mark 1 and 2), expose a single channel mapped to the right for - // some reason. - if (layout->mNumberChannelDescriptions == 1) { - return CUBEB_LAYOUT_MONO; - } else if (layout->mNumberChannelDescriptions == 2) { - return CUBEB_LAYOUT_STEREO; - } - - if (layout->mChannelLayoutTag != - kAudioChannelLayoutTag_UseChannelDescriptions) { - // kAudioChannelLayoutTag_UseChannelBitmap - // kAudioChannelLayoutTag_Mono - // kAudioChannelLayoutTag_Stereo - // .... - LOG("Only handle UseChannelDescriptions for now.\n"); - return CUBEB_LAYOUT_UNDEFINED; - } - - cubeb_channel_layout cl = 0; - for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) { - cubeb_channel cc = channel_label_to_cubeb_channel( - layout->mChannelDescriptions[i].mChannelLabel); - if (cc == CHANNEL_UNKNOWN) { - return CUBEB_LAYOUT_UNDEFINED; - } - cl |= cc; - } - - return cl; -} - -static cubeb_channel_layout -audiounit_get_preferred_channel_layout(AudioUnit output_unit) -{ - OSStatus rv = noErr; - UInt32 size = 0; - rv = AudioUnitGetPropertyInfo( - output_unit, kAudioDevicePropertyPreferredChannelLayout, - kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr); - if (rv != noErr) { - LOG("AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout " - "rv=%d", - rv); - return CUBEB_LAYOUT_UNDEFINED; - } - assert(size > 0); - - auto layout = make_sized_audio_channel_layout(size); - rv = AudioUnitGetProperty( - output_unit, kAudioDevicePropertyPreferredChannelLayout, - kAudioUnitScope_Output, AU_OUT_BUS, layout.get(), &size); - if (rv != noErr) { - LOG("AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv=%d", - rv); - return CUBEB_LAYOUT_UNDEFINED; - } - - return audiounit_convert_channel_layout(layout.get()); -} - -static cubeb_channel_layout -audiounit_get_current_channel_layout(AudioUnit output_unit) -{ - OSStatus rv = noErr; - UInt32 size = 0; - rv = AudioUnitGetPropertyInfo( - output_unit, kAudioUnitProperty_AudioChannelLayout, - kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr); - if (rv != noErr) { - LOG("AudioUnitGetPropertyInfo/kAudioUnitProperty_AudioChannelLayout rv=%d", - rv); - // This property isn't known before macOS 10.12, attempt another method. - return audiounit_get_preferred_channel_layout(output_unit); - } - assert(size > 0); - - auto layout = make_sized_audio_channel_layout(size); - rv = AudioUnitGetProperty(output_unit, kAudioUnitProperty_AudioChannelLayout, - kAudioUnitScope_Output, AU_OUT_BUS, layout.get(), - &size); - if (rv != noErr) { - LOG("AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv=%d", rv); - return CUBEB_LAYOUT_UNDEFINED; - } - - return audiounit_convert_channel_layout(layout.get()); -} - -static int -audiounit_create_unit(AudioUnit * unit, device_info * device); - -static OSStatus -audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype); +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); - - // Disabling this assert for bug 1083664 -- we seem to leak a stream - // assert(ctx->active_streams == 0); - if (audiounit_active_streams(ctx) > 0) { - LOG("(%p) API misuse, %d streams active when context destroyed!", ctx, - audiounit_active_streams(ctx)); - } - /* Unregister the callback if necessary. */ - if (ctx->input_collection_changed_callback) { - audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT); - } - if (ctx->output_collection_changed_callback) { - audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT); + if(ctx->collection_changed_callback) { + audiounit_remove_device_listener(ctx); } } - dispatch_release(ctx->serial_queue); - - delete ctx; + ctx->~cubeb(); + free(ctx); } -static void -audiounit_stream_destroy(cubeb_stream * stm); +static void audiounit_stream_destroy(cubeb_stream * stm); static int audio_stream_desc_init(AudioStreamBasicDescription * ss, @@ -1470,8 +1075,8 @@ audio_stream_desc_init(AudioStreamBasicDescription * ss, break; case CUBEB_SAMPLE_S16BE: ss->mBitsPerChannel = 16; - ss->mFormatFlags = - kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian; + ss->mFormatFlags = kAudioFormatFlagIsSignedInteger | + kAudioFormatFlagIsBigEndian; break; case CUBEB_SAMPLE_FLOAT32LE: ss->mBitsPerChannel = 32; @@ -1479,7 +1084,8 @@ audio_stream_desc_init(AudioStreamBasicDescription * ss, break; case CUBEB_SAMPLE_FLOAT32BE: ss->mBitsPerChannel = 32; - ss->mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsBigEndian; + ss->mFormatFlags = kAudioFormatFlagIsFloat | + kAudioFormatFlagIsBigEndian; break; default: return CUBEB_ERROR_INVALID_FORMAT; @@ -1499,543 +1105,29 @@ audio_stream_desc_init(AudioStreamBasicDescription * ss, return CUBEB_OK; } -void -audiounit_init_mixer(cubeb_stream * stm) -{ - // We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio - // data, it silently drop the channels so we need to remix the - // audio data by ourselves to keep all the information. - stm->mixer.reset(cubeb_mixer_create( - stm->output_stream_params.format, stm->output_stream_params.channels, - stm->output_stream_params.layout, stm->context->channels, - stm->context->layout)); - assert(stm->mixer); -} - -static int -audiounit_set_channel_layout(AudioUnit unit, io_side side, - cubeb_channel_layout layout) -{ - if (side != io_side::OUTPUT) { - return CUBEB_ERROR; - } - - if (layout == CUBEB_LAYOUT_UNDEFINED) { - // We leave everything as-is... - return CUBEB_OK; - } - - OSStatus r; - uint32_t nb_channels = cubeb_channel_layout_nb_channels(layout); - - // We do not use CoreAudio standard layout for lack of documentation on what - // the actual channel orders are. So we set a custom layout. - size_t size = offsetof(AudioChannelLayout, mChannelDescriptions[nb_channels]); - auto au_layout = make_sized_audio_channel_layout(size); - au_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; - au_layout->mNumberChannelDescriptions = nb_channels; - - uint32_t channels = 0; - cubeb_channel_layout channelMap = layout; - for (uint32_t i = 0; channelMap != 0; ++i) { - XASSERT(channels < nb_channels); - uint32_t channel = (channelMap & 1) << i; - if (channel != 0) { - au_layout->mChannelDescriptions[channels].mChannelLabel = - cubeb_channel_to_channel_label(static_cast<cubeb_channel>(channel)); - au_layout->mChannelDescriptions[channels].mChannelFlags = - kAudioChannelFlags_AllOff; - channels++; - } - channelMap = channelMap >> 1; - } - - r = AudioUnitSetProperty(unit, kAudioUnitProperty_AudioChannelLayout, - kAudioUnitScope_Input, AU_OUT_BUS, au_layout.get(), - size); - if (r != noErr) { - LOG("AudioUnitSetProperty/%s/kAudioUnitProperty_AudioChannelLayout rv=%d", - to_string(side), r); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -void -audiounit_layout_init(cubeb_stream * stm, io_side side) -{ - // We currently don't support the input layout setting. - if (side == io_side::INPUT) { - return; - } - - stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit); - - audiounit_set_channel_layout(stm->output_unit, io_side::OUTPUT, - stm->context->layout); -} - -static vector<AudioObjectID> -audiounit_get_sub_devices(AudioDeviceID device_id) -{ - vector<AudioDeviceID> sub_devices; - AudioObjectPropertyAddress property_address = { - kAudioAggregateDevicePropertyActiveSubDeviceList, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; - UInt32 size = 0; - OSStatus rv = AudioObjectGetPropertyDataSize(device_id, &property_address, 0, - nullptr, &size); - - if (rv != noErr) { - sub_devices.push_back(device_id); - return sub_devices; - } - - uint32_t count = static_cast<uint32_t>(size / sizeof(AudioObjectID)); - sub_devices.resize(count); - rv = AudioObjectGetPropertyData(device_id, &property_address, 0, nullptr, - &size, sub_devices.data()); - if (rv != noErr) { - sub_devices.clear(); - sub_devices.push_back(device_id); - } else { - LOG("Found %u sub-devices", count); - } - return sub_devices; -} - -static int -audiounit_create_blank_aggregate_device(AudioObjectID * plugin_id, - AudioDeviceID * aggregate_device_id) -{ - AudioObjectPropertyAddress address_plugin_bundle_id = { - kAudioHardwarePropertyPlugInForBundleID, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - UInt32 size = 0; - OSStatus r = AudioObjectGetPropertyDataSize( - kAudioObjectSystemObject, &address_plugin_bundle_id, 0, NULL, &size); - if (r != noErr) { - LOG("AudioObjectGetPropertyDataSize/" - "kAudioHardwarePropertyPlugInForBundleID, rv=%d", - r); - return CUBEB_ERROR; - } - - AudioValueTranslation translation_value; - CFStringRef in_bundle_ref = CFSTR("com.apple.audio.CoreAudio"); - translation_value.mInputData = &in_bundle_ref; - translation_value.mInputDataSize = sizeof(in_bundle_ref); - translation_value.mOutputData = plugin_id; - translation_value.mOutputDataSize = sizeof(*plugin_id); - - r = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &address_plugin_bundle_id, 0, nullptr, &size, - &translation_value); - if (r != noErr) { - LOG("AudioObjectGetPropertyData/kAudioHardwarePropertyPlugInForBundleID, " - "rv=%d", - r); - return CUBEB_ERROR; - } - - AudioObjectPropertyAddress create_aggregate_device_address = { - kAudioPlugInCreateAggregateDevice, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - r = AudioObjectGetPropertyDataSize( - *plugin_id, &create_aggregate_device_address, 0, nullptr, &size); - if (r != noErr) { - LOG("AudioObjectGetPropertyDataSize/kAudioPlugInCreateAggregateDevice, " - "rv=%d", - r); - return CUBEB_ERROR; - } - - CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable( - kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - struct timeval timestamp; - gettimeofday(×tamp, NULL); - long long int time_id = timestamp.tv_sec * 1000000LL + timestamp.tv_usec; - CFStringRef aggregate_device_name = CFStringCreateWithFormat( - NULL, NULL, CFSTR("%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id); - CFDictionaryAddValue(aggregate_device_dict, - CFSTR(kAudioAggregateDeviceNameKey), - aggregate_device_name); - CFRelease(aggregate_device_name); - - CFStringRef aggregate_device_UID = - CFStringCreateWithFormat(NULL, NULL, CFSTR("org.mozilla.%s_%llx"), - PRIVATE_AGGREGATE_DEVICE_NAME, time_id); - CFDictionaryAddValue(aggregate_device_dict, - CFSTR(kAudioAggregateDeviceUIDKey), - aggregate_device_UID); - CFRelease(aggregate_device_UID); - - int private_value = 1; - CFNumberRef aggregate_device_private_key = - CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_value); - CFDictionaryAddValue(aggregate_device_dict, - CFSTR(kAudioAggregateDeviceIsPrivateKey), - aggregate_device_private_key); - CFRelease(aggregate_device_private_key); - - int stacked_value = 0; - CFNumberRef aggregate_device_stacked_key = - CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stacked_value); - CFDictionaryAddValue(aggregate_device_dict, - CFSTR(kAudioAggregateDeviceIsStackedKey), - aggregate_device_stacked_key); - CFRelease(aggregate_device_stacked_key); - - r = AudioObjectGetPropertyData(*plugin_id, &create_aggregate_device_address, - sizeof(aggregate_device_dict), - &aggregate_device_dict, &size, - aggregate_device_id); - CFRelease(aggregate_device_dict); - if (r != noErr) { - LOG("AudioObjectGetPropertyData/kAudioPlugInCreateAggregateDevice, rv=%d", - r); - return CUBEB_ERROR; - } - LOG("New aggregate device %u", *aggregate_device_id); - - return CUBEB_OK; -} - -// The returned CFStringRef object needs to be released (via CFRelease) -// if it's not NULL, since the reference count of the returned CFStringRef -// object is increased. -static CFStringRef -get_device_name(AudioDeviceID id) -{ - UInt32 size = sizeof(CFStringRef); - CFStringRef UIname = nullptr; - AudioObjectPropertyAddress address_uuid = {kAudioDevicePropertyDeviceUID, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - OSStatus err = - AudioObjectGetPropertyData(id, &address_uuid, 0, nullptr, &size, &UIname); - return (err == noErr) ? UIname : NULL; -} - -static int -audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id, - AudioDeviceID input_device_id, - AudioDeviceID output_device_id) -{ - LOG("Add devices input %u and output %u into aggregate device %u", - input_device_id, output_device_id, aggregate_device_id); - const vector<AudioDeviceID> output_sub_devices = - audiounit_get_sub_devices(output_device_id); - const vector<AudioDeviceID> input_sub_devices = - audiounit_get_sub_devices(input_device_id); - - CFMutableArrayRef aggregate_sub_devices_array = - CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); - /* The order of the items in the array is significant and is used to determine - the order of the streams of the AudioAggregateDevice. */ - for (UInt32 i = 0; i < output_sub_devices.size(); i++) { - CFStringRef ref = get_device_name(output_sub_devices[i]); - if (ref == NULL) { - CFRelease(aggregate_sub_devices_array); - return CUBEB_ERROR; - } - CFArrayAppendValue(aggregate_sub_devices_array, ref); - CFRelease(ref); - } - for (UInt32 i = 0; i < input_sub_devices.size(); i++) { - CFStringRef ref = get_device_name(input_sub_devices[i]); - if (ref == NULL) { - CFRelease(aggregate_sub_devices_array); - return CUBEB_ERROR; - } - CFArrayAppendValue(aggregate_sub_devices_array, ref); - CFRelease(ref); - } - - AudioObjectPropertyAddress aggregate_sub_device_list = { - kAudioAggregateDevicePropertyFullSubDeviceList, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; - UInt32 size = sizeof(CFMutableArrayRef); - OSStatus rv = AudioObjectSetPropertyData( - aggregate_device_id, &aggregate_sub_device_list, 0, nullptr, size, - &aggregate_sub_devices_array); - CFRelease(aggregate_sub_devices_array); - if (rv != noErr) { - LOG("AudioObjectSetPropertyData/" - "kAudioAggregateDevicePropertyFullSubDeviceList, rv=%d", - rv); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -audiounit_set_master_aggregate_device(const AudioDeviceID aggregate_device_id) -{ - assert(aggregate_device_id != kAudioObjectUnknown); - AudioObjectPropertyAddress master_aggregate_sub_device = { - kAudioAggregateDevicePropertyMasterSubDevice, - kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; - - // Master become the 1st output sub device - AudioDeviceID output_device_id = - audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); - const vector<AudioDeviceID> output_sub_devices = - audiounit_get_sub_devices(output_device_id); - CFStringRef master_sub_device = get_device_name(output_sub_devices[0]); - - UInt32 size = sizeof(CFStringRef); - OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id, - &master_aggregate_sub_device, 0, - NULL, size, &master_sub_device); - if (master_sub_device) { - CFRelease(master_sub_device); - } - if (rv != noErr) { - LOG("AudioObjectSetPropertyData/" - "kAudioAggregateDevicePropertyMasterSubDevice, rv=%d", - rv); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -audiounit_activate_clock_drift_compensation( - const AudioDeviceID aggregate_device_id) -{ - assert(aggregate_device_id != kAudioObjectUnknown); - AudioObjectPropertyAddress address_owned = { - kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - - UInt32 qualifier_data_size = sizeof(AudioObjectID); - AudioClassID class_id = kAudioSubDeviceClassID; - void * qualifier_data = &class_id; - UInt32 size = 0; - OSStatus rv = AudioObjectGetPropertyDataSize( - aggregate_device_id, &address_owned, qualifier_data_size, qualifier_data, - &size); - if (rv != noErr) { - LOG("AudioObjectGetPropertyDataSize/kAudioObjectPropertyOwnedObjects, " - "rv=%d", - rv); - return CUBEB_ERROR; - } - - UInt32 subdevices_num = 0; - subdevices_num = size / sizeof(AudioObjectID); - AudioObjectID sub_devices[subdevices_num]; - size = sizeof(sub_devices); - - rv = AudioObjectGetPropertyData(aggregate_device_id, &address_owned, - qualifier_data_size, qualifier_data, &size, - sub_devices); - if (rv != noErr) { - LOG("AudioObjectGetPropertyData/kAudioObjectPropertyOwnedObjects, rv=%d", - rv); - return CUBEB_ERROR; - } - - AudioObjectPropertyAddress address_drift = { - kAudioSubDevicePropertyDriftCompensation, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - - // Start from the second device since the first is the master clock - for (UInt32 i = 1; i < subdevices_num; ++i) { - UInt32 drift_compensation_value = 1; - rv = AudioObjectSetPropertyData(sub_devices[i], &address_drift, 0, nullptr, - sizeof(UInt32), &drift_compensation_value); - if (rv != noErr) { - LOG("AudioObjectSetPropertyData/" - "kAudioSubDevicePropertyDriftCompensation, rv=%d", - rv); - return CUBEB_OK; - } - } - return CUBEB_OK; -} - -static int -audiounit_destroy_aggregate_device(AudioObjectID plugin_id, - AudioDeviceID * aggregate_device_id); -static void -audiounit_get_available_samplerate(AudioObjectID devid, - AudioObjectPropertyScope scope, - uint32_t * min, uint32_t * max, - uint32_t * def); -static int -audiounit_create_device_from_hwdev(cubeb_device_info * dev_info, - AudioObjectID devid, cubeb_device_type type); -static void -audiounit_device_destroy(cubeb_device_info * device); - -static void -audiounit_workaround_for_airpod(cubeb_stream * stm) -{ - cubeb_device_info input_device_info; - audiounit_create_device_from_hwdev(&input_device_info, stm->input_device.id, - CUBEB_DEVICE_TYPE_INPUT); - - cubeb_device_info output_device_info; - audiounit_create_device_from_hwdev(&output_device_info, stm->output_device.id, - CUBEB_DEVICE_TYPE_OUTPUT); - - std::string input_name_str(input_device_info.friendly_name); - std::string output_name_str(output_device_info.friendly_name); - - if (input_name_str.find("AirPods") != std::string::npos && - output_name_str.find("AirPods") != std::string::npos) { - uint32_t input_min_rate = 0; - uint32_t input_max_rate = 0; - uint32_t input_nominal_rate = 0; - audiounit_get_available_samplerate( - stm->input_device.id, kAudioObjectPropertyScopeGlobal, &input_min_rate, - &input_max_rate, &input_nominal_rate); - LOG("(%p) Input device %u, name: %s, min: %u, max: %u, nominal rate: %u", - stm, stm->input_device.id, input_device_info.friendly_name, - input_min_rate, input_max_rate, input_nominal_rate); - uint32_t output_min_rate = 0; - uint32_t output_max_rate = 0; - uint32_t output_nominal_rate = 0; - audiounit_get_available_samplerate( - stm->output_device.id, kAudioObjectPropertyScopeGlobal, - &output_min_rate, &output_max_rate, &output_nominal_rate); - LOG("(%p) Output device %u, name: %s, min: %u, max: %u, nominal rate: %u", - stm, stm->output_device.id, output_device_info.friendly_name, - output_min_rate, output_max_rate, output_nominal_rate); - - Float64 rate = input_nominal_rate; - AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - - OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id, &addr, 0, - nullptr, sizeof(Float64), &rate); - if (rv != noErr) { - LOG("Non fatal error, " - "AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, " - "rv=%d", - rv); - } - } - audiounit_device_destroy(&input_device_info); - audiounit_device_destroy(&output_device_info); -} - -/* - * Aggregate Device is a virtual audio interface which utilizes inputs and - * outputs of one or more physical audio interfaces. It is possible to use the - * clock of one of the devices as a master clock for all the combined devices - * and enable drift compensation for the devices that are not designated clock - * master. - * - * Creating a new aggregate device programmatically requires [0][1]: - * 1. Locate the base plug-in ("com.apple.audio.CoreAudio") - * 2. Create a dictionary that describes the aggregate device - * (don't add sub-devices in that step, prone to fail [0]) - * 3. Ask the base plug-in to create the aggregate device (blank) - * 4. Add the array of sub-devices. - * 5. Set the master device (1st output device in our case) - * 6. Enable drift compensation for the non-master devices - * - * [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html - * [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html - * [2] CoreAudio.framework/Headers/AudioHardware.h - * */ -static int -audiounit_create_aggregate_device(cubeb_stream * stm) -{ - int r = audiounit_create_blank_aggregate_device(&stm->plugin_id, - &stm->aggregate_device_id); - if (r != CUBEB_OK) { - LOG("(%p) Failed to create blank aggregate device", stm); - return CUBEB_ERROR; - } - - r = audiounit_set_aggregate_sub_device_list( - stm->aggregate_device_id, stm->input_device.id, stm->output_device.id); - if (r != CUBEB_OK) { - LOG("(%p) Failed to set aggregate sub-device list", stm); - audiounit_destroy_aggregate_device(stm->plugin_id, - &stm->aggregate_device_id); - return CUBEB_ERROR; - } - - r = audiounit_set_master_aggregate_device(stm->aggregate_device_id); - if (r != CUBEB_OK) { - LOG("(%p) Failed to set master sub-device for aggregate device", stm); - audiounit_destroy_aggregate_device(stm->plugin_id, - &stm->aggregate_device_id); - return CUBEB_ERROR; - } - - r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id); - if (r != CUBEB_OK) { - LOG("(%p) Failed to activate clock drift compensation for aggregate device", - stm); - audiounit_destroy_aggregate_device(stm->plugin_id, - &stm->aggregate_device_id); - return CUBEB_ERROR; - } - - audiounit_workaround_for_airpod(stm); - - return CUBEB_OK; -} - -static int -audiounit_destroy_aggregate_device(AudioObjectID plugin_id, - AudioDeviceID * aggregate_device_id) -{ - assert(aggregate_device_id && *aggregate_device_id != kAudioDeviceUnknown && - plugin_id != kAudioObjectUnknown); - AudioObjectPropertyAddress destroy_aggregate_device_addr = { - kAudioPlugInDestroyAggregateDevice, kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster}; - UInt32 size; - OSStatus rv = AudioObjectGetPropertyDataSize( - plugin_id, &destroy_aggregate_device_addr, 0, NULL, &size); - if (rv != noErr) { - LOG("AudioObjectGetPropertyDataSize/kAudioPlugInDestroyAggregateDevice, " - "rv=%d", - rv); - return CUBEB_ERROR; - } - - rv = AudioObjectGetPropertyData(plugin_id, &destroy_aggregate_device_addr, 0, - NULL, &size, aggregate_device_id); - if (rv != noErr) { - LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d", - rv); - return CUBEB_ERROR; - } - - LOG("Destroyed aggregate device %d", *aggregate_device_id); - *aggregate_device_id = kAudioObjectUnknown; - return CUBEB_OK; -} - static int -audiounit_new_unit_instance(AudioUnit * unit, device_info * device) +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. - if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) { + bool use_default_output = device == NULL && !is_input; + if (use_default_output) { desc.componentSubType = kAudioUnitSubType_DefaultOutput; } else { desc.componentSubType = kAudioUnitSubType_HALOutput; @@ -2052,86 +1144,43 @@ audiounit_new_unit_instance(AudioUnit * unit, device_info * device) rv = AudioComponentInstanceNew(comp, unit); if (rv != noErr) { - LOG("AudioComponentInstanceNew rv=%d", rv); + PRINT_ERROR_CODE("AudioComponentInstanceNew", rv); return CUBEB_ERROR; } - return CUBEB_OK; -} -enum enable_state { - DISABLE, - ENABLE, -}; - -static int -audiounit_enable_unit_scope(AudioUnit * unit, io_side side, enable_state state) -{ - OSStatus rv; - UInt32 enable = state; - rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO, - (side == io_side::INPUT) ? kAudioUnitScope_Input - : kAudioUnitScope_Output, - (side == io_side::INPUT) ? AU_IN_BUS : AU_OUT_BUS, - &enable, sizeof(UInt32)); - if (rv != noErr) { - LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO rv=%d", rv); - return CUBEB_ERROR; - } - return CUBEB_OK; -} - -static int -audiounit_create_unit(AudioUnit * unit, device_info * device) -{ - assert(*unit == nullptr); - assert(device); - - OSStatus rv; - int r; - - r = audiounit_new_unit_instance(unit, device); - if (r != CUBEB_OK) { - return r; - } - assert(*unit); - - if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) { - return CUBEB_OK; - } - - if (device->flags & DEV_INPUT) { - r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE); - if (r != CUBEB_OK) { - LOG("Failed to enable audiounit input scope"); - return r; + 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; } - r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, DISABLE); - if (r != CUBEB_OK) { - LOG("Failed to disable audiounit output scope"); - return r; + + 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; } - } else if (device->flags & DEV_OUTPUT) { - r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, ENABLE); - if (r != CUBEB_OK) { - LOG("Failed to enable audiounit output scope"); - return r; + + if (device == NULL) { + assert(is_input); + devid = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); + } else { + devid = reinterpret_cast<intptr_t>(device); } - r = audiounit_enable_unit_scope(unit, io_side::INPUT, DISABLE); - if (r != CUBEB_OK) { - LOG("Failed to disable audiounit input scope"); - return r; + 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; } - } else { - assert(false); - } - - rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, 0, &device->id, - sizeof(AudioDeviceID)); - if (rv != noErr) { - LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d", - rv); - return CUBEB_ERROR; } return CUBEB_OK; @@ -2140,28 +1189,51 @@ audiounit_create_unit(AudioUnit * unit, device_info * device) static int audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity) { - uint32_t size = - capacity * stream->latency_frames * stream->input_desc.mChannelsPerFrame; if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) { - stream->input_linear_buffer.reset(new auto_array_wrapper_impl<short>(size)); + stream->input_linear_buffer = new auto_array_wrapper( + new auto_array<short>(capacity * + stream->input_buffer_frames * + stream->input_desc.mChannelsPerFrame) ); } else { - stream->input_linear_buffer.reset(new auto_array_wrapper_impl<float>(size)); + stream->input_linear_buffer = new auto_array_wrapper( + new auto_array<float>(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(audiounit_active_streams(stm->context) > 0); - if (audiounit_active_streams(stm->context) == 1) { - return max(min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES), - SAFE_MIN_LATENCY_FRAMES); + assert(stm->context->active_streams > 0); + if (stm->context->active_streams == 1) { + return std::max(std::min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES), + SAFE_MIN_LATENCY_FRAMES); } - assert(stm->output_unit); // If more than one stream operates in parallel // allow only lower values of latency @@ -2169,42 +1241,42 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) 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); + r = AudioUnitGetProperty(stm->output_unit, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Output, + AU_OUT_BUS, + &output_buffer_size, + &size); if (r != noErr) { - LOG("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize " - "rv=%d", - r); + PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r); return 0; } - output_buffer_size = - max(min<uint32_t>(output_buffer_size, SAFE_MAX_LATENCY_FRAMES), - SAFE_MIN_LATENCY_FRAMES); + output_buffer_size = std::max(std::min<uint32_t>(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); + r = AudioUnitGetProperty(stm->input_unit, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Input, + AU_IN_BUS, + &input_buffer_size, + &size); if (r != noErr) { - LOG("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize " - "rv=%d", - r); + PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r); return 0; } - input_buffer_size = - max(min<uint32_t>(input_buffer_size, SAFE_MAX_LATENCY_FRAMES), - SAFE_MIN_LATENCY_FRAMES); + input_buffer_size = std::max(std::min<uint32_t>(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 = min<uint32_t>(input_buffer_size, output_buffer_size); + upper_latency_limit = std::min<uint32_t>(input_buffer_size, output_buffer_size); } else if (input_buffer_size != 0) { upper_latency_limit = input_buffer_size; } else if (output_buffer_size != 0) { @@ -2213,8 +1285,8 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) upper_latency_limit = SAFE_MAX_LATENCY_FRAMES; } - return max(min<uint32_t>(latency_frames, upper_latency_limit), - SAFE_MIN_LATENCY_FRAMES); + return std::max(std::min<uint32_t>(latency_frames, upper_latency_limit), + SAFE_MIN_LATENCY_FRAMES); } /* @@ -2226,103 +1298,130 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames) * - property has changed, remove the listener * */ static void -buffer_size_changed_callback(void * inClientData, AudioUnit inUnit, - AudioUnitPropertyID inPropertyID, - AudioUnitScope inScope, AudioUnitElement inElement) +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; - char const * au_type = "output"; + const char * au_type = "output"; - if (AU_IN_BUS == inElement) { + if (au == stm->input_unit) { au_scope = kAudioUnitScope_Output; au_type = "input"; } switch (inPropertyID) { - case kAudioDevicePropertyBufferFrameSize: { - if (inScope != au_scope) { + 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; } - 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, - io_side side) +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 (side == io_side::INPUT) { + 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); + int r = AudioUnitGetProperty(au, + kAudioDevicePropertyBufferFrameSize, + au_scope, + au_element, + &buffer_frames, + &size); if (r != noErr) { - LOG("AudioUnitGetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d", - to_string(side), r); + 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, - to_string(side), 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); + r = AudioUnitAddPropertyListener(au, + kAudioDevicePropertyBufferFrameSize, + buffer_size_changed_callback, + stm); if (r != noErr) { - LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize " - "rv=%d", - to_string(side), r); + 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, + r = AudioUnitSetProperty(au, + kAudioDevicePropertyBufferFrameSize, + au_scope, + au_element, + &new_size_frames, sizeof(new_size_frames)); if (r != noErr) { - LOG("AudioUnitSetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d", - to_string(side), r); + 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); + r = AudioUnitRemovePropertyListenerWithUserData(au, + kAudioDevicePropertyBufferFrameSize, + buffer_size_changed_callback, + stm); if (r != noErr) { - LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize " - "rv=%d", - to_string(side), r); + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); + } } return CUBEB_ERROR; @@ -2333,21 +1432,22 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, 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); + 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); + r = AudioUnitRemovePropertyListenerWithUserData(au, + kAudioDevicePropertyBufferFrameSize, + buffer_size_changed_callback, + stm); if (r != noErr) { - LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize " - "rv=%d", - to_string(side), r); + if (set_side == INPUT) { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r); + } else { + PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r); + } return CUBEB_ERROR; } @@ -2356,33 +1456,32 @@ audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, return CUBEB_ERROR; } - LOG("(%p) %s buffer size changed to %u frames.", stm, to_string(side), - new_size_frames); + 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) { - assert(stm && stm->input_unit); - int r = 0; UInt32 size; AURenderCallbackStruct aurcbs_in; - LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in " - "frames %u.", + 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, + r = AudioUnitGetProperty(stm->input_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + AU_IN_BUS, + &input_hw_desc, &size); if (r != noErr) { - LOG("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r); + PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } stm->input_hw_rate = input_hw_desc.mSampleRate; @@ -2396,7 +1495,8 @@ audiounit_configure_input(cubeb_stream * stm) } // Use latency to set buffer size - r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::INPUT); + 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; @@ -2407,22 +1507,26 @@ audiounit_configure_input(cubeb_stream * stm) 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, + r = AudioUnitSetProperty(stm->input_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + AU_IN_BUS, + &src_desc, sizeof(AudioStreamBasicDescription)); if (r != noErr) { - LOG("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r); + 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->latency_frames, sizeof(UInt32)); + r = AudioUnitSetProperty(stm->input_unit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + AU_IN_BUS, + &stm->input_buffer_frames, + sizeof(UInt32)); if (r != noErr) { - LOG("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice " - "rv=%d", - r); + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r); return CUBEB_ERROR; } @@ -2436,21 +1540,20 @@ audiounit_configure_input(cubeb_stream * stm) 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)); + r = AudioUnitSetProperty(stm->input_unit, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Global, + AU_OUT_BUS, + &aurcbs_in, + sizeof(aurcbs_in)); if (r != noErr) { - LOG("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback " - "rv=%d", - r); + PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r); return CUBEB_ERROR; } - - stm->frames_read = 0; - LOG("(%p) Input audiounit init successfully.", stm); return CUBEB_OK; @@ -2459,14 +1562,12 @@ audiounit_configure_input(cubeb_stream * stm) static int audiounit_configure_output(cubeb_stream * stm) { - assert(stm && stm->output_unit); - int r; AURenderCallbackStruct aurcbs_out; UInt32 size; - LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in " - "frames %u.", + + 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); @@ -2480,75 +1581,62 @@ audiounit_configure_output(cubeb_stream * stm) 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, + r = AudioUnitGetProperty(stm->output_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + AU_OUT_BUS, + &output_hw_desc, &size); if (r != noErr) { - LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r); + 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); - stm->context->channels = output_hw_desc.mChannelsPerFrame; - - // Set the input layout to match the output device layout. - audiounit_layout_init(stm, io_side::OUTPUT); - if (stm->context->channels != stm->output_stream_params.channels || - stm->context->layout != stm->output_stream_params.layout) { - LOG("Incompatible channel layouts detected, setting up remixer"); - audiounit_init_mixer(stm); - // We will be remixing the data before it reaches the output device. - // We need to adjust the number of channels and other - // AudioStreamDescription details. - stm->output_desc.mChannelsPerFrame = stm->context->channels; - stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) * - stm->output_desc.mChannelsPerFrame; - stm->output_desc.mBytesPerPacket = - stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket; - } else { - stm->mixer = nullptr; - } + 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, + r = AudioUnitSetProperty(stm->output_unit, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + AU_OUT_BUS, + &stm->output_desc, sizeof(AudioStreamBasicDescription)); if (r != noErr) { - LOG("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r); + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r); return CUBEB_ERROR; } - r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::OUTPUT); + 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)); + r = AudioUnitSetProperty(stm->output_unit, + kAudioUnitProperty_MaximumFramesPerSlice, + kAudioUnitScope_Global, + AU_OUT_BUS, + &stm->latency_frames, + sizeof(UInt32)); if (r != noErr) { - LOG("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice " - "rv=%d", - r); + 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)); + r = AudioUnitSetProperty(stm->output_unit, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Global, + AU_OUT_BUS, + &aurcbs_out, + sizeof(aurcbs_out)); if (r != noErr) { - LOG("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback " - "rv=%d", - r); + PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r); return CUBEB_ERROR; } - stm->frames_written = 0; - LOG("(%p) Output audiounit init successfully.", stm); return CUBEB_OK; } @@ -2558,37 +1646,11 @@ audiounit_setup_stream(cubeb_stream * stm) { stm->mutex.assert_current_thread_owns(); - if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) || - (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) { - LOG("(%p) Loopback not supported for audiounit.", stm); - return CUBEB_ERROR_NOT_SUPPORTED; - } - int r = 0; - - device_info in_dev_info = stm->input_device; - device_info out_dev_info = stm->output_device; - - if (has_input(stm) && has_output(stm) && - stm->input_device.id != stm->output_device.id) { - r = audiounit_create_aggregate_device(stm); - if (r != CUBEB_OK) { - stm->aggregate_device_id = kAudioObjectUnknown; - LOG("(%p) Create aggregate devices failed.", stm); - // !!!NOTE: It is not necessary to return here. If it does not - // return it will fallback to the old implementation. The intention - // is to investigate how often it fails. I plan to remove - // it after a couple of weeks. - return r; - } else { - in_dev_info.id = out_dev_info.id = stm->aggregate_device_id; - in_dev_info.flags = DEV_INPUT; - out_dev_info.flags = DEV_OUTPUT; - } - } - if (has_input(stm)) { - r = audiounit_create_unit(&stm->input_unit, &in_dev_info); + 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; @@ -2596,28 +1658,30 @@ audiounit_setup_stream(cubeb_stream * stm) } if (has_output(stm)) { - r = audiounit_create_unit(&stm->output_unit, &out_dev_info); + 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 latency is set to the other stream value. */ - if (audiounit_active_streams(stm->context) > 1) { + /* 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. */ + * 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); // Ugly error check - audiounit_set_global_latency(stm->context, stm->latency_frames); + assert(stm->latency_frames); // Ungly error check + audiounit_set_global_latency(stm, stm->latency_frames); } - /* Configure I/O stream */ + /* Setup Input Stream! */ if (has_input(stm)) { r = audiounit_configure_input(stm); if (r != CUBEB_OK) { @@ -2626,6 +1690,7 @@ audiounit_setup_stream(cubeb_stream * stm) } } + /* Setup Output Stream! */ if (has_output(stm)) { r = audiounit_configure_output(stm); if (r != CUBEB_OK) { @@ -2670,7 +1735,7 @@ audiounit_setup_stream(cubeb_stream * stm) return CUBEB_ERROR; } } -#else // TARGET_OS_IPHONE +#else // TARGET_OS_IPHONE //TODO: [[AVAudioSession sharedInstance] inputLatency] // http://stackoverflow.com/questions/13157523/kaudiodevicepropertybufferframesize-replacement-for-ios #endif @@ -2697,10 +1762,13 @@ audiounit_setup_stream(cubeb_stream * stm) /* Create resampler. Output params are unchanged * because we do not need conversion on the output. */ - stm->resampler.reset(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)); + 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; @@ -2709,7 +1777,7 @@ audiounit_setup_stream(cubeb_stream * stm) if (stm->input_unit != NULL) { r = AudioUnitInitialize(stm->input_unit); if (r != noErr) { - LOG("AudioUnitInitialize/input rv=%d", r); + PRINT_ERROR_CODE("AudioUnitInitialize/input", r); return CUBEB_ERROR; } } @@ -2717,221 +1785,175 @@ audiounit_setup_stream(cubeb_stream * stm) if (stm->output_unit != NULL) { r = AudioUnitInitialize(stm->output_unit); if (r != noErr) { - LOG("AudioUnitInitialize/output rv=%d", r); + PRINT_ERROR_CODE("AudioUnitInitialize/output", r); return CUBEB_ERROR; } - - stm->current_latency_frames = audiounit_get_device_presentation_latency( - stm->output_device.id, kAudioDevicePropertyScopeOutput); - - Float64 unit_s; - UInt32 size = sizeof(unit_s); - if (AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency, - kAudioUnitScope_Global, 0, &unit_s, - &size) == noErr) { - stm->current_latency_frames += - static_cast<uint32_t>(unit_s * stm->output_desc.mSampleRate); - } } 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); + // 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 all device change callback.", stm); + LOG("(%p) Could not install the device change callback.", stm); + return r; } return CUBEB_OK; } -cubeb_stream::cubeb_stream(cubeb * context) - : context(context), resampler(nullptr, cubeb_resampler_destroy), - mixer(nullptr, cubeb_mixer_destroy) -{ - PodZero(&input_desc, 1); - PodZero(&output_desc, 1); -} - -static void -audiounit_stream_destroy_internal(cubeb_stream * stm); - static int -audiounit_stream_init(cubeb * context, cubeb_stream ** stream, - char const * /* stream_name */, cubeb_devid input_device, +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_state_callback state_callback, + void * user_ptr) { - assert(context); - auto_lock context_lock(context->mutex); - audiounit_increment_active_streams(context); - unique_ptr<cubeb_stream, decltype(&audiounit_stream_destroy)> stm( - new cubeb_stream(context), audiounit_stream_destroy_internal); + 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; - - if ((input_device && !input_stream_params) || - (output_device && !output_stream_params)) { - return CUBEB_ERROR_INVALID_PARAMETER; - } + stm->device_changed_callback = NULL; if (input_stream_params) { stm->input_stream_params = *input_stream_params; - r = audiounit_set_device_info( - stm.get(), reinterpret_cast<uintptr_t>(input_device), io_side::INPUT); - if (r != CUBEB_OK) { - LOG("(%p) Fail to set device info for input.", stm.get()); - return r; - } + stm->input_device = input_device; + stm->is_default_input = input_device == nullptr || + (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == + reinterpret_cast<intptr_t>(input_device)); } if (output_stream_params) { stm->output_stream_params = *output_stream_params; - r = audiounit_set_device_info( - stm.get(), reinterpret_cast<uintptr_t>(output_device), io_side::OUTPUT); - if (r != CUBEB_OK) { - LOG("(%p) Fail to set device info for output.", stm.get()); - return r; - } + 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.get()); + r = audiounit_setup_stream(stm); } if (r != CUBEB_OK) { - LOG("(%p) Could not setup the audiounit stream.", stm.get()); + LOG("(%p) Could not setup the audiounit stream.", stm); + audiounit_stream_destroy(stm); return r; } - r = audiounit_install_system_changed_callback(stm.get()); + r = audiounit_install_system_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not install the device change callback.", stm.get()); + LOG("(%p) Could not install the device change callback.", stm); return r; } - *stream = stm.release(); - LOG("(%p) Cubeb stream init successful.", *stream); + *stream = stm; + LOG("Cubeb stream (%p) init successful.", stm); return CUBEB_OK; } static void -audiounit_close_stream(cubeb_stream * stm) +audiounit_close_stream(cubeb_stream *stm) { stm->mutex.assert_current_thread_owns(); if (stm->input_unit) { AudioUnitUninitialize(stm->input_unit); AudioComponentInstanceDispose(stm->input_unit); - stm->input_unit = nullptr; } - stm->input_linear_buffer.reset(); + audiounit_destroy_input_linear_buffer(stm); if (stm->output_unit) { AudioUnitUninitialize(stm->output_unit); AudioComponentInstanceDispose(stm->output_unit); - stm->output_unit = nullptr; } - stm->resampler.reset(); - stm->mixer.reset(); - - if (stm->aggregate_device_id != kAudioObjectUnknown) { - audiounit_destroy_aggregate_device(stm->plugin_id, - &stm->aggregate_device_id); - stm->aggregate_device_id = kAudioObjectUnknown; - } + cubeb_resampler_destroy(stm->resampler); } static void -audiounit_stream_destroy_internal(cubeb_stream * stm) +audiounit_stream_destroy(cubeb_stream * stm) { - stm->context->mutex.assert_current_thread_owns(); + 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 all device change listeners", stm); - } - auto_lock lock(stm->mutex); - audiounit_close_stream(stm); - assert(audiounit_active_streams(stm->context) >= 1); - audiounit_decrement_active_streams(stm->context); -} - -static void -audiounit_stream_destroy(cubeb_stream * stm) -{ - 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 all device change listeners", stm); + LOG("(%p) Could not uninstall the device changed callback", stm); } - if (!stm->shutdown.load()) { - auto_lock context_lock(stm->context->mutex); - audiounit_stream_stop_internal(stm); - stm->shutdown = true; - } + auto_lock context_lock(stm->context->mutex); + audiounit_stream_stop_internal(stm); - stm->destroy_pending = true; // Execute close in serial queue to avoid collision // with reinit when un/plug devices dispatch_sync(stm->context->serial_queue, ^() { - auto_lock context_lock(stm->context->mutex); - audiounit_stream_destroy_internal(stm); + 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); - delete stm; + + stm->~cubeb_stream(); + free(stm); } -static int +void audiounit_stream_start_internal(cubeb_stream * stm) { OSStatus r; if (stm->input_unit != NULL) { r = AudioOutputUnitStart(stm->input_unit); - if (r != noErr) { - LOG("AudioOutputUnitStart (input) rv=%d", r); - return CUBEB_ERROR; - } + assert(r == 0); } if (stm->output_unit != NULL) { r = AudioOutputUnitStart(stm->output_unit); - if (r != noErr) { - LOG("AudioOutputUnitStart (output) rv=%d", r); - return CUBEB_ERROR; - } + assert(r == 0); } - return CUBEB_OK; } static int @@ -2941,10 +1963,7 @@ audiounit_stream_start(cubeb_stream * stm) stm->shutdown = false; stm->draining = false; - int r = audiounit_stream_start_internal(stm); - if (r != CUBEB_OK) { - return r; - } + audiounit_stream_start_internal(stm); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); @@ -2983,12 +2002,9 @@ audiounit_stream_stop(cubeb_stream * stm) static int audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) { - assert(stm); - if (stm->current_latency_frames > stm->frames_played) { - *position = 0; - } else { - *position = stm->frames_played - stm->current_latency_frames; - } + auto_lock lock(stm->mutex); + + *position = stm->frames_played; return CUBEB_OK; } @@ -2996,10 +2012,77 @@ int audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) { #if TARGET_OS_IPHONE - // TODO + //TODO return CUBEB_ERROR_NOT_SUPPORTED; #else - *latency = stm->total_output_latency_frames; + 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<uint32_t>(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 } @@ -3008,8 +2091,10 @@ 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); + 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; @@ -3017,137 +2102,182 @@ audiounit_stream_get_volume(cubeb_stream * stm, float * volume) return CUBEB_OK; } -static int -audiounit_stream_set_volume(cubeb_stream * stm, float volume) +int audiounit_stream_set_volume(cubeb_stream * stm, float volume) { - assert(stm->output_unit); OSStatus r; - r = AudioUnitSetParameter(stm->output_unit, kHALOutputParam_Volume, - kAudioUnitScope_Global, 0, volume, 0); + + r = AudioUnitSetParameter(stm->output_unit, + kHALOutputParam_Volume, + kAudioUnitScope_Global, + 0, volume, 0); if (r != noErr) { - LOG("AudioUnitSetParameter/kHALOutputParam_Volume rv=%d", r); + PRINT_ERROR_CODE("AudioUnitSetParameter/kHALOutputParam_Volume", r); return CUBEB_ERROR; } return CUBEB_OK; } -unique_ptr<char[]> -convert_uint32_into_string(UInt32 data) +int audiounit_stream_set_panning(cubeb_stream * stm, float panning) { - // Simply create an empty string if no data. - size_t size = data == 0 ? 0 : 4; // 4 bytes for uint32. - auto str = unique_ptr<char[]>{new char[size + 1]}; // + 1 for '\0'. - str[size] = '\0'; - if (size < 4) { - return str; + if (stm->output_desc.mChannelsPerFrame > 2) { + return CUBEB_ERROR_INVALID_PARAMETER; } - // Reverse 0xWXYZ into 0xZYXW. - str[0] = (char)(data >> 24); - str[1] = (char)(data >> 16); - str[2] = (char)(data >> 8); - str[3] = (char)(data); - return str; + stm->panning.store(panning, std::memory_order_relaxed); + return CUBEB_OK; } -int -audiounit_get_default_device_datasource(cubeb_device_type type, UInt32 * data) +int audiounit_stream_get_current_device(cubeb_stream * stm, + cubeb_device ** const device) { - AudioDeviceID id = audiounit_get_default_device_id(type); - if (id == kAudioObjectUnknown) { - return CUBEB_ERROR; - } +#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; - UInt32 size = sizeof(*data); - /* This fails with some USB headsets (e.g., Plantronic .Audio 628). */ - OSStatus r = AudioObjectGetPropertyData( - id, - type == CUBEB_DEVICE_TYPE_INPUT ? &INPUT_DATA_SOURCE_PROPERTY_ADDRESS - : &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS, - 0, NULL, &size, data); - if (r != noErr) { - *data = 0; - } + AudioObjectPropertyAddress datasource_address = { + kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; - return CUBEB_OK; -} + AudioObjectPropertyAddress datasource_address_input = { + kAudioDevicePropertyDataSource, + kAudioDevicePropertyScopeInput, + kAudioObjectPropertyElementMaster + }; -int -audiounit_get_default_device_name(cubeb_stream * stm, - cubeb_device * const device, - cubeb_device_type type) -{ - assert(stm); - assert(device); + *device = NULL; - UInt32 data; - int r = audiounit_get_default_device_datasource(type, &data); - if (r != CUBEB_OK) { - return r; - } - char ** name = type == CUBEB_DEVICE_TYPE_INPUT ? &device->input_name - : &device->output_name; - *name = convert_uint32_into_string(data).release(); - if (!strlen(*name)) { // empty string. - LOG("(%p) name of %s device is empty!", stm, - type == CUBEB_DEVICE_TYPE_INPUT ? "input" : "output"); + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + return CUBEB_ERROR; } - 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 *device = new cubeb_device; if (!*device) { return CUBEB_ERROR; } PodZero(*device, 1); - int r = - audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_OUTPUT); - if (r != CUBEB_OK) { - return r; + 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; } - r = audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_INPUT); - if (r != CUBEB_OK) { - return r; + (*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) +int audiounit_stream_device_destroy(cubeb_stream * /* stream */, + cubeb_device * device) { - delete[] device->output_name; - delete[] device->input_name; + 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) +int audiounit_stream_register_device_changed_callback(cubeb_stream * stream, + cubeb_device_changed_callback device_changed_callback) { - auto_lock dev_cb_lock(stream->device_changed_callback_lock); /* Note: second register without unregister first causes 'nope' error. * Current implementation requires unregister before register a new cb. */ - assert(!device_changed_callback || !stream->device_changed_callback); + 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<uint32_t>(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) { @@ -3158,12 +2288,11 @@ audiounit_strref_to_cstr_utf8(CFStringRef strref) } len = CFStringGetLength(strref); - // Add 1 to size to allow for '\0' termination character. - size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1; - ret = new char[size]; + size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8); + ret = static_cast<char *>(malloc(size)); if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) { - delete[] ret; + free(ret); ret = NULL; } @@ -3173,18 +2302,15 @@ audiounit_strref_to_cstr_utf8(CFStringRef strref) static uint32_t audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope) { - AudioObjectPropertyAddress adr = {0, scope, - kAudioObjectPropertyElementMaster}; + 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) { + if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr && size > 0) { AudioBufferList * list = static_cast<AudioBufferList *>(alloca(size)); - if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) == - noErr) { + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) == noErr) { for (i = 0; i < list->mNumberBuffers; i++) ret += list->mBuffers[i].mNumberChannels; } @@ -3194,20 +2320,16 @@ audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope) } static void -audiounit_get_available_samplerate(AudioObjectID devid, - AudioObjectPropertyScope scope, - uint32_t * min, uint32_t * max, - uint32_t * def) +audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope scope, + uint32_t * min, uint32_t * max, uint32_t * def) { - AudioObjectPropertyAddress adr = {0, scope, - kAudioObjectPropertyElementMaster}; + 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) { + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &fvalue) == noErr) { *def = fvalue; } } @@ -3217,33 +2339,32 @@ audiounit_get_available_samplerate(AudioObjectID devid, AudioValueRange range; if (AudioObjectHasProperty(devid, &adr) && AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) { - uint32_t count = size / sizeof(AudioValueRange); - vector<AudioValueRange> ranges(count); + 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.data()) == noErr) { - for (uint32_t i = 0; i < count; i++) { + 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<uint32_t>(range.mMaximum); *min = static_cast<uint32_t>(range.mMinimum); } else { *min = *max = 0; } + } static UInt32 -audiounit_get_device_presentation_latency(AudioObjectID devid, - AudioObjectPropertyScope scope) +audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectPropertyScope scope) { - AudioObjectPropertyAddress adr = {0, scope, - kAudioObjectPropertyElementMaster}; - UInt32 size, dev, stream = 0; + AudioObjectPropertyAddress adr = { 0, scope, kAudioObjectPropertyElementMaster }; + UInt32 size, dev, stream = 0, offset; AudioStreamID sid[1]; adr.mSelector = kAudioDevicePropertyLatency; @@ -3260,411 +2381,354 @@ audiounit_get_device_presentation_latency(AudioObjectID devid, AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream); } - return dev + stream; + adr.mSelector = kAudioDevicePropertySafetyOffset; + size = sizeof(UInt32); + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &offset) != noErr) { + offset = 0; + } + + return dev + stream + offset; } -static int -audiounit_create_device_from_hwdev(cubeb_device_info * dev_info, - AudioObjectID devid, cubeb_device_type type) +static cubeb_device_info * +audiounit_create_device_from_hwdev(AudioObjectID devid, cubeb_device_type type) { - AudioObjectPropertyAddress adr = {0, 0, kAudioObjectPropertyElementMaster}; - UInt32 size; + 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 CUBEB_ERROR; + return NULL; } - UInt32 ch = audiounit_get_channel_count(devid, adr.mScope); + ch = audiounit_get_channel_count(devid, adr.mScope); if (ch == 0) { - return CUBEB_ERROR; + return NULL; } - PodZero(dev_info, 1); + ret = new cubeb_device_info; + PodZero(ret, 1); - CFStringRef device_id_str = nullptr; size = sizeof(CFStringRef); adr.mSelector = kAudioDevicePropertyDeviceUID; - OSStatus ret = - AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &device_id_str); - if (ret == noErr && device_id_str != NULL) { - dev_info->device_id = audiounit_strref_to_cstr_utf8(device_id_str); - static_assert(sizeof(cubeb_devid) >= sizeof(decltype(devid)), - "cubeb_devid can't represent devid"); - dev_info->devid = reinterpret_cast<cubeb_devid>(devid); - dev_info->group_id = dev_info->device_id; - CFRelease(device_id_str); - } - - CFStringRef friendly_name_str = nullptr; - UInt32 ds; - size = sizeof(UInt32); - adr.mSelector = kAudioDevicePropertyDataSource; - ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds); - if (ret == noErr) { - AudioValueTranslation trl = {&ds, sizeof(ds), &friendly_name_str, - sizeof(CFStringRef)}; - adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString; - size = sizeof(AudioValueTranslation); - AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl); + 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); } - // If there is no datasource for this device, fall back to the - // device name. - if (!friendly_name_str) { - size = sizeof(CFStringRef); - adr.mSelector = kAudioObjectPropertyName; - AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &friendly_name_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; + } + } - if (friendly_name_str) { - dev_info->friendly_name = audiounit_strref_to_cstr_utf8(friendly_name_str); - CFRelease(friendly_name_str); - } else { - // Couldn't get a datasource name nor a device name, return a - // valid string of length 0. - char * fallback_name = new char[1]; - fallback_name[0] = '\0'; - dev_info->friendly_name = fallback_name; + ret->friendly_name = audiounit_strref_to_cstr_utf8(str); + CFRelease(str); } - CFStringRef vendor_name_str = nullptr; size = sizeof(CFStringRef); adr.mSelector = kAudioObjectPropertyManufacturer; - ret = - AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &vendor_name_str); - if (ret == noErr && vendor_name_str != NULL) { - dev_info->vendor_name = audiounit_strref_to_cstr_utf8(vendor_name_str); - CFRelease(vendor_name_str); - } - - dev_info->type = type; - dev_info->state = CUBEB_DEVICE_STATE_ENABLED; - dev_info->preferred = (devid == audiounit_get_default_device_id(type)) - ? CUBEB_DEVICE_PREF_ALL - : CUBEB_DEVICE_PREF_NONE; - - dev_info->max_channels = ch; - dev_info->format = - (cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */ + 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 */ - dev_info->default_format = CUBEB_DEVICE_FMT_F32NE; - audiounit_get_available_samplerate(devid, adr.mScope, &dev_info->min_rate, - &dev_info->max_rate, - &dev_info->default_rate); + ret->default_format = CUBEB_DEVICE_FMT_F32NE; + audiounit_get_available_samplerate(devid, adr.mScope, + &ret->min_rate, &ret->max_rate, &ret->default_rate); - UInt32 latency = audiounit_get_device_presentation_latency(devid, adr.mScope); + latency = audiounit_get_device_presentation_latency(devid, adr.mScope); - AudioValueRange range; adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange; size = sizeof(AudioValueRange); - ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range); - if (ret == noErr) { - dev_info->latency_lo = latency + range.mMinimum; - dev_info->latency_hi = latency + range.mMaximum; + if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range) == noErr) { + ret->latency_lo = latency + range.mMinimum; + ret->latency_hi = latency + range.mMaximum; } else { - dev_info->latency_lo = - 10 * dev_info->default_rate / 1000; /* Default to 10ms */ - dev_info->latency_hi = - 100 * dev_info->default_rate / 1000; /* Default to 100ms */ + ret->latency_lo = 10 * ret->default_rate / 1000; /* Default to 10ms */ + ret->latency_hi = 100 * ret->default_rate / 1000; /* Default to 100ms */ } - return CUBEB_OK; -} - -bool -is_aggregate_device(cubeb_device_info * device_info) -{ - assert(device_info->friendly_name); - return !strncmp(device_info->friendly_name, PRIVATE_AGGREGATE_DEVICE_NAME, - strlen(PRIVATE_AGGREGATE_DEVICE_NAME)); + return ret; } static int audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type, - cubeb_device_collection * collection) + cubeb_device_collection ** collection) { - vector<AudioObjectID> input_devs; - vector<AudioObjectID> output_devs; + AudioObjectID * hwdevs = NULL; + uint32_t i, hwdevcount = 0; + OSStatus err; - // Count number of input and output devices. This is not - // necessarily the same as the count of raw devices supported by the - // system since, for example, with Soundflower installed, some - // devices may report as being both input *and* output and cubeb - // separates those into two different devices. - - if (type & CUBEB_DEVICE_TYPE_OUTPUT) { - output_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); + if ((err = audiounit_get_devices(&hwdevs, &hwdevcount)) != noErr) { + return CUBEB_ERROR; } - if (type & CUBEB_DEVICE_TYPE_INPUT) { - input_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); - } + *collection = static_cast<cubeb_device_collection *>(malloc(sizeof(cubeb_device_collection) + + sizeof(cubeb_device_info*) * (hwdevcount > 0 ? hwdevcount - 1 : 0))); + (*collection)->count = 0; - auto devices = new cubeb_device_info[output_devs.size() + input_devs.size()]; - collection->count = 0; + if (hwdevcount > 0) { + cubeb_device_info * cur; - if (type & CUBEB_DEVICE_TYPE_OUTPUT) { - for (auto dev : output_devs) { - auto device = &devices[collection->count]; - auto err = audiounit_create_device_from_hwdev(device, dev, - CUBEB_DEVICE_TYPE_OUTPUT); - if (err != CUBEB_OK || is_aggregate_device(device)) { - continue; + 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; } - collection->count += 1; } - } - if (type & CUBEB_DEVICE_TYPE_INPUT) { - for (auto dev : input_devs) { - auto device = &devices[collection->count]; - auto err = audiounit_create_device_from_hwdev(device, dev, - CUBEB_DEVICE_TYPE_INPUT); - if (err != CUBEB_OK || is_aggregate_device(device)) { - continue; + 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; } - collection->count += 1; } } - if (collection->count > 0) { - collection->device = devices; - } else { - delete[] devices; - collection->device = NULL; - } + delete [] hwdevs; return CUBEB_OK; } -static void -audiounit_device_destroy(cubeb_device_info * device) +/* qsort compare method. */ +int compare_devid(const void * a, const void * b) { - delete[] device->device_id; - delete[] device->friendly_name; - delete[] device->vendor_name; + return (*(AudioObjectID*)a - *(AudioObjectID*)b); } -static int -audiounit_device_collection_destroy(cubeb * /* context */, - cubeb_device_collection * collection) +static uint32_t +audiounit_get_devices_of_type(cubeb_device_type devtype, AudioObjectID ** devid_array) { - for (size_t i = 0; i < collection->count; i++) { - audiounit_device_destroy(&collection->device[i]); - } - delete[] collection->device; + assert(devid_array == NULL || *devid_array == NULL); - return CUBEB_OK; -} - -static vector<AudioObjectID> -audiounit_get_devices_of_type(cubeb_device_type devtype) -{ + AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; UInt32 size = 0; - OSStatus ret = AudioObjectGetPropertyDataSize( - kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size); + OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); if (ret != noErr) { - return vector<AudioObjectID>(); - } - vector<AudioObjectID> devices(size / sizeof(AudioObjectID)); - ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size, - devices.data()); - if (ret != noErr) { - return vector<AudioObjectID>(); + return 0; } + /* Total number of input and output devices. */ + uint32_t count = (uint32_t)(size / sizeof(AudioObjectID)); - // Remove the aggregate device from the list of devices (if any). - for (auto it = devices.begin(); it != devices.end();) { - CFStringRef name = get_device_name(*it); - if (name && CFStringFind(name, CFSTR("CubebAggregateDevice"), 0).location != - kCFNotFound) { - it = devices.erase(it); - } else { - it++; - } - if (name) { - CFRelease(name); - } + 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. */ - sort(devices.begin(), devices.end(), - [](AudioObjectID a, AudioObjectID b) { return a < b; }); + qsort(devices, count, sizeof(AudioObjectID), compare_devid); if (devtype == (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) { - return devices; + 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; + AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT) ? + kAudioDevicePropertyScopeInput : + kAudioDevicePropertyScopeOutput; - vector<AudioObjectID> devices_in_scope; - for (uint32_t i = 0; i < devices.size(); ++i) { + 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.push_back(devices[i]); + devices_in_scope[dev_count] = devices[i]; + ++dev_count; } } - return devices_in_scope; + 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) +audiounit_collection_changed_callback(AudioObjectID /* inObjectID */, + UInt32 /* inNumberAddresses */, + const AudioObjectPropertyAddress * /* inAddresses */, + void * inClientData) { cubeb * context = static_cast<cubeb *>(inClientData); + auto_lock lock(context->mutex); - // This can be called from inside an AudioUnit function, dispatch to another - // queue. - dispatch_async(context->serial_queue, ^() { - auto_lock lock(context->mutex); - if (!context->input_collection_changed_callback && - !context->output_collection_changed_callback) { - /* Listener removed while waiting in mutex, abort. */ - return; - } - if (context->input_collection_changed_callback) { - vector<AudioObjectID> devices = - audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); - /* Elements in the vector expected sorted. */ - if (context->input_device_array != devices) { - context->input_device_array = devices; - context->input_collection_changed_callback( - context, context->input_collection_changed_user_ptr); - } - } - if (context->output_collection_changed_callback) { - vector<AudioObjectID> devices = - audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); - /* Elements in the vector expected sorted. */ - if (context->output_device_array != devices) { - context->output_device_array = devices; - context->output_collection_changed_callback( - context, context->output_collection_changed_user_ptr); - } + 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) +audiounit_add_device_listener(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) { - context->mutex.assert_current_thread_owns(); - assert(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)); /* Note: second register without unregister first causes 'nope' error. * Current implementation requires unregister before register a new cb. */ - assert((devtype & CUBEB_DEVICE_TYPE_INPUT) && - !context->input_collection_changed_callback || - (devtype & CUBEB_DEVICE_TYPE_OUTPUT) && - !context->output_collection_changed_callback); - - if (!context->input_collection_changed_callback && - !context->output_collection_changed_callback) { - OSStatus ret = AudioObjectAddPropertyListener( - kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS, - audiounit_collection_changed_callback, context); - if (ret != noErr) { - return ret; - } - } - if (devtype & CUBEB_DEVICE_TYPE_INPUT) { - /* Expected empty after unregister. */ - assert(context->input_device_array.empty()); - context->input_device_array = - audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); - context->input_collection_changed_callback = collection_changed_callback; - context->input_collection_changed_user_ptr = user_ptr; - } - if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { - /* Expected empty after unregister. */ - assert(context->output_device_array.empty()); - context->output_device_array = - audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); - context->output_collection_changed_callback = collection_changed_callback; - context->output_collection_changed_user_ptr = user_ptr; + 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 noErr; + return ret; } static OSStatus -audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype) +audiounit_remove_device_listener(cubeb * context) { - context->mutex.assert_current_thread_owns(); + AudioObjectPropertyAddress devAddr; + devAddr.mSelector = kAudioHardwarePropertyDevices; + devAddr.mScope = kAudioObjectPropertyScopeGlobal; + devAddr.mElement = kAudioObjectPropertyElementMaster; - if (devtype & CUBEB_DEVICE_TYPE_INPUT) { - context->input_collection_changed_callback = nullptr; - context->input_collection_changed_user_ptr = nullptr; - context->input_device_array.clear(); - } - if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { - context->output_collection_changed_callback = nullptr; - context->output_collection_changed_user_ptr = nullptr; - context->output_device_array.clear(); - } - - if (context->input_collection_changed_callback || - context->output_collection_changed_callback) { - return noErr; - } /* Note: unregister a non registered cb is not a problem, not checking. */ - return AudioObjectRemovePropertyListener( - kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS, - audiounit_collection_changed_callback, context); + 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) +int audiounit_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) { - if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) { - return CUBEB_ERROR_INVALID_PARAMETER; - } OSStatus ret; auto_lock lock(context->mutex); if (collection_changed_callback) { ret = audiounit_add_device_listener(context, devtype, - collection_changed_callback, user_ptr); + collection_changed_callback, + user_ptr); } else { - ret = audiounit_remove_device_listener(context, devtype); + 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, - /*.device_collection_destroy =*/audiounit_device_collection_destroy, - /*.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_get_input_latency =*/NULL, - /*.stream_set_volume =*/audiounit_stream_set_volume, - /*.stream_set_name =*/NULL, - /*.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}; + /*.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 +}; |