iOS Change device speaker - ios

I need to implement iPhone speaker (ear and bottom) change during audio call (using TwilioVideo SDK for connection)
Mine code:
let audioSession = AVAudioSession.sharedInstance()
do {
if isSpeaker == false {
try audioSession.overrideOutputAudioPort(.speaker)
isSpeaker = true
} else {
try audioSession.overrideOutputAudioPort(.none)
isSpeaker = false
}
try audioSession.setActive(true)
} catch {
handleError(error.localizedDescription)
}
It works without any exceptions, but don't change audio output speaker

Twilio developer evangelist here.
You should not use AVAudioSession APIs directly with Twilio Video. Instead, you should use the TVIAudioController and set the audioOutput property to one of the options enumerated in TVIAudioOutput.
TVIAudioController.sharedController().audioOutput = .TVIAudioOutputVideoChatSpeaker
Let me know if that helps.

Related

iOS - AudioKit Crashes when receiving a phone call

AudioKit 4.9.3
iOS 11+
I am working on a project where the user is recording on the device using the microphone and it continues to record, even if the app is in the background. This works fine but when receiving a phone call I get an AudioKit error. I assume it has something to do with the phone taking over the mic or something. here is the error:
[avae] AVAEInternal.h:109
[AVAudioEngineGraph.mm:1544:Start: (err = PerformCommand(*ioNode,
kAUStartIO, NULL, 0)): error 561017449
AudioKit+StartStop.swift:restartEngineAfterRouteChange(_:):198:error
restarting engine after route change
basically everything that i have recording up until that point is lost.
here is my set up AudioKit code:
func configureAudioKit() {
AKSettings.audioInputEnabled = true
AKSettings.defaultToSpeaker = true
do {
try try audioSession.setCategory((AVAudioSession.Category.playAndRecord), options: AVAudioSession.CategoryOptions.mixWithOthers)
try audioSession.setActive(true)
audioSession.requestRecordPermission({ allowed in
DispatchQueue.main.async {
if allowed {
print("Audio recording session allowed")
self.configureAudioKitSession()
} else {
print("Audio recoding session not allowed")
}
}
})
} catch let error{
print("Audio recoding session not allowed: \(error.localizedDescription)")
}
}
func configureAudioKitSession() {
isMicPresent = AVAudioSession.sharedInstance().isInputAvailable
if !isMicPresent {
return
}
print("mic present and configuring audio session")
mic = AKMicrophone()
do{
let _ = try AKNodeRecorder(node: mic)
let recorderGain = AKBooster(mic, gain: 0)
AudioKit.output = recorderGain
//try AudioKit.start()
}
catch let error{
print("configure audioKit error: ", error)
}
}
and when tapping on the record button code:
do {
audioRecorder = try AVAudioRecorder(url: actualRecordingPath, settings: audioSettings)
audioRecorder?.record()
//print("Recording: \(isRecording)")
do{
try AudioKit.start()
}
catch let error{
print("Cannot start AudioKit", error.localizedDescription)
}
}
Current audio Settings:
private let audioSettings = [
AVFormatIDKey : Int(kAudioFormatMPEG4AAC),
AVSampleRateKey : 44100,
AVNumberOfChannelsKey : 2,
AVEncoderAudioQualityKey : AVAudioQuality.medium.rawValue
]
What can I do to ensure that I can get a proper recording, even when receiving a phone call? The error happens as soon as you receive the call - whether you choose to answer it or decline.
Any thoughts?
I've done work in this area, I'm afraid you cannot access the microphone(s) while a call or a VOIP call is in progress.
This is a basic privacy measure that is enforced by iOS for self-evident reasons.
AudioKit handles only the basic route change handling for an audio playback app. We've found that when an app becomes sufficiently complex, the framework can't effectively predestine the appropriate course of action when interruptions occur. So, I would suggest turning off AudioKit's route change handling and respond to the notifications yourself.
Also, I would putting AudioKit activation code in a button.

How to make a call on my iPhone with speaker mode in code

Is there an audioManager class in iOS as in Android so that I can set my phone call in speaker mode automatically?
audioManager = AudioManager()
audioManager.getSystemService(Context.AUDIO_SERVICE)
audioManager.setMode(AudioManager.MODE_IN_CALL)
audioManager.setSpeakerphoneOn(true)
You can change the output route of the current audio temporarily using the following method
do {
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
//Set the category as per your requirement
try audioSession.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
// or AVAudioSessionPortOverride.none to disable the speaker
}
catch let error{
print(error)
}

AVAudioSession - How to switch between speaker and headphones output

I'm trying to mimic behaviour as in Phone app during calling. You can easily switch output sources from/to speaker or headphones.
I know I can force speaker as an output when headphones are connected by calling:
try! audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try! audioSession.overrideOutputAudioPort(.speaker)
However, when I do that, I don't see any way to detect if headphones are still connected to the device.
I initially thought outputDataSources on AVAudioSession would return all posible outputs but it always returns nil.
Is there something I'm missing
You need to change the outputDataSources, as when you overrode it,
now it contains only the .Speaker option
in the Documentation you can find the solution to this,
If your app uses the playAndRecord category, calling this method with the AVAudioSession.PortOverride.speaker option causes audio to be routed to the built-in speaker and microphone regardless of other settings. This change remains in effect only until the current route changes or you call this method again with the AVAudioSession.PortOverride.none option.
Therefore the audio is routed to the built-in speaker, This change remains in effect only untill the current route changes or you call this method again with .noneOption.
it's not possible to forcefully direct sound to headphone unless an accessory is plugged to headphone jack (which activates a physical switch to direct voice to headphone).
So when you want to switch back to headphone, this should work.
And if there is no headphone connected will switch the output device to the small speaker output on the top of the device instead of the big speaker.
let session: AVAudioSession = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
try session.overrideOutputAudioPort(AVAudioSession.PortOverride.none)
try session.setActive(true)
} catch {
print("Couldn't override output audio port")
}
Read about this AVAdioSession/OverrideOutputAudioPort Here.
You can check if headset connected adding this extension,
extension AVAudioSession {
static var isHeadphonesConnected: Bool {
return sharedInstance().isHeadphonesConnected
}
var isHeadphonesConnected: Bool {
return !currentRoute.outputs.filter { $0.isHeadphones }.isEmpty
}
}
extension AVAudioSessionPortDescription {
var isHeadphones: Bool {
return portType == AVAudioSessionPortHeadphones
}
}
And simply use this line of code
session.isHeadphonesConnected

