Path: blob/master/dep/cubeb/src/cubeb_audiounit.cpp
4246 views
/*1* Copyright © 2011 Mozilla Foundation2*3* This program is made available under an ISC-style license. See the4* accompanying file LICENSE for details.5*/6#undef NDEBUG78#include <AudioUnit/AudioUnit.h>9#include <TargetConditionals.h>10#include <assert.h>11#include <mach/mach_time.h>12#include <pthread.h>13#include <stdlib.h>14#if !TARGET_OS_IPHONE15#include <AvailabilityMacros.h>16#include <CoreAudio/AudioHardware.h>17#include <CoreAudio/HostTime.h>18#include <CoreFoundation/CoreFoundation.h>19#endif20#include "cubeb-internal.h"21#include "cubeb/cubeb.h"22#include "cubeb_mixer.h"23#include <AudioToolbox/AudioToolbox.h>24#include <CoreAudio/CoreAudioTypes.h>25#if !TARGET_OS_IPHONE26#include "cubeb_osx_run_loop.h"27#endif28#include "cubeb_resampler.h"29#include "cubeb_ring_array.h"30#include <algorithm>31#include <atomic>32#include <set>33#include <string>34#include <sys/time.h>35#include <vector>3637using namespace std;3839#if MAC_OS_X_VERSION_MIN_REQUIRED < 10100040typedef UInt32 AudioFormatFlags;41#endif4243#define AU_OUT_BUS 044#define AU_IN_BUS 14546const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";47const char * PRIVATE_AGGREGATE_DEVICE_NAME = "CubebAggregateDevice";4849#ifdef ALOGV50#undef ALOGV51#endif52#define ALOGV(msg, ...) \53dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \54^{ \55LOGV(msg, ##__VA_ARGS__); \56})5758#ifdef ALOG59#undef ALOG60#endif61#define ALOG(msg, ...) \62dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), \63^{ \64LOG(msg, ##__VA_ARGS__); \65})6667/* Testing empirically, some headsets report a minimal latency that is very68* low, but this does not work in practice. Lie and say the minimum is 25669* frames. */70const uint32_t SAFE_MIN_LATENCY_FRAMES = 128;71const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;7273const AudioObjectPropertyAddress DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS = {74kAudioHardwarePropertyDefaultInputDevice, kAudioObjectPropertyScopeGlobal,75kAudioObjectPropertyElementMaster};7677const AudioObjectPropertyAddress DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS = {78kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal,79kAudioObjectPropertyElementMaster};8081const AudioObjectPropertyAddress DEVICE_IS_ALIVE_PROPERTY_ADDRESS = {82kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal,83kAudioObjectPropertyElementMaster};8485const AudioObjectPropertyAddress DEVICES_PROPERTY_ADDRESS = {86kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,87kAudioObjectPropertyElementMaster};8889const AudioObjectPropertyAddress INPUT_DATA_SOURCE_PROPERTY_ADDRESS = {90kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput,91kAudioObjectPropertyElementMaster};9293const AudioObjectPropertyAddress OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS = {94kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput,95kAudioObjectPropertyElementMaster};9697typedef uint32_t device_flags_value;9899enum device_flags {100DEV_UNKNOWN = 0x00, /* Unknown */101DEV_INPUT = 0x01, /* Record device like mic */102DEV_OUTPUT = 0x02, /* Playback device like speakers */103DEV_SYSTEM_DEFAULT = 0x04, /* System default device */104DEV_SELECTED_DEFAULT =1050x08, /* User selected to use the system default device */106};107108void109audiounit_stream_stop_internal(cubeb_stream * stm);110static int111audiounit_stream_start_internal(cubeb_stream * stm);112static void113audiounit_close_stream(cubeb_stream * stm);114static int115audiounit_setup_stream(cubeb_stream * stm);116static vector<AudioObjectID>117audiounit_get_devices_of_type(cubeb_device_type devtype);118static UInt32119audiounit_get_device_presentation_latency(AudioObjectID devid,120AudioObjectPropertyScope scope);121122#if !TARGET_OS_IPHONE123static AudioObjectID124audiounit_get_default_device_id(cubeb_device_type type);125static int126audiounit_uninstall_device_changed_callback(cubeb_stream * stm);127static int128audiounit_uninstall_system_changed_callback(cubeb_stream * stm);129static void130audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags);131#endif132133extern cubeb_ops const audiounit_ops;134135struct cubeb {136cubeb_ops const * ops = &audiounit_ops;137owned_critical_section mutex;138int active_streams = 0;139uint32_t global_latency_frames = 0;140cubeb_device_collection_changed_callback input_collection_changed_callback =141nullptr;142void * input_collection_changed_user_ptr = nullptr;143cubeb_device_collection_changed_callback output_collection_changed_callback =144nullptr;145void * output_collection_changed_user_ptr = nullptr;146// Store list of devices to detect changes147vector<AudioObjectID> input_device_array;148vector<AudioObjectID> output_device_array;149// The queue should be released when it’s no longer needed.150dispatch_queue_t serial_queue =151dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);152// Current used channel layout153atomic<cubeb_channel_layout> layout{CUBEB_LAYOUT_UNDEFINED};154uint32_t channels = 0;155};156157static unique_ptr<AudioChannelLayout, decltype(&free)>158make_sized_audio_channel_layout(size_t sz)159{160assert(sz >= sizeof(AudioChannelLayout));161AudioChannelLayout * acl =162reinterpret_cast<AudioChannelLayout *>(calloc(1, sz));163assert(acl); // Assert the allocation works.164return unique_ptr<AudioChannelLayout, decltype(&free)>(acl, free);165}166167enum class io_side {168INPUT,169OUTPUT,170};171172static char const *173to_string(io_side side)174{175switch (side) {176case io_side::INPUT:177return "input";178case io_side::OUTPUT:179return "output";180}181}182183struct device_info {184AudioDeviceID id = kAudioObjectUnknown;185device_flags_value flags = DEV_UNKNOWN;186};187188struct property_listener {189AudioDeviceID device_id;190const AudioObjectPropertyAddress * property_address;191AudioObjectPropertyListenerProc callback;192cubeb_stream * stream;193194property_listener(AudioDeviceID id,195const AudioObjectPropertyAddress * address,196AudioObjectPropertyListenerProc proc, cubeb_stream * stm)197: device_id(id), property_address(address), callback(proc), stream(stm)198{199}200};201202struct cubeb_stream {203explicit cubeb_stream(cubeb * context);204205/* Note: Must match cubeb_stream layout in cubeb.c. */206cubeb * context;207void * user_ptr = nullptr;208/**/209210cubeb_data_callback data_callback = nullptr;211cubeb_state_callback state_callback = nullptr;212cubeb_device_changed_callback device_changed_callback = nullptr;213owned_critical_section device_changed_callback_lock;214/* Stream creation parameters */215cubeb_stream_params input_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,216CUBEB_LAYOUT_UNDEFINED,217CUBEB_STREAM_PREF_NONE};218cubeb_stream_params output_stream_params = {CUBEB_SAMPLE_FLOAT32NE, 0, 0,219CUBEB_LAYOUT_UNDEFINED,220CUBEB_STREAM_PREF_NONE};221device_info input_device;222device_info output_device;223/* Format descriptions */224AudioStreamBasicDescription input_desc;225AudioStreamBasicDescription output_desc;226/* I/O AudioUnits */227AudioUnit input_unit = nullptr;228AudioUnit output_unit = nullptr;229/* I/O device sample rate */230Float64 input_hw_rate = 0;231Float64 output_hw_rate = 0;232/* Expected I/O thread interleave,233* calculated from I/O hw rate. */234int expected_output_callbacks_in_a_row = 0;235owned_critical_section mutex;236// Hold the input samples in every input callback iteration.237// Only accessed on input/output callback thread and during initial configure.238unique_ptr<auto_array_wrapper> input_linear_buffer;239/* Frame counters */240atomic<uint64_t> frames_played{0};241uint64_t frames_queued = 0;242// How many frames got read from the input since the stream started (includes243// padded silence)244atomic<int64_t> frames_read{0};245// How many frames got written to the output device since the stream started246atomic<int64_t> frames_written{0};247atomic<bool> shutdown{true};248atomic<bool> draining{false};249atomic<bool> reinit_pending{false};250atomic<bool> destroy_pending{false};251/* Latency requested by the user. */252uint32_t latency_frames = 0;253atomic<uint32_t> current_latency_frames{0};254atomic<uint32_t> total_output_latency_frames{0};255unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;256/* This is true if a device change callback is currently running. */257atomic<bool> switching_device{false};258atomic<bool> buffer_size_change_state{false};259AudioDeviceID aggregate_device_id =260kAudioObjectUnknown; // the aggregate device id261AudioObjectID plugin_id =262kAudioObjectUnknown; // used to create aggregate device263/* Mixer interface */264unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> mixer;265/* Buffer where remixing/resampling will occur when upmixing is required */266/* Only accessed from callback thread */267unique_ptr<uint8_t[]> temp_buffer;268size_t temp_buffer_size = 0; // size in bytes.269/* Listeners indicating what system events are monitored. */270unique_ptr<property_listener> default_input_listener;271unique_ptr<property_listener> default_output_listener;272unique_ptr<property_listener> input_alive_listener;273unique_ptr<property_listener> input_source_listener;274unique_ptr<property_listener> output_source_listener;275};276277bool278has_input(cubeb_stream * stm)279{280return stm->input_stream_params.rate != 0;281}282283bool284has_output(cubeb_stream * stm)285{286return stm->output_stream_params.rate != 0;287}288289cubeb_channel290channel_label_to_cubeb_channel(UInt32 label)291{292switch (label) {293case kAudioChannelLabel_Left:294return CHANNEL_FRONT_LEFT;295case kAudioChannelLabel_Right:296return CHANNEL_FRONT_RIGHT;297case kAudioChannelLabel_Center:298return CHANNEL_FRONT_CENTER;299case kAudioChannelLabel_LFEScreen:300return CHANNEL_LOW_FREQUENCY;301case kAudioChannelLabel_LeftSurround:302return CHANNEL_BACK_LEFT;303case kAudioChannelLabel_RightSurround:304return CHANNEL_BACK_RIGHT;305case kAudioChannelLabel_LeftCenter:306return CHANNEL_FRONT_LEFT_OF_CENTER;307case kAudioChannelLabel_RightCenter:308return CHANNEL_FRONT_RIGHT_OF_CENTER;309case kAudioChannelLabel_CenterSurround:310return CHANNEL_BACK_CENTER;311case kAudioChannelLabel_LeftSurroundDirect:312return CHANNEL_SIDE_LEFT;313case kAudioChannelLabel_RightSurroundDirect:314return CHANNEL_SIDE_RIGHT;315case kAudioChannelLabel_TopCenterSurround:316return CHANNEL_TOP_CENTER;317case kAudioChannelLabel_VerticalHeightLeft:318return CHANNEL_TOP_FRONT_LEFT;319case kAudioChannelLabel_VerticalHeightCenter:320return CHANNEL_TOP_FRONT_CENTER;321case kAudioChannelLabel_VerticalHeightRight:322return CHANNEL_TOP_FRONT_RIGHT;323case kAudioChannelLabel_TopBackLeft:324return CHANNEL_TOP_BACK_LEFT;325case kAudioChannelLabel_TopBackCenter:326return CHANNEL_TOP_BACK_CENTER;327case kAudioChannelLabel_TopBackRight:328return CHANNEL_TOP_BACK_RIGHT;329default:330return CHANNEL_UNKNOWN;331}332}333334AudioChannelLabel335cubeb_channel_to_channel_label(cubeb_channel channel)336{337switch (channel) {338case CHANNEL_FRONT_LEFT:339return kAudioChannelLabel_Left;340case CHANNEL_FRONT_RIGHT:341return kAudioChannelLabel_Right;342case CHANNEL_FRONT_CENTER:343return kAudioChannelLabel_Center;344case CHANNEL_LOW_FREQUENCY:345return kAudioChannelLabel_LFEScreen;346case CHANNEL_BACK_LEFT:347return kAudioChannelLabel_LeftSurround;348case CHANNEL_BACK_RIGHT:349return kAudioChannelLabel_RightSurround;350case CHANNEL_FRONT_LEFT_OF_CENTER:351return kAudioChannelLabel_LeftCenter;352case CHANNEL_FRONT_RIGHT_OF_CENTER:353return kAudioChannelLabel_RightCenter;354case CHANNEL_BACK_CENTER:355return kAudioChannelLabel_CenterSurround;356case CHANNEL_SIDE_LEFT:357return kAudioChannelLabel_LeftSurroundDirect;358case CHANNEL_SIDE_RIGHT:359return kAudioChannelLabel_RightSurroundDirect;360case CHANNEL_TOP_CENTER:361return kAudioChannelLabel_TopCenterSurround;362case CHANNEL_TOP_FRONT_LEFT:363return kAudioChannelLabel_VerticalHeightLeft;364case CHANNEL_TOP_FRONT_CENTER:365return kAudioChannelLabel_VerticalHeightCenter;366case CHANNEL_TOP_FRONT_RIGHT:367return kAudioChannelLabel_VerticalHeightRight;368case CHANNEL_TOP_BACK_LEFT:369return kAudioChannelLabel_TopBackLeft;370case CHANNEL_TOP_BACK_CENTER:371return kAudioChannelLabel_TopBackCenter;372case CHANNEL_TOP_BACK_RIGHT:373return kAudioChannelLabel_TopBackRight;374default:375return kAudioChannelLabel_Unknown;376}377}378379bool380is_common_sample_rate(Float64 sample_rate)381{382/* Some commonly used sample rates and their multiples and divisors. */383return sample_rate == 8000 || sample_rate == 16000 || sample_rate == 22050 ||384sample_rate == 32000 || sample_rate == 44100 || sample_rate == 48000 ||385sample_rate == 88200 || sample_rate == 96000;386}387388#if TARGET_OS_IPHONE389typedef UInt32 AudioDeviceID;390typedef UInt32 AudioObjectID;391392#define AudioGetCurrentHostTime mach_absolute_time393394#endif395396uint64_t397ConvertHostTimeToNanos(uint64_t host_time)398{399static struct mach_timebase_info timebase_info;400static bool initialized = false;401if (!initialized) {402mach_timebase_info(&timebase_info);403initialized = true;404}405406long double answer = host_time;407if (timebase_info.numer != timebase_info.denom) {408answer *= timebase_info.numer;409answer /= timebase_info.denom;410}411return (uint64_t)answer;412}413414static void415audiounit_increment_active_streams(cubeb * ctx)416{417ctx->mutex.assert_current_thread_owns();418ctx->active_streams += 1;419}420421static void422audiounit_decrement_active_streams(cubeb * ctx)423{424ctx->mutex.assert_current_thread_owns();425ctx->active_streams -= 1;426}427428static int429audiounit_active_streams(cubeb * ctx)430{431ctx->mutex.assert_current_thread_owns();432return ctx->active_streams;433}434435static void436audiounit_set_global_latency(cubeb * ctx, uint32_t latency_frames)437{438ctx->mutex.assert_current_thread_owns();439assert(audiounit_active_streams(ctx) == 1);440ctx->global_latency_frames = latency_frames;441}442443static void444audiounit_make_silent(AudioBuffer * ioData)445{446assert(ioData);447assert(ioData->mData);448memset(ioData->mData, 0, ioData->mDataByteSize);449}450451static OSStatus452audiounit_render_input(cubeb_stream * stm, AudioUnitRenderActionFlags * flags,453AudioTimeStamp const * tstamp, UInt32 bus,454UInt32 input_frames)455{456/* Create the AudioBufferList to store input. */457AudioBufferList input_buffer_list;458input_buffer_list.mBuffers[0].mDataByteSize =459stm->input_desc.mBytesPerFrame * input_frames;460input_buffer_list.mBuffers[0].mData = nullptr;461input_buffer_list.mBuffers[0].mNumberChannels =462stm->input_desc.mChannelsPerFrame;463input_buffer_list.mNumberBuffers = 1;464465/* Render input samples */466OSStatus r = AudioUnitRender(stm->input_unit, flags, tstamp, bus,467input_frames, &input_buffer_list);468469if (r != noErr) {470LOG("AudioUnitRender rv=%d", r);471if (r != kAudioUnitErr_CannotDoInCurrentContext) {472return r;473}474if (stm->output_unit) {475// kAudioUnitErr_CannotDoInCurrentContext is returned when using a BT476// headset and the profile is changed from A2DP to HFP/HSP. The previous477// output device is no longer valid and must be reset.478audiounit_reinit_stream_async(stm, DEV_INPUT | DEV_OUTPUT);479}480// For now state that no error occurred and feed silence, stream will be481// resumed once reinit has completed.482ALOGV("(%p) input: reinit pending feeding silence instead", stm);483stm->input_linear_buffer->push_silence(input_frames *484stm->input_desc.mChannelsPerFrame);485} else {486/* Copy input data in linear buffer. */487stm->input_linear_buffer->push(input_buffer_list.mBuffers[0].mData,488input_frames *489stm->input_desc.mChannelsPerFrame);490}491492/* Advance input frame counter. */493assert(input_frames > 0);494stm->frames_read += input_frames;495496ALOGV("(%p) input: buffers %u, size %u, channels %u, rendered frames %d, "497"total frames %lu.",498stm, (unsigned int)input_buffer_list.mNumberBuffers,499(unsigned int)input_buffer_list.mBuffers[0].mDataByteSize,500(unsigned int)input_buffer_list.mBuffers[0].mNumberChannels,501(unsigned int)input_frames,502stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame);503504return noErr;505}506507static OSStatus508audiounit_input_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,509AudioTimeStamp const * tstamp, UInt32 bus,510UInt32 input_frames, AudioBufferList * /* bufs */)511{512cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);513514assert(stm->input_unit != NULL);515assert(AU_IN_BUS == bus);516517if (stm->shutdown) {518ALOG("(%p) input shutdown", stm);519return noErr;520}521522if (stm->draining) {523OSStatus r = AudioOutputUnitStop(stm->input_unit);524assert(r == 0);525// Only fire state callback in input-only stream. For duplex stream,526// the state callback will be fired in output callback.527if (stm->output_unit == NULL) {528stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);529}530return noErr;531}532533OSStatus r = audiounit_render_input(stm, flags, tstamp, bus, input_frames);534if (r != noErr) {535return r;536}537538// Full Duplex. We'll call data_callback in the AudioUnit output callback.539if (stm->output_unit != NULL) {540return noErr;541}542543/* Input only. Call the user callback through resampler.544Resampler will deliver input buffer in the correct rate. */545assert(input_frames <= stm->input_linear_buffer->length() /546stm->input_desc.mChannelsPerFrame);547long total_input_frames =548stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;549long outframes = cubeb_resampler_fill(stm->resampler.get(),550stm->input_linear_buffer->data(),551&total_input_frames, NULL, 0);552if (outframes < 0) {553stm->shutdown = true;554OSStatus r = AudioOutputUnitStop(stm->input_unit);555assert(r == 0);556stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);557return noErr;558}559stm->draining = outframes < total_input_frames;560561// Reset input buffer562stm->input_linear_buffer->clear();563564return noErr;565}566567static void568audiounit_mix_output_buffer(cubeb_stream * stm, size_t output_frames,569void * input_buffer, size_t input_buffer_size,570void * output_buffer, size_t output_buffer_size)571{572assert(input_buffer_size >=573cubeb_sample_size(stm->output_stream_params.format) *574stm->output_stream_params.channels * output_frames);575assert(output_buffer_size >= stm->output_desc.mBytesPerFrame * output_frames);576577int r = cubeb_mixer_mix(stm->mixer.get(), output_frames, input_buffer,578input_buffer_size, output_buffer, output_buffer_size);579if (r != 0) {580LOG("Remix error = %d", r);581}582}583584// Return how many input frames (sampled at input_hw_rate) are needed to provide585// output_frames (sampled at output_stream_params.rate)586static int64_t587minimum_resampling_input_frames(cubeb_stream * stm, uint32_t output_frames)588{589if (stm->input_hw_rate == stm->output_stream_params.rate) {590// Fast path.591return output_frames;592}593return ceil(stm->input_hw_rate * output_frames /594stm->output_stream_params.rate);595}596597static OSStatus598audiounit_output_callback(void * user_ptr,599AudioUnitRenderActionFlags * /* flags */,600AudioTimeStamp const * tstamp, UInt32 bus,601UInt32 output_frames, AudioBufferList * outBufferList)602{603assert(AU_OUT_BUS == bus);604assert(outBufferList->mNumberBuffers == 1);605606cubeb_stream * stm = static_cast<cubeb_stream *>(user_ptr);607608uint64_t now = ConvertHostTimeToNanos(mach_absolute_time());609uint64_t audio_output_time = ConvertHostTimeToNanos(tstamp->mHostTime);610uint64_t output_latency_ns = audio_output_time - now;611612const int ns2s = 1e9;613// The total output latency is the timestamp difference + the stream latency +614// the hardware latency.615stm->total_output_latency_frames =616output_latency_ns * stm->output_hw_rate / ns2s +617stm->current_latency_frames;618619ALOGV("(%p) output: buffers %u, size %u, channels %u, frames %u, total input "620"frames %lu.",621stm, (unsigned int)outBufferList->mNumberBuffers,622(unsigned int)outBufferList->mBuffers[0].mDataByteSize,623(unsigned int)outBufferList->mBuffers[0].mNumberChannels,624(unsigned int)output_frames,625has_input(stm) ? stm->input_linear_buffer->length() /626stm->input_desc.mChannelsPerFrame627: 0);628629long input_frames = 0;630void *output_buffer = NULL, *input_buffer = NULL;631632if (stm->shutdown) {633ALOG("(%p) output shutdown.", stm);634audiounit_make_silent(&outBufferList->mBuffers[0]);635return noErr;636}637638if (stm->draining) {639OSStatus r = AudioOutputUnitStop(stm->output_unit);640assert(r == 0);641stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);642audiounit_make_silent(&outBufferList->mBuffers[0]);643return noErr;644}645646/* Get output buffer. */647if (stm->mixer) {648// If remixing needs to occur, we can't directly work in our final649// destination buffer as data may be overwritten or too small to start with.650size_t size_needed = output_frames * stm->output_stream_params.channels *651cubeb_sample_size(stm->output_stream_params.format);652if (stm->temp_buffer_size < size_needed) {653stm->temp_buffer.reset(new uint8_t[size_needed]);654stm->temp_buffer_size = size_needed;655}656output_buffer = stm->temp_buffer.get();657} else {658output_buffer = outBufferList->mBuffers[0].mData;659}660661stm->frames_written += output_frames;662663/* If Full duplex get also input buffer */664if (stm->input_unit != NULL) {665/* If the output callback came first and this is a duplex stream, we need to666* fill in some additional silence in the resampler.667* Otherwise, if we had more than expected callbacks in a row, or we're668* currently switching, we add some silence as well to compensate for the669* fact that we're lacking some input data. */670uint32_t input_frames_needed =671minimum_resampling_input_frames(stm, stm->frames_written);672long missing_frames = input_frames_needed - stm->frames_read;673if (missing_frames > 0) {674stm->input_linear_buffer->push_silence(missing_frames *675stm->input_desc.mChannelsPerFrame);676stm->frames_read = input_frames_needed;677678ALOG("(%p) %s pushed %ld frames of input silence.", stm,679stm->frames_read == 0 ? "Input hasn't started,"680: stm->switching_device ? "Device switching,"681: "Drop out,",682missing_frames);683}684input_buffer = stm->input_linear_buffer->data();685// Number of input frames in the buffer. It will change to actually used686// frames inside fill687input_frames =688stm->input_linear_buffer->length() / stm->input_desc.mChannelsPerFrame;689}690691/* Call user callback through resampler. */692long outframes = cubeb_resampler_fill(stm->resampler.get(), input_buffer,693input_buffer ? &input_frames : NULL,694output_buffer, output_frames);695696if (input_buffer) {697// Pop from the buffer the frames used by the the resampler.698stm->input_linear_buffer->pop(input_frames *699stm->input_desc.mChannelsPerFrame);700}701702if (outframes < 0 || outframes > output_frames) {703stm->shutdown = true;704OSStatus r = AudioOutputUnitStop(stm->output_unit);705assert(r == 0);706if (stm->input_unit) {707r = AudioOutputUnitStop(stm->input_unit);708assert(r == 0);709}710stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);711audiounit_make_silent(&outBufferList->mBuffers[0]);712return noErr;713}714715stm->draining = (UInt32)outframes < output_frames;716stm->frames_played = stm->frames_queued;717stm->frames_queued += outframes;718719/* Post process output samples. */720if (stm->draining) {721/* Clear missing frames (silence) */722size_t channels = stm->output_stream_params.channels;723size_t missing_samples = (output_frames - outframes) * channels;724size_t size_sample = cubeb_sample_size(stm->output_stream_params.format);725/* number of bytes that have been filled with valid audio by the callback.726*/727size_t audio_byte_count = outframes * channels * size_sample;728PodZero((uint8_t *)output_buffer + audio_byte_count,729missing_samples * size_sample);730}731732/* Mixing */733if (stm->mixer) {734audiounit_mix_output_buffer(stm, output_frames, output_buffer,735stm->temp_buffer_size,736outBufferList->mBuffers[0].mData,737outBufferList->mBuffers[0].mDataByteSize);738}739740return noErr;741}742743extern "C" {744int745audiounit_init(cubeb ** context, char const * /* context_name */)746{747#if !TARGET_OS_IPHONE748cubeb_set_coreaudio_notification_runloop();749#endif750751*context = new cubeb;752753return CUBEB_OK;754}755}756757static char const *758audiounit_get_backend_id(cubeb * /* ctx */)759{760return "audiounit";761}762763#if !TARGET_OS_IPHONE764765static int766audiounit_stream_get_volume(cubeb_stream * stm, float * volume);767static int768audiounit_stream_set_volume(cubeb_stream * stm, float volume);769770static int771audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)772{773assert(stm);774775device_info * info = nullptr;776cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;777778if (side == io_side::INPUT) {779info = &stm->input_device;780type = CUBEB_DEVICE_TYPE_INPUT;781} else if (side == io_side::OUTPUT) {782info = &stm->output_device;783type = CUBEB_DEVICE_TYPE_OUTPUT;784}785memset(info, 0, sizeof(device_info));786info->id = id;787788if (side == io_side::INPUT) {789info->flags |= DEV_INPUT;790} else if (side == io_side::OUTPUT) {791info->flags |= DEV_OUTPUT;792}793794AudioDeviceID default_device_id = audiounit_get_default_device_id(type);795if (default_device_id == kAudioObjectUnknown) {796return CUBEB_ERROR;797}798if (id == kAudioObjectUnknown) {799info->id = default_device_id;800info->flags |= DEV_SELECTED_DEFAULT;801}802803if (info->id == default_device_id) {804info->flags |= DEV_SYSTEM_DEFAULT;805}806807assert(info->id);808assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) ||809!(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT);810811return CUBEB_OK;812}813814static int815audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags)816{817auto_lock context_lock(stm->context->mutex);818assert((flags & DEV_INPUT && stm->input_unit) ||819(flags & DEV_OUTPUT && stm->output_unit));820if (!stm->shutdown) {821audiounit_stream_stop_internal(stm);822}823824int r = audiounit_uninstall_device_changed_callback(stm);825if (r != CUBEB_OK) {826LOG("(%p) Could not uninstall all device change listeners.", stm);827}828829{830auto_lock lock(stm->mutex);831float volume = 0.0;832int vol_rv = CUBEB_ERROR;833if (stm->output_unit) {834vol_rv = audiounit_stream_get_volume(stm, &volume);835}836837audiounit_close_stream(stm);838839/* Reinit occurs in one of the following case:840* - When the device is not alive any more841* - When the default system device change.842* - The bluetooth device changed from A2DP to/from HFP/HSP profile843* We first attempt to re-use the same device id, should that fail we will844* default to the (potentially new) default device. */845AudioDeviceID input_device =846flags & DEV_INPUT ? stm->input_device.id : kAudioObjectUnknown;847if (flags & DEV_INPUT) {848r = audiounit_set_device_info(stm, input_device, io_side::INPUT);849if (r != CUBEB_OK) {850LOG("(%p) Set input device info failed. This can happen when last "851"media device is unplugged",852stm);853return CUBEB_ERROR;854}855}856857/* Always use the default output on reinit. This is not correct in every858* case but it is sufficient for Firefox and prevent reinit from reporting859* failures. It will change soon when reinit mechanism will be updated. */860r = audiounit_set_device_info(stm, kAudioObjectUnknown, io_side::OUTPUT);861if (r != CUBEB_OK) {862LOG("(%p) Set output device info failed. This can happen when last media "863"device is unplugged",864stm);865return CUBEB_ERROR;866}867868if (audiounit_setup_stream(stm) != CUBEB_OK) {869LOG("(%p) Stream reinit failed.", stm);870if (flags & DEV_INPUT && input_device != kAudioObjectUnknown) {871// Attempt to re-use the same device-id failed, so attempt again with872// default input device.873audiounit_close_stream(stm);874if (audiounit_set_device_info(stm, kAudioObjectUnknown,875io_side::INPUT) != CUBEB_OK ||876audiounit_setup_stream(stm) != CUBEB_OK) {877LOG("(%p) Second stream reinit failed.", stm);878return CUBEB_ERROR;879}880}881}882883if (vol_rv == CUBEB_OK) {884audiounit_stream_set_volume(stm, volume);885}886887// If the stream was running, start it again.888if (!stm->shutdown) {889r = audiounit_stream_start_internal(stm);890if (r != CUBEB_OK) {891return CUBEB_ERROR;892}893}894}895return CUBEB_OK;896}897898static void899audiounit_reinit_stream_async(cubeb_stream * stm, device_flags_value flags)900{901if (std::atomic_exchange(&stm->reinit_pending, true)) {902// A reinit task is already pending, nothing more to do.903ALOG("(%p) re-init stream task already pending, cancelling request", stm);904return;905}906907// Use a new thread, through the queue, to avoid deadlock when calling908// Get/SetProperties method from inside notify callback909dispatch_async(stm->context->serial_queue, ^() {910if (stm->destroy_pending) {911ALOG("(%p) stream pending destroy, cancelling reinit task", stm);912return;913}914915if (audiounit_reinit_stream(stm, flags) != CUBEB_OK) {916if (audiounit_uninstall_system_changed_callback(stm) != CUBEB_OK) {917LOG("(%p) Could not uninstall system changed callback", stm);918}919stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);920LOG("(%p) Could not reopen the stream after switching.", stm);921}922stm->switching_device = false;923stm->reinit_pending = false;924});925}926927static char const *928event_addr_to_string(AudioObjectPropertySelector selector)929{930switch (selector) {931case kAudioHardwarePropertyDefaultOutputDevice:932return "kAudioHardwarePropertyDefaultOutputDevice";933case kAudioHardwarePropertyDefaultInputDevice:934return "kAudioHardwarePropertyDefaultInputDevice";935case kAudioDevicePropertyDeviceIsAlive:936return "kAudioDevicePropertyDeviceIsAlive";937case kAudioDevicePropertyDataSource:938return "kAudioDevicePropertyDataSource";939default:940return "Unknown";941}942}943944static OSStatus945audiounit_property_listener_callback(946AudioObjectID id, UInt32 address_count,947const AudioObjectPropertyAddress * addresses, void * user)948{949cubeb_stream * stm = (cubeb_stream *)user;950if (stm->switching_device) {951LOG("Switching is already taking place. Skip Event %s for id=%d",952event_addr_to_string(addresses[0].mSelector), id);953return noErr;954}955stm->switching_device = true;956957LOG("(%p) Audio device changed, %u events.", stm,958(unsigned int)address_count);959for (UInt32 i = 0; i < address_count; i++) {960switch (addresses[i].mSelector) {961case kAudioHardwarePropertyDefaultOutputDevice: {962LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice "963"for id=%d",964(unsigned int)i, id);965} break;966case kAudioHardwarePropertyDefaultInputDevice: {967LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice "968"for id=%d",969(unsigned int)i, id);970} break;971case kAudioDevicePropertyDeviceIsAlive: {972LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for "973"id=%d",974(unsigned int)i, id);975// If this is the default input device ignore the event,976// kAudioHardwarePropertyDefaultInputDevice will take care of the switch977if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) {978LOG("It's the default input device, ignore the event");979stm->switching_device = false;980return noErr;981}982} break;983case kAudioDevicePropertyDataSource: {984LOG("Event[%u] - mSelector == kAudioDevicePropertyDataSource for id=%d",985(unsigned int)i, id);986} break;987default:988LOG("Event[%u] - mSelector == Unexpected Event id %d, return",989(unsigned int)i, addresses[i].mSelector);990stm->switching_device = false;991return noErr;992}993}994995// Allow restart to choose the new default996device_flags_value switch_side = DEV_UNKNOWN;997if (has_input(stm)) {998switch_side |= DEV_INPUT;999}1000if (has_output(stm)) {1001switch_side |= DEV_OUTPUT;1002}10031004for (UInt32 i = 0; i < address_count; i++) {1005switch (addresses[i].mSelector) {1006case kAudioHardwarePropertyDefaultOutputDevice:1007case kAudioHardwarePropertyDefaultInputDevice:1008case kAudioDevicePropertyDeviceIsAlive:1009/* fall through */1010case kAudioDevicePropertyDataSource: {1011auto_lock dev_cb_lock(stm->device_changed_callback_lock);1012if (stm->device_changed_callback) {1013stm->device_changed_callback(stm->user_ptr);1014}1015break;1016}1017}1018}10191020audiounit_reinit_stream_async(stm, switch_side);10211022return noErr;1023}10241025OSStatus1026audiounit_add_listener(const property_listener * listener)1027{1028assert(listener);1029return AudioObjectAddPropertyListener(listener->device_id,1030listener->property_address,1031listener->callback, listener->stream);1032}10331034OSStatus1035audiounit_remove_listener(const property_listener * listener)1036{1037assert(listener);1038return AudioObjectRemovePropertyListener(1039listener->device_id, listener->property_address, listener->callback,1040listener->stream);1041}10421043static int1044audiounit_install_device_changed_callback(cubeb_stream * stm)1045{1046OSStatus rv;1047int r = CUBEB_OK;10481049if (stm->output_unit) {1050/* This event will notify us when the data source on the same device1051* changes, for example when the user plugs in a normal (non-usb) headset in1052* the headphone jack. */1053stm->output_source_listener.reset(new property_listener(1054stm->output_device.id, &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,1055&audiounit_property_listener_callback, stm));1056rv = audiounit_add_listener(stm->output_source_listener.get());1057if (rv != noErr) {1058stm->output_source_listener.reset();1059LOG("AudioObjectAddPropertyListener/output/"1060"kAudioDevicePropertyDataSource rv=%d, device id=%d",1061rv, stm->output_device.id);1062r = CUBEB_ERROR;1063}1064}10651066if (stm->input_unit) {1067/* This event will notify us when the data source on the input device1068* changes. */1069stm->input_source_listener.reset(new property_listener(1070stm->input_device.id, &INPUT_DATA_SOURCE_PROPERTY_ADDRESS,1071&audiounit_property_listener_callback, stm));1072rv = audiounit_add_listener(stm->input_source_listener.get());1073if (rv != noErr) {1074stm->input_source_listener.reset();1075LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource "1076"rv=%d, device id=%d",1077rv, stm->input_device.id);1078r = CUBEB_ERROR;1079}10801081/* Event to notify when the input is going away. */1082stm->input_alive_listener.reset(new property_listener(1083stm->input_device.id, &DEVICE_IS_ALIVE_PROPERTY_ADDRESS,1084&audiounit_property_listener_callback, stm));1085rv = audiounit_add_listener(stm->input_alive_listener.get());1086if (rv != noErr) {1087stm->input_alive_listener.reset();1088LOG("AudioObjectAddPropertyListener/input/"1089"kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d",1090rv, stm->input_device.id);1091r = CUBEB_ERROR;1092}1093}10941095return r;1096}10971098static int1099audiounit_install_system_changed_callback(cubeb_stream * stm)1100{1101OSStatus r;11021103if (stm->output_unit) {1104/* This event will notify us when the default audio device changes,1105* for example when the user plugs in a USB headset and the system chooses1106* it automatically as the default, or when another device is chosen in the1107* dropdown list. */1108stm->default_output_listener.reset(new property_listener(1109kAudioObjectSystemObject, &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS,1110&audiounit_property_listener_callback, stm));1111r = audiounit_add_listener(stm->default_output_listener.get());1112if (r != noErr) {1113stm->default_output_listener.reset();1114LOG("AudioObjectAddPropertyListener/output/"1115"kAudioHardwarePropertyDefaultOutputDevice rv=%d",1116r);1117return CUBEB_ERROR;1118}1119}11201121if (stm->input_unit) {1122/* This event will notify us when the default input device changes. */1123stm->default_input_listener.reset(new property_listener(1124kAudioObjectSystemObject, &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS,1125&audiounit_property_listener_callback, stm));1126r = audiounit_add_listener(stm->default_input_listener.get());1127if (r != noErr) {1128stm->default_input_listener.reset();1129LOG("AudioObjectAddPropertyListener/input/"1130"kAudioHardwarePropertyDefaultInputDevice rv=%d",1131r);1132return CUBEB_ERROR;1133}1134}11351136return CUBEB_OK;1137}11381139static int1140audiounit_uninstall_device_changed_callback(cubeb_stream * stm)1141{1142OSStatus rv;1143// Failing to uninstall listeners is not a fatal error.1144int r = CUBEB_OK;11451146if (stm->output_source_listener) {1147rv = audiounit_remove_listener(stm->output_source_listener.get());1148if (rv != noErr) {1149LOG("AudioObjectRemovePropertyListener/output/"1150"kAudioDevicePropertyDataSource rv=%d, device id=%d",1151rv, stm->output_device.id);1152r = CUBEB_ERROR;1153}1154stm->output_source_listener.reset();1155}11561157if (stm->input_source_listener) {1158rv = audiounit_remove_listener(stm->input_source_listener.get());1159if (rv != noErr) {1160LOG("AudioObjectRemovePropertyListener/input/"1161"kAudioDevicePropertyDataSource rv=%d, device id=%d",1162rv, stm->input_device.id);1163r = CUBEB_ERROR;1164}1165stm->input_source_listener.reset();1166}11671168if (stm->input_alive_listener) {1169rv = audiounit_remove_listener(stm->input_alive_listener.get());1170if (rv != noErr) {1171LOG("AudioObjectRemovePropertyListener/input/"1172"kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d",1173rv, stm->input_device.id);1174r = CUBEB_ERROR;1175}1176stm->input_alive_listener.reset();1177}11781179return r;1180}11811182static int1183audiounit_uninstall_system_changed_callback(cubeb_stream * stm)1184{1185OSStatus r;11861187if (stm->default_output_listener) {1188r = audiounit_remove_listener(stm->default_output_listener.get());1189if (r != noErr) {1190return CUBEB_ERROR;1191}1192stm->default_output_listener.reset();1193}11941195if (stm->default_input_listener) {1196r = audiounit_remove_listener(stm->default_input_listener.get());1197if (r != noErr) {1198return CUBEB_ERROR;1199}1200stm->default_input_listener.reset();1201}1202return CUBEB_OK;1203}12041205/* Get the acceptable buffer size (in frames) that this device can work with. */1206static int1207audiounit_get_acceptable_latency_range(AudioValueRange * latency_range)1208{1209UInt32 size;1210OSStatus r;1211AudioDeviceID output_device_id;1212AudioObjectPropertyAddress output_device_buffer_size_range = {1213kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyScopeOutput,1214kAudioObjectPropertyElementMaster};12151216output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);1217if (output_device_id == kAudioObjectUnknown) {1218LOG("Could not get default output device id.");1219return CUBEB_ERROR;1220}12211222/* Get the buffer size range this device supports */1223size = sizeof(*latency_range);12241225r = AudioObjectGetPropertyData(output_device_id,1226&output_device_buffer_size_range, 0, NULL,1227&size, latency_range);1228if (r != noErr) {1229LOG("AudioObjectGetPropertyData/buffer size range rv=%d", r);1230return CUBEB_ERROR;1231}12321233return CUBEB_OK;1234}1235#endif /* !TARGET_OS_IPHONE */12361237static AudioObjectID1238audiounit_get_default_device_id(cubeb_device_type type)1239{1240const AudioObjectPropertyAddress * adr;1241if (type == CUBEB_DEVICE_TYPE_OUTPUT) {1242adr = &DEFAULT_OUTPUT_DEVICE_PROPERTY_ADDRESS;1243} else if (type == CUBEB_DEVICE_TYPE_INPUT) {1244adr = &DEFAULT_INPUT_DEVICE_PROPERTY_ADDRESS;1245} else {1246return kAudioObjectUnknown;1247}12481249AudioDeviceID devid;1250UInt32 size = sizeof(AudioDeviceID);1251if (AudioObjectGetPropertyData(kAudioObjectSystemObject, adr, 0, NULL, &size,1252&devid) != noErr) {1253return kAudioObjectUnknown;1254}12551256return devid;1257}12581259int1260audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)1261{1262#if TARGET_OS_IPHONE1263// TODO: [[AVAudioSession sharedInstance] maximumOutputNumberOfChannels]1264*max_channels = 2;1265#else1266UInt32 size;1267OSStatus r;1268AudioDeviceID output_device_id;1269AudioStreamBasicDescription stream_format;1270AudioObjectPropertyAddress stream_format_address = {1271kAudioDevicePropertyStreamFormat, kAudioDevicePropertyScopeOutput,1272kAudioObjectPropertyElementMaster};12731274assert(ctx && max_channels);12751276output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);1277if (output_device_id == kAudioObjectUnknown) {1278return CUBEB_ERROR;1279}12801281size = sizeof(stream_format);12821283r = AudioObjectGetPropertyData(output_device_id, &stream_format_address, 0,1284NULL, &size, &stream_format);1285if (r != noErr) {1286LOG("AudioObjectPropertyAddress/StreamFormat rv=%d", r);1287return CUBEB_ERROR;1288}12891290*max_channels = stream_format.mChannelsPerFrame;1291#endif1292return CUBEB_OK;1293}12941295static int1296audiounit_get_min_latency(cubeb * /* ctx */, cubeb_stream_params /* params */,1297uint32_t * latency_frames)1298{1299#if TARGET_OS_IPHONE1300// TODO: [[AVAudioSession sharedInstance] inputLatency]1301return CUBEB_ERROR_NOT_SUPPORTED;1302#else1303AudioValueRange latency_range;1304if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) {1305LOG("Could not get acceptable latency range.");1306return CUBEB_ERROR;1307}13081309*latency_frames =1310max<uint32_t>(latency_range.mMinimum, SAFE_MIN_LATENCY_FRAMES);1311#endif13121313return CUBEB_OK;1314}13151316static int1317audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate)1318{1319#if TARGET_OS_IPHONE1320// TODO1321return CUBEB_ERROR_NOT_SUPPORTED;1322#else1323UInt32 size;1324OSStatus r;1325Float64 fsamplerate;1326AudioDeviceID output_device_id;1327AudioObjectPropertyAddress samplerate_address = {1328kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal,1329kAudioObjectPropertyElementMaster};13301331output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);1332if (output_device_id == kAudioObjectUnknown) {1333return CUBEB_ERROR;1334}13351336size = sizeof(fsamplerate);1337r = AudioObjectGetPropertyData(output_device_id, &samplerate_address, 0, NULL,1338&size, &fsamplerate);13391340if (r != noErr) {1341return CUBEB_ERROR;1342}13431344*rate = static_cast<uint32_t>(fsamplerate);1345#endif1346return CUBEB_OK;1347}13481349static cubeb_channel_layout1350audiounit_convert_channel_layout(AudioChannelLayout * layout)1351{1352// When having one or two channel, force mono or stereo. Some devices (namely,1353// Bose QC35, mark 1 and 2), expose a single channel mapped to the right for1354// some reason.1355if (layout->mNumberChannelDescriptions == 1) {1356return CUBEB_LAYOUT_MONO;1357} else if (layout->mNumberChannelDescriptions == 2) {1358return CUBEB_LAYOUT_STEREO;1359}13601361if (layout->mChannelLayoutTag !=1362kAudioChannelLayoutTag_UseChannelDescriptions) {1363// kAudioChannelLayoutTag_UseChannelBitmap1364// kAudioChannelLayoutTag_Mono1365// kAudioChannelLayoutTag_Stereo1366// ....1367LOG("Only handle UseChannelDescriptions for now.\n");1368return CUBEB_LAYOUT_UNDEFINED;1369}13701371cubeb_channel_layout cl = 0;1372for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; ++i) {1373cubeb_channel cc = channel_label_to_cubeb_channel(1374layout->mChannelDescriptions[i].mChannelLabel);1375if (cc == CHANNEL_UNKNOWN) {1376return CUBEB_LAYOUT_UNDEFINED;1377}1378cl |= cc;1379}13801381return cl;1382}13831384static cubeb_channel_layout1385audiounit_get_preferred_channel_layout(AudioUnit output_unit)1386{1387OSStatus rv = noErr;1388UInt32 size = 0;1389rv = AudioUnitGetPropertyInfo(1390output_unit, kAudioDevicePropertyPreferredChannelLayout,1391kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr);1392if (rv != noErr) {1393LOG("AudioUnitGetPropertyInfo/kAudioDevicePropertyPreferredChannelLayout "1394"rv=%d",1395rv);1396return CUBEB_LAYOUT_UNDEFINED;1397}1398assert(size > 0);13991400auto layout = make_sized_audio_channel_layout(size);1401rv = AudioUnitGetProperty(1402output_unit, kAudioDevicePropertyPreferredChannelLayout,1403kAudioUnitScope_Output, AU_OUT_BUS, layout.get(), &size);1404if (rv != noErr) {1405LOG("AudioUnitGetProperty/kAudioDevicePropertyPreferredChannelLayout rv=%d",1406rv);1407return CUBEB_LAYOUT_UNDEFINED;1408}14091410return audiounit_convert_channel_layout(layout.get());1411}14121413static cubeb_channel_layout1414audiounit_get_current_channel_layout(AudioUnit output_unit)1415{1416OSStatus rv = noErr;1417UInt32 size = 0;1418rv = AudioUnitGetPropertyInfo(1419output_unit, kAudioUnitProperty_AudioChannelLayout,1420kAudioUnitScope_Output, AU_OUT_BUS, &size, nullptr);1421if (rv != noErr) {1422LOG("AudioUnitGetPropertyInfo/kAudioUnitProperty_AudioChannelLayout rv=%d",1423rv);1424// This property isn't known before macOS 10.12, attempt another method.1425return audiounit_get_preferred_channel_layout(output_unit);1426}1427assert(size > 0);14281429auto layout = make_sized_audio_channel_layout(size);1430rv = AudioUnitGetProperty(output_unit, kAudioUnitProperty_AudioChannelLayout,1431kAudioUnitScope_Output, AU_OUT_BUS, layout.get(),1432&size);1433if (rv != noErr) {1434LOG("AudioUnitGetProperty/kAudioUnitProperty_AudioChannelLayout rv=%d", rv);1435return CUBEB_LAYOUT_UNDEFINED;1436}14371438return audiounit_convert_channel_layout(layout.get());1439}14401441static int1442audiounit_create_unit(AudioUnit * unit, device_info * device);14431444static OSStatus1445audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype);14461447static void1448audiounit_destroy(cubeb * ctx)1449{1450{1451auto_lock lock(ctx->mutex);14521453// Disabling this assert for bug 1083664 -- we seem to leak a stream1454// assert(ctx->active_streams == 0);1455if (audiounit_active_streams(ctx) > 0) {1456LOG("(%p) API misuse, %d streams active when context destroyed!", ctx,1457audiounit_active_streams(ctx));1458}14591460// Destroying a cubeb context with device collection callbacks registered1461// is misuse of the API, assert then attempt to clean up.1462assert(!ctx->input_collection_changed_callback &&1463!ctx->input_collection_changed_user_ptr &&1464!ctx->output_collection_changed_callback &&1465!ctx->output_collection_changed_user_ptr);14661467/* Unregister the callback if necessary. */1468if (ctx->input_collection_changed_callback) {1469audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT);1470}1471if (ctx->output_collection_changed_callback) {1472audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_OUTPUT);1473}1474}14751476dispatch_release(ctx->serial_queue);14771478delete ctx;1479}14801481static void1482audiounit_stream_destroy(cubeb_stream * stm);14831484static int1485audio_stream_desc_init(AudioStreamBasicDescription * ss,1486const cubeb_stream_params * stream_params)1487{1488switch (stream_params->format) {1489case CUBEB_SAMPLE_S16LE:1490ss->mBitsPerChannel = 16;1491ss->mFormatFlags = kAudioFormatFlagIsSignedInteger;1492break;1493case CUBEB_SAMPLE_S16BE:1494ss->mBitsPerChannel = 16;1495ss->mFormatFlags =1496kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian;1497break;1498case CUBEB_SAMPLE_FLOAT32LE:1499ss->mBitsPerChannel = 32;1500ss->mFormatFlags = kAudioFormatFlagIsFloat;1501break;1502case CUBEB_SAMPLE_FLOAT32BE:1503ss->mBitsPerChannel = 32;1504ss->mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsBigEndian;1505break;1506default:1507return CUBEB_ERROR_INVALID_FORMAT;1508}15091510ss->mFormatID = kAudioFormatLinearPCM;1511ss->mFormatFlags |= kLinearPCMFormatFlagIsPacked;1512ss->mSampleRate = stream_params->rate;1513ss->mChannelsPerFrame = stream_params->channels;15141515ss->mBytesPerFrame = (ss->mBitsPerChannel / 8) * ss->mChannelsPerFrame;1516ss->mFramesPerPacket = 1;1517ss->mBytesPerPacket = ss->mBytesPerFrame * ss->mFramesPerPacket;15181519ss->mReserved = 0;15201521return CUBEB_OK;1522}15231524void1525audiounit_init_mixer(cubeb_stream * stm)1526{1527// We can't rely on macOS' AudioUnit to properly downmix (or upmix) the audio1528// data, it silently drop the channels so we need to remix the1529// audio data by ourselves to keep all the information.1530stm->mixer.reset(cubeb_mixer_create(1531stm->output_stream_params.format, stm->output_stream_params.channels,1532stm->output_stream_params.layout, stm->context->channels,1533stm->context->layout));1534assert(stm->mixer);1535}15361537static int1538audiounit_set_channel_layout(AudioUnit unit, io_side side,1539cubeb_channel_layout layout)1540{1541if (side != io_side::OUTPUT) {1542return CUBEB_ERROR;1543}15441545if (layout == CUBEB_LAYOUT_UNDEFINED) {1546// We leave everything as-is...1547return CUBEB_OK;1548}15491550OSStatus r;1551uint32_t nb_channels = cubeb_channel_layout_nb_channels(layout);15521553// We do not use CoreAudio standard layout for lack of documentation on what1554// the actual channel orders are. So we set a custom layout.1555size_t size = offsetof(AudioChannelLayout, mChannelDescriptions[nb_channels]);1556auto au_layout = make_sized_audio_channel_layout(size);1557au_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;1558au_layout->mNumberChannelDescriptions = nb_channels;15591560uint32_t channels = 0;1561cubeb_channel_layout channelMap = layout;1562for (uint32_t i = 0; channelMap != 0; ++i) {1563XASSERT(channels < nb_channels);1564uint32_t channel = (channelMap & 1) << i;1565if (channel != 0) {1566au_layout->mChannelDescriptions[channels].mChannelLabel =1567cubeb_channel_to_channel_label(static_cast<cubeb_channel>(channel));1568au_layout->mChannelDescriptions[channels].mChannelFlags =1569kAudioChannelFlags_AllOff;1570channels++;1571}1572channelMap = channelMap >> 1;1573}15741575r = AudioUnitSetProperty(unit, kAudioUnitProperty_AudioChannelLayout,1576kAudioUnitScope_Input, AU_OUT_BUS, au_layout.get(),1577size);1578if (r != noErr) {1579LOG("AudioUnitSetProperty/%s/kAudioUnitProperty_AudioChannelLayout rv=%d",1580to_string(side), r);1581return CUBEB_ERROR;1582}15831584return CUBEB_OK;1585}15861587void1588audiounit_layout_init(cubeb_stream * stm, io_side side)1589{1590// We currently don't support the input layout setting.1591if (side == io_side::INPUT) {1592return;1593}15941595stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit);15961597audiounit_set_channel_layout(stm->output_unit, io_side::OUTPUT,1598stm->context->layout);1599}16001601static vector<AudioObjectID>1602audiounit_get_sub_devices(AudioDeviceID device_id)1603{1604vector<AudioDeviceID> sub_devices;1605AudioObjectPropertyAddress property_address = {1606kAudioAggregateDevicePropertyActiveSubDeviceList,1607kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};1608UInt32 size = 0;1609OSStatus rv = AudioObjectGetPropertyDataSize(device_id, &property_address, 0,1610nullptr, &size);16111612if (rv != noErr) {1613sub_devices.push_back(device_id);1614return sub_devices;1615}16161617uint32_t count = static_cast<uint32_t>(size / sizeof(AudioObjectID));1618sub_devices.resize(count);1619rv = AudioObjectGetPropertyData(device_id, &property_address, 0, nullptr,1620&size, sub_devices.data());1621if (rv != noErr) {1622sub_devices.clear();1623sub_devices.push_back(device_id);1624} else {1625LOG("Found %u sub-devices", count);1626}1627return sub_devices;1628}16291630static int1631audiounit_create_blank_aggregate_device(AudioObjectID * plugin_id,1632AudioDeviceID * aggregate_device_id)1633{1634AudioObjectPropertyAddress address_plugin_bundle_id = {1635kAudioHardwarePropertyPlugInForBundleID, kAudioObjectPropertyScopeGlobal,1636kAudioObjectPropertyElementMaster};1637UInt32 size = 0;1638OSStatus r = AudioObjectGetPropertyDataSize(1639kAudioObjectSystemObject, &address_plugin_bundle_id, 0, NULL, &size);1640if (r != noErr) {1641LOG("AudioObjectGetPropertyDataSize/"1642"kAudioHardwarePropertyPlugInForBundleID, rv=%d",1643r);1644return CUBEB_ERROR;1645}16461647AudioValueTranslation translation_value;1648CFStringRef in_bundle_ref = CFSTR("com.apple.audio.CoreAudio");1649translation_value.mInputData = &in_bundle_ref;1650translation_value.mInputDataSize = sizeof(in_bundle_ref);1651translation_value.mOutputData = plugin_id;1652translation_value.mOutputDataSize = sizeof(*plugin_id);16531654r = AudioObjectGetPropertyData(kAudioObjectSystemObject,1655&address_plugin_bundle_id, 0, nullptr, &size,1656&translation_value);1657if (r != noErr) {1658LOG("AudioObjectGetPropertyData/kAudioHardwarePropertyPlugInForBundleID, "1659"rv=%d",1660r);1661return CUBEB_ERROR;1662}16631664AudioObjectPropertyAddress create_aggregate_device_address = {1665kAudioPlugInCreateAggregateDevice, kAudioObjectPropertyScopeGlobal,1666kAudioObjectPropertyElementMaster};1667r = AudioObjectGetPropertyDataSize(1668*plugin_id, &create_aggregate_device_address, 0, nullptr, &size);1669if (r != noErr) {1670LOG("AudioObjectGetPropertyDataSize/kAudioPlugInCreateAggregateDevice, "1671"rv=%d",1672r);1673return CUBEB_ERROR;1674}16751676CFMutableDictionaryRef aggregate_device_dict = CFDictionaryCreateMutable(1677kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,1678&kCFTypeDictionaryValueCallBacks);1679struct timeval timestamp;1680gettimeofday(×tamp, NULL);1681long long int time_id = timestamp.tv_sec * 1000000LL + timestamp.tv_usec;1682CFStringRef aggregate_device_name = CFStringCreateWithFormat(1683NULL, NULL, CFSTR("%s_%llx"), PRIVATE_AGGREGATE_DEVICE_NAME, time_id);1684CFDictionaryAddValue(aggregate_device_dict,1685CFSTR(kAudioAggregateDeviceNameKey),1686aggregate_device_name);1687CFRelease(aggregate_device_name);16881689CFStringRef aggregate_device_UID =1690CFStringCreateWithFormat(NULL, NULL, CFSTR("org.mozilla.%s_%llx"),1691PRIVATE_AGGREGATE_DEVICE_NAME, time_id);1692CFDictionaryAddValue(aggregate_device_dict,1693CFSTR(kAudioAggregateDeviceUIDKey),1694aggregate_device_UID);1695CFRelease(aggregate_device_UID);16961697int private_value = 1;1698CFNumberRef aggregate_device_private_key =1699CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_value);1700CFDictionaryAddValue(aggregate_device_dict,1701CFSTR(kAudioAggregateDeviceIsPrivateKey),1702aggregate_device_private_key);1703CFRelease(aggregate_device_private_key);17041705int stacked_value = 0;1706CFNumberRef aggregate_device_stacked_key =1707CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stacked_value);1708CFDictionaryAddValue(aggregate_device_dict,1709CFSTR(kAudioAggregateDeviceIsStackedKey),1710aggregate_device_stacked_key);1711CFRelease(aggregate_device_stacked_key);17121713r = AudioObjectGetPropertyData(*plugin_id, &create_aggregate_device_address,1714sizeof(aggregate_device_dict),1715&aggregate_device_dict, &size,1716aggregate_device_id);1717CFRelease(aggregate_device_dict);1718if (r != noErr) {1719LOG("AudioObjectGetPropertyData/kAudioPlugInCreateAggregateDevice, rv=%d",1720r);1721return CUBEB_ERROR;1722}1723LOG("New aggregate device %u", *aggregate_device_id);17241725return CUBEB_OK;1726}17271728// The returned CFStringRef object needs to be released (via CFRelease)1729// if it's not NULL, since the reference count of the returned CFStringRef1730// object is increased.1731static CFStringRef1732get_device_name(AudioDeviceID id)1733{1734UInt32 size = sizeof(CFStringRef);1735CFStringRef UIname = nullptr;1736AudioObjectPropertyAddress address_uuid = {kAudioDevicePropertyDeviceUID,1737kAudioObjectPropertyScopeGlobal,1738kAudioObjectPropertyElementMaster};1739OSStatus err =1740AudioObjectGetPropertyData(id, &address_uuid, 0, nullptr, &size, &UIname);1741return (err == noErr) ? UIname : NULL;1742}17431744static int1745audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id,1746AudioDeviceID input_device_id,1747AudioDeviceID output_device_id)1748{1749LOG("Add devices input %u and output %u into aggregate device %u",1750input_device_id, output_device_id, aggregate_device_id);1751const vector<AudioDeviceID> output_sub_devices =1752audiounit_get_sub_devices(output_device_id);1753const vector<AudioDeviceID> input_sub_devices =1754audiounit_get_sub_devices(input_device_id);17551756CFMutableArrayRef aggregate_sub_devices_array =1757CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);1758/* The order of the items in the array is significant and is used to determine1759the order of the streams of the AudioAggregateDevice. */1760for (UInt32 i = 0; i < output_sub_devices.size(); i++) {1761CFStringRef ref = get_device_name(output_sub_devices[i]);1762if (ref == NULL) {1763CFRelease(aggregate_sub_devices_array);1764return CUBEB_ERROR;1765}1766CFArrayAppendValue(aggregate_sub_devices_array, ref);1767CFRelease(ref);1768}1769for (UInt32 i = 0; i < input_sub_devices.size(); i++) {1770CFStringRef ref = get_device_name(input_sub_devices[i]);1771if (ref == NULL) {1772CFRelease(aggregate_sub_devices_array);1773return CUBEB_ERROR;1774}1775CFArrayAppendValue(aggregate_sub_devices_array, ref);1776CFRelease(ref);1777}17781779AudioObjectPropertyAddress aggregate_sub_device_list = {1780kAudioAggregateDevicePropertyFullSubDeviceList,1781kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};1782UInt32 size = sizeof(CFMutableArrayRef);1783OSStatus rv = AudioObjectSetPropertyData(1784aggregate_device_id, &aggregate_sub_device_list, 0, nullptr, size,1785&aggregate_sub_devices_array);1786CFRelease(aggregate_sub_devices_array);1787if (rv != noErr) {1788LOG("AudioObjectSetPropertyData/"1789"kAudioAggregateDevicePropertyFullSubDeviceList, rv=%d",1790rv);1791return CUBEB_ERROR;1792}17931794return CUBEB_OK;1795}17961797static int1798audiounit_set_master_aggregate_device(const AudioDeviceID aggregate_device_id)1799{1800assert(aggregate_device_id != kAudioObjectUnknown);1801AudioObjectPropertyAddress master_aggregate_sub_device = {1802kAudioAggregateDevicePropertyMasterSubDevice,1803kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};18041805// Master become the 1st output sub device1806AudioDeviceID output_device_id =1807audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);1808const vector<AudioDeviceID> output_sub_devices =1809audiounit_get_sub_devices(output_device_id);1810CFStringRef master_sub_device = get_device_name(output_sub_devices[0]);18111812UInt32 size = sizeof(CFStringRef);1813OSStatus rv = AudioObjectSetPropertyData(aggregate_device_id,1814&master_aggregate_sub_device, 0,1815NULL, size, &master_sub_device);1816if (master_sub_device) {1817CFRelease(master_sub_device);1818}1819if (rv != noErr) {1820LOG("AudioObjectSetPropertyData/"1821"kAudioAggregateDevicePropertyMasterSubDevice, rv=%d",1822rv);1823return CUBEB_ERROR;1824}18251826return CUBEB_OK;1827}18281829static int1830audiounit_activate_clock_drift_compensation(1831const AudioDeviceID aggregate_device_id)1832{1833assert(aggregate_device_id != kAudioObjectUnknown);1834AudioObjectPropertyAddress address_owned = {1835kAudioObjectPropertyOwnedObjects, kAudioObjectPropertyScopeGlobal,1836kAudioObjectPropertyElementMaster};18371838UInt32 qualifier_data_size = sizeof(AudioObjectID);1839AudioClassID class_id = kAudioSubDeviceClassID;1840void * qualifier_data = &class_id;1841UInt32 size = 0;1842OSStatus rv = AudioObjectGetPropertyDataSize(1843aggregate_device_id, &address_owned, qualifier_data_size, qualifier_data,1844&size);1845if (rv != noErr) {1846LOG("AudioObjectGetPropertyDataSize/kAudioObjectPropertyOwnedObjects, "1847"rv=%d",1848rv);1849return CUBEB_ERROR;1850}18511852UInt32 subdevices_num = 0;1853subdevices_num = size / sizeof(AudioObjectID);1854AudioObjectID sub_devices[subdevices_num];1855size = sizeof(sub_devices);18561857rv = AudioObjectGetPropertyData(aggregate_device_id, &address_owned,1858qualifier_data_size, qualifier_data, &size,1859sub_devices);1860if (rv != noErr) {1861LOG("AudioObjectGetPropertyData/kAudioObjectPropertyOwnedObjects, rv=%d",1862rv);1863return CUBEB_ERROR;1864}18651866AudioObjectPropertyAddress address_drift = {1867kAudioSubDevicePropertyDriftCompensation, kAudioObjectPropertyScopeGlobal,1868kAudioObjectPropertyElementMaster};18691870// Start from the second device since the first is the master clock1871for (UInt32 i = 1; i < subdevices_num; ++i) {1872UInt32 drift_compensation_value = 1;1873rv = AudioObjectSetPropertyData(sub_devices[i], &address_drift, 0, nullptr,1874sizeof(UInt32), &drift_compensation_value);1875if (rv != noErr) {1876LOG("AudioObjectSetPropertyData/"1877"kAudioSubDevicePropertyDriftCompensation, rv=%d",1878rv);1879return CUBEB_OK;1880}1881}1882return CUBEB_OK;1883}18841885static int1886audiounit_destroy_aggregate_device(AudioObjectID plugin_id,1887AudioDeviceID * aggregate_device_id);1888static void1889audiounit_get_available_samplerate(AudioObjectID devid,1890AudioObjectPropertyScope scope,1891uint32_t * min, uint32_t * max,1892uint32_t * def);1893static int1894audiounit_create_device_from_hwdev(cubeb_device_info * dev_info,1895AudioObjectID devid, cubeb_device_type type);1896static void1897audiounit_device_destroy(cubeb_device_info * device);18981899static void1900audiounit_workaround_for_airpod(cubeb_stream * stm)1901{1902cubeb_device_info input_device_info;1903audiounit_create_device_from_hwdev(&input_device_info, stm->input_device.id,1904CUBEB_DEVICE_TYPE_INPUT);19051906cubeb_device_info output_device_info;1907audiounit_create_device_from_hwdev(&output_device_info, stm->output_device.id,1908CUBEB_DEVICE_TYPE_OUTPUT);19091910std::string input_name_str(input_device_info.friendly_name);1911std::string output_name_str(output_device_info.friendly_name);19121913if (input_name_str.find("AirPods") != std::string::npos &&1914output_name_str.find("AirPods") != std::string::npos) {1915uint32_t input_min_rate = 0;1916uint32_t input_max_rate = 0;1917uint32_t input_nominal_rate = 0;1918audiounit_get_available_samplerate(1919stm->input_device.id, kAudioObjectPropertyScopeGlobal, &input_min_rate,1920&input_max_rate, &input_nominal_rate);1921LOG("(%p) Input device %u, name: %s, min: %u, max: %u, nominal rate: %u",1922stm, stm->input_device.id, input_device_info.friendly_name,1923input_min_rate, input_max_rate, input_nominal_rate);1924uint32_t output_min_rate = 0;1925uint32_t output_max_rate = 0;1926uint32_t output_nominal_rate = 0;1927audiounit_get_available_samplerate(1928stm->output_device.id, kAudioObjectPropertyScopeGlobal,1929&output_min_rate, &output_max_rate, &output_nominal_rate);1930LOG("(%p) Output device %u, name: %s, min: %u, max: %u, nominal rate: %u",1931stm, stm->output_device.id, output_device_info.friendly_name,1932output_min_rate, output_max_rate, output_nominal_rate);19331934Float64 rate = input_nominal_rate;1935AudioObjectPropertyAddress addr = {kAudioDevicePropertyNominalSampleRate,1936kAudioObjectPropertyScopeGlobal,1937kAudioObjectPropertyElementMaster};19381939OSStatus rv = AudioObjectSetPropertyData(stm->aggregate_device_id, &addr, 0,1940nullptr, sizeof(Float64), &rate);1941if (rv != noErr) {1942LOG("Non fatal error, "1943"AudioObjectSetPropertyData/kAudioDevicePropertyNominalSampleRate, "1944"rv=%d",1945rv);1946}1947}1948audiounit_device_destroy(&input_device_info);1949audiounit_device_destroy(&output_device_info);1950}19511952/*1953* Aggregate Device is a virtual audio interface which utilizes inputs and1954* outputs of one or more physical audio interfaces. It is possible to use the1955* clock of one of the devices as a master clock for all the combined devices1956* and enable drift compensation for the devices that are not designated clock1957* master.1958*1959* Creating a new aggregate device programmatically requires [0][1]:1960* 1. Locate the base plug-in ("com.apple.audio.CoreAudio")1961* 2. Create a dictionary that describes the aggregate device1962* (don't add sub-devices in that step, prone to fail [0])1963* 3. Ask the base plug-in to create the aggregate device (blank)1964* 4. Add the array of sub-devices.1965* 5. Set the master device (1st output device in our case)1966* 6. Enable drift compensation for the non-master devices1967*1968* [0] https://lists.apple.com/archives/coreaudio-api/2006/Apr/msg00092.html1969* [1] https://lists.apple.com/archives/coreaudio-api/2005/Jul/msg00150.html1970* [2] CoreAudio.framework/Headers/AudioHardware.h1971* */1972static int1973audiounit_create_aggregate_device(cubeb_stream * stm)1974{1975int r = audiounit_create_blank_aggregate_device(&stm->plugin_id,1976&stm->aggregate_device_id);1977if (r != CUBEB_OK) {1978LOG("(%p) Failed to create blank aggregate device", stm);1979return CUBEB_ERROR;1980}19811982r = audiounit_set_aggregate_sub_device_list(1983stm->aggregate_device_id, stm->input_device.id, stm->output_device.id);1984if (r != CUBEB_OK) {1985LOG("(%p) Failed to set aggregate sub-device list", stm);1986audiounit_destroy_aggregate_device(stm->plugin_id,1987&stm->aggregate_device_id);1988return CUBEB_ERROR;1989}19901991r = audiounit_set_master_aggregate_device(stm->aggregate_device_id);1992if (r != CUBEB_OK) {1993LOG("(%p) Failed to set master sub-device for aggregate device", stm);1994audiounit_destroy_aggregate_device(stm->plugin_id,1995&stm->aggregate_device_id);1996return CUBEB_ERROR;1997}19981999r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id);2000if (r != CUBEB_OK) {2001LOG("(%p) Failed to activate clock drift compensation for aggregate device",2002stm);2003audiounit_destroy_aggregate_device(stm->plugin_id,2004&stm->aggregate_device_id);2005return CUBEB_ERROR;2006}20072008audiounit_workaround_for_airpod(stm);20092010return CUBEB_OK;2011}20122013static int2014audiounit_destroy_aggregate_device(AudioObjectID plugin_id,2015AudioDeviceID * aggregate_device_id)2016{2017assert(aggregate_device_id && *aggregate_device_id != kAudioDeviceUnknown &&2018plugin_id != kAudioObjectUnknown);2019AudioObjectPropertyAddress destroy_aggregate_device_addr = {2020kAudioPlugInDestroyAggregateDevice, kAudioObjectPropertyScopeGlobal,2021kAudioObjectPropertyElementMaster};2022UInt32 size;2023OSStatus rv = AudioObjectGetPropertyDataSize(2024plugin_id, &destroy_aggregate_device_addr, 0, NULL, &size);2025if (rv != noErr) {2026LOG("AudioObjectGetPropertyDataSize/kAudioPlugInDestroyAggregateDevice, "2027"rv=%d",2028rv);2029return CUBEB_ERROR;2030}20312032rv = AudioObjectGetPropertyData(plugin_id, &destroy_aggregate_device_addr, 0,2033NULL, &size, aggregate_device_id);2034if (rv != noErr) {2035LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d",2036rv);2037return CUBEB_ERROR;2038}20392040LOG("Destroyed aggregate device %d", *aggregate_device_id);2041*aggregate_device_id = kAudioObjectUnknown;2042return CUBEB_OK;2043}20442045static int2046audiounit_new_unit_instance(AudioUnit * unit, device_info * device)2047{2048AudioComponentDescription desc;2049AudioComponent comp;2050OSStatus rv;20512052desc.componentType = kAudioUnitType_Output;2053#if TARGET_OS_IPHONE2054desc.componentSubType = kAudioUnitSubType_RemoteIO;2055#else2056// Use the DefaultOutputUnit for output when no device is specified2057// so we retain automatic output device switching when the default2058// changes. Once we have complete support for device notifications2059// and switching, we can use the AUHAL for everything.2060if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) {2061desc.componentSubType = kAudioUnitSubType_DefaultOutput;2062} else {2063desc.componentSubType = kAudioUnitSubType_HALOutput;2064}2065#endif2066desc.componentManufacturer = kAudioUnitManufacturer_Apple;2067desc.componentFlags = 0;2068desc.componentFlagsMask = 0;2069comp = AudioComponentFindNext(NULL, &desc);2070if (comp == NULL) {2071LOG("Could not find matching audio hardware.");2072return CUBEB_ERROR;2073}20742075rv = AudioComponentInstanceNew(comp, unit);2076if (rv != noErr) {2077LOG("AudioComponentInstanceNew rv=%d", rv);2078return CUBEB_ERROR;2079}2080return CUBEB_OK;2081}20822083enum enable_state {2084DISABLE,2085ENABLE,2086};20872088static int2089audiounit_enable_unit_scope(AudioUnit * unit, io_side side, enable_state state)2090{2091OSStatus rv;2092UInt32 enable = state;2093rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_EnableIO,2094(side == io_side::INPUT) ? kAudioUnitScope_Input2095: kAudioUnitScope_Output,2096(side == io_side::INPUT) ? AU_IN_BUS : AU_OUT_BUS,2097&enable, sizeof(UInt32));2098if (rv != noErr) {2099LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO rv=%d", rv);2100return CUBEB_ERROR;2101}2102return CUBEB_OK;2103}21042105static int2106audiounit_create_unit(AudioUnit * unit, device_info * device)2107{2108assert(*unit == nullptr);2109assert(device);21102111OSStatus rv;2112int r;21132114r = audiounit_new_unit_instance(unit, device);2115if (r != CUBEB_OK) {2116return r;2117}2118assert(*unit);21192120if ((device->flags & DEV_SYSTEM_DEFAULT) && (device->flags & DEV_OUTPUT)) {2121return CUBEB_OK;2122}21232124if (device->flags & DEV_INPUT) {2125r = audiounit_enable_unit_scope(unit, io_side::INPUT, ENABLE);2126if (r != CUBEB_OK) {2127LOG("Failed to enable audiounit input scope");2128return r;2129}2130r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, DISABLE);2131if (r != CUBEB_OK) {2132LOG("Failed to disable audiounit output scope");2133return r;2134}2135} else if (device->flags & DEV_OUTPUT) {2136r = audiounit_enable_unit_scope(unit, io_side::OUTPUT, ENABLE);2137if (r != CUBEB_OK) {2138LOG("Failed to enable audiounit output scope");2139return r;2140}2141r = audiounit_enable_unit_scope(unit, io_side::INPUT, DISABLE);2142if (r != CUBEB_OK) {2143LOG("Failed to disable audiounit input scope");2144return r;2145}2146} else {2147assert(false);2148}21492150rv = AudioUnitSetProperty(*unit, kAudioOutputUnitProperty_CurrentDevice,2151kAudioUnitScope_Global, 0, &device->id,2152sizeof(AudioDeviceID));2153if (rv != noErr) {2154LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d",2155rv);2156return CUBEB_ERROR;2157}21582159return CUBEB_OK;2160}21612162static int2163audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)2164{2165uint32_t size =2166capacity * stream->latency_frames * stream->input_desc.mChannelsPerFrame;2167if (stream->input_desc.mFormatFlags & kAudioFormatFlagIsSignedInteger) {2168stream->input_linear_buffer.reset(new auto_array_wrapper_impl<short>(size));2169} else {2170stream->input_linear_buffer.reset(new auto_array_wrapper_impl<float>(size));2171}2172assert(stream->input_linear_buffer->length() == 0);21732174return CUBEB_OK;2175}21762177static uint32_t2178audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)2179{2180// For the 1st stream set anything within safe min-max2181assert(audiounit_active_streams(stm->context) > 0);2182if (audiounit_active_streams(stm->context) == 1) {2183return max(min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),2184SAFE_MIN_LATENCY_FRAMES);2185}2186assert(stm->output_unit);21872188// If more than one stream operates in parallel2189// allow only lower values of latency2190int r;2191UInt32 output_buffer_size = 0;2192UInt32 size = sizeof(output_buffer_size);2193if (stm->output_unit) {2194r = AudioUnitGetProperty(2195stm->output_unit, kAudioDevicePropertyBufferFrameSize,2196kAudioUnitScope_Output, AU_OUT_BUS, &output_buffer_size, &size);2197if (r != noErr) {2198LOG("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize "2199"rv=%d",2200r);2201return 0;2202}22032204output_buffer_size =2205max(min<uint32_t>(output_buffer_size, SAFE_MAX_LATENCY_FRAMES),2206SAFE_MIN_LATENCY_FRAMES);2207}22082209UInt32 input_buffer_size = 0;2210if (stm->input_unit) {2211r = AudioUnitGetProperty(2212stm->input_unit, kAudioDevicePropertyBufferFrameSize,2213kAudioUnitScope_Input, AU_IN_BUS, &input_buffer_size, &size);2214if (r != noErr) {2215LOG("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize "2216"rv=%d",2217r);2218return 0;2219}22202221input_buffer_size =2222max(min<uint32_t>(input_buffer_size, SAFE_MAX_LATENCY_FRAMES),2223SAFE_MIN_LATENCY_FRAMES);2224}22252226// Every following active streams can only set smaller latency2227UInt32 upper_latency_limit = 0;2228if (input_buffer_size != 0 && output_buffer_size != 0) {2229upper_latency_limit = min<uint32_t>(input_buffer_size, output_buffer_size);2230} else if (input_buffer_size != 0) {2231upper_latency_limit = input_buffer_size;2232} else if (output_buffer_size != 0) {2233upper_latency_limit = output_buffer_size;2234} else {2235upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;2236}22372238return max(min<uint32_t>(latency_frames, upper_latency_limit),2239SAFE_MIN_LATENCY_FRAMES);2240}22412242/*2243* Change buffer size is prone to deadlock thus we change it2244* following the steps:2245* - register a listener for the buffer size property2246* - change the property2247* - wait until the listener is executed2248* - property has changed, remove the listener2249* */2250static void2251buffer_size_changed_callback(void * inClientData, AudioUnit inUnit,2252AudioUnitPropertyID inPropertyID,2253AudioUnitScope inScope, AudioUnitElement inElement)2254{2255cubeb_stream * stm = (cubeb_stream *)inClientData;22562257AudioUnit au = inUnit;2258AudioUnitScope au_scope = kAudioUnitScope_Input;2259AudioUnitElement au_element = inElement;2260char const * au_type = "output";22612262if (AU_IN_BUS == inElement) {2263au_scope = kAudioUnitScope_Output;2264au_type = "input";2265}22662267switch (inPropertyID) {22682269case kAudioDevicePropertyBufferFrameSize: {2270if (inScope != au_scope) {2271break;2272}2273UInt32 new_buffer_size;2274UInt32 outSize = sizeof(UInt32);2275OSStatus r =2276AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope,2277au_element, &new_buffer_size, &outSize);2278if (r != noErr) {2279LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current "2280"buffer size",2281stm);2282} else {2283LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size "2284"= %d for scope %d",2285stm, au_type, new_buffer_size, inScope);2286}2287stm->buffer_size_change_state = true;2288break;2289}2290}2291}22922293static int2294audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames,2295io_side side)2296{2297AudioUnit au = stm->output_unit;2298AudioUnitScope au_scope = kAudioUnitScope_Input;2299AudioUnitElement au_element = AU_OUT_BUS;23002301if (side == io_side::INPUT) {2302au = stm->input_unit;2303au_scope = kAudioUnitScope_Output;2304au_element = AU_IN_BUS;2305}23062307uint32_t buffer_frames = 0;2308UInt32 size = sizeof(buffer_frames);2309int r = AudioUnitGetProperty(au, kAudioDevicePropertyBufferFrameSize,2310au_scope, au_element, &buffer_frames, &size);2311if (r != noErr) {2312LOG("AudioUnitGetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d",2313to_string(side), r);2314return CUBEB_ERROR;2315}23162317if (new_size_frames == buffer_frames) {2318LOG("(%p) No need to update %s buffer size already %u frames", stm,2319to_string(side), buffer_frames);2320return CUBEB_OK;2321}23222323r = AudioUnitAddPropertyListener(au, kAudioDevicePropertyBufferFrameSize,2324buffer_size_changed_callback, stm);2325if (r != noErr) {2326LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "2327"rv=%d",2328to_string(side), r);2329return CUBEB_ERROR;2330}23312332stm->buffer_size_change_state = false;23332334r = AudioUnitSetProperty(au, kAudioDevicePropertyBufferFrameSize, au_scope,2335au_element, &new_size_frames,2336sizeof(new_size_frames));2337if (r != noErr) {2338LOG("AudioUnitSetProperty/%s/kAudioDevicePropertyBufferFrameSize rv=%d",2339to_string(side), r);23402341r = AudioUnitRemovePropertyListenerWithUserData(2342au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback,2343stm);2344if (r != noErr) {2345LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "2346"rv=%d",2347to_string(side), r);2348}23492350return CUBEB_ERROR;2351}23522353int count = 0;2354while (!stm->buffer_size_change_state && count++ < 30) {2355struct timespec req, rem;2356req.tv_sec = 0;2357req.tv_nsec = 100000000L; // 0.1 sec2358if (nanosleep(&req, &rem) < 0) {2359LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time "2360"%ld nano secs \n",2361stm, rem.tv_nsec);2362}2363LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count);2364}23652366r = AudioUnitRemovePropertyListenerWithUserData(2367au, kAudioDevicePropertyBufferFrameSize, buffer_size_changed_callback,2368stm);2369if (r != noErr) {2370LOG("AudioUnitAddPropertyListener/%s/kAudioDevicePropertyBufferFrameSize "2371"rv=%d",2372to_string(side), r);2373return CUBEB_ERROR;2374}23752376if (!stm->buffer_size_change_state && count >= 30) {2377LOG("(%p) Error, did not get buffer size change callback ...", stm);2378return CUBEB_ERROR;2379}23802381LOG("(%p) %s buffer size changed to %u frames.", stm, to_string(side),2382new_size_frames);2383return CUBEB_OK;2384}23852386static int2387audiounit_configure_input(cubeb_stream * stm)2388{2389assert(stm && stm->input_unit);23902391int r = 0;2392UInt32 size;2393AURenderCallbackStruct aurcbs_in;23942395LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in "2396"frames %u.",2397stm, stm->input_stream_params.rate, stm->input_stream_params.channels,2398stm->input_stream_params.format, stm->latency_frames);23992400/* Get input device sample rate. */2401AudioStreamBasicDescription input_hw_desc;2402size = sizeof(AudioStreamBasicDescription);2403r = AudioUnitGetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat,2404kAudioUnitScope_Input, AU_IN_BUS, &input_hw_desc,2405&size);2406if (r != noErr) {2407LOG("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r);2408return CUBEB_ERROR;2409}2410stm->input_hw_rate = input_hw_desc.mSampleRate;2411LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);24122413/* Set format description according to the input params. */2414r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);2415if (r != CUBEB_OK) {2416LOG("(%p) Setting format description for input failed.", stm);2417return r;2418}24192420// Use latency to set buffer size2421r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::INPUT);2422if (r != CUBEB_OK) {2423LOG("(%p) Error in change input buffer size.", stm);2424return CUBEB_ERROR;2425}24262427AudioStreamBasicDescription src_desc = stm->input_desc;2428/* Input AudioUnit must be configured with device's sample rate.2429we will resample inside input callback. */2430src_desc.mSampleRate = stm->input_hw_rate;24312432r = AudioUnitSetProperty(stm->input_unit, kAudioUnitProperty_StreamFormat,2433kAudioUnitScope_Output, AU_IN_BUS, &src_desc,2434sizeof(AudioStreamBasicDescription));2435if (r != noErr) {2436LOG("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat rv=%d", r);2437return CUBEB_ERROR;2438}24392440/* Frames per buffer in the input callback. */2441r = AudioUnitSetProperty(2442stm->input_unit, kAudioUnitProperty_MaximumFramesPerSlice,2443kAudioUnitScope_Global, AU_IN_BUS, &stm->latency_frames, sizeof(UInt32));2444if (r != noErr) {2445LOG("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice "2446"rv=%d",2447r);2448return CUBEB_ERROR;2449}24502451// Input only capacity2452unsigned int array_capacity = 1;2453if (has_output(stm)) {2454// Full-duplex increase capacity2455array_capacity = 8;2456}2457if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {2458return CUBEB_ERROR;2459}24602461aurcbs_in.inputProc = audiounit_input_callback;2462aurcbs_in.inputProcRefCon = stm;24632464r = AudioUnitSetProperty(2465stm->input_unit, kAudioOutputUnitProperty_SetInputCallback,2466kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_in, sizeof(aurcbs_in));2467if (r != noErr) {2468LOG("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback "2469"rv=%d",2470r);2471return CUBEB_ERROR;2472}24732474stm->frames_read = 0;24752476LOG("(%p) Input audiounit init successfully.", stm);24772478return CUBEB_OK;2479}24802481static int2482audiounit_configure_output(cubeb_stream * stm)2483{2484assert(stm && stm->output_unit);24852486int r;2487AURenderCallbackStruct aurcbs_out;2488UInt32 size;24892490LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in "2491"frames %u.",2492stm, stm->output_stream_params.rate, stm->output_stream_params.channels,2493stm->output_stream_params.format, stm->latency_frames);24942495r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);2496if (r != CUBEB_OK) {2497LOG("(%p) Could not initialize the audio stream description.", stm);2498return r;2499}25002501/* Get output device sample rate. */2502AudioStreamBasicDescription output_hw_desc;2503size = sizeof(AudioStreamBasicDescription);2504memset(&output_hw_desc, 0, size);2505r = AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat,2506kAudioUnitScope_Output, AU_OUT_BUS, &output_hw_desc,2507&size);2508if (r != noErr) {2509LOG("AudioUnitGetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);2510return CUBEB_ERROR;2511}2512stm->output_hw_rate = output_hw_desc.mSampleRate;2513if (!is_common_sample_rate(stm->output_desc.mSampleRate)) {2514/* For uncommon sample rates, we may run into issues with the OS2515resampler if we don't do the resampling ourselves, so set the2516AudioUnit sample rate to the hardware rate and resample. */2517stm->output_desc.mSampleRate = stm->output_hw_rate;2518}2519LOG("(%p) Output device sampling rate: %.2f", stm,2520output_hw_desc.mSampleRate);2521stm->context->channels = output_hw_desc.mChannelsPerFrame;25222523// Set the input layout to match the output device layout.2524audiounit_layout_init(stm, io_side::OUTPUT);2525if (stm->context->channels != stm->output_stream_params.channels ||2526stm->context->layout != stm->output_stream_params.layout) {2527LOG("Incompatible channel layouts detected, setting up remixer");2528audiounit_init_mixer(stm);2529// We will be remixing the data before it reaches the output device.2530// We need to adjust the number of channels and other2531// AudioStreamDescription details.2532stm->output_desc.mChannelsPerFrame = stm->context->channels;2533stm->output_desc.mBytesPerFrame = (stm->output_desc.mBitsPerChannel / 8) *2534stm->output_desc.mChannelsPerFrame;2535stm->output_desc.mBytesPerPacket =2536stm->output_desc.mBytesPerFrame * stm->output_desc.mFramesPerPacket;2537} else {2538stm->mixer = nullptr;2539}25402541r = AudioUnitSetProperty(stm->output_unit, kAudioUnitProperty_StreamFormat,2542kAudioUnitScope_Input, AU_OUT_BUS, &stm->output_desc,2543sizeof(AudioStreamBasicDescription));2544if (r != noErr) {2545LOG("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat rv=%d", r);2546return CUBEB_ERROR;2547}25482549r = audiounit_set_buffer_size(stm, stm->latency_frames, io_side::OUTPUT);2550if (r != CUBEB_OK) {2551LOG("(%p) Error in change output buffer size.", stm);2552return CUBEB_ERROR;2553}25542555/* Frames per buffer in the input callback. */2556r = AudioUnitSetProperty(2557stm->output_unit, kAudioUnitProperty_MaximumFramesPerSlice,2558kAudioUnitScope_Global, AU_OUT_BUS, &stm->latency_frames, sizeof(UInt32));2559if (r != noErr) {2560LOG("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice "2561"rv=%d",2562r);2563return CUBEB_ERROR;2564}25652566aurcbs_out.inputProc = audiounit_output_callback;2567aurcbs_out.inputProcRefCon = stm;2568r = AudioUnitSetProperty(2569stm->output_unit, kAudioUnitProperty_SetRenderCallback,2570kAudioUnitScope_Global, AU_OUT_BUS, &aurcbs_out, sizeof(aurcbs_out));2571if (r != noErr) {2572LOG("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback "2573"rv=%d",2574r);2575return CUBEB_ERROR;2576}25772578stm->frames_written = 0;25792580LOG("(%p) Output audiounit init successfully.", stm);2581return CUBEB_OK;2582}25832584static int2585audiounit_setup_stream(cubeb_stream * stm)2586{2587stm->mutex.assert_current_thread_owns();25882589if ((stm->input_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK) ||2590(stm->output_stream_params.prefs & CUBEB_STREAM_PREF_LOOPBACK)) {2591LOG("(%p) Loopback not supported for audiounit.", stm);2592return CUBEB_ERROR_NOT_SUPPORTED;2593}25942595int r = 0;25962597device_info in_dev_info = stm->input_device;2598device_info out_dev_info = stm->output_device;25992600if (has_input(stm) && has_output(stm) &&2601stm->input_device.id != stm->output_device.id) {2602r = audiounit_create_aggregate_device(stm);2603if (r != CUBEB_OK) {2604stm->aggregate_device_id = kAudioObjectUnknown;2605LOG("(%p) Create aggregate devices failed.", stm);2606// !!!NOTE: It is not necessary to return here. If it does not2607// return it will fallback to the old implementation. The intention2608// is to investigate how often it fails. I plan to remove2609// it after a couple of weeks.2610return r;2611} else {2612in_dev_info.id = out_dev_info.id = stm->aggregate_device_id;2613in_dev_info.flags = DEV_INPUT;2614out_dev_info.flags = DEV_OUTPUT;2615}2616}26172618if (has_input(stm)) {2619r = audiounit_create_unit(&stm->input_unit, &in_dev_info);2620if (r != CUBEB_OK) {2621LOG("(%p) AudioUnit creation for input failed.", stm);2622return r;2623}2624}26252626if (has_output(stm)) {2627r = audiounit_create_unit(&stm->output_unit, &out_dev_info);2628if (r != CUBEB_OK) {2629LOG("(%p) AudioUnit creation for output failed.", stm);2630return r;2631}2632}26332634/* Latency cannot change if another stream is operating in parallel. In this2635* case latency is set to the other stream value. */2636if (audiounit_active_streams(stm->context) > 1) {2637LOG("(%p) More than one active stream, use global latency.", stm);2638stm->latency_frames = stm->context->global_latency_frames;2639} else {2640/* Silently clamp the latency down to the platform default, because we2641* synthetize the clock from the callbacks, and we want the clock to update2642* often. */2643stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);2644assert(stm->latency_frames); // Ugly error check2645audiounit_set_global_latency(stm->context, stm->latency_frames);2646}26472648/* Configure I/O stream */2649if (has_input(stm)) {2650r = audiounit_configure_input(stm);2651if (r != CUBEB_OK) {2652LOG("(%p) Configure audiounit input failed.", stm);2653return r;2654}2655}26562657if (has_output(stm)) {2658r = audiounit_configure_output(stm);2659if (r != CUBEB_OK) {2660LOG("(%p) Configure audiounit output failed.", stm);2661return r;2662}2663}26642665// Setting the latency doesn't work well for USB headsets (eg. plantronics).2666// Keep the default latency for now.2667#if 02668buffer_size = latency;26692670/* Get the range of latency this particular device can work with, and clamp2671* the requested latency to this acceptable range. */2672#if !TARGET_OS_IPHONE2673if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) {2674return CUBEB_ERROR;2675}26762677if (buffer_size < (unsigned int) latency_range.mMinimum) {2678buffer_size = (unsigned int) latency_range.mMinimum;2679} else if (buffer_size > (unsigned int) latency_range.mMaximum) {2680buffer_size = (unsigned int) latency_range.mMaximum;2681}26822683/**2684* Get the default buffer size. If our latency request is below the default,2685* set it. Otherwise, use the default latency.2686**/2687size = sizeof(default_buffer_size);2688if (AudioUnitGetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize,2689kAudioUnitScope_Output, 0, &default_buffer_size, &size) != 0) {2690return CUBEB_ERROR;2691}26922693if (buffer_size < default_buffer_size) {2694/* Set the maximum number of frame that the render callback will ask for,2695* effectively setting the latency of the stream. This is process-wide. */2696if (AudioUnitSetProperty(stm->output_unit, kAudioDevicePropertyBufferFrameSize,2697kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size)) != 0) {2698return CUBEB_ERROR;2699}2700}2701#else // TARGET_OS_IPHONE2702//TODO: [[AVAudioSession sharedInstance] inputLatency]2703// http://stackoverflow.com/questions/13157523/kaudiodevicepropertybufferframesize-replacement-for-ios2704#endif2705#endif27062707/* We use a resampler because input AudioUnit operates2708* reliable only in the capture device sample rate.2709* Resampler will convert it to the user sample rate2710* and deliver it to the callback. */2711uint32_t target_sample_rate;2712if (has_input(stm)) {2713target_sample_rate = stm->input_stream_params.rate;2714} else {2715assert(has_output(stm));2716target_sample_rate = stm->output_stream_params.rate;2717}27182719cubeb_stream_params input_unconverted_params;2720if (has_input(stm)) {2721input_unconverted_params = stm->input_stream_params;2722/* Use the rate of the input device. */2723input_unconverted_params.rate = stm->input_hw_rate;2724}27252726cubeb_stream_params output_unconverted_params;2727if (has_output(stm)) {2728output_unconverted_params = stm->output_stream_params;2729output_unconverted_params.rate = stm->output_desc.mSampleRate;2730}27312732/* Create resampler. */2733stm->resampler.reset(cubeb_resampler_create(2734stm, has_input(stm) ? &input_unconverted_params : NULL,2735has_output(stm) ? &output_unconverted_params : NULL, target_sample_rate,2736stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP,2737CUBEB_RESAMPLER_RECLOCK_NONE));2738if (!stm->resampler) {2739LOG("(%p) Could not create resampler.", stm);2740return CUBEB_ERROR;2741}27422743if (stm->input_unit != NULL) {2744r = AudioUnitInitialize(stm->input_unit);2745if (r != noErr) {2746LOG("AudioUnitInitialize/input rv=%d", r);2747return CUBEB_ERROR;2748}2749}27502751if (stm->output_unit != NULL) {2752r = AudioUnitInitialize(stm->output_unit);2753if (r != noErr) {2754LOG("AudioUnitInitialize/output rv=%d", r);2755return CUBEB_ERROR;2756}27572758stm->current_latency_frames = audiounit_get_device_presentation_latency(2759stm->output_device.id, kAudioDevicePropertyScopeOutput);27602761Float64 unit_s;2762UInt32 size = sizeof(unit_s);2763if (AudioUnitGetProperty(stm->output_unit, kAudioUnitProperty_Latency,2764kAudioUnitScope_Global, 0, &unit_s,2765&size) == noErr) {2766stm->current_latency_frames +=2767static_cast<uint32_t>(unit_s * stm->output_desc.mSampleRate);2768}2769}27702771if (stm->input_unit && stm->output_unit) {2772// According to the I/O hardware rate it is expected a specific pattern of2773// callbacks for example is input is 44100 and output is 48000 we expected2774// no more than 2 out callback in a row.2775stm->expected_output_callbacks_in_a_row =2776ceilf(stm->output_hw_rate / stm->input_hw_rate);2777}27782779r = audiounit_install_device_changed_callback(stm);2780if (r != CUBEB_OK) {2781LOG("(%p) Could not install all device change callback.", stm);2782}27832784return CUBEB_OK;2785}27862787cubeb_stream::cubeb_stream(cubeb * context)2788: context(context), resampler(nullptr, cubeb_resampler_destroy),2789mixer(nullptr, cubeb_mixer_destroy)2790{2791PodZero(&input_desc, 1);2792PodZero(&output_desc, 1);2793}27942795static void2796audiounit_stream_destroy_internal(cubeb_stream * stm);27972798static int2799audiounit_stream_init(cubeb * context, cubeb_stream ** stream,2800char const * /* stream_name */, cubeb_devid input_device,2801cubeb_stream_params * input_stream_params,2802cubeb_devid output_device,2803cubeb_stream_params * output_stream_params,2804unsigned int latency_frames,2805cubeb_data_callback data_callback,2806cubeb_state_callback state_callback, void * user_ptr)2807{2808assert(context);2809auto_lock context_lock(context->mutex);2810audiounit_increment_active_streams(context);2811unique_ptr<cubeb_stream, decltype(&audiounit_stream_destroy)> stm(2812new cubeb_stream(context), audiounit_stream_destroy_internal);2813int r;2814*stream = NULL;2815assert(latency_frames > 0);28162817/* These could be different in the future if we have both2818* full-duplex stream and different devices for input vs output. */2819stm->data_callback = data_callback;2820stm->state_callback = state_callback;2821stm->user_ptr = user_ptr;2822stm->latency_frames = latency_frames;28232824if ((input_device && !input_stream_params) ||2825(output_device && !output_stream_params)) {2826return CUBEB_ERROR_INVALID_PARAMETER;2827}2828if (input_stream_params) {2829stm->input_stream_params = *input_stream_params;2830r = audiounit_set_device_info(2831stm.get(), reinterpret_cast<uintptr_t>(input_device), io_side::INPUT);2832if (r != CUBEB_OK) {2833LOG("(%p) Fail to set device info for input.", stm.get());2834return r;2835}2836}2837if (output_stream_params) {2838stm->output_stream_params = *output_stream_params;2839r = audiounit_set_device_info(2840stm.get(), reinterpret_cast<uintptr_t>(output_device), io_side::OUTPUT);2841if (r != CUBEB_OK) {2842LOG("(%p) Fail to set device info for output.", stm.get());2843return r;2844}2845}28462847{2848// It's not critical to lock here, because no other thread has been started2849// yet, but it allows to assert that the lock has been taken in2850// `audiounit_setup_stream`.2851auto_lock lock(stm->mutex);2852r = audiounit_setup_stream(stm.get());2853}28542855if (r != CUBEB_OK) {2856LOG("(%p) Could not setup the audiounit stream.", stm.get());2857return r;2858}28592860r = audiounit_install_system_changed_callback(stm.get());2861if (r != CUBEB_OK) {2862LOG("(%p) Could not install the device change callback.", stm.get());2863return r;2864}28652866*stream = stm.release();2867LOG("(%p) Cubeb stream init successful.", *stream);2868return CUBEB_OK;2869}28702871static void2872audiounit_close_stream(cubeb_stream * stm)2873{2874stm->mutex.assert_current_thread_owns();28752876if (stm->input_unit) {2877AudioUnitUninitialize(stm->input_unit);2878AudioComponentInstanceDispose(stm->input_unit);2879stm->input_unit = nullptr;2880}28812882stm->input_linear_buffer.reset();28832884if (stm->output_unit) {2885AudioUnitUninitialize(stm->output_unit);2886AudioComponentInstanceDispose(stm->output_unit);2887stm->output_unit = nullptr;2888}28892890stm->resampler.reset();2891stm->mixer.reset();28922893if (stm->aggregate_device_id != kAudioObjectUnknown) {2894audiounit_destroy_aggregate_device(stm->plugin_id,2895&stm->aggregate_device_id);2896stm->aggregate_device_id = kAudioObjectUnknown;2897}2898}28992900static void2901audiounit_stream_destroy_internal(cubeb_stream * stm)2902{2903stm->context->mutex.assert_current_thread_owns();29042905int r = audiounit_uninstall_system_changed_callback(stm);2906if (r != CUBEB_OK) {2907LOG("(%p) Could not uninstall the device changed callback", stm);2908}2909r = audiounit_uninstall_device_changed_callback(stm);2910if (r != CUBEB_OK) {2911LOG("(%p) Could not uninstall all device change listeners", stm);2912}29132914auto_lock lock(stm->mutex);2915audiounit_close_stream(stm);2916assert(audiounit_active_streams(stm->context) >= 1);2917audiounit_decrement_active_streams(stm->context);2918}29192920static void2921audiounit_stream_destroy(cubeb_stream * stm)2922{2923int r = audiounit_uninstall_system_changed_callback(stm);2924if (r != CUBEB_OK) {2925LOG("(%p) Could not uninstall the device changed callback", stm);2926}2927r = audiounit_uninstall_device_changed_callback(stm);2928if (r != CUBEB_OK) {2929LOG("(%p) Could not uninstall all device change listeners", stm);2930}29312932if (!stm->shutdown.load()) {2933auto_lock context_lock(stm->context->mutex);2934audiounit_stream_stop_internal(stm);2935stm->shutdown = true;2936}29372938stm->destroy_pending = true;2939// Execute close in serial queue to avoid collision2940// with reinit when un/plug devices2941dispatch_sync(stm->context->serial_queue, ^() {2942auto_lock context_lock(stm->context->mutex);2943audiounit_stream_destroy_internal(stm);2944});29452946LOG("Cubeb stream (%p) destroyed successful.", stm);2947delete stm;2948}29492950static int2951audiounit_stream_start_internal(cubeb_stream * stm)2952{2953OSStatus r;2954if (stm->input_unit != NULL) {2955r = AudioOutputUnitStart(stm->input_unit);2956if (r != noErr) {2957LOG("AudioOutputUnitStart (input) rv=%d", r);2958return CUBEB_ERROR;2959}2960}2961if (stm->output_unit != NULL) {2962r = AudioOutputUnitStart(stm->output_unit);2963if (r != noErr) {2964LOG("AudioOutputUnitStart (output) rv=%d", r);2965return CUBEB_ERROR;2966}2967}2968return CUBEB_OK;2969}29702971static int2972audiounit_stream_start(cubeb_stream * stm)2973{2974auto_lock context_lock(stm->context->mutex);2975stm->shutdown = false;2976stm->draining = false;29772978int r = audiounit_stream_start_internal(stm);2979if (r != CUBEB_OK) {2980return r;2981}29822983stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);29842985LOG("Cubeb stream (%p) started successfully.", stm);2986return CUBEB_OK;2987}29882989void2990audiounit_stream_stop_internal(cubeb_stream * stm)2991{2992OSStatus r;2993if (stm->input_unit != NULL) {2994r = AudioOutputUnitStop(stm->input_unit);2995assert(r == 0);2996}2997if (stm->output_unit != NULL) {2998r = AudioOutputUnitStop(stm->output_unit);2999assert(r == 0);3000}3001}30023003static int3004audiounit_stream_stop(cubeb_stream * stm)3005{3006auto_lock context_lock(stm->context->mutex);3007stm->shutdown = true;30083009audiounit_stream_stop_internal(stm);30103011stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);30123013LOG("Cubeb stream (%p) stopped successfully.", stm);3014return CUBEB_OK;3015}30163017static int3018audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position)3019{3020assert(stm);3021if (stm->current_latency_frames > stm->frames_played) {3022*position = 0;3023} else {3024*position = stm->frames_played - stm->current_latency_frames;3025}3026return CUBEB_OK;3027}30283029int3030audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency)3031{3032#if TARGET_OS_IPHONE3033// TODO3034return CUBEB_ERROR_NOT_SUPPORTED;3035#else3036*latency = stm->total_output_latency_frames;3037return CUBEB_OK;3038#endif3039}30403041static int3042audiounit_stream_get_volume(cubeb_stream * stm, float * volume)3043{3044assert(stm->output_unit);3045OSStatus r = AudioUnitGetParameter(stm->output_unit, kHALOutputParam_Volume,3046kAudioUnitScope_Global, 0, volume);3047if (r != noErr) {3048LOG("AudioUnitGetParameter/kHALOutputParam_Volume rv=%d", r);3049return CUBEB_ERROR;3050}3051return CUBEB_OK;3052}30533054static int3055audiounit_stream_set_volume(cubeb_stream * stm, float volume)3056{3057assert(stm->output_unit);3058OSStatus r;3059r = AudioUnitSetParameter(stm->output_unit, kHALOutputParam_Volume,3060kAudioUnitScope_Global, 0, volume, 0);30613062if (r != noErr) {3063LOG("AudioUnitSetParameter/kHALOutputParam_Volume rv=%d", r);3064return CUBEB_ERROR;3065}3066return CUBEB_OK;3067}30683069unique_ptr<char[]>3070convert_uint32_into_string(UInt32 data)3071{3072// Simply create an empty string if no data.3073size_t size = data == 0 ? 0 : 4; // 4 bytes for uint32.3074auto str = unique_ptr<char[]>{new char[size + 1]}; // + 1 for '\0'.3075str[size] = '\0';3076if (size < 4) {3077return str;3078}30793080// Reverse 0xWXYZ into 0xZYXW.3081str[0] = (char)(data >> 24);3082str[1] = (char)(data >> 16);3083str[2] = (char)(data >> 8);3084str[3] = (char)(data);3085return str;3086}30873088int3089audiounit_get_default_device_datasource(cubeb_device_type type, UInt32 * data)3090{3091AudioDeviceID id = audiounit_get_default_device_id(type);3092if (id == kAudioObjectUnknown) {3093return CUBEB_ERROR;3094}30953096UInt32 size = sizeof(*data);3097/* This fails with some USB headsets (e.g., Plantronic .Audio 628). */3098OSStatus r = AudioObjectGetPropertyData(3099id,3100type == CUBEB_DEVICE_TYPE_INPUT ? &INPUT_DATA_SOURCE_PROPERTY_ADDRESS3101: &OUTPUT_DATA_SOURCE_PROPERTY_ADDRESS,31020, NULL, &size, data);3103if (r != noErr) {3104*data = 0;3105}31063107return CUBEB_OK;3108}31093110int3111audiounit_get_default_device_name(cubeb_stream * stm,3112cubeb_device * const device,3113cubeb_device_type type)3114{3115assert(stm);3116assert(device);31173118UInt32 data;3119int r = audiounit_get_default_device_datasource(type, &data);3120if (r != CUBEB_OK) {3121return r;3122}3123char ** name = type == CUBEB_DEVICE_TYPE_INPUT ? &device->input_name3124: &device->output_name;3125*name = convert_uint32_into_string(data).release();3126if (!strlen(*name)) { // empty string.3127LOG("(%p) name of %s device is empty!", stm,3128type == CUBEB_DEVICE_TYPE_INPUT ? "input" : "output");3129}3130return CUBEB_OK;3131}31323133int3134audiounit_stream_get_current_device(cubeb_stream * stm,3135cubeb_device ** const device)3136{3137#if TARGET_OS_IPHONE3138// TODO3139return CUBEB_ERROR_NOT_SUPPORTED;3140#else3141*device = new cubeb_device;3142if (!*device) {3143return CUBEB_ERROR;3144}3145PodZero(*device, 1);31463147int r =3148audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_OUTPUT);3149if (r != CUBEB_OK) {3150return r;3151}31523153r = audiounit_get_default_device_name(stm, *device, CUBEB_DEVICE_TYPE_INPUT);3154if (r != CUBEB_OK) {3155return r;3156}31573158return CUBEB_OK;3159#endif3160}31613162int3163audiounit_stream_device_destroy(cubeb_stream * /* stream */,3164cubeb_device * device)3165{3166delete[] device->output_name;3167delete[] device->input_name;3168delete device;3169return CUBEB_OK;3170}31713172int3173audiounit_stream_register_device_changed_callback(3174cubeb_stream * stream,3175cubeb_device_changed_callback device_changed_callback)3176{3177auto_lock dev_cb_lock(stream->device_changed_callback_lock);3178/* Note: second register without unregister first causes 'nope' error.3179* Current implementation requires unregister before register a new cb. */3180assert(!device_changed_callback || !stream->device_changed_callback);3181stream->device_changed_callback = device_changed_callback;3182return CUBEB_OK;3183}31843185static char *3186audiounit_strref_to_cstr_utf8(CFStringRef strref)3187{3188CFIndex len, size;3189char * ret;3190if (strref == NULL) {3191return NULL;3192}31933194len = CFStringGetLength(strref);3195// Add 1 to size to allow for '\0' termination character.3196size = CFStringGetMaximumSizeForEncoding(len, kCFStringEncodingUTF8) + 1;3197ret = new char[size];31983199if (!CFStringGetCString(strref, ret, size, kCFStringEncodingUTF8)) {3200delete[] ret;3201ret = NULL;3202}32033204return ret;3205}32063207static uint32_t3208audiounit_get_channel_count(AudioObjectID devid, AudioObjectPropertyScope scope)3209{3210AudioObjectPropertyAddress adr = {0, scope,3211kAudioObjectPropertyElementMaster};3212UInt32 size = 0;3213uint32_t i, ret = 0;32143215adr.mSelector = kAudioDevicePropertyStreamConfiguration;32163217if (AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr &&3218size > 0) {3219AudioBufferList * list = static_cast<AudioBufferList *>(alloca(size));3220if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, list) ==3221noErr) {3222for (i = 0; i < list->mNumberBuffers; i++)3223ret += list->mBuffers[i].mNumberChannels;3224}3225}32263227return ret;3228}32293230static void3231audiounit_get_available_samplerate(AudioObjectID devid,3232AudioObjectPropertyScope scope,3233uint32_t * min, uint32_t * max,3234uint32_t * def)3235{3236AudioObjectPropertyAddress adr = {0, scope,3237kAudioObjectPropertyElementMaster};32383239adr.mSelector = kAudioDevicePropertyNominalSampleRate;3240if (AudioObjectHasProperty(devid, &adr)) {3241UInt32 size = sizeof(Float64);3242Float64 fvalue = 0.0;3243if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &fvalue) ==3244noErr) {3245*def = fvalue;3246}3247}32483249adr.mSelector = kAudioDevicePropertyAvailableNominalSampleRates;3250UInt32 size = 0;3251AudioValueRange range;3252if (AudioObjectHasProperty(devid, &adr) &&3253AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) {3254uint32_t count = size / sizeof(AudioValueRange);3255vector<AudioValueRange> ranges(count);3256range.mMinimum = 9999999999.0;3257range.mMaximum = 0.0;3258if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size,3259ranges.data()) == noErr) {3260for (uint32_t i = 0; i < count; i++) {3261if (ranges[i].mMaximum > range.mMaximum)3262range.mMaximum = ranges[i].mMaximum;3263if (ranges[i].mMinimum < range.mMinimum)3264range.mMinimum = ranges[i].mMinimum;3265}3266}3267*max = static_cast<uint32_t>(range.mMaximum);3268*min = static_cast<uint32_t>(range.mMinimum);3269} else {3270*min = *max = 0;3271}3272}32733274static UInt323275audiounit_get_device_presentation_latency(AudioObjectID devid,3276AudioObjectPropertyScope scope)3277{3278AudioObjectPropertyAddress adr = {0, scope,3279kAudioObjectPropertyElementMaster};3280UInt32 size, dev, stream = 0;3281AudioStreamID sid[1];32823283adr.mSelector = kAudioDevicePropertyLatency;3284size = sizeof(UInt32);3285if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &dev) != noErr) {3286dev = 0;3287}32883289adr.mSelector = kAudioDevicePropertyStreams;3290size = sizeof(sid);3291if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, sid) == noErr) {3292adr.mSelector = kAudioStreamPropertyLatency;3293size = sizeof(UInt32);3294AudioObjectGetPropertyData(sid[0], &adr, 0, NULL, &size, &stream);3295}32963297return dev + stream;3298}32993300static int3301audiounit_create_device_from_hwdev(cubeb_device_info * dev_info,3302AudioObjectID devid, cubeb_device_type type)3303{3304AudioObjectPropertyAddress adr = {0, 0, kAudioObjectPropertyElementMaster};3305UInt32 size;33063307if (type == CUBEB_DEVICE_TYPE_OUTPUT) {3308adr.mScope = kAudioDevicePropertyScopeOutput;3309} else if (type == CUBEB_DEVICE_TYPE_INPUT) {3310adr.mScope = kAudioDevicePropertyScopeInput;3311} else {3312return CUBEB_ERROR;3313}33143315UInt32 ch = audiounit_get_channel_count(devid, adr.mScope);3316if (ch == 0) {3317return CUBEB_ERROR;3318}33193320PodZero(dev_info, 1);33213322CFStringRef device_id_str = nullptr;3323size = sizeof(CFStringRef);3324adr.mSelector = kAudioDevicePropertyDeviceUID;3325OSStatus ret =3326AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &device_id_str);3327if (ret == noErr && device_id_str != NULL) {3328dev_info->device_id = audiounit_strref_to_cstr_utf8(device_id_str);3329static_assert(sizeof(cubeb_devid) >= sizeof(decltype(devid)),3330"cubeb_devid can't represent devid");3331dev_info->devid = reinterpret_cast<cubeb_devid>(devid);3332dev_info->group_id = dev_info->device_id;3333CFRelease(device_id_str);3334}33353336CFStringRef friendly_name_str = nullptr;3337UInt32 ds;3338size = sizeof(UInt32);3339adr.mSelector = kAudioDevicePropertyDataSource;3340ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &ds);3341if (ret == noErr) {3342AudioValueTranslation trl = {&ds, sizeof(ds), &friendly_name_str,3343sizeof(CFStringRef)};3344adr.mSelector = kAudioDevicePropertyDataSourceNameForIDCFString;3345size = sizeof(AudioValueTranslation);3346AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &trl);3347}33483349// If there is no datasource for this device, fall back to the3350// device name.3351if (!friendly_name_str) {3352size = sizeof(CFStringRef);3353adr.mSelector = kAudioObjectPropertyName;3354AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &friendly_name_str);3355}33563357if (friendly_name_str) {3358dev_info->friendly_name = audiounit_strref_to_cstr_utf8(friendly_name_str);3359CFRelease(friendly_name_str);3360} else {3361// Couldn't get a datasource name nor a device name, return a3362// valid string of length 0.3363char * fallback_name = new char[1];3364fallback_name[0] = '\0';3365dev_info->friendly_name = fallback_name;3366}33673368CFStringRef vendor_name_str = nullptr;3369size = sizeof(CFStringRef);3370adr.mSelector = kAudioObjectPropertyManufacturer;3371ret =3372AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &vendor_name_str);3373if (ret == noErr && vendor_name_str != NULL) {3374dev_info->vendor_name = audiounit_strref_to_cstr_utf8(vendor_name_str);3375CFRelease(vendor_name_str);3376}33773378dev_info->type = type;3379dev_info->state = CUBEB_DEVICE_STATE_ENABLED;3380dev_info->preferred = (devid == audiounit_get_default_device_id(type))3381? CUBEB_DEVICE_PREF_ALL3382: CUBEB_DEVICE_PREF_NONE;33833384dev_info->max_channels = ch;3385dev_info->format =3386(cubeb_device_fmt)CUBEB_DEVICE_FMT_ALL; /* CoreAudio supports All! */3387/* kAudioFormatFlagsAudioUnitCanonical is deprecated, prefer floating point */3388dev_info->default_format = CUBEB_DEVICE_FMT_F32NE;3389audiounit_get_available_samplerate(devid, adr.mScope, &dev_info->min_rate,3390&dev_info->max_rate,3391&dev_info->default_rate);33923393UInt32 latency = audiounit_get_device_presentation_latency(devid, adr.mScope);33943395AudioValueRange range;3396adr.mSelector = kAudioDevicePropertyBufferFrameSizeRange;3397size = sizeof(AudioValueRange);3398ret = AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, &range);3399if (ret == noErr) {3400dev_info->latency_lo = latency + range.mMinimum;3401dev_info->latency_hi = latency + range.mMaximum;3402} else {3403dev_info->latency_lo =340410 * dev_info->default_rate / 1000; /* Default to 10ms */3405dev_info->latency_hi =3406100 * dev_info->default_rate / 1000; /* Default to 100ms */3407}34083409return CUBEB_OK;3410}34113412bool3413is_aggregate_device(cubeb_device_info * device_info)3414{3415assert(device_info->friendly_name);3416return !strncmp(device_info->friendly_name, PRIVATE_AGGREGATE_DEVICE_NAME,3417strlen(PRIVATE_AGGREGATE_DEVICE_NAME));3418}34193420static int3421audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type,3422cubeb_device_collection * collection)3423{3424vector<AudioObjectID> input_devs;3425vector<AudioObjectID> output_devs;34263427// Count number of input and output devices. This is not3428// necessarily the same as the count of raw devices supported by the3429// system since, for example, with Soundflower installed, some3430// devices may report as being both input *and* output and cubeb3431// separates those into two different devices.34323433if (type & CUBEB_DEVICE_TYPE_OUTPUT) {3434output_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);3435}34363437if (type & CUBEB_DEVICE_TYPE_INPUT) {3438input_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);3439}34403441auto devices = new cubeb_device_info[output_devs.size() + input_devs.size()];3442collection->count = 0;34433444if (type & CUBEB_DEVICE_TYPE_OUTPUT) {3445for (auto dev : output_devs) {3446auto device = &devices[collection->count];3447auto err = audiounit_create_device_from_hwdev(device, dev,3448CUBEB_DEVICE_TYPE_OUTPUT);3449if (err != CUBEB_OK || is_aggregate_device(device)) {3450continue;3451}3452collection->count += 1;3453}3454}34553456if (type & CUBEB_DEVICE_TYPE_INPUT) {3457for (auto dev : input_devs) {3458auto device = &devices[collection->count];3459auto err = audiounit_create_device_from_hwdev(device, dev,3460CUBEB_DEVICE_TYPE_INPUT);3461if (err != CUBEB_OK || is_aggregate_device(device)) {3462continue;3463}3464collection->count += 1;3465}3466}34673468if (collection->count > 0) {3469collection->device = devices;3470} else {3471delete[] devices;3472collection->device = NULL;3473}34743475return CUBEB_OK;3476}34773478static void3479audiounit_device_destroy(cubeb_device_info * device)3480{3481delete[] device->device_id;3482delete[] device->friendly_name;3483delete[] device->vendor_name;3484}34853486static int3487audiounit_device_collection_destroy(cubeb * /* context */,3488cubeb_device_collection * collection)3489{3490for (size_t i = 0; i < collection->count; i++) {3491audiounit_device_destroy(&collection->device[i]);3492}3493delete[] collection->device;34943495return CUBEB_OK;3496}34973498static vector<AudioObjectID>3499audiounit_get_devices_of_type(cubeb_device_type devtype)3500{3501UInt32 size = 0;3502OSStatus ret = AudioObjectGetPropertyDataSize(3503kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS, 0, NULL, &size);3504if (ret != noErr) {3505return vector<AudioObjectID>();3506}3507vector<AudioObjectID> devices(size / sizeof(AudioObjectID));3508ret = AudioObjectGetPropertyData(kAudioObjectSystemObject,3509&DEVICES_PROPERTY_ADDRESS, 0, NULL, &size,3510devices.data());3511if (ret != noErr) {3512return vector<AudioObjectID>();3513}35143515// Remove the aggregate device from the list of devices (if any).3516for (auto it = devices.begin(); it != devices.end();) {3517CFStringRef name = get_device_name(*it);3518if (name && CFStringFind(name, CFSTR("CubebAggregateDevice"), 0).location !=3519kCFNotFound) {3520it = devices.erase(it);3521} else {3522it++;3523}3524if (name) {3525CFRelease(name);3526}3527}35283529/* Expected sorted but did not find anything in the docs. */3530sort(devices.begin(), devices.end(),3531[](AudioObjectID a, AudioObjectID b) { return a < b; });35323533if (devtype == (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) {3534return devices;3535}35363537AudioObjectPropertyScope scope = (devtype == CUBEB_DEVICE_TYPE_INPUT)3538? kAudioDevicePropertyScopeInput3539: kAudioDevicePropertyScopeOutput;35403541vector<AudioObjectID> devices_in_scope;3542for (uint32_t i = 0; i < devices.size(); ++i) {3543/* For device in the given scope channel must be > 0. */3544if (audiounit_get_channel_count(devices[i], scope) > 0) {3545devices_in_scope.push_back(devices[i]);3546}3547}35483549return devices_in_scope;3550}35513552static OSStatus3553audiounit_collection_changed_callback(3554AudioObjectID /* inObjectID */, UInt32 /* inNumberAddresses */,3555const AudioObjectPropertyAddress * /* inAddresses */, void * inClientData)3556{3557cubeb * context = static_cast<cubeb *>(inClientData);35583559// This can be called from inside an AudioUnit function, dispatch to another3560// queue.3561dispatch_async(context->serial_queue, ^() {3562auto_lock lock(context->mutex);3563if (!context->input_collection_changed_callback &&3564!context->output_collection_changed_callback) {3565/* Listener removed while waiting in mutex, abort. */3566return;3567}3568if (context->input_collection_changed_callback) {3569vector<AudioObjectID> devices =3570audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);3571/* Elements in the vector expected sorted. */3572if (context->input_device_array != devices) {3573context->input_device_array = devices;3574context->input_collection_changed_callback(3575context, context->input_collection_changed_user_ptr);3576}3577}3578if (context->output_collection_changed_callback) {3579vector<AudioObjectID> devices =3580audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);3581/* Elements in the vector expected sorted. */3582if (context->output_device_array != devices) {3583context->output_device_array = devices;3584context->output_collection_changed_callback(3585context, context->output_collection_changed_user_ptr);3586}3587}3588});3589return noErr;3590}35913592static OSStatus3593audiounit_add_device_listener(3594cubeb * context, cubeb_device_type devtype,3595cubeb_device_collection_changed_callback collection_changed_callback,3596void * user_ptr)3597{3598context->mutex.assert_current_thread_owns();3599assert(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT));3600/* Note: second register without unregister first causes 'nope' error.3601* Current implementation requires unregister before register a new cb. */3602assert((devtype & CUBEB_DEVICE_TYPE_INPUT) &&3603!context->input_collection_changed_callback ||3604(devtype & CUBEB_DEVICE_TYPE_OUTPUT) &&3605!context->output_collection_changed_callback);36063607if (!context->input_collection_changed_callback &&3608!context->output_collection_changed_callback) {3609OSStatus ret = AudioObjectAddPropertyListener(3610kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS,3611audiounit_collection_changed_callback, context);3612if (ret != noErr) {3613return ret;3614}3615}3616if (devtype & CUBEB_DEVICE_TYPE_INPUT) {3617/* Expected empty after unregister. */3618assert(context->input_device_array.empty());3619context->input_device_array =3620audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT);3621context->input_collection_changed_callback = collection_changed_callback;3622context->input_collection_changed_user_ptr = user_ptr;3623}3624if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {3625/* Expected empty after unregister. */3626assert(context->output_device_array.empty());3627context->output_device_array =3628audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT);3629context->output_collection_changed_callback = collection_changed_callback;3630context->output_collection_changed_user_ptr = user_ptr;3631}3632return noErr;3633}36343635static OSStatus3636audiounit_remove_device_listener(cubeb * context, cubeb_device_type devtype)3637{3638context->mutex.assert_current_thread_owns();36393640if (devtype & CUBEB_DEVICE_TYPE_INPUT) {3641context->input_collection_changed_callback = nullptr;3642context->input_collection_changed_user_ptr = nullptr;3643context->input_device_array.clear();3644}3645if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) {3646context->output_collection_changed_callback = nullptr;3647context->output_collection_changed_user_ptr = nullptr;3648context->output_device_array.clear();3649}36503651if (context->input_collection_changed_callback ||3652context->output_collection_changed_callback) {3653return noErr;3654}3655/* Note: unregister a non registered cb is not a problem, not checking. */3656return AudioObjectRemovePropertyListener(3657kAudioObjectSystemObject, &DEVICES_PROPERTY_ADDRESS,3658audiounit_collection_changed_callback, context);3659}36603661int3662audiounit_register_device_collection_changed(3663cubeb * context, cubeb_device_type devtype,3664cubeb_device_collection_changed_callback collection_changed_callback,3665void * user_ptr)3666{3667if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {3668return CUBEB_ERROR_INVALID_PARAMETER;3669}3670OSStatus ret;3671auto_lock lock(context->mutex);3672if (collection_changed_callback) {3673ret = audiounit_add_device_listener(context, devtype,3674collection_changed_callback, user_ptr);3675} else {3676ret = audiounit_remove_device_listener(context, devtype);3677}3678return (ret == noErr) ? CUBEB_OK : CUBEB_ERROR;3679}36803681cubeb_ops const audiounit_ops = {3682/*.init =*/audiounit_init,3683/*.get_backend_id =*/audiounit_get_backend_id,3684/*.get_max_channel_count =*/audiounit_get_max_channel_count,3685/*.get_min_latency =*/audiounit_get_min_latency,3686/*.get_preferred_sample_rate =*/audiounit_get_preferred_sample_rate,3687/*.get_supported_input_processing_params =*/NULL,3688/*.enumerate_devices =*/audiounit_enumerate_devices,3689/*.device_collection_destroy =*/audiounit_device_collection_destroy,3690/*.destroy =*/audiounit_destroy,3691/*.stream_init =*/audiounit_stream_init,3692/*.stream_destroy =*/audiounit_stream_destroy,3693/*.stream_start =*/audiounit_stream_start,3694/*.stream_stop =*/audiounit_stream_stop,3695/*.stream_get_position =*/audiounit_stream_get_position,3696/*.stream_get_latency =*/audiounit_stream_get_latency,3697/*.stream_get_input_latency =*/NULL,3698/*.stream_set_volume =*/audiounit_stream_set_volume,3699/*.stream_set_name =*/NULL,3700/*.stream_get_current_device =*/audiounit_stream_get_current_device,3701/*.stream_set_input_mute =*/NULL,3702/*.stream_set_input_processing_params =*/NULL,3703/*.stream_device_destroy =*/audiounit_stream_device_destroy,3704/*.stream_register_device_changed_callback =*/3705audiounit_stream_register_device_changed_callback,3706/*.register_device_collection_changed =*/3707audiounit_register_device_collection_changed};370837093710