-
Notifications
You must be signed in to change notification settings - Fork 189
Using BlueALSA as default ALSA PCM
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
, and it was necessary to build bluez-alsa with the configuration option--enable-cli
.
It is possible to use a BlueALSA device as the default for ALSA, but with certain limitations:
-
Bluetooth devices are not always connected, so it may be necessary to define a "fallback" to the local soundcard to ensure there is always a default.
-
A BlueALSA PCM does not support multiple client connections, and there is no equivalent to the "dmix" plugin for use with BlueALSA. So you need to be sure that only one process at a time has the default audio device open.
-
If you require that an active stream can continue to run uninterrupted by switching to the soundcard when a bluetooth device disconnects, then you will be disappointed. That is beyond the capabilities of ALSA alone, and you will have to resort to using an audio server such as PulseAudio or PipeWire.
-
Some applications just do not properly use the libasound API where a PCM device is unplugged. When an API call returns the error code ENODEV ("No such device"), or the PCM state is SND_PCM_STATE_DISCONNECTED, the only correct action for the application is to close its handle on the PCM. Some do not this, so cannot recover from disconnection of a bluetooth device. Even if the device subsequently re-connects, the old pcm handle is still invalid and cannot be used. There is nothing that BlueALSA or ALSA can do about this, it is a bug in the application; the only work-around is to quit then re-start the application.
-
Many applications assume that the volume control for the default PCM is called "Master". Therefore their internal volume controls will be ineffective with a bluetooth default device. The ALSA "remap" ctl plugin, introduced in alsa-lib version 1.2.5, which defines aliases for control elements can sometimes be used to overcome this limitation, see Using BlueALSA with other ALSA plugins for more information.
-
If the BlueALSA mixer is used as default (ctl.default) then it will be used as default for both Playback and Capture by all applications.
If your system can work within these limitations, and you can tolerate some manual intervention, then the ideas presented here may be helpful. We consider only A2DP playback devices here in order to keep the presentation simple; but the same principles can also be applied to capture devices and possibly SCO profile too.
This article relies on features that were introduced in bluez-alsa 3.1.0. The examples will not work with older releases.
The simplest possible case is just to override the definition of the default device. To have bluealsa as default for playback, but use the soundcard for capture, put the following into your ~/.asoundrc file:
pcm.!default {
type asym
capture.pcm cards.pcm.default
playback.pcm bluealsa
hint.description "Capture: sysdefault, Playback: BlueALSA Bluetooth
Default Audio Device"
}
ctl.!default {
type bluealsa
hint.description "Default control device (BlueALSA Bluetooth)"
}
The above will cause the BlueALSA mixer to be default for both playback and
capture. It is not possible to define separate defaults for ctl
devices. Omit
the ctl.!default
definition if you prefer.
Now this is fine until the bluetooth device disconnects. Then there will be no default playback device. So next we consider how to introduce a "fallback".
The ALSA configuration has no concept of "fallback" defaults, it is left to each application to implement its own approach. The best we can do within libasound is to modify the configuration when a device becomes unavailable.
We can edit our ~/.asoundrc file every time the bluetooth device connects or disconnects, but even with some automation this can be unreliable - and if an ALSA configuration file is corrupted this can make all devices unusable.
So we try to avoid edits and instead use symbolic links to point to different files as needed. Rather than maintaining two complete versions of ~/.asoundrc, we separate out the bluealsa defaults and have a single ~/.asoundrc file that can be used in all cases.
The BlueALSA specific configuration can be stored in a file called
~/.asoundrc-bluealsa
bluealsa.default {
pcm {
name pcm.bluealsa
description "Bluealsa Bluetooth"
}
ctl.type bluealsa
}
We put the following into our ~/.asoundrc
bluealsa.default.link {
@func concat
strings [
{
@func getenv
vars [ HOME ]
default "/tmp"
}
"/.asoundrc-default"
]
}
pcm.!default {
type asym
capture.pcm cards.pcm.default
playback.pcm {
@func refer
file {
@func refer
name bluealsa.default.link
}
name "bluealsa.default.pcm.name"
default "cards.pcm.default"
}
hint.description {
@func concat
strings [
"Capture: sysdefault, Playback: "
{
@func refer
name "bluealsa.default.pcm.description"
default "sysdefault"
}
"
Default Audio Device"
]
}
}
ctl.!default {
@func refer
file {
@func refer
name bluealsa.default.link
}
name "bluealsa.default.ctl"
default {
@func refer
name ctl.sysdefault
}
hint.description "Default control device"
}
Again, omit the ctl!.default
definiton if you do not want the BlueALSA mixer to
become default for both playback and capture.
Now the definition of pcm.default
depends on the contents of a file called
~/.asoundrc-default
.
If we create a symbolic link of this name pointing to
~/.asoundrc-bluealsa
then the default device will be bluealsa
. If we point
the symbolic link at /dev/null
then the default device reverts to the system
default.
If the symbolic link does not exist, libasound will emit a warning when it reads its configuration, but will still correctly load the configuration and use the system default.
Applications using libasound normally read in the ALSA configuration when
the first device is opened, and then refer to that cached configuration if they
later open or re-open devices. So once an application has started, it will not
see later changes to the definition of pcm.default
. Using the above
configuration alone it will be necessary to stop and restart every audio
application when a bluetooth device connects or disconnects. We can improve on
this for some applications by using an environment variable that changes
libasound's default behaviour.
Unfortunately, many applications do not handle PCM device disconnection properly at all, and so for these their audio output is broken if playing to a BlueALSA device which disconnects. For such applications there is no workaround. The default fallback mechanism described here will not work because it requires the application to close its handle on the disconnected device and then open a new handle on
default
to get the fallback device instead.
When an application opens a device, libasound will update its configuration by
checking the timestamp on its core configuration file(s). These files are set
in an environment variable ALSA_CONFIG_PATH
. If that variable is not set,
libasound uses a built-in default, usually /usr/share/alsa/alsa.conf
. We can
include our bluetooth defaults in the core configuration by including the
symbolic link file above in the ALSA_CONFIG_PATH
. We need to make sure that
the ALSA core configuration is also included. So we export in our environment:
ALSA_CONFIG_PATH=/usr/share/alsa/alsa.conf:$HOME/.asoundrc-default
This variable must be defined in the environment of every application that uses
libasound. For command-line applications it can be set in the users ~/.bashrc
(or equivalent for other shells). For GUI desktop launchers, consult the
documentation for your system.
Now every time an application opens the default
device, libasound will update
its definition of pcm.default
and the application will hopefully be given the
correct device. It is important that the application closes then re-opens the
default
device for this to work - as noted above not all applications do this.
The above is as far as we can go using ALSA components alone. To have the default device updated automatically requires some external program to modify our symbolic link.
The BlueALSA service emits D-Bus PCMAdded
and PCMRemoved
signals which can
be used to provide some automation here.
As a very simple example we can use a script that uses the bluealsactl
utility to listen for the bluealsa signals and then manage the symbolic link
according to whether any A2DP sinks are connected.
#!/bin/bash
# Determine correct name for `bluealsactl` utility
BLUEALSACTL=$(type -p bluealsactl 2>/dev/null) || BLUEALSACTL=$(type -p bluealsa-cli 2>/dev/null)
if [[ -z "$BLUEALSACTL" ]] ; then
echo "ERROR: Cannot find either `bluealsactl` or `bluealsa-cli`" >&2
exit 1
fi
# create a temporary named pipe to communicate with bluealsactl monitor
FIFO_PATH=$(mktemp -u)
mkfifo $FIFO_PATH
# attach it to unused file descriptor FIFO_FD
exec {FIFO_FD}<>$FIFO_PATH
# unlink the named pipe
rm $FIFO_PATH
# make sure the pipeline is shut down if this script interrupted
trap "kill %1; exec {FIFO_FD}>&-; ln -sf /dev/null ~/.asoundrc-default; exit 0" INT TERM
# start bluealsactl monitor in background
"$BLUEALSACTL" --quiet monitor >&$FIFO_FD &
until false; do
if [[ $("$BLUEALSACTL" --quiet list-pcms | grep -Ec '/a2dp(src|sink)/sink$') -gt 0 ]] ; then
ln -sf ~/.asoundrc-bluealsa ~/.asoundrc-default
else
ln -sf /dev/null ~/.asoundrc-default
fi
read
done <&$FIFO_FD
This script should be run in the background (with the user's account, not as root) when the user logs in so that the symbolic link is always kept updated and ready for use.