AVFoundation adding audio input mutes audio playback

I have an app that records audio and video using AVFoundation. I want any audio playback from other apps (or the system) to continue while recording audio in my app, but when adding the input to the session an already playing audio from another app gets magically muted.
The corresponding code looks like this:
// set up audio device
if(m_audioenabled) {
print("Audio enabled")
if self.m_audioDevice != nil {
do {
let audioinput = try AVCaptureDeviceInput(device: self.m_audioDevice!)
if self.m_session.canAddInput(audioinput) {
self.m_session.addInput(audioinput)
}
} catch _ {
print("failed adding audio capture device as input to capture session")
}
}
m_audioDataOutput = AVCaptureAudioDataOutput()
m_audioDataOutput?.setSampleBufferDelegate(self, queue: self.m_captureSessionQueueAudio)
if self.m_session.canAddOutput(m_audioDataOutput!){
self.m_session.addOutput(m_audioDataOutput!)
}
}
If I comment out the call to canAddInput(...) the audio keeps playing, when I call it, audio playback gets muted.
How can I disable that behavior ?
Please note the migrating to another Audio-API is not an option.
Sounds like your AVAudioSession is non-mixable (the default, I think). Try activating a mixable audio session before you set up the AVCaptureSession:
let session = AVAudioSession.sharedInstance()
try! session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.mixWithOthers])
try! session.setActive(true)

How to disable speakerphone in TwilioVoice iOS (Twilio Programmable Voice)

When a call is in progress (sent or received) the speakerphone is engaged already, and the speakerphone button does not react to presses to turn it off.
Is there a way to toggle it in code or enable it to be toggled in the UI? I believe this UI is Apple's core audio phone call UI.
This happens with Twilio's quickstart demo code from here:
https://github.com/twilio/voice-quickstart-swift
Try the following to force output to speaker:
if !session.overrideOutputAudioPort(AVAudioSessionPortOverride.Speaker, error:&error) {
println("could not set output to speaker")
if let e = error {
println(e.localizedDescription)
}
}
SWIFT 3.0
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, mode: AVAudioSessionModeVoiceChat, options: .mixWithOthers)
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
try AVAudioSession.sharedInstance().setActive(true)
Overide output port to none.
SWIFT 4
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, mode: AVAudioSession.Mode.voiceChat, options: .mixWithOthers)
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
try AVAudioSession.sharedInstance().setActive(true)
You can try below code snippet
func moveToVoiceCall() {
audioDevice.block = {
DefaultAudioDevice.DefaultAVAudioSessionConfigurationBlock()
do {
try AVAudioSession.sharedInstance().setMode(.voiceChat)
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
} catch {
print(error)
}
}
audioDevice.block();
}

Resources