Skip to content

TechNote_BluetoothAudio

Robert Wu edited this page Feb 14, 2023 · 9 revisions

Bluetooth Audio

Bluetooth audio devices work differently during a phone call and listening to music. Most Bluetooth audio devices expose two endpoints, A2DP and SCO.

A2DP (Advanced Audio Distribution Profile)

A2DP is a protocol supported on most Bluetooth Audio devices. This protocol is used for high quality music.

To achieve high quality music, Android encodes music with a codec. The headset receives this data and decodes it into PCM data. Android and headsets should support the SBC codec. Additional codecs like aptX, AAC, and LDAC are supported on a per device basis.

SCO (Synchronous Connection Oriented Link)

During a voice call, SCO is used. The underlying protocols HSP/HFP are supported on most Bluetooth devices. HSP (HeadSet Profile) is used for voice communication and HFP (Hands-Free Profile) was meant to control a phone from another unit.

Compared to A2DP, SCO has worse audio quality but lower latency.

Old Method of enabling SCO

Bluetooth audio devices default to A2DP when not in a voice call. You can add some functionality in your app to add a callback whenever a new audio device is added to see if a SCO device is added.

mAudioManager.registerAudioDeviceCallback(new AudioDeviceCallback() {
    @Override
    public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {

        List<AudioDeviceListEntry> deviceList =
                AudioDeviceListEntry.createListFrom(addedDevices, mDirectionType);
        if (deviceList.size() > 0){
            mDeviceAdapter.addAll(deviceList);
        }
    }
}, null);

You can check whether an audio device is SCO from AudioDeviceInfo.

@RequiresApi(api = Build.VERSION_CODES.M)
private boolean isScoDevice(int deviceId) {
    if (deviceId == 0) return false; // Unspecified
    AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    final AudioDeviceInfo[] devices;
    devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
    for (AudioDeviceInfo device : devices) {
        if (device.getId() == deviceId) {
            return device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
        }
    }
    return false;
}

You can set setDeviceId() on a stream to set the audio device as SCO. Also, you need to start Bluetooth SCO in Android.

private void startBluetoothSco() {
    AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    myAudioMgr.startBluetoothSco();
}

Please call startBluetoothSco() and setDeviceId() at similar times. Not doing so will result in the lack of sound. See HelloOboe for a full example of turning on a Bluetooth SCO device when the Bluetooth SCO endpoint is selected.

Remember to call stopBluetoothSco() when the headset is disconnected. By also adding an onAudioDevicesRemoved() callback to registerAudioDeviceCallback, you can figure out when this happens.

New method of enabling SCO headset.

startBluetoothSco() and stopBluetoothSco() is recently deprecated. The alternate method is calling setCommunicationDevice() and clearCommunicationDevice() introduced in S. Below is a snippet calling setCommuicationDevice() with a device id. Please call isScoDevice() first because setCommuicationDevice() will throw if the device type is not a communication device.

@RequiresApi(api = Build.VERSION_CODES.S)
private void setCommunicationDevice(int deviceId) {
    AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    final AudioDeviceInfo[] devices;
    devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
    for (AudioDeviceInfo device : devices) {
        if (device.getId() == deviceId) {
            audioManager.setCommunicationDevice(device);
            return;
        }
    }
}

Similar to setBluetoothSco(), you should clear the communication device when something else is in use. That function is clearCommunicationDevice(). The HelloOboe example also covers setCommunicationDevice() and clearCommunicationDevice().

How to reduce the latency of a headset

In most cases, Android itself has a latency between 30-100 ms. Most of the latency comes from buffering on various headsets.

If a specific headset has a high latency, try changing the Bluetooth Audio Codec in developer settings. Also, try toggling "Disable Bluetooth A2DP hardware offload".

My Bluetooth headset is glitching

A2DP uses a variety of codecs including SBC, AAC, aptX, aptX HD, and LDAC. This allows raw PCM data to be compressed into less data for better audio quality.

Some Android phones implement Bluetooth A2DP offloading. This allows the encoding to be done on a phone specific DSP.

If there are Bluetooth glitches for a specific device, try switching the codec or disabling A2DP offloading in the developer settings.

Clone this wiki locally