Skip to content

Commit 1f4b4d3

Browse files
authored
Fix deadlock and device collection changed callback issue on OSX (#401)
* Add an automated test to ensure creating a duplex stream with a device collection changed callback works. * Fix a deadlock When creating an aggregate device, and a device collection change callback was installed, a deadlock occured, because the device collection change listener was being called synchronously. Use GCD to send a task to a serial queue, and carefuly unlock the mutex in audiounit_stream_destroy to not deadlock on the queue. * Don't call the device collection change callback when creating cubeb's own aggregate device. * Remove std:: prefix, there is a using namespace std; now. * Address review comments * Address even more comments.
1 parent 4c18a84 commit 1f4b4d3

2 files changed

Lines changed: 147 additions & 62 deletions

File tree

src/cubeb_audiounit.cpp

Lines changed: 99 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@
3232
#include <algorithm>
3333
#include <atomic>
3434
#include <vector>
35+
#include <set>
3536
#include <sys/time.h>
3637
#include <string>
3738

39+
using namespace std;
40+
3841
#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000
3942
typedef UInt32 AudioFormatFlags;
4043
#endif
@@ -65,34 +68,34 @@ void audiounit_stream_stop_internal(cubeb_stream * stm);
6568
void audiounit_stream_start_internal(cubeb_stream * stm);
6669
static void audiounit_close_stream(cubeb_stream *stm);
6770
static int audiounit_setup_stream(cubeb_stream *stm);
68-
static std::vector<AudioObjectID>
71+
static vector<AudioObjectID>
6972
audiounit_get_devices_of_type(cubeb_device_type devtype);
7073

7174
extern cubeb_ops const audiounit_ops;
7275

7376
struct cubeb {
7477
cubeb_ops const * ops = &audiounit_ops;
7578
owned_critical_section mutex;
76-
std::atomic<int> active_streams{ 0 };
79+
atomic<int> active_streams{ 0 };
7780
uint32_t global_latency_frames = 0;
7881
cubeb_device_collection_changed_callback collection_changed_callback = nullptr;
7982
void * collection_changed_user_ptr = nullptr;
8083
/* Differentiate input from output devices. */
8184
cubeb_device_type collection_changed_devtype = CUBEB_DEVICE_TYPE_UNKNOWN;
82-
std::vector<AudioObjectID> devtype_device_array;
85+
vector<AudioObjectID> devtype_device_array;
8386
// The queue is asynchronously deallocated once all references to it are released
8487
dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL);
8588
// Current used channel layout
86-
std::atomic<cubeb_channel_layout> layout{ CUBEB_LAYOUT_UNDEFINED };
89+
atomic<cubeb_channel_layout> layout{ CUBEB_LAYOUT_UNDEFINED };
8790
};
8891

89-
static std::unique_ptr<AudioChannelLayout, decltype(&free)>
92+
static unique_ptr<AudioChannelLayout, decltype(&free)>
9093
make_sized_audio_channel_layout(size_t sz)
9194
{
9295
assert(sz >= sizeof(AudioChannelLayout));
9396
AudioChannelLayout * acl = reinterpret_cast<AudioChannelLayout *>(calloc(1, sz));
9497
assert(acl); // Assert the allocation works.
95-
return std::unique_ptr<AudioChannelLayout, decltype(&free)>(acl, free);
98+
return unique_ptr<AudioChannelLayout, decltype(&free)>(acl, free);
9699
}
97100

