diff options
Diffstat (limited to 'media/libcubeb/osx-linearize-operations.patch')
-rw-r--r-- | media/libcubeb/osx-linearize-operations.patch | 968 |
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 000000000..9f4f31bca --- /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; + } + |