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