Mypal/media/libcubeb/osx-linearize-operations.patch

969 lines
33 KiB
Diff

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