969 lines
33 KiB
Diff
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;
|
|
}
|
|
|