Change Audio input programmatically doesn't change Audio engine input AVFoundation

I'm trying to change the audio input (microphone) between all the available devices from AVAudioSession.sharedInstance().availableInputs. I'm using AVAudioSession.routeChangeNotification to get automatic route changes when devices get connected/disconnected and change the preferred input with setPreferredInput, then I restart my audioEngine and it works fine.

But when I try to change the preferred input programmatically It doesn't change the audio capture inputNode. But keeps the last connected device and capturing.

Even the AVAudioSession.sharedInstance().currentRoute.inputs changes but the audioEngine?.inputNode doesn't change to setPreferredInput call.

WhatsApp seems to have done that without any issues.

Any suggestions or leads are highly appreciated. Thanks.

Replies

These are some related code segments. enableMic programmatically is the issue.

private var audioEngine: AVAudioEngine?
private var inputNode: AVAudioNode!

private func setupAudioSession() {
    do {
        let session = AVAudioSession.sharedInstance()
        try session.setCategory(.playAndRecord, options: [.defaultToSpeaker, .allowBluetooth])
        try session.setActive(true)
    } catch {
        print(error)
    }
}

func setupAudioEngine() {
    audioEngine?.stop()
    audioEngine?.reset()

    setupAudioSession()

    audioEngine = AVAudioEngine()
    inputNode = audioEngine?.inputNode

    guard let hardwareSampleRate = audioEngine?.inputNode.inputFormat(forBus: 0).sampleRate else { return }

    let format = AVAudioFormat(standardFormatWithSampleRate: hardwareSampleRate, channels: 1)

    let requiredBufferSize: AVAudioFrameCount = 4800

    inputNode.installTap(onBus: 0,
                         bufferSize: requiredBufferSize,
                         format: format
    ) { [self] (buffer: AVAudioPCMBuffer, _: AVAudioTime) in
       /* process the buffer */
    }

    audioEngine?.prepare()
}

func startRecording() {
    do {
        try audioEngine?.start()
    } catch {
        print("Could not start audioEngine: \(error)")
    }
}

private func observeRouteChanges() {
    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleRouteChange),
                                           name: AVAudioSession.routeChangeNotification,
                                           object: nil)
}

@objc func handleRouteChange(notification: Notification) {
    guard let userInfo = notification.userInfo,
          let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
          let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
        return
    }

    switch reason {

    case .newDeviceAvailable, .oldDeviceUnavailable:
       /* enableMic with current route AVAudioSession.sharedInstance().currentRoute.inputs.first */

    default: ()
    }

}

private func enableMic(mic: AVAudioSessionPortDescription?) {
    let session = AVAudioSession.sharedInstance()
    do {
        try session.setPreferredInput(mic)
        /* re-setup the audio engine and start */
    } catch {
        print(error)
    }
}