We are developing a voice call application that uses AVRoutePickerView, allowing users to switch between an iPhone, a speaker, and Bluetooth earphones. However, we have encountered an intermittent issue: when switching from the speaker to Bluetooth earphones, the earphones remain in a loading state, and the audio channel subsequently switches back to the speaker. How can we resolve this issue? Here is the sample code:
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playAndRecord, mode: .default, options: [.allowBluetooth])
try session.overrideOutputAudioPort(isBluetoothConnected ? .none : .speaker)
try session.setPreferredSampleRate(48000)
try session.setPreferredIOBufferDuration(0.02)
try session.setActive(true, options: [])
INFO(" >>> start: hasBluetooth=\(isBluetoothConnected), inputs: \(session.currentRoute.inputs.map { $0.portType }) outputs: \(session.currentRoute.outputs.map { $0.portType })")
} catch {
ERROR("(error.localizedDescription)")
}
We are developing a voice call application that uses AVRoutePickerView, allowing users to switch between an iPhone, a speaker, and Bluetooth earphones. However, we have encountered an intermittent issue: when switching from the speaker to Bluetooth earphones, the earphones remain in a loading state, and the audio channel subsequently switches back to the speaker. How can we resolve this issue?
This isn't really going to work, at least not the way you think/hope it will. The problem here is that your CallKit works by manipulating your AudioSession "behind your back", basically converting it from a standard "PlayAndRecord" session into a special "PhoneCall" audio session.
That conversion is REALLY important because the underlying reason why your phone call doesn't act like a "normal" audio session. For example, the standard audio session priority rules say that:
-
You can't activate a non-mixable playback or ANY recording session from the background -> CallKit does this.
-
A foreground app (like a music player) will interrupt the existing audio session owner -> except that's not what phone calls do.
You can actually hear the difference if you listen closely, as the maximum volume of the phone audio session is slightly higher than the standard audio session.
In any case, the problem with manipulating the audio session like you're trying to do here is that directly calling "setActive" is going to end up doing one of two things:
-
Best case, the phone audio session is already active, so the setActive will fail and nothing happens, since you can't activate a session that's already active.
-
Worst case, the phone audio session isn't active, so the setActive works. Your PlayAndRecord audio session is now active, which means the PhoneCall audio session may not be able to activate properly.
Once you're in state #2, things get weird. Sometimes everything seems to work fine, as you basically end up with a half-configured phone session that sort of works. Worst case, you have a true "PlayAndRecord" audio session, which opens up the door to all sorts of problems. Most notably, an incoming call can cause a complete session interruption, suspending your app immediately, effectively ending your call with minimal notice.
That last issue is actually the biggest issue that led us to create CallKit in the first place. CallKit apps cannot activate their own sessions like this.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware