2

This question is a broader description of the bug documented here. I'm reproducing the steps below on iPhone 8 and iOS 14.7.

How can I achieve my AVAudioSession and AVAudioEngine instances to work properly, even when I disconnect a microphone after setting it as a preferred input, since Route Change Notifications do not work in the situation above?


When there are multiple audio inputs available to the iOS, I can set the active one with .setPreferredInput(...).

When .setPreferredInput(...) is called, both the preferredInput and the active input given by currentRoute are set to the requested input/microphone.

Also, I can subscribe to route change, audio interruption and OS Media Reset/Lost notifications given by the OS - this communication is managed by AVAudioSession - . These notifications work properly when I connect and disconnect IO audio devices, like wired earphones, wired EarPods, bluetooth headsets and so on. These notifications also work properly while I have a running AVAudioEngine in my application, with input and output nodes capturing and reproducing audio while these notifications are fired.

All the considerations here are being reproduced with AVAudioEngine category set to .playAndRecord and with the AVAudioEngine running as: inputNode->mainMixerNode->outputNode.

However, a particular situation arrives when I select a microphone to be active, and then I disconnect this mic. This is reproduced by calling .setPreferredInput(...) and unplugging the mic.

In the situation mentioned above, the route change notification is not called. Instead, the OS Media Reset/Lost events MAY be called in a range from some milliseconds, to 6 seconds, or even not being fired at all. While in this "empty" route state, the active input is preempted by the OS to be nil.

The uncertainty of the mentioned situation is a problem, because I cannot find a suited and secure solution for these steps above when they are reproduced. The user may, for example, ask for an audio node to play when I have a player node output connected to the main mixer node input. When this happens, It crashes with the message: player did not see an IO cycle. (mentioned here). I attempted some workarounds, by double-checking if the active input is not nil in every critical action. Attempting to overwrite routes in the "idle" state between unplugging and OS Media Lost/Reset being fired also crashes the app.

Also, notice that, EarPods are plugged while both the AVAudioEngine is running and the AVAudioSession instance is active, the EarPods are automatically set as the active input/output WITHOUT calling .setPreferredInput(...), since it's the default OS behavior, which can be tracked by Route Change notifications. When the EarPods are unplugged in the following this situation, the OS gracefully handles it by firing .oldDeviceUnavailable, and both the AVAudioSession and AVAudioEngine instances automatically follows to work with the built-in mic/speaker. It doesn't happen when .setPreferredInput(...) is called before unplugging the EarPods.

My sample application is here.

  • I am glad that I'm not alone on this! I see this from time to time but have never suspected it could come from `.setPrefferredInput(...)` – Arshia Aug 10 '21 at 10:51
  • In my experience and several exchanges with apple DTS, we should not control AVAudioEngine from any AVAudioSession handleRouteChange notification. According to this exchange, handleRouteChange notification of AVAudioSession is for high-level information only. In my case, removing AVAudioEngine modifications from that notification has reduced the crash but has not made it disappear! – Arshia Aug 10 '21 at 10:53
  • @Arshia thanks for contributing! :) In my production app, I’m rerouting my AVAudioEngine connections when there’s a route change because I propagate the AVAudioFormat from the input node input bus and from the output node output bus. I have noticed that the AVAudioFormat sample rate in the IO busses may flutuate from 48000 to 44100 depending on the connected device (bluetooth earphones, loudspeaker, etc). When this IO sample rate flutuates, I had to ensure that all busses have the same sample rate, otherwise popcorn noise would rise from ressampling processing inside nodes after a route change – Miguel de Sousa Aug 10 '21 at 11:05
  • The mentioned workaround above is a… workaround, disco vered by trial and error. I’ll experiment what you proposed ASAP – Miguel de Sousa Aug 10 '21 at 11:08
  • Another thing regarding sampling rate: I see that in some libraries, they assume that the SR of Input is equal to SR of Output nodes. That's NOT necessarily true! Some "cheap" devices will retain an SR of 44.1k where the capture on the device retains 48khz. And according to Apple, you must honour this difference in your Audio Graph (by for example inserting Mixers in appropriate sections etc). If not, you'll experience broken graphs with some users and one some Bluetooth (or wired) devices. – Arshia Aug 10 '21 at 11:21
  • Funny enough, I'm dealing with this same error right now: One thing I notice is that attempts for example to change AVAudioSession parameters especially when they require deactivating before change and activating after change increases the chance of this crash happening. Looks like an internal timing issue. – Arshia Aug 10 '21 at 11:23
  • Any news on this on your side? I have found a way to systematically reproduce this crash: Just connect an AirPlay while AVAudioEngine is running, and then revert back to Internal Speakers and evoke a Play. From what I see, AVAudioSession sends the correct new/old output when connecting AirPlay Speakers. But when disconnecting them (for internal speakers) they are NOT correct so we don't get any chance to update AVAudioEngine nodes!!! – Arshia Mar 23 '22 at 12:07
  • @Arshia sorry about being so late to answer it :( I think the solution I found was to clear all the engine nodes and create another one from scratch in these situations – Miguel de Sousa Jun 01 '22 at 21:04

0 Answers0