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; }