The “Lost Tracks, iOS & Bluetooth” Journey 2017

Back in the early days of the development of Lost Tracks, I knew that I wanted to use the microphone for gameplay somehow. I didn’t know what exactly, but I knew the game was about getting courage to speak so it seemed obvious that it should use the microphone.

A decision like that appeared harmless, but it turned out that is wasn’t as simple as pressing “Build and run” in Unity… It isn’t a complete nightmare, though, so keep on reading.

The Issues

The issues begin as soon as you want to both playback and record audio. By default, this makes iOS assume you’re making an app that is designed for voice chat or similar. For starters, this results in audio being played back through the receiver (the speaker above the display), which is not very loud, not stereo and doesn’t sound good.

To fix the “sound not playing through speaker issue” you simply add an option when setting up the audio session on iOS, like so

[[AVAudioSession sharedInstance] 
 	setCategory:AVAudioSessionCategoryPlayAndRecord 
 	withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker 
error:nil];

So far, so good. But what about Bluetooth?

Before iOS 10, there was no way of adding proper Bluetooth support. You could try adding the option AVAudio​Session​Category​Option​Allow​Bluetooth, but this would cause the audio to very low quality because it would force it to use the HFP profile for both input AND output as described by Apple below:

If an application uses the setPreferredInput:error: method to select a Bluetooth HFP input, the output will automatically be changed to the Bluetooth HFP output. Moreover, selecting a Bluetooth HFP output using the MPVolumeView‘s route picker will automatically change the input to the Bluetooth HFP input. Therefore both the input and output will always end up on the Bluetooth HFP device even though only the input or output was set individually.

That was the end of the road – At, least prior to iOS 10!

The Solution

Beginning with iOS 10 we have a new option to let us indeed use the high quality A2DP profile for Bluetooth audio, while still getting audio from the device microphone input. I present:

AVAudio​Session​Category​Option​Allow​Bluetooth​A2DP

Setting it up is just as easy as the previous code snippet, and while you’re at it, you may want to add support for AirPlay as well.

Thus, the final solution looks like this

[[AVAudioSession sharedInstance] 
	setCategory:AVAudioSessionCategoryPlayAndRecord
	withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetoothA2DP | AVAudioSessionCategoryOptionAllowAirPlay
error:nil];

Notice, that I didn’t add the AVAudio​Session​Category​Option​Allow​Bluetooth option, since I’m 99% sure it would put you back into the HFP issue.

Also note, that this solution does not record audio through the Bluetooth headset, but uses the built-in microphone. I guess it’s not possible to output using A2DP while receiving input using HFP.

I noticed that switching Bluetooth devices on/off and plugging/unplugging headphones caused iOS to not automatically switch back to Bluetooth, without setting up a new AVAudioSession using the code above.

More information on the AVAudioSession can be found in the API Reference.

Conclusion

I hope this will help out others solve their Bluetooth and recording issues with iOS. I’ve spend many days and nights banging my head against the wall trying to get this to work prior to iOS 10.