Record video with audio via Bluetooth

I'm trying to record video, with audio input coming from a connected Bluetooth headset. However I haven't been able to get an AVCaptureDevice that represents the headset, so I can't add it to my AVCaptureSession.


I tried to find it using a discovery session, but it never finds the headset-- only the built in microphone. That may not be surprising since I'm asking for .builtInMicrophone, but the enumeration has no members for external audio inputs. I tried leaving the device type array empty but that finds no devices.


let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInMicrophone], mediaType: AVMediaType.audio, position: .unspecified)
print("Found \(discoverySession.devices.count) devices")
for device in discoverySession.devices {
    print("Device: \(device)")
}


After doing some searching I tried setting up the AVAudioSession to specifically allow Bluetooth. However this has had no effect on the above.


private let session = AVCaptureSession()
// later...

session.usesApplicationAudioSession = true
session.automaticallyConfiguresApplicationAudioSession = false
        
do {
    try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: [.allowBluetooth])
    try AVAudioSession.sharedInstance().setActive(true)
} catch  {
    print("Error messing with audio session: \(error)")
}


For completeness I also tried the deprecated AVCaptureDevice.devices() method, but it doesn't find the Bluetooth headset either.


I know that the headset is available because AVAudioSession can see it. However I haven't been able to get from the availableInputs array to something I can use in an AVCaptureSession. The following code finds the headset, but what would I do with the result? It's not an AVCaptureDevice nor can I construct one from the entries in the array. Setting the "preferred" input doesn't have any effect that I know how to use.


if let availableInputs = AVAudioSession.sharedInstance().availableInputs {
    print("Found \(availableInputs.count) inputs")
    for input in availableInputs {
        print("Input: \(input)")
        if input.portType == AVAudioSessionPortBluetoothHFP {
            print("Setting preferred input")
            do {
                try AVAudioSession.sharedInstance().setPreferredInput(input)
            } catch {
                print("Error setting preferred input: \(error)")
            }
        }
    }
}


Given that the Bluetooth headset is connected and available, how do I set it as the audio input for my capture session?

Answered by bombbombtom in 240328022

This seems to be a case of the SDK giving confusing information while doing the right thing.


If you configure the audio session as above with .allowBluetooth and a Bluetooth headset is connected, then you'll get Buetooth audio. However, the discovery session will still claim to be using the iPhone's built-in microphone. Essentially you need to pay attention to AVAudioSession's idea about what its preferred input is while ignoring the fact that AVCaptureDevice still says it's using the built-in mic. I spent a lot of time trying to figure out how to get an AVCaptureDevice that refers to the Bluetooth input without realizing that it's not only impossible but unnecessary.

Accepted Answer

This seems to be a case of the SDK giving confusing information while doing the right thing.


If you configure the audio session as above with .allowBluetooth and a Bluetooth headset is connected, then you'll get Buetooth audio. However, the discovery session will still claim to be using the iPhone's built-in microphone. Essentially you need to pay attention to AVAudioSession's idea about what its preferred input is while ignoring the fact that AVCaptureDevice still says it's using the built-in mic. I spent a lot of time trying to figure out how to get an AVCaptureDevice that refers to the Bluetooth input without realizing that it's not only impossible but unnecessary.

To clarify:

The AVCaptureDevice for audio never uses bluetooth by default. Bluetooth is of course important for some specialty use cases, but the audio quality is low and the latency extremely high for typical video + audio capture scenarios (remember, the video must be synchronized to the audio -- it's preferable for the audio clock to be rock solid).


As you discovered, you can force bluetooth usage by telling your AVCaptureSession that you want to manually configure your app audio session (captureSession.automaticallyConfiguresApplicationAudioSession = false), then set (at a minimum) PlayAndRecord + allow bluetooth.


The AVCaptureDevice for audio follows the standard audio device rules for iOS, where the last one in wins. For instance, while idle, it identifies itself as the built in microphone. Not till your route goes active does it actually know which device is currently active (such as headphone mic, line in, etc).

Record video with audio via Bluetooth
 
 
Q