In my app I want to check for the AVAudio portTypes already connected to the phone. The code below works for BluetoothA2DP and Headphones, but not BluetoothHFP when I connect the phone to my car handsfree. Can anyone help me?! I think have been through all the SO posts on Handsfree/AV/Bluetooth, and many others, but can't work out why it is not recognising the BluetoothHFP output portType.
import AVFoundation
func startCheckAVConnection() {
// set the AVAudioSession to allow bluetooth. This do/try/catch doesn't seem to make a difference if it is here or not.
do{
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: AVAudioSessionCategoryOptions.allowBluetooth)
} catch{
print(error)
}
// Check possible outputs for handsfree
let outputs = AVAudioSession.sharedInstance().currentRoute.outputs
if outputs.count != 0 {
for output in outputs {
if output.portType == AVAudioSessionPortBluetoothA2DP {
peripheralLabel.text = "connected to BluetoothA2DP"
} else if output.portType == AVAudioSessionPortBluetoothHFP { // NOT RECOGNISED
peripheralLabel.text = "connected to BluetoothHFP"
} else if output.portType == AVAudioSessionPortHeadphones {
peripheralLabel.text = "connected to Headphones"
}
}
} else {
peripheralLabel.text = "Please connect handsfree"
}
// Add observer for audioRouteChangeListener
NotificationCenter.default.addObserver(
self,
selector: #selector(TopVC.audioRouteChangeListener(_:)),
name: NSNotification.Name.AVAudioSessionRouteChange,
object: nil)
}
I needed to add setActive after setCategory.
do { try AVAudioSession.sharedInstance().setActive(true)
print("setActive")
} catch {
print(error)
}
Try changing your category setup to:
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: AVAudioSessionCategoryOptions.allowBluetooth)
HFP doesn't work with multi-route category AFAICT.
Related
I am working on a Video/Audio Call App where i need to provide four options related to the Audio Output:
Speaker, Built in mic, Any BLE Device supporting audio, No Audio output
Below functions i have used:
static func setBuiltInMic() {
let outputs = audioSession.availableInputs
for output in outputs! {
if output.portType.rawValue == AVAudioSession.Port.builtInMic.rawValue {
do {
try audioSession.setPreferredInput(output)
} catch let error {
print("Setting Built in Mic Port: \(error.localizedDescription)")
}
}
}
}
static func setAndCheckBLEAudioPort() -> Bool {
let outputs = audioSession.availableInputs
for output in outputs! {
if output.portType.rawValue == AVAudioSession.Port.bluetoothHFP.rawValue {
do {
try audioSession.setPreferredInput(output)
return true
} catch let error {
print("Setting BLE Port: \(error.localizedDescription)")
return false
}
}
}
return false
}
static func setupAudioSession(isSpeakerEnabled: Bool) {
do {
try audioSession.setCategory(.playAndRecord)
try audioSession.setMode(.voiceChat)
try audioSession.overrideOutputAudioPort(isSpeakerEnabled ? .speaker : .none)
try audioSession.setActive(true, options: [])
} catch let error as NSError {
print("Fail: \(error.localizedDescription)")
}
}
But this doesn't work Audio keeps coming from different source like speaker even if i try to mute it using setupAudioSession
Anyone has an idea or reference for me to look into it?
The code is working fine, it was an issue with a third party library used for audio and video calls prior to Twilio.
The main issue here is, when the microphone input is added, the device loses any haptic system sounds.
I setup the audio session here:
let audioSession = AVAudioSession.sharedInstance()
do {
try self.audioSession.setCategory(.playAndRecord, options: .mixWithOthers)
try self.audioSession.setAllowHapticsAndSystemSoundsDuringRecording(true)
try self.audioSession.setActive(true)
} catch { }
I make sure I am using setAllowHapticsAndSystemSoundsDuringRecording.
Though out the app, I am adding the microphone and removing it on-demand:
do {
let microphonePermission = AVCaptureDevice.authorizationStatus(for: AVMediaType.audio)
if microphonePermission != .denied && microphonePermission != .restricted {
let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)
let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice!)
if self.session.canAddInput(audioDeviceInput) {
self.session.addInput(audioDeviceInput)
}
else { print("Could not add audio device input to the session.") }
} else {
}
}
catch { print("Could not create audio device input: \(error).") }
As soon as the microphone is added, it loses haptic feedback and system sounds.
It seems that my app is not working with AirPods. Right now I'm using this code for the playback and record:
let audioSession = AVAudioSession.sharedInstance()
do { try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, with: AVAudioSessionCategoryOptions.defaultToSpeaker)
}catch {
print("audioSession properties weren't set because of an error.")
}
Will it be enough if I change defaultToSpeaker to allowBluetooth?
P.S. I know it's quite a stupid question because it'd be much simpler to just change this line and check, but I don't have AirPods with me right now, so the only option for me is to upload the new build to Testflight (and I want to do this with minimum iterations).
update: (quite naive approach — but all I need is to use bluetooth headphones if they are available):
func selectDevice(audioSession: AVAudioSession) {
var headphonesExist = false
var bluetoothExist = false
var speakerExist = false
let currentRoute = AVAudioSession.sharedInstance().currentRoute
for output in audioSession.currentRoute.outputs {
print(output)
if output.portType == AVAudioSessionPortHeadphones || output.portType == AVAudioSessionPortHeadsetMic {
headphonesExist = true
}
if output.portType == AVAudioSessionPortBluetoothA2DP || output.portType == AVAudioSessionPortBluetoothHFP {
bluetoothExist = true
}
if output.portType == AVAudioSessionPortBuiltInSpeaker {
speakerExist = true
}
}
if bluetoothExist == true {
do { try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, with: AVAudioSessionCategoryOptions.allowBluetooth) } catch {
print("error with audiosession: bluetooth")
}
}
}
You need to add support for bluetooth to your options parameter like so:
[AVAudioSessionCategoryOptions.defaultToSpeaker, .allowBluetoothA2DP]
.allowBluetoothA2DP will allow for high quality audio output to the bluetooth device and restrict microphone input on said device, while .allowBluetooth will default HFP compatible (input/output) bluetooth devices to the lower quality HFP, which supports microphone input.
Here is a full source to request proper permission.
All you have to do is to add a mode with '.allowBluetoothA2DP'
I've applied on Swift 5
func requestPermissionForAudioRecording(){
recordingSession = AVAudioSession.sharedInstance()
do {
// only with this without options, will not capable with your Airpod, the Bluetooth device.
// try recordingSession.setCategory(.playAndRecord, mode: .default)
try recordingSession.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker, .allowAirPlay, .allowBluetoothA2DP])
try recordingSession.setActive(true)
recordingSession.requestRecordPermission() { allowed in
DispatchQueue.main.async {
if allowed {
// Recording permission has been allowed
self.recordingPermissionGranted = true
} else {
// failed to record!
}
}
}
} catch let err {
// failed to record!
print("AudioSession couldn't be set!", err)
}
}
I am using AVAudioSession to listen to voice input. it works fine for wired headphones but it is not working for connected bluetooth device. Following is the code I am using to set input to bluetooth mic
func setupSessionForRecording() {
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.allowBluetooth])
} catch let error as NSError {
debugPrint("Error in listening "+error.localizedDescription)
}
var inputsPriority: [(type: String, input: AVAudioSessionPortDescription?)] = [
(AVAudioSessionPortLineIn, nil),
(AVAudioSessionPortHeadsetMic, nil),
(AVAudioSessionPortBluetoothHFP, nil),
(AVAudioSessionPortUSBAudio, nil),
(AVAudioSessionPortCarAudio, nil),
(AVAudioSessionPortBuiltInMic, nil),
]
for availableInput in audioSession.availableInputs! {
guard let index = inputsPriority.index(where: { $0.type == availableInput.portType }) else { continue }
inputsPriority[index].input = availableInput
}
guard let input = inputsPriority.filter({ $0.input != nil }).first?.input else {
fatalError("No Available Ports For Recording")
}
do {
try audioSession.setPreferredInput(input)
try audioSession.setMode(AVAudioSessionModeMeasurement)
try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
try audioSession.setPreferredIOBufferDuration(10)
} catch {
fatalError("Error Setting Up Audio Session")
}
}
This code stops taking input from device mic and I also get sound in bluetooth headset that it is ready to listen but it doesn't pick any input from device.
Also,
When I am trying to play any audio into bluetooth headset It doesn't work. Here is the code to play audio
do {
let output = AVAudioSession.sharedInstance().currentRoute.outputs[0].portType
if output == "Receiver" || output == "Speaker"{
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
}
else{
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
}
print("Voice Out \(output)" )
} catch let error as NSError {
print("audioSession error: \(error.localizedDescription)")
os_log("Error during changing the current audio route: %#" , log: PollyVoiceViewController.log, type: .error, error)
} catch {
os_log("Unknown error during changing the current audio route", log: PollyVoiceViewController.log, type: .error)
}
do {
let soundData = try Data(contentsOf: url as URL)
self.audioPlayer = try AVAudioPlayer(data: soundData)
self.audioPlayer?.prepareToPlay()
self.audioPlayer?.volume = 3.0
self.audioPlayer?.delegate = self
self.audioPlayer?.play()
} catch let error as NSError {
print("Error getting the audio file"+error.description)
}
The reason is: BluetoothHFP is not available in AVAudioSessionModeMeasurement mode
AVAudioSessionModeMeasurement
After you set try audioSession.setMode(AVAudioSessionModeMeasurement), the audioSession.availableInputs is not contain the BluetoothHFP.
This mode is intended for apps that need to minimize the amount of system-supplied signal processing to input and output signals. If recording on devices with more than one built-in microphone, the primary microphone is used.
And in the document of setPreferredInput(_:)
The AVAudioSessionPortDescription must be in the availableInputs array.
The value of the inPort parameter must be one of the AVAudioSessionPortDescription objects in the availableInputs array. If this parameter specifies a port that is not already part of the current audio route and the app’s session controls audio routing, this method initiates a route change to use the preferred port.
And it must set up after setting the mode.
You must set a preferred input port only after setting the audio session’s category and mode and activating the session.
I am designing an iOS application using PJSIP for VOIP Calling,
I start the call on speaker and on a button press i am able to make the call on normal microphone, but i am unable to make it on speaker again after pressing the speaker button.
I have used the following code to achieve that functionality :-
if sender.titleLabel?.text == "Speaker"{
do {
try audioSession.overrideOutputAudioPort(.none)
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try audioSession.setMode(AVAudioSessionModeVoiceChat)
try audioSession.setActive(true)
} catch {
print("AVAudioSession cannot be set: \(error)")
}
}else{
do {
try audioSession.overrideOutputAudioPort(.speaker)
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try audioSession.setMode(AVAudioSessionModeVoiceChat)
try audioSession.setActive(true)
} catch {
print("AVAudioSession cannot be set: \(error)")
}
}
Im not sure but I think your code will work while using NSNotificationCenter. Send notificaion for speaker in your if statement, and one notification for that else code.
var speaker = false
on press button action:
if speaker == false {
NSNotificationCenter.defaultCenter().postNotificationName("speakerON", object: nil)
} else {
NSNotificationCenter.defaultCenter().postNotificationName("speakerOFF", object: nil)
}
on viewDidLoad:
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(YourClassName.methodOfReceivedNotification(_:)), name:"speakerON", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(YourClassName.methodOfReceivedNotification(_:)), name:"speakerOFF", object: nil)
your functions:
func speakerON() {
try audioSession.overrideOutputAudioPort(.none)
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try audioSession.setMode(AVAudioSessionModeVoiceChat)
try audioSession.setActive(true)
}
func speakerOFF() {
try audioSession.overrideOutputAudioPort(.speaker)
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try audioSession.setMode(AVAudioSessionModeVoiceChat)
try audioSession.setActive(true)
}
Please see this link if you want to learn more about NSNotificationCenter:
https://stackoverflow.com/questions/24049020/nsnotificationcenter-addobserver-in-swift