summaryrefslogtreecommitdiff
path: root/media/libcubeb/osx-linearize-operations.patch
diff options
context:
space:
mode:
Diffstat (limited to 'media/libcubeb/osx-linearize-operations.patch')
-rw-r--r--media/libcubeb/osx-linearize-operations.patch968
1 files changed, 968 insertions, 0 deletions
diff --git a/media/libcubeb/osx-linearize-operations.patch b/media/libcubeb/osx-linearize-operations.patch
new file mode 100644
index 0000000000..9f4f31bcaf
--- /dev/null
+++ b/media/libcubeb/osx-linearize-operations.patch
@@ -0,0 +1,968 @@
+From: Paul Adenot <paul@paul.cx>
+Subject: Linearize operations on AudioUnits to sidestep a deadlock.
+
+---
+
+diff --git a/src/cubeb_audiounit.cpp b/src/cubeb_audiounit.cpp
+--- a/src/cubeb_audiounit.cpp
++++ b/src/cubeb_audiounit.cpp
+@@ -53,40 +53,45 @@ typedef UInt32 AudioFormatFlags;
+
+ #define AU_OUT_BUS 0
+ #define AU_IN_BUS 1
+
+ #define PRINT_ERROR_CODE(str, r) do { \
+ LOG("System call failed: %s (rv: %d)", str, r); \
+ } while(0)
+
++const char * DISPATCH_QUEUE_LABEL = "org.mozilla.cubeb";
++
+ /* Testing empirically, some headsets report a minimal latency that is very
+ * low, but this does not work in practice. Lie and say the minimum is 256
+ * frames. */
+ const uint32_t SAFE_MIN_LATENCY_FRAMES = 256;
+ const uint32_t SAFE_MAX_LATENCY_FRAMES = 512;
+
+ void audiounit_stream_stop_internal(cubeb_stream * stm);
+ void audiounit_stream_start_internal(cubeb_stream * stm);
+-static void close_audiounit_stream(cubeb_stream * stm);
+-static int setup_audiounit_stream(cubeb_stream * stm);
++static void audiounit_close_stream(cubeb_stream *stm);
++static int audiounit_setup_stream(cubeb_stream *stm);
+
+ extern cubeb_ops const audiounit_ops;
+
+ struct cubeb {
+ cubeb_ops const * ops;
+ owned_critical_section mutex;
+ std::atomic<int> active_streams;
++ uint32_t global_latency_frames = 0;
+ int limit_streams;
+ cubeb_device_collection_changed_callback collection_changed_callback;
+ void * collection_changed_user_ptr;
+ /* Differentiate input from output devices. */
+ cubeb_device_type collection_changed_devtype;
+ uint32_t devtype_device_count;
+ AudioObjectID * devtype_device_array;
++ // The queue is asynchronously deallocated once all references to it are released
++ dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
+ };
+
+ class auto_array_wrapper
+ {
+ public:
+ explicit auto_array_wrapper(auto_array<float> * ar)
+ : float_ar(ar)
+ , short_ar(nullptr)
+@@ -205,16 +210,17 @@ struct cubeb_stream {
+ cubeb_resampler * resampler;
+ /* This is the number of output callback we got in a row. This is usually one,
+ * but can be two when the input and output rate are different, and more when
+ * a device has been plugged or unplugged, as there can be some time before
+ * the device is ready. */
+ std::atomic<int> output_callback_in_a_row;
+ /* This is true if a device change callback is currently running. */
+ std::atomic<bool> switching_device;
++ std::atomic<bool> buffer_size_change_state{ false };
+ };
+
+ bool has_input(cubeb_stream * stm)
+ {
+ return stm->input_stream_params.rate != 0;
+ }
+
+ bool has_output(cubeb_stream * stm)
+@@ -256,16 +262,24 @@ audiotimestamp_to_latency(AudioTimeStamp
+
+ uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
+ uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
+
+ return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
+ }
+
+ static void
++audiounit_set_global_latency(cubeb_stream * stm, uint32_t latency_frames)
++{
++ stm->mutex.assert_current_thread_owns();
++ assert(stm->context->active_streams == 1);
++ stm->context->global_latency_frames = latency_frames;
++}
++
++static void
+ audiounit_make_silent(AudioBuffer * ioData)
+ {
+ assert(ioData);
+ assert(ioData->mData);
+ memset(ioData->mData, 0, ioData->mDataByteSize);
+ }
+
+ static OSStatus
+@@ -576,29 +590,54 @@ audiounit_get_input_device_id(AudioDevic
+ device_id);
+ if (r != noErr) {
+ return CUBEB_ERROR;
+ }
+
+ return CUBEB_OK;
+ }
+
++static int
++audiounit_reinit_stream(cubeb_stream * stm, bool is_started)
++{
++ if (is_started) {
++ audiounit_stream_stop_internal(stm);
++ }
++
++ {
++ auto_lock lock(stm->mutex);
++
++ audiounit_close_stream(stm);
++
++ if (audiounit_setup_stream(stm) != CUBEB_OK) {
++ LOG("(%p) Stream reinit failed.", stm);
++ return CUBEB_ERROR;
++ }
++
++ // Reset input frames to force new stream pre-buffer
++ // silence if needed, check `is_extra_input_needed()`
++ stm->frames_read = 0;
++
++ // If the stream was running, start it again.
++ if (is_started) {
++ audiounit_stream_start_internal(stm);
++ }
++ }
++ return CUBEB_OK;
++}
++
+ static OSStatus
+ audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
+ const AudioObjectPropertyAddress * addresses,
+ void * user)
+ {
+ cubeb_stream * stm = (cubeb_stream*) user;
+- int rv;
+- bool was_running = false;
+-
+ stm->switching_device = true;
+-
+ // Note if the stream was running or not
+- was_running = !stm->shutdown;
++ bool was_running = !stm->shutdown;
+
+ LOG("(%p) Audio device changed, %d events.", stm, address_count);
+ for (UInt32 i = 0; i < address_count; i++) {
+ switch(addresses[i].mSelector) {
+ case kAudioHardwarePropertyDefaultOutputDevice: {
+ LOG("Event[%d] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", i);
+ // Allow restart to choose the new default
+ stm->output_device = nullptr;
+@@ -639,38 +678,25 @@ audiounit_property_listener_callback(Aud
+ if (stm->device_changed_callback) {
+ stm->device_changed_callback(stm->user_ptr);
+ }
+ break;
+ }
+ }
+ }
+
+- // This means the callback won't be called again.
+- audiounit_stream_stop_internal(stm);
+-
+- {
+- auto_lock lock(stm->mutex);
+- close_audiounit_stream(stm);
+- rv = setup_audiounit_stream(stm);
+- if (rv != CUBEB_OK) {
+- LOG("(%p) Could not reopen a stream after switching.", stm);
++ // Use a new thread, through the queue, to avoid deadlock when calling
++ // Get/SetProperties method from inside notify callback
++ dispatch_async(stm->context->serial_queue, ^() {
++ if (audiounit_reinit_stream(stm, was_running) != CUBEB_OK) {
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+- return noErr;
++ LOG("(%p) Could not reopen the stream after switching.", stm);
+ }
+-
+- stm->frames_read = 0;
+-
+- // If the stream was running, start it again.
+- if (was_running) {
+- audiounit_stream_start_internal(stm);
+- }
+- }
+-
+- stm->switching_device = false;
++ stm->switching_device = false;
++ });
+
+ return noErr;
+ }
+
+ OSStatus
+ audiounit_add_listener(cubeb_stream * stm, AudioDeviceID id, AudioObjectPropertySelector selector,
+ AudioObjectPropertyScope scope, AudioObjectPropertyListenerProc listener)
+ {
+@@ -1155,18 +1181,17 @@ audiounit_init_input_linear_buffer(cubeb
+
+ static void
+ audiounit_destroy_input_linear_buffer(cubeb_stream * stream)
+ {
+ delete stream->input_linear_buffer;
+ }
+
+ static uint32_t
+-audiounit_clamp_latency(cubeb_stream * stm,
+- uint32_t latency_frames)
++audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
+ {
+ // For the 1st stream set anything within safe min-max
+ assert(stm->context->active_streams > 0);
+ if (stm->context->active_streams == 1) {
+ return std::max(std::min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
+ SAFE_MIN_LATENCY_FRAMES);
+ }
+
+@@ -1219,26 +1244,374 @@ audiounit_clamp_latency(cubeb_stream * s
+ } else {
+ upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;
+ }
+
+ return std::max(std::min<uint32_t>(latency_frames, upper_latency_limit),
+ SAFE_MIN_LATENCY_FRAMES);
+ }
+
++/*
++ * Change buffer size is prone to deadlock thus we change it
++ * following the steps:
++ * - register a listener for the buffer size property
++ * - change the property
++ * - wait until the listener is executed
++ * - property has changed, remove the listener
++ * */
++static void
++buffer_size_changed_callback(void * inClientData,
++ AudioUnit inUnit,
++ AudioUnitPropertyID inPropertyID,
++ AudioUnitScope inScope,
++ AudioUnitElement inElement)
++{
++ cubeb_stream * stm = (cubeb_stream *)inClientData;
++
++ AudioUnit au = inUnit;
++ AudioUnitScope au_scope = kAudioUnitScope_Input;
++ AudioUnitElement au_element = inElement;
++ const char * au_type = "output";
++
++ if (au == stm->input_unit) {
++ au_scope = kAudioUnitScope_Output;
++ au_type = "input";
++ }
++
++ switch (inPropertyID) {
++
++ case kAudioDevicePropertyBufferFrameSize: {
++ if (inScope != au_scope) {
++ break;
++ }
++ UInt32 new_buffer_size;
++ UInt32 outSize = sizeof(UInt32);
++ OSStatus r = AudioUnitGetProperty(au,
++ kAudioDevicePropertyBufferFrameSize,
++ au_scope,
++ au_element,
++ &new_buffer_size,
++ &outSize);
++ if (r != noErr) {
++ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: Cannot get current buffer size", stm);
++ } else {
++ LOG("(%p) Event: kAudioDevicePropertyBufferFrameSize: New %s buffer size = %d for scope %d", stm,
++ au_type, new_buffer_size, inScope);
++ }
++ stm->buffer_size_change_state = true;
++ break;
++ }
++ }
++}
++
++enum set_buffer_size_side {
++ INPUT,
++ OUTPUT,
++};
++
+ static int
+-setup_audiounit_stream(cubeb_stream * stm)
++audiounit_set_buffer_size(cubeb_stream * stm, uint32_t new_size_frames, set_buffer_size_side set_side)
++{
++ AudioUnit au = stm->output_unit;
++ AudioUnitScope au_scope = kAudioUnitScope_Input;
++ AudioUnitElement au_element = AU_OUT_BUS;
++ const char * au_type = "output";
++
++ if (set_side == INPUT) {
++ au = stm->input_unit;
++ au_scope = kAudioUnitScope_Output;
++ au_element = AU_IN_BUS;
++ au_type = "input";
++ }
++
++ uint32_t buffer_frames = 0;
++ UInt32 size = sizeof(buffer_frames);
++ int r = AudioUnitGetProperty(au,
++ kAudioDevicePropertyBufferFrameSize,
++ au_scope,
++ au_element,
++ &buffer_frames,
++ &size);
++ if (r != noErr) {
++ if (set_side == INPUT) {
++ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
++ } else {
++ PRINT_ERROR_CODE("AudioUnitGetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
++ }
++ return CUBEB_ERROR;
++ }
++
++ if (new_size_frames == buffer_frames) {
++ LOG("(%p) No need to update %s buffer size already %u frames", stm, au_type, buffer_frames);
++ return CUBEB_OK;
++ }
++
++ r = AudioUnitAddPropertyListener(au,
++ kAudioDevicePropertyBufferFrameSize,
++ buffer_size_changed_callback,
++ stm);
++ if (r != noErr) {
++ if (set_side == INPUT) {
++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
++ } else {
++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
++ }
++ return CUBEB_ERROR;
++ }
++
++ stm->buffer_size_change_state = false;
++
++ r = AudioUnitSetProperty(au,
++ kAudioDevicePropertyBufferFrameSize,
++ au_scope,
++ au_element,
++ &new_size_frames,
++ sizeof(new_size_frames));
++ if (r != noErr) {
++ if (set_side == INPUT) {
++ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
++ } else {
++ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
++ }
++
++ r = AudioUnitRemovePropertyListenerWithUserData(au,
++ kAudioDevicePropertyBufferFrameSize,
++ buffer_size_changed_callback,
++ stm);
++ if (r != noErr) {
++ if (set_side == INPUT) {
++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
++ } else {
++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
++ }
++ }
++
++ return CUBEB_ERROR;
++ }
++
++ int count = 0;
++ while (!stm->buffer_size_change_state && count++ < 30) {
++ struct timespec req, rem;
++ req.tv_sec = 0;
++ req.tv_nsec = 100000000L; // 0.1 sec
++ if (nanosleep(&req , &rem) < 0 ) {
++ LOG("(%p) Warning: nanosleep call failed or interrupted. Remaining time %ld nano secs \n", stm, rem.tv_nsec);
++ }
++ LOG("(%p) audiounit_set_buffer_size : wait count = %d", stm, count);
++ }
++
++ r = AudioUnitRemovePropertyListenerWithUserData(au,
++ kAudioDevicePropertyBufferFrameSize,
++ buffer_size_changed_callback,
++ stm);
++ if (r != noErr) {
++ return CUBEB_ERROR;
++ if (set_side == INPUT) {
++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/input/kAudioDevicePropertyBufferFrameSize", r);
++ } else {
++ PRINT_ERROR_CODE("AudioUnitAddPropertyListener/output/kAudioDevicePropertyBufferFrameSize", r);
++ }
++ }
++
++ if (!stm->buffer_size_change_state && count >= 30) {
++ LOG("(%p) Error, did not get buffer size change callback ...", stm);
++ return CUBEB_ERROR;
++ }
++
++ LOG("(%p) %s buffer size changed to %u frames.", stm, au_type, new_size_frames);
++ return CUBEB_OK;
++}
++
++static int
++audiounit_configure_input(cubeb_stream * stm)
++{
++ int r = 0;
++ UInt32 size;
++ AURenderCallbackStruct aurcbs_in;
++
++ LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
++ stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
++ stm->input_stream_params.format, stm->latency_frames);
++
++ /* Get input device sample rate. */
++ AudioStreamBasicDescription input_hw_desc;
++ size = sizeof(AudioStreamBasicDescription);
++ r = AudioUnitGetProperty(stm->input_unit,
++ kAudioUnitProperty_StreamFormat,
++ kAudioUnitScope_Input,
++ AU_IN_BUS,
++ &input_hw_desc,
++ &size);
++ if (r != noErr) {
++ PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
++ return CUBEB_ERROR;
++ }
++ stm->input_hw_rate = input_hw_desc.mSampleRate;
++ LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
++
++ /* Set format description according to the input params. */
++ r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
++ if (r != CUBEB_OK) {
++ LOG("(%p) Setting format description for input failed.", stm);
++ return r;
++ }
++
++ // Use latency to set buffer size
++ stm->input_buffer_frames = stm->latency_frames;
++ r = audiounit_set_buffer_size(stm, stm->input_buffer_frames, INPUT);
++ if (r != CUBEB_OK) {
++ LOG("(%p) Error in change input buffer size.", stm);
++ return CUBEB_ERROR;
++ }
++
++ AudioStreamBasicDescription src_desc = stm->input_desc;
++ /* Input AudioUnit must be configured with device's sample rate.
++ we will resample inside input callback. */
++ src_desc.mSampleRate = stm->input_hw_rate;
++
++ r = AudioUnitSetProperty(stm->input_unit,
++ kAudioUnitProperty_StreamFormat,
++ kAudioUnitScope_Output,
++ AU_IN_BUS,
++ &src_desc,
++ sizeof(AudioStreamBasicDescription));
++ if (r != noErr) {
++ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
++ return CUBEB_ERROR;
++ }
++
++ /* Frames per buffer in the input callback. */
++ r = AudioUnitSetProperty(stm->input_unit,
++ kAudioUnitProperty_MaximumFramesPerSlice,
++ kAudioUnitScope_Global,
++ AU_IN_BUS,
++ &stm->input_buffer_frames,
++ sizeof(UInt32));
++ if (r != noErr) {
++ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
++ return CUBEB_ERROR;
++ }
++
++ // Input only capacity
++ unsigned int array_capacity = 1;
++ if (has_output(stm)) {
++ // Full-duplex increase capacity
++ array_capacity = 8;
++ }
++ if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
++ return CUBEB_ERROR;
++ }
++
++ assert(stm->input_unit != NULL);
++ aurcbs_in.inputProc = audiounit_input_callback;
++ aurcbs_in.inputProcRefCon = stm;
++
++ r = AudioUnitSetProperty(stm->input_unit,
++ kAudioOutputUnitProperty_SetInputCallback,
++ kAudioUnitScope_Global,
++ AU_OUT_BUS,
++ &aurcbs_in,
++ sizeof(aurcbs_in));
++ if (r != noErr) {
++ PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
++ return CUBEB_ERROR;
++ }
++ LOG("(%p) Input audiounit init successfully.", stm);
++
++ return CUBEB_OK;
++}
++
++static int
++audiounit_configure_output(cubeb_stream * stm)
++{
++ int r;
++ AURenderCallbackStruct aurcbs_out;
++ UInt32 size;
++
++
++ LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
++ stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
++ stm->output_stream_params.format, stm->latency_frames);
++
++ r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
++ if (r != CUBEB_OK) {
++ LOG("(%p) Could not initialize the audio stream description.", stm);
++ return r;
++ }
++
++ /* Get output device sample rate. */
++ AudioStreamBasicDescription output_hw_desc;
++ size = sizeof(AudioStreamBasicDescription);
++ memset(&output_hw_desc, 0, size);
++ r = AudioUnitGetProperty(stm->output_unit,
++ kAudioUnitProperty_StreamFormat,
++ kAudioUnitScope_Output,
++ AU_OUT_BUS,
++ &output_hw_desc,
++ &size);
++ if (r != noErr) {
++ PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
++ return CUBEB_ERROR;
++ }
++ stm->output_hw_rate = output_hw_desc.mSampleRate;
++ LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
++
++ r = AudioUnitSetProperty(stm->output_unit,
++ kAudioUnitProperty_StreamFormat,
++ kAudioUnitScope_Input,
++ AU_OUT_BUS,
++ &stm->output_desc,
++ sizeof(AudioStreamBasicDescription));
++ if (r != noErr) {
++ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
++ return CUBEB_ERROR;
++ }
++
++ r = audiounit_set_buffer_size(stm, stm->latency_frames, OUTPUT);
++ if (r != CUBEB_OK) {
++ LOG("(%p) Error in change output buffer size.", stm);
++ return CUBEB_ERROR;
++ }
++
++ /* Frames per buffer in the input callback. */
++ r = AudioUnitSetProperty(stm->output_unit,
++ kAudioUnitProperty_MaximumFramesPerSlice,
++ kAudioUnitScope_Global,
++ AU_OUT_BUS,
++ &stm->latency_frames,
++ sizeof(UInt32));
++ if (r != noErr) {
++ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_MaximumFramesPerSlice", r);
++ return CUBEB_ERROR;
++ }
++
++ assert(stm->output_unit != NULL);
++ aurcbs_out.inputProc = audiounit_output_callback;
++ aurcbs_out.inputProcRefCon = stm;
++ r = AudioUnitSetProperty(stm->output_unit,
++ kAudioUnitProperty_SetRenderCallback,
++ kAudioUnitScope_Global,
++ AU_OUT_BUS,
++ &aurcbs_out,
++ sizeof(aurcbs_out));
++ if (r != noErr) {
++ PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
++ return CUBEB_ERROR;
++ }
++
++ LOG("(%p) Output audiounit init successfully.", stm);
++ return CUBEB_OK;
++}
++
++static int
++audiounit_setup_stream(cubeb_stream * stm)
+ {
+ stm->mutex.assert_current_thread_owns();
+
+- int r;
+- AURenderCallbackStruct aurcbs_in;
+- AURenderCallbackStruct aurcbs_out;
+- UInt32 size;
+-
++ int r = 0;
+ if (has_input(stm)) {
+ r = audiounit_create_unit(&stm->input_unit, true,
+ &stm->input_stream_params,
+ stm->input_device);
+ if (r != CUBEB_OK) {
+ LOG("(%p) AudioUnit creation for input failed.", stm);
+ return r;
+ }
+@@ -1249,180 +1622,46 @@ setup_audiounit_stream(cubeb_stream * st
+ &stm->output_stream_params,
+ stm->output_device);
+ if (r != CUBEB_OK) {
+ LOG("(%p) AudioUnit creation for output failed.", stm);
+ return r;
+ }
+ }
+
++ /* Latency cannot change if another stream is operating in parallel. In this case
++ * latecy is set to the other stream value. */
++ if (stm->context->active_streams > 1) {
++ LOG("(%p) More than one active stream, use global latency.", stm);
++ stm->latency_frames = stm->context->global_latency_frames;
++ } else {
++ /* Silently clamp the latency down to the platform default, because we
++ * synthetize the clock from the callbacks, and we want the clock to update
++ * often. */
++ stm->latency_frames = audiounit_clamp_latency(stm, stm->latency_frames);
++ assert(stm->latency_frames); // Ungly error check
++ audiounit_set_global_latency(stm, stm->latency_frames);
++ }
++
+ /* Setup Input Stream! */
+ if (has_input(stm)) {
+- LOG("(%p) Opening input side: rate %u, channels %u, format %d, latency in frames %u.",
+- stm, stm->input_stream_params.rate, stm->input_stream_params.channels,
+- stm->input_stream_params.format, stm->latency_frames);
+- /* Get input device sample rate. */
+- AudioStreamBasicDescription input_hw_desc;
+- size = sizeof(AudioStreamBasicDescription);
+- r = AudioUnitGetProperty(stm->input_unit,
+- kAudioUnitProperty_StreamFormat,
+- kAudioUnitScope_Input,
+- AU_IN_BUS,
+- &input_hw_desc,
+- &size);
+- if (r != noErr) {
+- PRINT_ERROR_CODE("AudioUnitGetProperty/input/kAudioUnitProperty_StreamFormat", r);
+- return CUBEB_ERROR;
+- }
+- stm->input_hw_rate = input_hw_desc.mSampleRate;
+- LOG("(%p) Input device sampling rate: %.2f", stm, stm->input_hw_rate);
+-
+- /* Set format description according to the input params. */
+- r = audio_stream_desc_init(&stm->input_desc, &stm->input_stream_params);
++ r = audiounit_configure_input(stm);
+ if (r != CUBEB_OK) {
+- LOG("(%p) Setting format description for input failed.", stm);
++ LOG("(%p) Configure audiounit input failed.", stm);
+ return r;
+ }
+-
+- // Use latency to set buffer size
+- stm->input_buffer_frames = stm->latency_frames;
+- LOG("(%p) Input buffer frame count %u.", stm, unsigned(stm->input_buffer_frames));
+- r = AudioUnitSetProperty(stm->input_unit,
+- kAudioDevicePropertyBufferFrameSize,
+- kAudioUnitScope_Output,
+- AU_IN_BUS,
+- &stm->input_buffer_frames,
+- sizeof(UInt32));
+- if (r != noErr) {
+- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioDevicePropertyBufferFrameSize", r);
+- return CUBEB_ERROR;
+- }
+-
+- AudioStreamBasicDescription src_desc = stm->input_desc;
+- /* Input AudioUnit must be configured with device's sample rate.
+- we will resample inside input callback. */
+- src_desc.mSampleRate = stm->input_hw_rate;
+-
+- r = AudioUnitSetProperty(stm->input_unit,
+- kAudioUnitProperty_StreamFormat,
+- kAudioUnitScope_Output,
+- AU_IN_BUS,
+- &src_desc,
+- sizeof(AudioStreamBasicDescription));
+- if (r != noErr) {
+- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_StreamFormat", r);
+- return CUBEB_ERROR;
+- }
+-
+- /* Frames per buffer in the input callback. */
+- r = AudioUnitSetProperty(stm->input_unit,
+- kAudioUnitProperty_MaximumFramesPerSlice,
+- kAudioUnitScope_Output,
+- AU_IN_BUS,
+- &stm->input_buffer_frames,
+- sizeof(UInt32));
+- if (r != noErr) {
+- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioUnitProperty_MaximumFramesPerSlice", r);
+- return CUBEB_ERROR;
+- }
+-
+- // Input only capacity
+- unsigned int array_capacity = 1;
+- if (has_output(stm)) {
+- // Full-duplex increase capacity
+- array_capacity = 8;
+- }
+- if (audiounit_init_input_linear_buffer(stm, array_capacity) != CUBEB_OK) {
+- return CUBEB_ERROR;
+- }
+-
+- assert(stm->input_unit != NULL);
+- aurcbs_in.inputProc = audiounit_input_callback;
+- aurcbs_in.inputProcRefCon = stm;
+-
+- r = AudioUnitSetProperty(stm->input_unit,
+- kAudioOutputUnitProperty_SetInputCallback,
+- kAudioUnitScope_Global,
+- AU_OUT_BUS,
+- &aurcbs_in,
+- sizeof(aurcbs_in));
+- if (r != noErr) {
+- PRINT_ERROR_CODE("AudioUnitSetProperty/input/kAudioOutputUnitProperty_SetInputCallback", r);
+- return CUBEB_ERROR;
+- }
+- LOG("(%p) Input audiounit init successfully.", stm);
+ }
+
+ /* Setup Output Stream! */
+ if (has_output(stm)) {
+- LOG("(%p) Opening output side: rate %u, channels %u, format %d, latency in frames %u.",
+- stm, stm->output_stream_params.rate, stm->output_stream_params.channels,
+- stm->output_stream_params.format, stm->latency_frames);
+- r = audio_stream_desc_init(&stm->output_desc, &stm->output_stream_params);
++ r = audiounit_configure_output(stm);
+ if (r != CUBEB_OK) {
+- LOG("(%p) Could not initialize the audio stream description.", stm);
++ LOG("(%p) Configure audiounit output failed.", stm);
+ return r;
+ }
+-
+- /* Get output device sample rate. */
+- AudioStreamBasicDescription output_hw_desc;
+- size = sizeof(AudioStreamBasicDescription);
+- memset(&output_hw_desc, 0, size);
+- r = AudioUnitGetProperty(stm->output_unit,
+- kAudioUnitProperty_StreamFormat,
+- kAudioUnitScope_Output,
+- AU_OUT_BUS,
+- &output_hw_desc,
+- &size);
+- if (r != noErr) {
+- PRINT_ERROR_CODE("AudioUnitGetProperty/output/tkAudioUnitProperty_StreamFormat", r);
+- return CUBEB_ERROR;
+- }
+- stm->output_hw_rate = output_hw_desc.mSampleRate;
+- LOG("(%p) Output device sampling rate: %.2f", stm, output_hw_desc.mSampleRate);
+-
+- r = AudioUnitSetProperty(stm->output_unit,
+- kAudioUnitProperty_StreamFormat,
+- kAudioUnitScope_Input,
+- AU_OUT_BUS,
+- &stm->output_desc,
+- sizeof(AudioStreamBasicDescription));
+- if (r != noErr) {
+- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_StreamFormat", r);
+- return CUBEB_ERROR;
+- }
+-
+- // Use latency to calculate buffer size
+- uint32_t output_buffer_frames = stm->latency_frames;
+- LOG("(%p) Output buffer frame count %u.", stm, output_buffer_frames);
+- r = AudioUnitSetProperty(stm->output_unit,
+- kAudioDevicePropertyBufferFrameSize,
+- kAudioUnitScope_Input,
+- AU_OUT_BUS,
+- &output_buffer_frames,
+- sizeof(output_buffer_frames));
+- if (r != noErr) {
+- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioDevicePropertyBufferFrameSize", r);
+- return CUBEB_ERROR;
+- }
+-
+- assert(stm->output_unit != NULL);
+- aurcbs_out.inputProc = audiounit_output_callback;
+- aurcbs_out.inputProcRefCon = stm;
+- r = AudioUnitSetProperty(stm->output_unit,
+- kAudioUnitProperty_SetRenderCallback,
+- kAudioUnitScope_Global,
+- AU_OUT_BUS,
+- &aurcbs_out,
+- sizeof(aurcbs_out));
+- if (r != noErr) {
+- PRINT_ERROR_CODE("AudioUnitSetProperty/output/kAudioUnitProperty_SetRenderCallback", r);
+- return CUBEB_ERROR;
+- }
+- LOG("(%p) Output audiounit init successfully.", stm);
+ }
+
+ // Setting the latency doesn't work well for USB headsets (eg. plantronics).
+ // Keep the default latency for now.
+ #if 0
+ buffer_size = latency;
+
+ /* Get the range of latency this particular device can work with, and clamp
+@@ -1535,63 +1774,60 @@ audiounit_stream_init(cubeb * context,
+ void * user_ptr)
+ {
+ cubeb_stream * stm;
+ int r;
+
+ assert(context);
+ *stream = NULL;
+
++ assert(latency_frames > 0);
+ if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) {
+ LOG("Reached the stream limit of %d", CUBEB_STREAM_MAX);
+ return CUBEB_ERROR;
+ }
+- context->active_streams += 1;
+
+ stm = (cubeb_stream *) calloc(1, sizeof(cubeb_stream));
+ assert(stm);
+ // Placement new to call the ctors of cubeb_stream members.
+ new (stm) cubeb_stream();
+
+ /* These could be different in the future if we have both
+ * full-duplex stream and different devices for input vs output. */
+ stm->context = context;
+ stm->data_callback = data_callback;
+ stm->state_callback = state_callback;
+ stm->user_ptr = user_ptr;
++ stm->latency_frames = latency_frames;
+ stm->device_changed_callback = NULL;
+ if (input_stream_params) {
+ stm->input_stream_params = *input_stream_params;
+ stm->input_device = input_device;
+ stm->is_default_input = input_device == nullptr ||
+ (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) ==
+ reinterpret_cast<intptr_t>(input_device));
+ }
+ if (output_stream_params) {
+ stm->output_stream_params = *output_stream_params;
+ stm->output_device = output_device;
+ }
+
+ /* Init data members where necessary */
+ stm->hw_latency_frames = UINT64_MAX;
+
+- /* Silently clamp the latency down to the platform default, because we
+- * synthetize the clock from the callbacks, and we want the clock to update
+- * often. */
+- stm->latency_frames = audiounit_clamp_latency(stm, latency_frames);
+- assert(latency_frames > 0);
+-
+ stm->switching_device = false;
+
++ auto_lock context_lock(context->mutex);
+ {
+ // It's not critical to lock here, because no other thread has been started
+ // yet, but it allows to assert that the lock has been taken in
+- // `setup_audiounit_stream`.
++ // `audiounit_setup_stream`.
++ context->active_streams += 1;
+ auto_lock lock(stm->mutex);
+- r = setup_audiounit_stream(stm);
++ r = audiounit_setup_stream(stm);
+ }
+
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not setup the audiounit stream.", stm);
+ audiounit_stream_destroy(stm);
+ return r;
+ }
+
+@@ -1602,17 +1838,17 @@ audiounit_stream_init(cubeb * context,
+ }
+
+ *stream = stm;
+ LOG("Cubeb stream (%p) init successful.", stm);
+ return CUBEB_OK;
+ }
+
+ static void
+-close_audiounit_stream(cubeb_stream * stm)
++audiounit_close_stream(cubeb_stream *stm)
+ {
+ stm->mutex.assert_current_thread_owns();
+ if (stm->input_unit) {
+ AudioUnitUninitialize(stm->input_unit);
+ AudioComponentInstanceDispose(stm->input_unit);
+ }
+
+ audiounit_destroy_input_linear_buffer(stm);
+@@ -1625,33 +1861,36 @@ close_audiounit_stream(cubeb_stream * st
+ cubeb_resampler_destroy(stm->resampler);
+ }
+
+ static void
+ audiounit_stream_destroy(cubeb_stream * stm)
+ {
+ stm->shutdown = true;
+
++ auto_lock context_locl(stm->context->mutex);
+ audiounit_stream_stop_internal(stm);
+
+ {
+ auto_lock lock(stm->mutex);
+- close_audiounit_stream(stm);
++ audiounit_close_stream(stm);
+ }
+
+ #if !TARGET_OS_IPHONE
+ int r = audiounit_uninstall_device_changed_callback(stm);
+ if (r != CUBEB_OK) {
+ LOG("(%p) Could not uninstall the device changed callback", stm);
+ }
+ #endif
+
+ assert(stm->context->active_streams >= 1);
+ stm->context->active_streams -= 1;
+
++ LOG("Cubeb stream (%p) destroyed successful.", stm);
++
+ stm->~cubeb_stream();
+ free(stm);
+ }
+
+ void
+ audiounit_stream_start_internal(cubeb_stream * stm)
+ {
+ OSStatus r;
+@@ -1666,16 +1905,17 @@ audiounit_stream_start_internal(cubeb_st
+ }
+
+ static int
+ audiounit_stream_start(cubeb_stream * stm)
+ {
+ stm->shutdown = false;
+ stm->draining = false;
+
++ auto_lock context_locl(stm->context->mutex);
+ audiounit_stream_start_internal(stm);
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
+
+ LOG("Cubeb stream (%p) started successfully.", stm);
+ return CUBEB_OK;
+ }
+
+@@ -1693,16 +1933,17 @@ audiounit_stream_stop_internal(cubeb_str
+ }
+ }
+
+ static int
+ audiounit_stream_stop(cubeb_stream * stm)
+ {
+ stm->shutdown = true;
+
++ auto_lock context_locl(stm->context->mutex);
+ audiounit_stream_stop_internal(stm);
+
+ stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
+
+ LOG("Cubeb stream (%p) stopped successfully.", stm);
+ return CUBEB_OK;
+ }
+