What this is about:
I have an iOS "Guitar Effect" app that gets audio signal from input, process it and plays the result audio back to user via output. The app dosn't work with BuiltIn microphone of iOS device (because of feedback) - users have to connect guitar via special device: either analog like iRig or digital like iRig HD.
Starting from iOS 16 I face a weird behaviour of the AVAudioSession that breaks my app. In iOS 16 the input of the AVAudioSession Route is always MicrophoneBuiltIn - no matter if I connect any external microphones like iRig device or headphones with microphone. Even if I try to manually switch to external microphone by assigning the preferredInput for AVAudioSession it doesn't change the route - input is always MicrophoneBuiltIn. In iOS 15 and earlier iOS automatically change the input of the route to any external microphone you attach to the iOS device. And you may control the input by assigning preferredInput property for AVAudioSession.
This is an smallest example project to reproduce the issue.
Project Structure:
This is a very small project created to reproduce the issue. All the code is in ViewController class.
- I create a playAndRecord AVAudioSession and subscribe for routeChangeNotification notification:
NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange), name: AVAudioSession.routeChangeNotification, object: nil)
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSession.Category.playAndRecord, options: .mixWithOthers)
try audioSession.setActive(true, options: [])
} catch {
print("AVAudioSession init error: \(error)")
}
- When I get a notification - I print the list of available audio inputs, preferred input and current audio route:
@objc func handleRouteChange(notification: Notification) {
print("\nHANDLE ROUTE CHANGE")
print("AVAILABLE INPUTS: \(AVAudioSession.sharedInstance().availableInputs ?? [])")
print("PREFERRED INPUT: \(String(describing: AVAudioSession.sharedInstance().preferredInput))")
print("CURRENT ROUTE: \(AVAudioSession.sharedInstance().currentRoute)\n")
}
- I have a button that displays an alert with the list of all available audio inputs and providing the way to set each input as preferred:
@IBAction func selectPreferredInputClick(_ sender: UIButton) {
let inputs = AVAudioSession.sharedInstance().availableInputs ?? []
let title = "Select Preferred Input"
let message = "Current Preferred Input: \(String(describing: AVAudioSession.sharedInstance().preferredInput?.portName))\nCurrent Route Input \(String(describing: AVAudioSession.sharedInstance().currentRoute.inputs.first?.portName))"
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
for input in inputs {
alert.addAction(UIAlertAction(title: input.portName, style: .default) {_ in
print("\n\(title)")
print("\(message) New Preferred Input: \(input.portName)\n")
do {
try AVAudioSession.sharedInstance().setPreferredInput(input)
} catch {
print("Set Preferred Input Error: \(error)")
}
})
}
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
present(alert, animated: true)
}
iOS 16 Behaviour:
When I launch the app without any external mics attached and initiate the AVAudioSession. Then I attach the iRig device (which is basically the external microphone) and I have the following result: the MicrophoneWired appears in the list of available inputs but input of the route is still MicrophoneBuiltIn. Then I tried to change preferredInput of the AVAudioSession first to MicrophoneWired, then to MicrophoneBuiltIn and then to MicrophoneWired again: No matter what is preferredInput the input device of AudioSession route is MicrophoneBuiltIn. Sorry for image - forum doesn't allow to post the log message:
iOS 15 Behaviour:
Everything is different (and much better) in iOS 15. When I launch the app without any external mics attached and initiate the AVAudioSession. Then I attach the iRig device (which is basically the external microphone) and I have the following result:
The input of the AVAudioSession route is MicrophoneWired. Then I try to change the preferred input of the AVAudioSession and it works fine - the input of the route matches the preferred input of the AVAudioSession. Sorry for image - forum doesn't allow to post the log message:
Conclusion:
Please let me know if there is any way to make the behaviour of iOS 16 the same it is on iOS 15 and below. I searched the release notes of iOS 16 and didn't find any mention of AVAudioSession. If there is no way to do it please let me know what is the proper way to manage input source of the route of AVAudioSession. Any advice is highly appreciated.