iOS plug headphone stucks audio (handleRouteChange issue + fix)


#1

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.


Critical section issue between ~iOSAudioIODevice and handleRouteChange