Bug: Core Audio device can only be used once

Problem
A Core Audio device can only be used once per process.

Cause
What happens is that the latest call to AudioDeviceStart() will unregister the callback from the previous call to AudioDeviceStart(), preventing multiple callbacks to be registered to the same underlying Core Audio device. This happens because the audioIOProc function pointer gets passed instead of the created AudioDeviceIOProcID.

How to fix?
Pass audioProcID instead of audioIOProc to AudioDeviceStart() and AudioDeviceStop() at line 668 and 699 respectively.

Patch:
juce_mac_CoreAudio.cpp.patch (1.1 KB))

I’m not sure I fully understand the use case for registering multiple callbacks with a single CoreAudioIODevice object. If you’re going via the AudioDeviceManager then it’ll handle multiple callbacks with the CallbackHandler class and if you’re using the AudioIODevice object directly then you can only pass a AudioIODeviceCallback object in via the start() method.

Can you post some code which exhibits the problem?

Sorry for the confusion, Im not talking about registering AudioIODeviceCallbacks here but about registering a callback with a Core Audio device directly using Core Audio’s AudioDeviceStart() method.

The problem is that if you have 2 CoreAudioIODevice instances representing the same underlying CoreAudio device, only one gets audio callbacks (to the static audioIOProc function). This happens because all calls to AudioDeviceStart are given the same static function pointer, for every instance of the CoreAudioIODevice.

Besides that, why would you want to create IOProcID’s with calls to AudioDeviceCreateIOProcID() but not use them?

I will see if I can come up with a short piece of code to explain the problem.

Correct me if I’m wrong, but I think I’ve experienced that bug too…

I was creating a per sample editor inside my sampler. For some complicated reason and feature requests the, editor should be able to render to a different audio device than the main sampler. Using a whole new AudioDeviceManager was a very easy way to achieve this goal AND it didn’t make my software design more or less useless as the AudioDeviceManager-Instance for the main sampler is as “outside” of the software as it was possible weither the editor lives very “deep inside” the sampler.

Using different audio devices works like a charm, just as I expected it to work. But using the same audio device (e.g. by mistake or by default initialisation for the device manager) stopped the main sampler from working. I didn’t examine this bug, 'cause using the same device was in my case not necessary anyway. But I thought it should be possible as multiple applications on my computer can render on the same audio device (and even output channels)

You are correct, this is exactly the problem I’m describing here.

1 Like

Code example:

// Create Core Audio backend
mCoreAudioIODeviceType.reset(juce::AudioIODeviceType::createAudioIODeviceType_CoreAudio());
jassert(mCoreAudioIODeviceType);

// Get default audio device name
mCoreAudioIODeviceType->scanForDevices();
auto defaultDeviceName =
	mCoreAudioIODeviceType->getDeviceNames()[mCoreAudioIODeviceType->getDefaultDeviceIndex(false)];
jassert(defaultDeviceName.isNotEmpty());

DBG("Opening device with name: " << defaultDeviceName);

// Create audio devices
mDeviceA.reset(mCoreAudioIODeviceType->createDevice(defaultDeviceName, {}));
mDeviceB.reset(mCoreAudioIODeviceType->createDevice(defaultDeviceName, {}));

juce::String errorMessage;
errorMessage = mDeviceA->open(0, 3, 44100, 128);
jassert(errorMessage.isEmpty());
errorMessage = mDeviceB->open(0, 3, 44100, 128);
jassert(errorMessage.isEmpty());

mDeviceA->start(&mCallbackA);
// The next call fails to attach the underlying Core Audio IOProc callback because the pointer given to
// AudioDeviceStat() is already registered for device A
mDeviceB->start(&mCallbackB);

// Now only device A will get called back

And a runnable demo:

CoreAudioMultiDeviceUsage.zip (2.6 KB)

Sure I agree that the code is incorrect there and we should be passing the ID that’s created, I just wanted to double-check the use case and see an example of where it was causing issues. Thanks for providing the detailed example! I’ll get the change merged to develop shortly.

1 Like

Great! Thanks! Much appreciated!