| Profile | Direction | Quality | Use case |
|---|---|---|---|
A2DP (Advanced Audio Distribution Profile) | Output only | High quality (44.1/48 kHz stereo) | Music, media playback, TTS |
HFP (Hands-Free Profile) | Bidirectional | 8 kHz mono | Voice capture from the glasses microphone |
import AVFoundation let audioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(.playback, mode: .default, options: []) try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
AVAudioPlayer, AVSpeechSynthesizer, or any standard audio API to play audio, then verify the active route before assuming output is on the glasses.let player = try AVAudioPlayer(contentsOf: audioFileURL) player.play()
let hasA2DPOutput = audioSession.currentRoute.outputs.contains {
$0.portType == .bluetoothA2DP
}
.allowBluetoothHFP for microphone capture; A2DP output options do not provide microphone access. If your SDK only exposes the older .allowBluetooth option, use that option for HFP.import AVFoundation
// Request microphone permission
let granted = await withCheckedContinuation { continuation in
AVAudioApplication.requestRecordPermission { granted in
continuation.resume(returning: granted)
}
}
guard granted else { return }
// Configure the session for HFP
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(.playAndRecord, mode: .default, options: [.allowBluetoothHFP])
try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
if let hfpInput = audioSession.availableInputs?.first(where: { $0.portType == .bluetoothHFP }) {
try audioSession.setPreferredInput(hfpInput)
}
AVAudioEnginelet audioEngine = AVAudioEngine()
let inputNode = audioEngine.inputNode
let format = inputNode.inputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: format) { buffer, _ in
// Forward the buffer to your audio pipeline (e.g., LiveKit, a file writer, etc.)
handleAudioBuffer(buffer)
}
audioEngine.prepare()
try audioEngine.start()
// Allow the Bluetooth HFP route to settle
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
// Verify HFP is actually routed
let hasHFPRoute = audioSession.currentRoute.inputs.contains { $0.portType == .bluetoothHFP }
guard hasHFPRoute else {
audioEngine.stop()
try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
throw MyError.hfpRouteUnavailable
}
removeTap(onBus:) to stop receiving input buffers, then deactivate the session. If you need to return to A2DP playback, reconfigure the audio session with .playback category after deactivation.inputNode.removeTap(onBus: 0) audioEngine.stop() try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
import android.content.Context
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.os.Build
private fun routeAudioToBluetooth(context: Context): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
return false
}
val audioManager = context.getSystemService(AudioManager::class.java)
// Get list of currently available devices
val devices = audioManager.availableCommunicationDevices
// User chooses one of the devices from the list.
val userSelectedDeviceType = AudioDeviceInfo.TYPE_BLUETOOTH_SCO
val selectedDevice = devices.firstOrNull { device ->
device.type == userSelectedDeviceType
}
if (selectedDevice == null) {
return false
}
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
return audioManager.setCommunicationDevice(selectedDevice)
}