-
Notifications
You must be signed in to change notification settings - Fork 189
PulseAudio integration
Important
This document refers to BlueALSA components by the names used in the latest sources. For release v4.3.1 or earlier please note that:
- The
bluealsad
daemon was calledbluealsa
- The
bluealsactl
utility was calledbluealsa-cli
Pipewire (https://pipewire.org) is now the most commonly used audio server on modern Linux desktops, and is also very widely used in server installations too. PulseAudio (https://www.freedesktop.org/wiki/Software/PulseAudio/) was the de-facto Linux standard audio server, particularly for desktop installations, before Pipewire was adopted, and is still used by many distributions. Many linux distributions' desktops require either Pipewire or PulseAudio in order to provide full functionality.
Both Pipewire and PulseAudio have their own Bluetooth audio implementation which is sufficient in many common usage scenarios. If you need to use Pipewire or PulseAudio for any reason then it is recommended to at least try its internal Bluetooth audio implementation before attempting this integration. Conversely, if you do not need a general purpose audio server, then it is recommended to use BlueALSA for Bluetooth audio and not to run Pipewire or PulseAudio at all.
Of course, there may be rare occasions when you find it necessary to run BlueALSA in combination with Pipewire or PulseAudio. This wiki article describes one method of achieving this in an integrated way.
Bluez allows only one service to register as provider of a Bluetooth profile, so it is necessary to disable the Pipewire or PulseAudio Bluetooth modules in order to use BlueALSA.
If using Pipewire, the Pipewire Bluetooth SPA module can be disabled using Wireplumber. Wireplumber changed its configuration file format and syntax in release 0.5.0; so this method depends on the Wireplumber version in use.
-
Wireplumber release 0.5.0 and later:
Override the default configuration by creating the file
/etc/wireplumber/wireplumber.conf.d/disable-bluetooth.conf
with the following contents:
wireplumber.profiles = { main = { hardware.bluetooth = disabled monitor.bluez = disabled monitor.bluez.seat-monitoring = disabled } }
-
Wireplumber release 0.4.17 and earlier:
For these releases it is sufficient to override the single lua file which by default enables Bluetooth support. So we only need to create an empty file:
/etc/wireplumber/bluetooth.lua.d/90-enable-all.lua
With both methods it is necessary to re-start the wireplumber
service to
apply the configuration change.
systemctl --user restart wireplumber
To restore Bluetooth support in Pipewire, simply delete the file created above, then restart Wireplumber.
If using PulseAudio, there are three ways to disable the PulseAudio Bluetooth modules.
-
Uninstall the module packages
Many distributions deliver the PulseAudio Bluetooth modules as separate packages, and so for these the simplest solution is to uninstall those packages. For example, on Ubuntu
sudo apt remove pulseaudio-module-bluetooth
-
Remove the modules from the PulseAudio configuration
If the modules cannot be uninstalled, then they can still be disabled in the PulseAudio configuration. This is achieved by editing the file
/etc/pulse/default.pa
to comment out the Bluetooth module entries as shown here:
### Automatically load driver modules for Bluetooth hardware #.ifexists module-bluetooth-policy.so #load-module module-bluetooth-policy #.endif #.ifexists module-bluetooth-discover.so #load-module module-bluetooth-discover #.endif
Restart the PulseAudio service to read the new configuration. On most desktop systems this service is configured to automatically restart, so it is sufficient to type:
pulseaudio --kill
-
Unload the PulseAudio Bluetooth modules at runtime
It is possible to temporarily remove the modules at runtime with the commands:
pactl unload-module module-bluetooth-policy pactl unload-module module-bluetooth-discover
Note that these commands will have to be re-run each time PulseAudio is restarted.
With the above changes, it is possible to run the BlueALSA service at the same time as Pipewire or PulseAudio. Applications will have to connect to ALSA directly using the ALSA API to use Bluetooth devices; neither Pipewire nor PulseAudio will see them. So applications that are configured to use the Pipewire or PulseAudio APIs (including most desktop volume control panels) will be unable to use Bluetooth audio. That may be good enough for many systems, so this section is optional and only relevant if you need to use BlueALSA devices via the Pipewire or PulseAudio API.
Important
This approach should be considered as "experimental" and as such suffers many compromises and limitations. It is not suitable for production systems.
In particular, both Pipewire and PulseAudio rely on an accurate timer on the associated sound card to achieve stream synchronization, for example with a video stream or for loopback to another audio device. A BlueALSA PCM has no associated sound card, and the ALSA external IO plugin API does not support period time-stamping, so that stream synchronization is not possible when using BlueALSA with PulseAudio and difficult with Pipewire. This issue can also cause regular "glitches" in the audio stream if Pipewire or PulseAudio is trying to synchronise the stream.
The Pipewire native API permits the creation of ALSA PCM nodes only from ALSA
sound cards; it cannot be used to create PCM nodes that are based on the ALSA
ioplug
API (such as BlueALSA). However, from release 0.3.79, Pipewire's
pulseaudio emulation API does support loading of such ALSA devices, so we can
use PulseAudio tools with Pipewire to achieve our desired result.
Pipewire and PulseAudio immediately open each BlueALSA PCM device that is loaded. To prevent wastage of Bluetooth bandwidth and remote device battery charge, and to avoid problems with some devices that support both A2DP and SCO PCMs, the PCM must be "suspended" when not actively transferring audio.
Pipewire suspends a BlueALSA device immediately that its stream stops, so there
is no extra configuration required; but for PulseAudio it is necessary to
explicitly suspend the device. Most PulseAudio GUI tools (including
pavucontrol
) do not allow the user to manually suspend and unsuspend sinks and
sources. The workaround for this is to enable the "suspend-on-idle" feature,
with a sufficiently low timeout. Some linux distributions enable this feature,
others do not. To check if your PulseAudio instance has this module loaded,
type:
pactl list modules
and look for the module-suspend-on-idle entry:
Module #20
Name: module-suspend-on-idle
Argument: timeout=5
Usage counter: n/a
Properties:
module.author = "Lennart Poettering"
module.description = "When a sink/source is idle for too long, suspend it"
module.version = "11.1"
The Module number, and other parameter values, may differ.
If the module is loaded, check the "Argument:" parameter. We need a low timeout value because there will be silence of that amount of time when switching from A2DP to SCO. If the Argument is blank, the default timeout is 5 seconds.
If the module is not loaded or the timeout needs to be changed, we must edit the file /etc/pulse/default.pa. Add an entry (or edit an existing entry) as:
### Automatically suspend sinks/sources that become idle for too long
load-module module-suspend-on-idle timeout=2
Important
pavucontrol
by default shows volume meters for each device, and these
prevent the devices from being suspended while pavucontrol
is running.
To disable the volume meters, and thus enable the auto suspend feature when
pavucontrol
is running, go to the pavucontrol Configuration tab and
uncheck the "Show volume meters" checkbox in the bottom left-hand corner.
Each Bluetooth device that you wish to use with Pipewire or PulseAudio needs to be added individually as a sink, source, or both as appropriate. If you wish to use both A2DP and SCO profiles for a single device, then they too will need to be added individually.
The way to add a device is to load the module-alsa-sink
or
module-alsa-source
modules with appropriate parameters. In order to later
remove a device, it is helpful to make a note of the module index allocated to
it. The utility pactl
returns that index on successful completion. The
complete list of parameters for these modules can be seen here:
https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modules/
Always give the device a unique internal name, so that we can use it to identify
the correct node whwn using PulseAudio or Pipewire command line tools such as
pactl
, pacmd
or pw-cli
. We recommend a name starting with "bluealsa" and
including the device address and profile to make it easily identifiable. For
example bluealsa.sink.XX_XX_XX_XX_XX_XX.a2dp
.
It is helpful to also give the device a user-friendly description for use in the
description displayed in graphical tools such as pavucontrol
or other desktop
sound control applications/widgets, by using the sink_properties
or
source_properties
parameter as appropriate.
It is also possible to set the display icon in the source_properties parameter - it will default to "audio-card" but you may prefer to use "audio-speakers-bluetooth", "audio-headphones-bluetooth", "audio-headset-bluetooth", "phone-bluetooth", or maybe just "bluetooth". The icons actually available will depend on your desktop distribution.
The device must first be connected before adding to PulseAudio.
For example, to load a playback (sink) device, set its internal name (sink_name for sinks, source_name for sources), set its user-friendly description and icon, and record its allocated module index, type:
MODULE=$(pactl load-module module-alsa-sink device='bluealsa:DEV=XX:XX:XX:XX:XX:XX,PROFILE=a2dp' sink_name=UniqueInternalName sink_properties="device.description='My\ Friendly\ Description'device.icon_name=bluetooth")
(Note the need to escape space characters with a backslash in the description field. Do not leave any space between property settings.)
To remove the device from PulseAudio, type
pactl unload-module $MODULE
The procedure for a capture (source) device is the same, except to use 'source' in place of 'sink' in the commands:
MODULE=$(pactl load-module module-alsa-source device='bluealsa:DEV=XX:XX:XX:XX:XX:XX,PROFILE=a2dp' source_name=MyFriendlyName source_properties="device.description='My\ Friendly\ Description'device.icon_name=bluetooth")
A common use-case with Pipewire or PulseAudio is to use sinks to play audio from
applications to speakers, but to use sources only to route incoming audio from a
device (e.g. a mobile phone) directly to the speakers using the Pipewire or
PulseAudio internal loopback module. However, this module needs to be able to
synchronise the Bluetooth stream with the output device, and as noted above this
does not work with BlueALSA PCMs. So for this specific case with BlueALSA, the
best results are usually obtained by using bluealsa-aplay
and not to load the
BlueALSA PCM using module-alsa-source. Note that both Pipewire and PulseAudio
are normally run in the user's session, so bluealsa-aplay must also be run under
the user's account to have permission to connect to the audio service. See the
section bluealsa-aplay below.
If it is required to direct the audio to recording applications, then it is
necessary to use module-alsa-source
. As there is no need to control the rate
at which samples are read from the device when recording to a file, this
normally works well.
For example, if you use bluealsa.source.XX_XX_XX_XX_XX_XX.A2DP
as the
source_name when loading the BlueALSA PCM, then to record a stream from it use:
pw-record --target bluealsa.source.XX_XX_XX_XX_XX_XX.A2DP filename.wav
or
parecord --device bluealsa.source.XX_XX_XX_XX_XX_XX.A2DP filename.wav
Neither Pipewire nor PulseAudio use the PCM delay property of an ALSA PCM.
For PulseAudio there is no alternative method informing an application of the delay, so video synchronization is impossible.
When using Pipewire, there is a node parameter called "ProcessLatency", which
works much the same way as the BlueALSA "DelayAdjustment" property, and we can
use "ProcessLatency" to "fix" the delay reported to the application. For
example, if bluealsactl info
reports Delay: 286.0 ms
, then (assuming we
set the sink_name to bluealsa.sink.XX_XX_XX_XX_XX_XX.a2dp
) we can do:
pw-cli set-param 'bluealsa.sink.XX_XX_XX_XX_XX_XX.a2dp' ProcessLatency '{ ns = 286000000 }'
It is possible to automate the above procedure by running a service that listens
for D-Bus ObjectManager events from org.bluealsa and invokes the above pactl
commands in response to the addition and removal of BlueALSA PCMs.
This example uses the program bluealsa-agent
from the
bluealsa-autoconfig project
to deal with the BlueALSA D-Bus interface. Install the following script as a
bluealsa-agent
command (for example called "pipewire"), and run the
bluealsa-agent
service with the command line option --status=Running,Delay
.
Note
The current "main" branch of the bluealsa-autoconfig project is compatible only with BlueALSA release 4.3.1 or earlier. If you are using the latest BlueALSA sources, then use the bluealsa-autoconfig dev branch
On Bluetooth source nodes (a2dp-source, hfp-ag, hsp-ag) the PulseAudio sinks and sources are created as soon as the device connects; on target nodes (a2dp-sink, hfp-hf, hsp-hs) the sinks and sources are created only when the audio stream is started by the remote device.
#!/bin/bash
# Some variables which can be used to customise this script.
# Select which device types to exclude from loading into pipewire/pulseaudio.
# A space separated list of types, where valid types are:
# a2dp_source a2dp_sink sco_source sco_sink
# outputs (speakers, headphones) are "sinks",
# inputs (microphones) are "sources".
# By default no devices are excluded
# For example, to exclude all sources (recommended when using with
# bluealsa-aplay):
# EXCLUDE="a2dp_source sco_source"
EXCLUDE=
# Change this if you wish to use a different icon for clients such as
# pavucontrol.
ICON_NAME="bluetooth"
# For BlueALSA playback devices only, set this to "yes" to use the most recently
# connected BlueALSA PCM as the Pipewire or Pulseaudio default device.
SET_DEFAULT_SINK=
# For BlueALSA capture devices only, setting this to "yes" will enable the
# Pipewire/PulseAudio loopback module to feed the Bluetooth stream to a local
# speaker.
# Note that this rarely works well and is not recommended.
ENABLE_LOOPBACK=
# Pipewire node name or PulseAudio sink name to use for loopback output.
# Leave blank to select the default sink. Must be a valid pipewire node name or
# pulseaudio sink name, for example "alsa_output.pci-0000_00_1b.0.analog-stereo"
LOOPBACK_DEVICE_NAME=
# Directory in which to store state information for each connected device
DATADIR="/tmp/bluealsa-agent/$(basename $0)"
# Do not change anything below this line.
pwcli=$(type -p pw-cli) && "$pwcli" ls Core &>/dev/null || pwcli=
state_file="${2:1}"
state_file="${DATADIR}/${state_file//\//_}"
declare -A pa_format=(
[U8]=u8
[S16_LE]=s16le
[S24_LE]=s24le
[S32_LE]=s32le
)
set_latency() {
[[ "$pwcli" ]] || return
local name="$1"
if [[ -z "$name" ]] ; then
local addr="$BLUEALSA_PCM_PROPERTY_ADDRESS"
local type="$BLUEALSA_PCM_PROPERTY_TRANSPORT_TYPE"
local mode="$BLUEALSA_PCM_PROPERTY_MODE"
name="bluealsa.${mode}.${addr//:/_}.$type"
fi
local delay=$(( BLUEALSA_PCM_PROPERTY_DELAY * 100000))
local client_delay=$(( BLUEALSA_PCM_PROPERTY_CLIENT_DELAY * 100000))
local latency=$(( delay + client_delay ))
(( latency > 0 )) || return
"$pwcli" set-param "$name" ProcessLatency "{ ns = $latency }" &>/dev/null
}
unload_module() {
read module_id default_id <"$state_file"
if [[ "$module_id" ]] ; then
pactl unload-module "$module_id" 2>/dev/null
rm -f "$state_file"
if [[ "$default_id" ]] ; then
pactl set-default-sink "$default_id"
fi
fi
}
load_module() {
[[ "${EXCLUDE,,}" =~ "${BLUEALSA_PCM_PROPERTY_TRANSPORT_TYPE,,}_${BLUEALSA_PCM_PROPERTY_MODE,,}" ]] && return
[[ -f "$state_file" ]] && unload_module
[[ -d "$DATADIR" ]] || mkdir -p "$DATADIR" || {
echo "Cannot create state directory [$DATADIR]" >&2
return 1
}
local addr="$BLUEALSA_PCM_PROPERTY_ADDRESS"
local type="$BLUEALSA_PCM_PROPERTY_TRANSPORT_TYPE"
local profile="$BLUEALSA_PCM_PROPERTY_PROFILE"
local device="bluealsa:DEV=$addr,PROFILE=$type"
local mode="$BLUEALSA_PCM_PROPERTY_MODE"
local alias="$BLUEALSA_PCM_PROPERTY_NAME"
local name="bluealsa.${mode}.${addr//:/_}.$type"
local description="Bluetooth:\ ${alias// /\\ }\ ($profile)"
local format="${pa_format[$BLUEALSA_PCM_PROPERTY_FORMAT]}"
[[ -n "$format" ]] || return
local channels="$BLUEALSA_PCM_PROPERTY_CHANNELS"
local rate="$BLUEALSA_PCM_PROPERTY_SAMPLING"
local module_id=$(pactl load-module "module-alsa-$mode" "format='$format'" "rate='$rate'" "channels='$channels'" "device='$device'" "${mode}_name='$name'" "${mode}_properties=device.description='$description'device.icon_name='$ICON_NAME'")
[[ "$module_id" ]] || return
set_latency "$name"
default_id=
if [[ "$SET_DEFAULT_SINK" == yes && "$mode" == "sink" ]] ; then
default_id="$(pactl get-default-sink)"
pactl set-default-sink "$name"
fi
echo "$module_id $default_id" >"$state_file"
if [[ "$ENABLE_LOOPBACK" == yes && "$mode" == "source" ]] ; then
local sink=
[[ "$LOOPBACK_DEVICE_NAME" ]] && sink="sink='$LOOPBACK_DEVICE_NAME'"
pactl load-module module-loopback "source_dont_move='true'" "source='$name'" "$sink" "format='$format'" "rate='$rate'" "channels='$channels'" >/dev/null
fi
}
case "$BLUEALSA_PCM_PROPERTY_TRANSPORT" in
*-source|*-AG)
case "$1" in
"add") load_module ;;
"remove") unload_module ;;
"update") set_latency ;;
esac
;;
*)
case "$1" in
"update")
[[ "$BLUEALSA_PCM_PROPERTY_CHANGES" == *RUNNING* ]] || exit
if [[ "$BLUEALSA_PCM_PROPERTY_RUNNING" == "true" ]] ; then
load_module
else
unload_module
fi
;;
"remove")
unload_module
;;
esac
;;
esac
See the comments near the top of the script for more information on customising various aspects of the script.
Note
When switching a stream from an A2DP sink to a SCO sink on the same device, there may be several seconds of silence before the stream resumes, because with some Bluetooth devices SCO playback will only commence when the A2DP device is suspended by PulseAudio or vice-versa.
It is possible to have the sound from bluealsa-aplay
played through Pipewire
(or PulseAudio), and controlled using Pipewire or PulseAudio clients.
Note that both Pipewire and PulseAudio are normally run in the user's session,
so bluealsa-aplay
must also be run under the user's account to have
permission to connect to the audio service.
To run bluealsa-aplay
as a user service, create a service unit file called
/etc/systemd/user/bluealsa-aplay.service
containing:
[Unit]
Description=BlueALSA player service
Documentation=man:bluealsa-aplay(1)
Requisite=dbus.service
[Service]
Type=simple
ExecStart=/usr/bin/bluealsa-aplay --pcm=pipewire --volume=software
Restart=on-failure
[Install]
WantedBy=default.target
To use the PulseAudio ALSA plugin, or to select a specific output device rather
than the default, modify the bluealsa-aplay
command line. For example:
--pcm=pipewire:NODE=alsa_output.pci-0000_00_1b.0.analog-stereo
--pcm=pulse:DEVICE=alsa_output.pci-0000_00_1b.0.analog-stereo
Both the pipewire
and pulse
ALSA ctl plugins have one control called
Master
, but this control does not provide any dB scale information. So
bluealsa-aplay
is unable to use this control to implement remote volume
control.
If you wish to allow the remote device to control the volume in hardware then it
is necessary to by-pass the pipewire/pulse Master
control and operate the
underlying ALSA device control directly. For this to work reliably it is
necessary to choose a specific output PCM using the --pcm=
option above and
then set the --mixer-device=
and --mixer-name
options appropriately.
For example:
bluealsa-aplay --pcm=pipewire:NODE=alsa_output.pci-0000_00_1b.0.analog-stereo --volume=mixer --mixer-device=hw:PCH --mixer-name=Master
or when using PulseAudio:
bluealsa-aplay --pcm=pulse:DEVICE=alsa_output.pci_0000_00_1b.0.analog-stereo --volume=mixer --mixer-device=hw:PCH --mixer-name=Master
To record a Bluetooth stream at the same time as playing it through speakers,
use the PulseAudio monitor
of the output device. This also works with
Pipewire using its PulseAudio compatibility. So if bluealsa-aplay
is playing a
stream to alsa_output.pci-0000_00_1b.0.analog-stereo
then we can record it as
parecord --device alsa_output.pci-0000_00_1b.0.analog-stereo.monitor filename.wav