-
Notifications
You must be signed in to change notification settings - Fork 192
Using BlueALSA with HFP and HSP Devices
This article discusses some issues around using BlueALSA with the SCO profiles, the "Hands Free" profile (HFP) and the "Headset" profile (HSP).
Both HFP and HSP are designed for use with voice audio: they have low sample rates and deliberately prioritize (relatively) low latency over audio quality. HSP is the older, and simpler, of the two; HFP adds a lot of complexity in order to fully support telephony applications.
BlueALSA does not implement any telephony functions, so it supports only a subset of the HFP specification but has full support for HSP (except the button press requirement).
-
Make absolutely sure that
PulseAudio
is not running - otherwise it will certainly ruin your day. Not only does it steal the bluetooth audio profile endpoints from BlueALSA, but it also steals the ALSA PCM devices, making it difficult, or impossible, for ALSA applications to access the devices directly. -
bluealsa-aplay
is not duplex. It captures from bluetooth and plays back to a local speaker. Some other application is required to capture from local microphone and play back to bluetooth. See Using ALSA utilities for audio transfer below. -
Not all bluetooth adapters are capable of supporting SCO data links with Linux. Most work fine, some allow outgoing (playback) streams only, others do not work at all. There does not appear to be any on-line database of known working modules, and no certain pattern to which brands or types will work. BlueALSA implements a workaround that enables SCO in most Broadcom modules, and there is also a workaround for some Texas Instruments' modules, but there is no known equivalent for other brands. See The SCO Routing Issue below.
Support for the HSP-HS role was not added to Bluez 5 until release 5.55 (September 2020). So although BlueALSA has support for HSP-HS, it cannot be used with earlier releases of Bluez. With Bluez 5.55 and later, Capture and Playback PCMs can be opened at the same time, so full duplex operation is permitted.
The audio connection is created by the AG device, and the HS device can only wait for that to happen. The HSP specification allows the AG to create the audio connection at any time after the profile is connected; but the HS device cannot request or force an audio connection. For BlueALSA this means that although The PCMs exist and clients can connect to them, there may not be any audio streams available. See Using ALSA utilities for audio transfer below for more information.
BlueALSA has full support for audio connections in the HSP-AG role. A client merely needs to open a PCM on the device and the audio connection is created. Capture and Playback PCMs can be opened at the same time.
In the HFP-HF role BlueALSA is playing the part of the hands-free unit in the HFP model, so the bluetooth devices connected are typically mobile 'phones. Most 'phones, perhaps all, require full support for the HFP specification. So although it may be (theoretically) possible to implement this using the BlueALSA D-Bus RFCOMM API, it would certainly not be trivial and is beyond the scope of this article. We recommend the use of oFono to handle the HFP-HF protocol messaging in combination with BlueALSA to handle the audio connection.
Start the bluealsa service with the option -p hfp-ofono
(you can also enable the a2dp profile options too if required). It will detect a running instance of ofono and register with it to provide audio support. When a 'phone initiates an audio connection BlueALSA will present the connection to ALSA as two PCMs, for Playback and Capture.
HFP, like HSP, requires that the audio connection is created by the AG device, and this causes similar issues for BlueALSA in the HFP-HF role as in the HSP-HS role. See Using ALSA utilities for audio transfer below for more information.
When using oFono it may be possible to use the D-Bus VoiceCall interface of oFono to identify when an HFP audio connection is available.
When BlueALSA is in the HFP-AG role it is playing the part of the 'phone within the HFP model, so the bluetooth devices it connects to are headsets or speakers.
Currently, BlueALSA does not support using oFono in the HFP-AG role when mSBC is enabled; it is not possible to acquire the transport (i.e. open a SCO audio connection) because of the way that oFono establishes the "codec connection". oFono only registers the HFP-AG profile role when the local host has a phone modem attached, so it is not generally appropriate for systems designed exclusively for internet audio (e.g. IP phones, web-based on-line meetings, etc.)
We can use BlueALSA's in-built HFP support in the HFP-AG role. BlueALSA's internal HFP-AG implementation is incomplete, so does not comply with the HFP specification, but it is sufficient to enable most HFP hands-free units and headphones to successfully transfer audio in both directions. There is no support for dialling or other call management functions though.
The Bluetooth Hands-Free Profile specification mandates that compliant devices must allow audio connections to be established during a call, but it is only optional for them to allow audio connections outside of a call. This choice has given rise to varied behaviours among different hands-free devices. For the purposes of this article, we distinguish three types of Hands-Free device:
-
Full support for out-of-call audio connections
These devices accept full duplex audio connections without any need for call setup commands. They can be used in much the same way as HSP devices above.
-
Playback-only support for out-of-call audio connections
These devices allow audio connections to be opened outside of a call, but do not enable their microphone until a call is established. With these a BlueALSA client can playback audio without any need for call management commands, but must begin a call session before it can capture audio from the device microphone (otherwise it will capture only silence).
-
No support for out-of-call audio connections
These devices do not permit audio connections until a call session has begun.
Unfortunately the specification does not define any way for the AG to enquire whether the HF device supports out-of-call audio connections, so it is not possible for BlueALSA to determine this. So BlueALSA implements enough of the specification to be able to set up an out-of-call audio connection (i.e. it works with devices of group 1 in the above classification). For HF devices in group 2 BlueALSA will playback audio to the device speaker, but will not enable the device microphone; and for group 3 neither the microphone nor the speaker will be enabled. BlueALSA does however provide a D-Bus RFCOMM API to clients so that they can implement their own support for devices in groups 2 and 3.
The bluez-alsa project also includes a simple utility called bluealsa-rfcomm
to give access to that API from the command-line or scripts.
The D-Bus API is documented here: https://github.com/Arkq/bluez-alsa/blob/master/doc/bluealsa-api.txt
Possibly the simplest way to create an audio connection to a HF when an in-progress call is required is to simulate the message sequence that is used by a 'phone to transfer the audio of an in-progress call to a hands-free unit. The sequence is this:
- inform the HF that we have a connection to our service provider:
+CIEV:1,1
- inform the HF that we have a strong mobile signal:
+CIEV:5,5
- inform the HF that a call is in progress:
+CIEV:2,1
We must also inform the HF device that the call is finished when we are no longer transfering audio with this sequence:
- inform the HF that a call is terminated:
+CIEV:2,0
- inform the HF that we have lost the mobile signal:
+CIEV:5,0
- inform the HF that we have lost the connection our service provider:
+CIEV:1,0
We can use the blueasa-rfcomm
utility to perform this procedure from the command line:
transfer call:
user@host:~ bluealsa-rfcomm /org/bluealsa/hci0/dev_01_23_45_67_89_AB
01:23:45:67:89:AB> +CIEV:1,1
01:23:45:67:89:AB> +CIEV:5,5
01:23:45:67:89:AB> +CIEV:2,1
01:23:45:67:89:AB>
terminate call:
user@host:~ bluealsa-rfcomm /org/bluealsa/hci0/dev_01_23_45_67_89_AB
01:23:45:67:89:AB> +CIEV:2,0
01:23:45:67:89:AB> +CIEV:5,0
01:23:45:67:89:AB> +CIEV:1,0
01:23:45:67:89:AB>
Alternatively, we can store the sequences as shell scripts; the following example scripts can be used as:
user@host:~ hfp-ag-start 01:23:45:67:89:AB
user@host:~ aplay -D bluealsa:01:23:45:67:89:AB,sco audio.wav
user@host:~ hfp-ag-stop 01:23:45:67:89:AB
/usr/local/bin/hfp-ag-start
#!/bin/sh
if test -z "$1" ; then
exit 1
fi
device=`echo "$1" | tr '[a-z]' '[A-Z]'`
device=`echo "$device" | tr : _`
bluealsa-rfcomm "/org/bluealsa/hci0/dev_$device" <<EOF
+CIEV:1,1
+CIEV:5,5
+CIEV:2,1
EOF
/usr/local/bin/hfp-ag-stop
#!/bin/sh
if test -z "$1" ; then
exit 1
fi
device=`echo "$1" | tr '[a-z]' '[A-Z]'`
device=`echo "$device" | tr : _`
bluealsa-rfcomm "/org/bluealsa/hci0/dev_$device" <<EOF
+CIEV:2,0
+CIEV:5,0
+CIEV:1,0
EOF
The bluetooth module used by an adapter generally has a number of physical interfaces. The HCI (host-controller interface) is a virtual interface implemented on a physical serial interface (typically USB or UART, but other types can also be used). Other common hardware interfaces that are not used for HCI include i2s/PCM, i2c, etc. The Bluetooth Core Specification allows manufacturers to route SCO traffic through any interface, so they do not have to use the HCI for this.
Bluez (and the Linux kernel bluetooth subsystem) can only communicate with a bluetooth module through the HCI. So for BlueALSA (and other Bluez based services) to handle SCO profiles the SCO data must be transferred over the HCI. Therefore BlueALSA cannot support HFP or HSP with adapters that use some other interface for SCO.
Broadcom adapters allow the routing of SCO packets to be selected by a custom vendor HCI command. By default they route SCO to a PCM interface, but an application can use that custom command to change the routing to the HCI. BlueALSA sends this command when used with a Broadcom module, so HFP/HSP hopefully be available with adapters based on these modules.
Texas Instruments also have a custom vendor command for selecting HCI as the SCO interface that is known to work on some of their module families; however it is not clear from their documentation whether this command works on all their BT modules. The command can be invoked from the command line using the bluez utility hcitool
thus:
hcitool cmd 0x3f 0x0210 0x01 0x00 0x00 0x00 0xFF
This command should be run be before any remote devices are connected, and must be re-run whenever the TI adapter is powered on. If we can gather evidence that this command works on all TI module families then it may be possible to have it invoked by BlueALSA in the same way as is done for Broadcom modules. The list of Texas Instruments module families with which this is known to work is:
- CC256x
- WL183x
If you have a different TI module which appears to suffer this SCO routing issue then you can help by testing the above command then updating the above list.
Other brands may implement similar custom commands, but none at present has been made public so it is not possible for BlueALSA to support them unless they already default to using the HCI for SCO.
Most adapters do pass SCO data over the HCI, so you would need to test yours to know if is affected by this issue. One way to test is to use the hciconfig
utility included by many distributions. To do this, first run hciconfig
and make a note of the RX and TX packets for sco:
user@host:~ hciconfig
hci0: Type: Primary Bus: USB
BD Address: FE:DC:BA:09:87:65 ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING PSCAN ISCAN
RX bytes:2305 acl:0 sco:0 events:228 errors:0
TX bytes:37729 acl:0 sco:0 commands:227 errors:0
Now connect a bluetooth device that supports HFP or HSP and play a short audio:
user@host:~ aplay -D bluealsa:01:23:45:67:89:AB,sco /usr/share/sounds/alsa/Front_Centre.wav
If the device plays the audio, then you know that outgoing SCO is working. Because SCO is a duplex link, there will also be SCO data received over the HCI, even if no application is capturing it (in that case the packets are discarded by the kernel driver). So now we run hciconfig
again:
user@host:~ hciconfig
hci0: Type: Primary Bus: USB
BD Address: FE:DC:BA:09:87:65 ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING PSCAN ISCAN
RX bytes:38449 acl:158 sco:601 events:356 errors:0
TX bytes:67745 acl:139 sco:500 commands:259 errors:0
In the above example, the RX sco: packet count is sco:601, so we know that the adapter is suitable for use with SCO on Linux. If the RX bytes still reports sco:0
then your adapter does not route SCO over the HCI. If you did hear the sound from the device even though RX sco: is zero, that indicates that the adapter will process SCO packets sent from the host over the HCI but will not send the return packets by the same route. In that case it is possible to playback to a HFP/HSP speaker but not to capture from a HFP/HSP microphone. If the TX sco: packet count is zero, that indicates audio is not passing from your application to BlueALSA; there must be some issue other than SCO routing.
-
Avoid resampling if possible. If resampling is necessary, try to do it only on the playback device (card for incoming audio, BlueALSA for outgoing audio) and use natively supported formats and rates for the capture device.
-
If the hardware device supports the Bluetooth audio parameters natively, then use those parameters for capture and playback. If it does not support the Bluetooth rate (8000Hz for CVSD and 16000Hz for mSBC), then try to choose a supported rate that is a multiple of the Bluetooth rate (e.g. most hardware devices will support a rate of 48000Hz)
To find the native parameters of your hardware devices you can use
aplay
andarecord
. Select the correct card and device number on that card by using the-D
option.For playback devices (speakers):
aplay -D hw:0,0 --dump-hw-params -t raw /dev/null
For capture devices (microphones):
arecord -D hw:0,0 --dump-hw-params | :
Ignore any warning or error messages at the start and end of the output, we are only interested in HW Params of the device. The output will be something like:
HW Params of device "hw:0,0": -------------------- ACCESS: MMAP_INTERLEAVED RW_INTERLEAVED FORMAT: S16_LE S32_LE SUBFORMAT: STD SAMPLE_BITS: [16 32] FRAME_BITS: [32 64] CHANNELS: 2 RATE: [44100 192000] PERIOD_TIME: (83 11888617) PERIOD_SIZE: [16 524288] PERIOD_BYTES: [128 2097152] PERIODS: [2 32] BUFFER_TIME: (166 23777234) BUFFER_SIZE: [32 1048576] BUFFER_BYTES: [128 4194304] TICK_TIME: ALL --------------------
So we see that this device has rates from 44100 to 192000. We want a multiple of 8000 or 16000, so the nearest available is 48000 and we choose that.
-
Avoid use of ALSA
dmix
anddsnoop
plugins if possible. See the bluealsa-aplay manual page and the wiki page ALSA devices with bluealsa-aplay for more information and work-arounds if these plugins cannot be avoided. For best results, use the ALSAhw
PCM device, or if resampling is necessary use the ALSAplughw
device. Note that for many cards, thedefault
ALSA PCM will includedmix
ordsnoop
. -
Do not start microphone capture until the BlueALSA playback device is ready. On AG nodes this means waiting for the codec to be set, and on HF/HS nodes this means waiting until the audio connection is open. BlueALSA creates the PCM when the service connection is established. The audio connection is opened by the AG device some time after the service connection is established, so there is a period after the PCM is created during which it is not capable of transfering audio. Depending on the configuration of your application this can lead to very high latency if captured samples are stored in a buffer waiting for the BlueALSA PCM to begin transferring (e.g. a Linux pipe can store over 4 seconds of 16-bit audio sampled at 8000Hz). At the present time BlueALSA does not emit any signal when the audio connection is ready.
In order to capture audio from a local microphone and transfer it to a bluetooth device, it is possible to pipe from arecord
into aplay
. In their default configurations this will lead to very high latency because of the way they configure their buffers and because of the time taken to establish a bluetooth SCO connection.
To avoid this latency you can set more appropriate period and buffer sizes from the command line; and you should delay the start of arecord
slightly to permit aplay
to complete the SCO setup before capturing begins. You should always explictly specify the audio format too. For example the following command can achieve latency of less than 100ms:
{ sleep 1; arecord -D plughw:0,0 -t raw -r 8000 -f s16_le -c 1 --period-time=10000 --buffer-time=30000; } | aplay -D bluealsa:DEV=01:23:45:67:89:AB,PROFILE=sco -t raw -f s16_le -c 1 -r 8000 --period-time=20000 --buffer-time=60000
To avoid potential issues (XRUNs, excessively large delays) when used with HFP-HF or HSP-HS profile it is advisable to avoid starting outgoing audio applications until after it is known that the HFP/HSP audio connection is available. When using HFP-HF with oFono, one way to detect audio availablility is to monitor the property State
of the VoiceCall interface: when the state is "active" then audio is guaranteed to be available.
For incoming audio streams, arecord
will simply block until the stream starts, without any issues.
arecord -D bluealsa:DEV=01:23:45:67:89:AB,PROFILE=sco -t raw -f s16_le -c 1 -r 8000 --period-time=20000 --buffer-time=60000 | aplay -D plughw:0,0 -t raw -f s16_le -c 1 -r 8000 --period-time=10000 --buffer-time=30000
SCO audio streams are always duplex, so when the AG opens the audio connection both incoming and outgoing audio is unblocked. So another strategy for avoiding large delays in outgoing audio on HS/HF nodes is to listen for incoming audio to begin, and when that happens then start the outgoing audio application. Unfortunately none of the ALSA utilities can achieve this alone, but the bluealsa-cli
utility can help here. If starting the ALSA utilities from a script, we can wait for the audio stream to open with:
bluealsa-cli open SOURCE_PCM_PATH | dd bs=2 count=1 of=/dev/null
SOURCE_PCM_PATH
must be the D-Bus object path of the BlueALSA source PCM, as printed by bluealsa-cli list-pcms
. For example /org/bluealsa/hci0/dev_11_22_33_44_55_66/hsphs/source
The above command pipeline blocks until dd
has read one audio sample (or until the AG device disconnects). When it returns then start the incoming and outgoing audio applications. This method results in a loss of a tiny amount of incoming audio (typically less than 10 milliseconds). In most use cases this is not noticeable.
alsaloop
can transfer audio in either direction, or in both directions at the same time. It permits more direct control over the latency than using arecord and aplay, and also compensates for clock drift between the devices. However it can become unstable if the target latency cannot be achieved. It uses the delay value reported by the ALSA devices to calculate the latency, so it is important that the BlueALSA PCM DELAY
parameter is set to 0 so as not to exaggerate the Bluetooth delay.
It is best to avoid resampling if possible, however if the local device does not support the SCO audio format (for example many budget cards do not support mono streams or 8000Hz sample rate) then avoid resampling the rate by the capture device as the ALSA rate
plugin can cause problems here. So it is recommended to use the SCO format (s16_le) and channels (1) for both directions, and the SCO rate (8000) when capturing from BlueALSA but a rate directly supported by the local microphone (eg 48000) when capturing from local device.
For example, to capture from the local microphone with a target latency of 100 milliseconds:
alsaloop -C plughw:0,0 -P bluealsa:DEV=01:23:45:67:89:AB,PROFILE=sco -r 48000 -c 1 -f s16_le -t 100000 --sync=samplerate
To perform full duplex audio between local device and BlueALSA:
alsaloop -g /dev/stdin <<EOF
-C bluealsa:DEV=01:23:45:67:89:AB,PROFILE=sco -P plughw:0,0 -r 8000 -c 1 -f s16_le -t 100000 --sync=samplerate -T 1
-C plughw:0,0 -P bluealsa:DEV=01:23:45:67:89:AB,PROFILE=sco -r 48000 -c 1 -f s16_le -t 100000 --sync=samplerate -T 2
EOF
alsaloop
is not suitable for use on HFP-HF or HSP-HS devices unless it can be guaranteed that it will not be started before the audio connection is available. See the notes on this for aplay/arecord above.
Setting local microphone control levels with alsamixer or amixer is critical to avoid high noise levels and clipping. Unfortunately there does not appear to be any general, reliable, guidance on this. Here are some simple tips to get you started - they assume you are using the internal microphone on card 0 ("Internal Mic" on "hw:0"); you will need to adjust those names for other devices.
- make sure "Capture" is enabled on your card:
amixer -D hw:0 sset Capture cap
- set the "Capture" control to 0 dB
amixer -D hw:0 sset Capture 0dB
- set the relevant "Mic Boost" to zero:
amixer -D hw:0 sset "Internal Mic Boost" 0
If the sound level from the microphone is too low, steadily increase the "Capture" control level until the "hiss" becomes noticable. If the sound level is still too low, increase the microphone boost level, one step at a time, again stopping before the sound becomes distorted or the "hiss" becomes too loud.
The TI WL1831MOD is a WiFi/Bluetooth combo chip that is supported by stock BT drivers in the Linux kernel. The chip provides both HCI and I2S pins. The chip is connected to an Intel SoM and would require custom software support to use ALSA over the I2S pins. Use of the HCI pins can be handled via BlueAlsa. The HCI pins are connected to a UART, which means they show up in Linux as /dev/ttyS[num]. HFP with BlueAlsa will be supported with ofonod.
The TI WL1831MOD defaults to HFP over I2S. However, the audio can be routed to the HCI pins using a Vendor Specific command issued with the hcitool utility (see the section on The SCO Routing Issue). Sadly this command is not included in the TI WL1832MOD documentation, but can be found in a similar chip's documentation.
The following steps are taken to test HFP on this chip.
- Start pairing on remote device.
- Bring up the hci interface on the local device.
- (enable GPIO as needed)
- hciattach "/dev/ttyS[num]" texas 115200
- hciconfig hci0 up
- hciconfig hci0 name "BlueAlsaTest"
- Route audio from I2S pins to the HCI pins.
- hcitool cmd 0x3f 0x0210 0x01 0x78 0xff 0x01 0xff
- Enable ofonod (giving it time to come up gracefully) and BlueAlsa.
- ofonod && sleep 2
- bluealsa -S -i hci0 -p hfp-ofono
- Enable the bluetooth device.
- bluetoothctl power on
- bluetoothctl discoverable on
- bluetoothctl pairable on
- Use bluetoothctl to scan for a specified client
- bluetoothctl --timeout 30 scan on
- Configure BT agents for the chip
bluetoothctl << EOF <br>
trust [remote BT mac] <br>
agent KeyboardDisplay <br>
default-agent <br>
EOF
Launch bluetoothctl cli to pair and connect the local device to the remote device.
- bluetoothctl
- bluetooth> pair [remote BT mac]
- (Enter PIN when requested, on both devices)
- bluetooth> connect [remote BT mac]
Note: Routing audio to the HCI pins may be integrated into bluealsa at a later time which means that manual step will no longer be required.
At this point the TI WL1831MOD is connected to the remote device, such as a mobile phone. If you use the phone to dial into, for example, a Jitsi session you can then test the connection in both directions without requiring the local device to have speakers configured.
- Play audio from TI device to Jitsi
- aplay -D bluealsa:[local BT mac],sco [wav file]
- (listen on speakers configured to Jitsi session)
- Record audio from Jitsi to TI device
- arecord -D bluealsa:[local BT mac],sco [wav file]
- (talk on mic configured to Jitsi session)
- Ctrl-C to stop arecord
- Copy recorded wav to desktop and playback to verify audio.