iostext-to-speechavaudiosessionios-bluetoothavspeechsynthesizer

iOS App Bluetooth Audio Coming out in "Phone Mode."


I have an iOS app which is producing text to speech (TTS) audio (AVSpeechSynthesizer). One user is saying that the audio over his car Bluetooth speaker is coming out in "phone mode" (presumably the audio when making or receiving phone calls) as opposed to "music mode" the way that apps like Youtube and the music and maps apps are. This also causes the handling of incoming phone calls not to work properly with the car Bluetooth speaker.

Unfortunately, I am at a loss to understand why, or even that there is a distinction between "phone" and "music" mode. When using the phone's speakers, there is no such problem with handling incoming phone calls. The issue is only with Bluetooth.

The AVAudioSession initialization code is as follows.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        do {
            let session = AVAudioSession.sharedInstance()
            try session.setCategory(AVAudioSession.Category.playAndRecord, options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP])
            try session.overrideOutputAudioPort(AVAudioSession.PortOverride.none)
            try session.setActive(true, options: .notifyOthersOnDeactivation)
            
        } catch let error {
            print("audioSession properties weren't set. Error: \(error.localizedDescription)")
        }
        
        return true
    }

Also, the AVSpeechSynthesizer code is as follows

let synthesizer = AVSpeechSynthesizer()
let utterance = AVSpeechUtterance(string: newText)
synthesizer.speak(utterance)

Is there anything else this code should be doing, or perhaps is doing wrong?

Thanks in advance.


Solution

  • What you're calling "phone mode" is HFP (Hands Free Profile). You've included .allowBluetooth which means "prefer using HFP." (It's a very confusing enum name.)

    What you're calling "music mode" is A2DP, which you're allowing via .allowBluetoothA2DP.

    However, A2DP is not bidirectional, which you're requesting with .playAndRecord. So the session uses HFP.

    The audio quality of HFP is notably worse than A2DP.

    For TTS, there shouldn't be a need for a microphone, so you can replace .playAndRecord with .play (and I'd probably drop .allowBluetooth). If you require a microphone for some other purpose, you should drop .allowBluetoothA2DP, and there's no (standard) way to avoid using HFP to communicate over Bluetooth.

    There are non-standard ways to solve this if you were the manufacturer of the car and the app. You could open a second A2DP channel to the phone, or you could implement a proprietary microphone protocol over BLE or iAP2. But there's no way to do this with standard devices while talking to an iPhone. (If both devices support aptX, there are some other options, but iPhones don't and I haven't heard any hints that they will.)

    Note that you can change the category and options, and activate or deactivate the session at any time. So if you need the microphone sometimes, you can switch to .playAndRecord only when you need it and minimize the impact on users when they don't need the microphone.