Audio devices

Last modified by Sylvain Berfini on 2023/02/16 14:26

How to select which device will be used for capture / playback

Linphone SDK allows you to control the I/O audio devices through our API thanks to AudioDevice objects.

It represents a hardware device available at a given time, with its capabilities (Play, Record or both) and its type (Earpiece, Speaker, Microphone, Bluetooth, etc...).

You can get currently available devices by calling core.getAudioDevices(). It returns a list with one device of each type having a given capability. If two or more devices with the same type and capability are found, only the first one will be added to the list.

For example if your device is connected to a bluetooth headset, you will only have one bluetooth device in the returned list with both capabilities.

To retrieve the full list of audio devices, use core.getExtendedAudioDevices().

The callback onAudioDevicesListUpdated(core) will be triggered on the CoreListener as soon as a new audio device has been connected to or disconnected from the device.

On Android, any connected/disconnected Bluetooth device or wired headset/headphones device will automatically be added/removed from the devices list and will trigger this callback.

On Android, BLUETOOTH_CONNECT permission must be granted for the automatic bluetooth device detection to work!

If you want to change the default output device, you can use core.setDefaultInputAudioDevice() or core.setDefaultOutputAudioDevice().
After that, any incoming/outgoing call will automatically use this device by default.

You can also retrieve the current default with core.getDefaultInputAudioDevice() or core.getDefaultOutputAudioDevice().

You can dynamically change the output device for the all ongoing calls or only a particular call by calling respectively core.setOutputAudioDevice() or call.setOutputAudioDevice() and providing the desired audio device as an argument.

Upon a successful change, the onAudioDeviceChanged(core, audioDevice) callback will be triggered on the CoreListener.

You can call core.getOutputAudioDevice() / core.getInputAudioDevice() or call.getOutputAudioDevice() / call.getInputAudioDevice() to check which device is currently being used.

Finally, starting SDK 5.2.0, you will be able to use callParams.setOutputAudioDevice() and callParams.setInputAudioDevice() when creating a CallParams object before starting a call, it will override the default input/output audio device settings in the Core for this specific call.

Android specifics

On Android, you have two audio drivers: OpenSLES and AAudio.

OpenSLES has been deprecated by Google in favor of AAudio, but AAudio is only available on devices running Android 8 or newer.
 

While AAudio API allows to choose the device to use with great precision, it's not the case when using OpenSLES.

We use AudioManager APIs to apply the audio device selection as best as possible, but is has some downfalls.
At the end of each call, to ensure we don't mess with other apps too much, we disable Bluetooth & Speakerphone on AudioManager.

Also, when you plug a wired headset / headphones, the default "earpiece" audio route is automatically replaced by the headset / headphones and you can't force the audio route to the earpiece.

Finally, on many devices, OpenSLES isn't capable on using the hardware echo canceller if there is one.
When that's the case, we remove the capture OpenSLES sound card so it will fallback to the "ANDROID SND (deprecated)" capture sound card based on AudioTrack Java APIs that is capable of using the hardware echo canceller.

Ultimately, if for some reason you prefer doing the audio routing yourself and don't want our soundcards to interfere, you can disable all calls to Android's AudioManager from our SDK using the following configuration flag:

[sound]
android_disable_audio_route_changes=1

Setting this flag will prevent OpenSLES sound cards to switch audio device to speakerphone or bluetooth and will prevent AAudio sound cards to switch audio device to bluetooth only, unless you are implementing a self-managed service from TelecomManager API like we do in linphone-android.

That feature is only available in SDK 5.1.30 and newer.

Hardware echo canceller not working

The hardware echo canceller on Android often only works if the AudioManager's mode is set to IN_COMMUNICATIONS.
Any linphone-sdk newer than 5.0 will do that automatically, but make sure you don't modify it's value in your app.

If the device declares having a hardware echo canceller but it doesn't work, you can use the following Kotlin code to tell our SDK to use the software one instead:

core.mediastreamerFactory.setDeviceInfo(android.os.Build.MANUFACTURER, android.os.Build.MODEL, android.os.Build.DEVICE, org.linphone.mediastream.Factory.DEVICE_HAS_BUILTIN_AEC_CRAPPY, <calibrated value>, 0)
core.reloadSoundDevices()

To get the correct <calibrated value>, use the echo canceller calibration option in linphone-android's audio settings.
Do it a few times and keep the lowest value.

iOS specifics

Due to iOS API limitations, the available Output devices are not accessible to be listed and selected, with one exception being the iPhone Speaker. To work around these limitations, the audio devices are listed using the AVAudioSession availableInputs function, with the assumption that external devices such as bluetooth headset will act as both input and output. 

While no call is active, the AVAudioSession will be in playback mode and only two devices will be listed: 
- iPhone microphone (input), which is implicitly considered to be paired with the iPhone Earpiece (output)
- iPhone Speaker (output), which is implicitly considered to be paired with the iPhone Microphone (input)

When answering an incoming call while using a bluetooth headset, the audio route selected will depend on how you answer : 
- answering with your phone will redirect the audio route to the  iPhone microphone/earpiece pair
- answering with your bluetooth headset will cause the audio route to remain through the used headset
This mirrors the iOS classic behaviour when receiving a GSM call