1
votes

I'm trying to achieve a very simple task in iOS. I would like to get audio from a Bluetooth microphone and play the audio simultaneously using the built-in speaker.

Here is what I have right now. The app allows users to choose a speaker and a microphone source. However, whenever I pick a built-in speaker, the app will switch to a built-in microphone. And whenever I pick a Bluetooth microphone, the app will use the Bluetooth speaker. There is no way I can use the Bluetooth microphone and the built-in speaker separately.

import UIKit
import AVFoundation
import AVKit

class ViewController: UIViewController {

    var engine = AVAudioEngine()
    let player = AVAudioPlayerNode()
    let audioSession = AVAudioSession()
    var routePickerView = AVRoutePickerView(frame: CGRect(x: 0, y: 0, width: 0, height: 50))
    let bus = 0
    var isRunning = false

    @IBOutlet weak var viewHolder: UIStackView!

    override func viewDidLoad() {
        super.viewDidLoad()

        setUpAVRoutePicker()
        setUpAVSession()
    }

    func setUpAVRoutePicker() {
        viewHolder.addArrangedSubview(routePickerView)
    }

    func setUpAVSession() {
        do {
            try audioSession.setCategory(AVAudioSession.Category.playAndRecord, options: [.defaultToSpeaker, .allowBluetooth, .allowBluetoothA2DP, .allowAirPlay])
            try audioSession.setMode(AVAudioSession.Mode.default)
            try audioSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
            try audioSession.setActive(true)
        } catch {
            print("Error setting up AV session!")
            print(error)
        }
    }

    @IBAction func start(_ sender: AnyObject) {
        isRunning = !isRunning
        sender.setTitle(isRunning ? "Stop" : "Start", for: .normal)

        if isRunning {
            engine.attach(player)

            let inputFormat = engine.inputNode.inputFormat(forBus: bus)

            engine.connect(player, to: engine.mainMixerNode, format: inputFormat)

            engine.inputNode.installTap(onBus: bus, bufferSize: 512, format: inputFormat) { (buffer, time) -> Void in
                self.player.scheduleBuffer(buffer)
            }

            do {
                try engine.start()
            } catch {
                print("Engine start error")
                print(error)
                return
            }
            player.play()
        } else {
            engine.inputNode.removeTap(onBus: bus)
            engine.stop()
            player.stop()
        }

    }

    @IBAction func onInputBtnClicked(_ sender: AnyObject) {
        let controller = UIAlertController(title: "Select Input", message: "", preferredStyle: UIAlertController.Style.actionSheet)

        for input in audioSession.availableInputs ?? [] {
            controller.addAction(UIAlertAction(title: input.portName, style: UIAlertAction.Style.default, handler: { action in
            do {
                try self.audioSession.setPreferredInput(input)
            } catch {
                print("Setting preferred input error")
                print(error)
            }
            }))
        }
        present(controller, animated: true, completion: nil)
    }


}

It seems like it's impossible to achieve that (https://stackoverflow.com/a/24519938/6308776), which is kinda crazy. I know that it's possible to achieve such a simple task on Android, but it looks like it's not possible for iOS. Does anyone have any ideas?

p/s: I would like to use a Bluetooth headset, and I don't want to have the transmission via wifi.

1

1 Answers

0
votes

From Apple:

https://forums.developer.apple.com/thread/62954

... if an application is using setPreferredInput to select a Bluetooth HFP input, the output should automatically be changed to the Bluetooth HFP output corresponding with that input. Moreover, selecting a Bluetooth HFP output using the MPVolumeView's route picker should automatically change the input to the Bluetooth HFP input corresponding with that output. In other words, both the input and output should always end up on the same Bluetooth HFP device chosen for either input/output even though only the input or output was set individually. This is the intended behavior, but if it's not happening we definitely want to know about it.