98101
enum io_side {
@@ -156,33 +159,33 @@ struct cubeb_stream {
156159
owned_critical_section mutex;
157160
/* Hold the input samples in every
158161
* input callback iteration */
159-
std::unique_ptr<auto_array_wrapper> input_linear_buffer;
162+
unique_ptr<auto_array_wrapper> input_linear_buffer;
160163
owned_critical_section input_linear_buffer_lock;
161164
// After the resampling some input data remains stored inside
162165
// the resampler. This number is used in order to calculate
163166
// the number of extra silence frames in input.
164-
std::atomic<uint32_t> available_input_frames{ 0 };
167+
atomic<uint32_t> available_input_frames{ 0 };
165168
/* Frames on input buffer */
166-
std::atomic<uint32_t> input_buffer_frames{ 0 };
169+
atomic<uint32_t> input_buffer_frames{ 0 };
167170
/* Frame counters */
168-
std::atomic<uint64_t> frames_played{ 0 };
171+
atomic<uint64_t> frames_played{ 0 };
169172
uint64_t frames_queued = 0;
170-
std::atomic<int64_t> frames_read{ 0 };
171-
std::atomic<bool> shutdown{ true };
172-
std::atomic<bool> draining{ false };
173+
atomic<int64_t> frames_read{ 0 };
174+
atomic<bool> shutdown{ true };
175+
atomic<bool> draining{ false };
173176
/* Latency requested by the user. */
174177
uint32_t latency_frames = 0;
175-
std::atomic<uint64_t> current_latency_frames{ 0 };
178+
atomic<uint64_t> current_latency_frames{ 0 };
176179
uint64_t hw_latency_frames = UINT64_MAX;
177-
std::atomic<float> panning{ 0 };
178-
std::unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
180+
atomic<float> panning{ 0 };
181+
unique_ptr<cubeb_resampler, decltype(&cubeb_resampler_destroy)> resampler;
179182
/* This is true if a device change callback is currently running. */
180-
std::atomic<bool> switching_device{ false };
181-
std::atomic<bool> buffer_size_change_state{ false };
183+
atomic<bool> switching_device{ false };
184+
atomic<bool> buffer_size_change_state{ false };
182185
AudioDeviceID aggregate_device_id = 0; // the aggregate device id
183186
AudioObjectID plugin_id = 0; // used to create aggregate device
184187
/* Mixer interface */
185-
std::unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> mixer;
188+
unique_ptr<cubeb_mixer, decltype(&cubeb_mixer_destroy)> mixer;
186189
};
187190

188191
bool has_input(cubeb_stream * stm)
@@ -526,7 +529,7 @@ audiounit_output_callback(void * user_ptr,
526529

527530
AudioFormatFlags outaff = stm->output_desc.mFormatFlags;
528531
float panning = (stm->output_desc.mChannelsPerFrame == 2) ?
529-
stm->panning.load(std::memory_order_relaxed) : 0.0f;
532+
stm->panning.load(memory_order_relaxed) : 0.0f;
530533

531534
/* Post process output samples. */
532535
if (stm->draining) {
@@ -1065,7 +1068,7 @@ audiounit_get_min_latency(cubeb * /* ctx */,
10651068
return CUBEB_ERROR;
10661069
}
10671070

1068-
*latency_frames = std::max<uint32_t>(latency_range.mMinimum,
1071+
*latency_frames = max<uint32_t>(latency_range.mMinimum,
10691072
SAFE_MIN_LATENCY_FRAMES);
10701073
#endif
10711074

@@ -1409,10 +1412,10 @@ audiounit_layout_init(cubeb_stream * stm, io_side side)
14091412
stm->context->layout = audiounit_get_current_channel_layout(stm->output_unit);
14101413
}
14111414

1412-
static std::vector<AudioObjectID>
1415+
static vector<AudioObjectID>
14131416
audiounit_get_sub_devices(AudioDeviceID device_id)
14141417
{
1415-
std::vector<AudioDeviceID> sub_devices;
1418+
vector<AudioDeviceID> sub_devices;
14161419
AudioObjectPropertyAddress property_address = { kAudioAggregateDevicePropertyActiveSubDeviceList,
14171420
kAudioObjectPropertyScopeGlobal,
14181421
kAudioObjectPropertyElementMaster };
@@ -1551,8 +1554,8 @@ audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id,
15511554
{
15521555
LOG("Add devices input %u and output %u into aggregate device %u",
15531556
input_device_id, output_device_id, aggregate_device_id);
1554-
const std::vector<AudioDeviceID> output_sub_devices = audiounit_get_sub_devices(output_device_id);
1555-
const std::vector<AudioDeviceID> input_sub_devices = audiounit_get_sub_devices(input_device_id);
1557+
const vector<AudioDeviceID> output_sub_devices = audiounit_get_sub_devices(output_device_id);
1558+
const vector<AudioDeviceID> input_sub_devices = audiounit_get_sub_devices(input_device_id);
15561559

15571560
CFMutableArrayRef aggregate_sub_devices_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
15581561
/* The order of the items in the array is significant and is used to determine the order of the streams
@@ -1603,7 +1606,7 @@ audiounit_set_master_aggregate_device(const AudioDeviceID aggregate_device_id)
16031606

16041607
// Master become the 1st output sub device
16051608
AudioDeviceID output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
1606-
const std::vector<AudioDeviceID> output_sub_devices = audiounit_get_sub_devices(output_device_id);
1609+
const vector<AudioDeviceID> output_sub_devices = audiounit_get_sub_devices(output_device_id);
16071610
CFStringRef master_sub_device = get_device_name(output_sub_devices[0]);
16081611

16091612
UInt32 size = sizeof(CFStringRef);
@@ -1964,7 +1967,7 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
19641967
// For the 1st stream set anything within safe min-max
19651968
assert(stm->context->active_streams > 0);
19661969
if (stm->context->active_streams == 1) {
1967-
return std::max(std::min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
1970+
return max(min<uint32_t>(latency_frames, SAFE_MAX_LATENCY_FRAMES),
19681971
SAFE_MIN_LATENCY_FRAMES);
19691972
}
19701973
assert(stm->output_unit);
@@ -1986,7 +1989,7 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
19861989
return 0;
19871990
}
19881991

1989-
output_buffer_size = std::max(std::min<uint32_t>(output_buffer_size, SAFE_MAX_LATENCY_FRAMES),
1992+
output_buffer_size = max(min<uint32_t>(output_buffer_size, SAFE_MAX_LATENCY_FRAMES),
19901993
SAFE_MIN_LATENCY_FRAMES);
19911994
}
19921995

@@ -2003,14 +2006,14 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
20032006
return 0;
20042007
}
20052008

2006-
input_buffer_size = std::max(std::min<uint32_t>(input_buffer_size, SAFE_MAX_LATENCY_FRAMES),
2009+
input_buffer_size = max(min<uint32_t>(input_buffer_size, SAFE_MAX_LATENCY_FRAMES),
20072010
SAFE_MIN_LATENCY_FRAMES);
20082011
}
20092012

20102013
// Every following active streams can only set smaller latency
20112014
UInt32 upper_latency_limit = 0;
20122015
if (input_buffer_size != 0 && output_buffer_size != 0) {
2013-
upper_latency_limit = std::min<uint32_t>(input_buffer_size, output_buffer_size);
2016+
upper_latency_limit = min<uint32_t>(input_buffer_size, output_buffer_size);
20142017
} else if (input_buffer_size != 0) {
20152018
upper_latency_limit = input_buffer_size;
20162019
} else if (output_buffer_size != 0) {
@@ -2019,7 +2022,7 @@ audiounit_clamp_latency(cubeb_stream * stm, uint32_t latency_frames)
20192022
upper_latency_limit = SAFE_MAX_LATENCY_FRAMES;
20202023
}
20212024

2022-
return std::max(std::min<uint32_t>(latency_frames, upper_latency_limit),
2025+
return max(min<uint32_t>(latency_frames, upper_latency_limit),
20232026
SAFE_MIN_LATENCY_FRAMES);
20242027
}
20252028

@@ -2565,8 +2568,8 @@ audiounit_stream_init(cubeb * context,
25652568
cubeb_state_callback state_callback,
25662569
void * user_ptr)
25672570
{
2568-
std::unique_ptr<cubeb_stream, decltype(&audiounit_stream_destroy)> stm(new cubeb_stream(context),
2569-
audiounit_stream_destroy);
2571+
unique_ptr<cubeb_stream, decltype(&audiounit_stream_destroy)> stm(new cubeb_stream(context),
2572+
audiounit_stream_destroy);
25702573
context->active_streams += 1;
25712574
int r;
25722575

@@ -2669,8 +2672,10 @@ audiounit_stream_destroy(cubeb_stream * stm)
26692672
LOG("(%p) Could not uninstall all device change listeners", stm);
26702673
}
26712674

2672-
auto_lock context_lock(stm->context->mutex);
2673-
audiounit_stream_stop_internal(stm);
2675+
{
2676+
auto_lock context_lock(stm->context->mutex);
2677+
audiounit_stream_stop_internal(stm);
2678+
}
26742679

26752680
// Execute close in serial queue to avoid collision
26762681
// with reinit when un/plug devices
@@ -2679,6 +2684,7 @@ audiounit_stream_destroy(cubeb_stream * stm)
26792684
audiounit_close_stream(stm);
26802685
});
26812686

2687+
auto_lock context_lock(stm->context->mutex);
26822688
assert(stm->context->active_streams >= 1);
26832689
stm->context->active_streams -= 1;
26842690

@@ -2868,7 +2874,7 @@ int audiounit_stream_set_panning(cubeb_stream * stm, float panning)
28682874
return CUBEB_ERROR_INVALID_PARAMETER;
28692875
}
28702876

2871-
stm->panning.store(panning, std::memory_order_relaxed);
2877+
stm->panning.store(panning, memory_order_relaxed);
28722878
return CUBEB_OK;
28732879
}
28742880

@@ -3049,7 +3055,7 @@ audiounit_get_available_samplerate(AudioObjectID devid, AudioObjectPropertyScope
30493055
if (AudioObjectHasProperty(devid, &adr) &&
30503056
AudioObjectGetPropertyDataSize(devid, &adr, 0, NULL, &size) == noErr) {
30513057
uint32_t count = size / sizeof(AudioValueRange);
3052-
std::vector<AudioValueRange> ranges(count);
3058+
vector<AudioValueRange> ranges(count);
30533059
range.mMinimum = 9999999999.0;
30543060
range.mMaximum = 0.0;
30553061
if (AudioObjectGetPropertyData(devid, &adr, 0, NULL, &size, ranges.data()) == noErr) {
@@ -3199,8 +3205,8 @@ static int
31993205
audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type,
32003206
cubeb_device_collection * collection)
32013207
{
3202-
std::vector<AudioObjectID> input_devs;
3203-
std::vector<AudioObjectID> output_devs;
3208+
vector<AudioObjectID> input_devs;
3209+
vector<AudioObjectID> output_devs;
32043210

32053211
// Count number of input and output devices. This is not
32063212
// necessarily the same as the count of raw devices supported by the
@@ -3265,7 +3271,7 @@ audiounit_device_collection_destroy(cubeb * /* context */,
32653271
return CUBEB_OK;
32663272
}
32673273

3268-
static std::vector<AudioObjectID>
3274+
static vector<AudioObjectID>
32693275
audiounit_get_devices_of_type(cubeb_device_type devtype)
32703276
{
32713277
AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices,
@@ -3274,18 +3280,18 @@ audiounit_get_devices_of_type(cubeb_device_type devtype)
32743280
UInt32 size = 0;
32753281
OSStatus ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size);
32763282
if (ret != noErr) {
3277-
return std::vector<AudioObjectID>();
3283+
return vector<AudioObjectID>();
32783284
}
32793285
/* Total number of input and output devices. */
32803286
uint32_t count = (uint32_t)(size / sizeof(AudioObjectID));
32813287

3282-
std::vector<AudioObjectID> devices(count);
3288+
vector<AudioObjectID> devices(count);
32833289
ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, devices.data());
32843290
if (ret != noErr) {
3285-
return std::vector<AudioObjectID>();
3291+
return vector<AudioObjectID>();
32863292
}
32873293
/* Expected sorted but did not find anything in the docs. */
3288-
std::sort(devices.begin(), devices.end(), [](AudioObjectID a, AudioObjectID b) {
3294+
sort(devices.begin(), devices.end(), [](AudioObjectID a, AudioObjectID b) {
32893295
return a < b;
32903296
});
32913297

@@ -3297,7 +3303,7 @@ audiounit_get_devices_of_type(cubeb_device_type devtype)
32973303
kAudioDevicePropertyScopeInput :
32983304
kAudioDevicePropertyScopeOutput;
32993305

3300-
std::vector<AudioObjectID> devices_in_scope;
3306+
vector<AudioObjectID> devices_in_scope;
33013307
for (uint32_t i = 0; i < count; ++i) {
33023308
/* For device in the given scope channel must be > 0. */
33033309
if (audiounit_get_channel_count(devices[i], scope) > 0) {
@@ -3315,27 +3321,58 @@ audiounit_collection_changed_callback(AudioObjectID /* inObjectID */,
33153321
void * inClientData)
33163322
{
33173323
cubeb * context = static_cast<cubeb *>(inClientData);
3318-
auto_lock lock(context->mutex);
33193324

3320-
if (context->collection_changed_callback == NULL) {
3321-
/* Listener removed while waiting in mutex, abort. */
3322-
return noErr;
3323-
}
3325+
// This can be called from inside an AudioUnit function, dispatch to another queue.
3326+
dispatch_async(context->serial_queue, ^() {
3327+
auto_lock lock(context->mutex);
3328+
if (context->collection_changed_callback == NULL) {
3329+
/* Listener removed while waiting in mutex, abort. */
3330+
return;
3331+
}
3332+
3333+
/* Differentiate input from output changes. */
3334+
if (context->collection_changed_devtype == CUBEB_DEVICE_TYPE_INPUT ||
3335+
context->collection_changed_devtype == CUBEB_DEVICE_TYPE_OUTPUT) {
3336+
vector<AudioObjectID> devices = audiounit_get_devices_of_type(context->collection_changed_devtype);
3337+
/* When count is the same examine the devid for the case of coalescing. */
3338+
if (context->devtype_device_array == devices) {
3339+
/* Device changed for the other scope, ignore. */
3340+
return;
3341+
} else {
3342+
/* Also don't trigger the user callback if the new added device is private
3343+
* aggregate device: compute the set of new devices, and remove those
3344+
* with the name of our private aggregate devices. */
3345+
set<AudioObjectID> current_devices(devices.begin(), devices.end());
3346+
set<AudioObjectID> previous_devices(context->devtype_device_array.begin(),
3347+
context->devtype_device_array.end());
3348+
set<AudioObjectID> new_devices;
3349+
set_difference(current_devices.begin(), current_devices.end(),
3350+
previous_devices.begin(), previous_devices.end(),
3351+
inserter(new_devices, new_devices.begin()));
3352+
3353+
for (auto it = new_devices.begin(); it != new_devices.end();) {
3354+
CFStringRef name = get_device_name(*it);
3355+
if (CFStringFind(name, CFSTR("CubebAggregateDevice"), 0).location !=
3356+
kCFNotFound) {
3357+
it = new_devices.erase(it);
3358+
} else {
3359+
it++;
3360+
}
3361+
}
33243362

3325-
/* Differentiate input from output changes. */
3326-
if (context->collection_changed_devtype == CUBEB_DEVICE_TYPE_INPUT ||
3327-
context->collection_changed_devtype == CUBEB_DEVICE_TYPE_OUTPUT) {
3328-
std::vector<AudioObjectID> devices = audiounit_get_devices_of_type(context->collection_changed_devtype);
3329-
/* When count is the same examine the devid for the case of coalescing. */
3330-
if (context->devtype_device_array == devices) {
3331-
/* Device changed for the other scope, ignore. */
3332-
return noErr;
3363+
// If this set of new devices is empty, it means this was triggerd
3364+
// solely by creating an aggregate device, no need to trigger the user
3365+
// callback.
3366+
if (new_devices.empty()) {
3367+
return;
3368+
}
3369+
}
3370+
/* Device on desired scope changed. */
3371+
context->devtype_device_array = devices;
33333372
}
3334-
/* Device on desired scope changed. */
3335-
context->devtype_device_array = devices;
3336-
}
33373373

3338-
context->collection_changed_callback(context, context->collection_changed_user_ptr);
3374+
context->collection_changed_callback(context, context->collection_changed_user_ptr);
3375+
});
33393376
return noErr;
33403377
}
33413378

0 commit comments

Comments
 (0)