I have been experiencing an issue with handleRouteChange on iOs devices (Fix joined)
The issue is that every time I plug/unplug headphones, IOThread and Notify Thread would deadlock.
- As we know, at the moment, handleRouteChange is called from “AVAudioSession Notify Thread” and basically removes Audiounit, creates a new instance and calls callback->audioDeviceAboutToStart.
- Inside my app I require Audiounit (For IAA support), so I modified juce a little bit so I have AudioUnit in my app (synced whithin audioDeviceAboutToStart, no issue here). Every IO Render callback I do calls to AudioUnitGetProperty(audiounit, …).
When route change, in “Notify Thread” juce re-instanciate audiounit and calls AudioComponentInstanceDisplose(audiounit), this method locks and wait for IOThread to finish (it has a long timeout though). At the same time IOThread is still running and calls, in my code, AudioUnitGetProperty and waits for the lock. This results in a deadlock.
What I propose to fix it is (see code bellow), when route change, set callback to NULL (stop()).
Only then, dispose old audiounit, re-create audiounit, and then restore callback (start()).
This method guaranties that no “non-juce” code is called while juce disposes and re-creates Audiounit.
Also, the method “AudioDeviceStopped” is then called (as it should) before disposing old audiounit.
See updated code bellow (juce_ios_Audio.cpp:
void handleRouteChange (const char* reason)
{
JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason);
fixAudioRouteIfSetToReceiver();
if (isRunning)
{
AudioIODeviceCallback* lastCallback = callback; //+++ADD
invokeAudioDeviceErrorCallback (reason);
updateSampleRateAndAudioInput();
updateCurrentBufferSize();
stop(); //+++ADD
createAudioUnit();
setAudioSessionActive (true);
if (audioUnit != 0)
{
UInt32 formatSize = sizeof (format);
AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize);
AudioOutputUnitStart (audioUnit);
}
start(lastCallback); //+++ADD
//DELETE if (callback != nullptr)
//DELETE callback->audioDeviceAboutToStart (this);
}
}
Hope this will help someone and maybe updated by Juce.