-
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). It covers only BlueALSA's built-in support, and does not discuss usage with oFono.
There is a distinct lack of documentation for the use of oFono with BlueALSA. It would be very helpful if any user of that set-up could contribute a wiki article here - even if its only a skeleton of notes that someone else could build on later.
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.
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, BlueALSA takes care of the audio connection setup when a client opens a PCM, so no other action is required by the client. Capture and Playback PCMs can be opened at the same time, so full duplex operation is permitted.
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.
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.
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 API to clients so that they can implement their own support for devices in groups 2 and 3. See the section Use of RFCOMM below.
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. The HFP specification mandates the audio connection of an on-going call can be transferred from the AG to the HF, by the AG (i.e the 'phone must be able to initiate such a transfer). That transfer requires no responses from the HF, other than to accept the SCO link connection, which is implemented by BlueALSA. So in this case there is no need for any client RFCOMM interaction.
In the HFP-AG role, BlueALSA clients must perform the call session management exchange with the HF device for those HF devices that require it. BlueALSA provides an RFCOMM API to support this, and a simple utility 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
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.
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
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 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