AVAudioSession allowBluetoothA2DP not working on external device iOS17 playAndRecord session

I saw a similar post to this where the issue was fixed in the final iOS17 release, but I am still having a problem since my situation is slightly different. I am wondering if its a bug based on current behavior, but was hoping maybe someone knows if the issue is with my setup?

I am trying to monitor an external USB camera's audio input using the new iOS17 external call for iPad, over bluetooth.

func setupAudioSession() {
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setCategory(.playAndRecord, mode: .default, options: [.allowBluetoothA2DP, .defaultToSpeaker] )
            try audioSession.setActive(true)
          
        } catch {
            print("Failed to set up audio session: \(error)")
        }
}

When my execution order is:

setupAudioSession() // code above
// discover and connect the session AV inputs to the external USB
// start an audio engine
// connect audio engine output to a buffer queue and play
// start the session

My app does not allow routing to the headphones and directly switches to speaker. However if I change the order to this:

setupAudioSession() // code above
// discover and connect the session AV inputs to the external USB
// start an audio engine
// start the session --- !starting before connecting output!
// connect audio engine output to a buffer queue and play

The session stays connected to Bluetooth and plays over the headphones. However if I suspend the app and relaunch, I get switched back to speaker. I understand maybe the system is trying to prevent an audio feedback loop? But don't understand why launching the session early would get the desired behavior. Any help would be greatly appreciated.

Post not yet marked as solved Up vote post of oddforms Down vote post of oddforms
612 views

Replies

I answered my own problem in the unlikely case someone else has a similar setup. The audio session doesn't seem to like bluetooth when plugged into an output that is attached to a buffer that hasn't yet filled. That was my understanding at least. Because of that, the session needed to be setup as the very last thing, once the output has some audio data coming through. My solution was to listen for data on the audio player and once it was filled, setup the audio session. It looked like this:

        print("Starting Tap")
        audioPlayerNode?.installTap(onBus: 0, bufferSize: 64, format: nil) { (buffer, time) in
            if buffer.frameLength > 0 {
                self.setupAudioSession()
                self.printCurrentAudioSessionState()
                self.audioPlayerNode?.removeTap(onBus: 0)
                print("Tap Stopped")
            }
        }

Maybe that will help someone. The audio session setup remained the same.