-
Notifications
You must be signed in to change notification settings - Fork 3
Bluetooth audio sink
This introduces the software stack in Bluetooth audio receiver servers, also known as A2DP audio sinks. Although it covers information on the stack that might be relevant on many devices and distros, the main focus here is Raspberry Pi hardware and the Raspberry Pi OS (formerly Raspbian)
NOTE: this article has recently been separated from multi-room-audio so may need some tidying up
see also:
-
Bluetooth hardware devices
- basic diagnostics on radio operations
-
Audio & Video diagnostics
- troubleshooting devices and configuration for Audio and Video input and output
-
multi-room-audio
- usingg the snapcast service for routing music, including from a bluetooth sink
-
lubuild Music and Multimedia
- includes sommon software for serving and controlling audio streams
-
Volumio and MPD
- Dedicated headless music player
-
Audio Hub
- looks at software for playing audio files, especially music
-
Ready-made-input-devices
- covers the bluetooth software and configuration for using device controllers like the PS3 remote
The Bluetooth profile Advanced Audio Distribution Profile (A2DP) is sometimes also called Bluetooth Audio Streaming. Some devices also implement the Audio/Video Remote Control Profile (AVRCP) for volume and other controls.
Note that several sources mention contention issues when both Bluetooth and Wifi radios are connected on the Raspberry Pi system on chip (SoC). This would impact the performance, and therefore are very disruptive to audio quality.
Suggested solutions include:
- not connecting via both at the same time
- using an external (USB) radio device for one
See Lubuild's about Sound software in Ubuntu to understand more about the difference between the linux ALSA sound driver and Pulse sound mixer (or 'sound server'). In theory a sound driver like ALSA sound driver should be less complex than a sound mixer like Pulse Audio. If all else were equal, it makes sense that ALSA could make for more efficient Bluetooth sound transfers.
This may be one of the reasons why some people suggest that blue-alsa is more efficient than BlueZ over Pulse Audio. But efficiency is not everything, and simplicity and supportability can also be important. Let's look at them now.
BlueZ is the 'official' Linux bluetooth stack, and is the one most commonly shipped by distros (other than Android). The code was originally created and maintained by Qualcomm, chip manufacturer.
Up until version 4, you could connect directly between Alsa and Bluetooth streams. Since version 5, however you must go via PulseAudio. Although this gives practical flexibility for most common scenarios, adding a separate sound mixer layer into the stack might not be ideal for all applications.
For applications that are highly sensitive to latency (e.g. high quality audio) on low-powered hardware (e.g. RaspberryPi), there is an alternative. The bluealsa project, formerly known as bluez-alsa, skips the PulseAudio layer. It will register the audio profiles with BlueZ so that, via an alsa plugin, apps can simply access the ALSA PCM device called bluealsa.
https://github.com/Arkq/bluez-alsa
The core depends on only:
- alsa-lib
- bluez >= 5.0
- glib with GIO support
- sbc
Optional components also add support for (with package):
- AAC (fdk-aac)
- apt-X, apple's HQ audio codec (openaptx)
- rfcomm for serial port modem (readline)
- hcitop diagnostics utility (libbsd, ncurses)
BlueALSA acts as a proxy between BlueZ and ALSA. During system startup, bluealsa runs as a root, registers org.bluealsa in D-Bus for audio device access.
For details, like user context being member of audio group see also https://wiki.archlinux.org/index.php/Bluetooth_headset#Headset_via_Bluez5.2Fbluez-alsa
- other User context issues with bluealsa
Raspberry Pi OS (currently Bullseye) Lite edition includes bluez as well as its own pi-bluetooth driver (including bthelper and hciuart services). However that low level bluetooth software does not predicate other choices. The base image also includes alsa-utils
, but pulseaudio is not installed.
See this very useful in-depth article on the stack in Pi OS and how to work with devices and diagnose issues. It covers the difference between alsa and pulse, and their impact. Note that where it says that pulse is installed, it may be referring to the Desktop edition.
On top of the base bluetooth and sound driver software, you will need:
- A2DP audio stream API
-
bluealsa
is a Linux daemon giving applications access to Bluetooth audio streams using the Bluetooth A2DP, HFP and/or HSP profiles. It provides a D-Bus API to applications, and can be used by ALSA applications via libasound plugins.
-
- A2DP audio player service
-
bluealsa-aplay
uses that API to capture audio streams from Bluetooth devices and play them to an ALSA playback device (help). You can wrap this utility as a service.
-
- Bluetooth open pairing agent
- this should start after the bluetooth service, and promiscuously allow connections from devices, either not asking for authentication or using a default 0000 passkey. This could be done with an agent using
NoInputNoOutput
mode. Most examples use a python agent, with dbus APIs to allow the incoming bluetooth connections. An alternative might be using shell script andbluetoothctl
'sscan
andconnect
commands (e.g. this).
- this should start after the bluetooth service, and promiscuously allow connections from devices, either not asking for authentication or using a default 0000 passkey. This could be done with an agent using
This is inspired by a historical version of the Rpi ready install below.
This previous version of it used
- alsa-base
- bluealsa
- not available in Raspberry Pi OS repositories
- but you could still obtain from the bookworm version of Raspbian
- as long as you remain on 32-bit OS
- for more on OS choices for Pis see Mugammapi Distros # Comparing distros
- python
- similar to the bluez sample
- and a smattering of automatically set-up config
NB the script below may require a workaround with later OS versions due to security improvements:
curl -O http://archive.raspbian.org/raspbian/pool/main/r/raspbian-archive-keyring/raspbian-archive-keyring_20120528.2_all.deb
sudo dpkg -i raspbian-archive-keyring_20120528.2_all.deb
#### Bluetooth A2DP sink using bluealsa
# This is based on using the built-in Pi 3 bluetooth radio, but networking with
# ethernet ONLY, so that the internal wifi radio is disabled and can't interfere
# Pi OS 11 'Bullseye' Lite includes bluetooth and bthelper@hci0 services only
# bluealsa not present in Pi bullseye repos - get it from 'Bookworm Raspbian'
# credit https://www.sigmdel.ca/michel/ha/rpi/bluetooth_in_rpios_02_en.html#bluealsa3
echo "deb http://archive.raspbian.org/raspbian/ bookworm main" | sudo tee /etc/apt/sources.list.d/armbian.list
printf 'Package: *\nPin: release n=bookworm\nPin-Priority: 100\n' | sudo tee --append /etc/apt/preferences.d/limit-bookworm
sudo apt update
sudo apt install -y bluez-alsa-utils
##### options for newly-installed bluez-alsa.service
sudo tee /etc/default/bluez-alsa <<EOF!
# Config file for blues-alsa.service - set the service runtime options
# credit https://www.sigmdel.ca/michel/ha/rpi/bluetooth_in_rpios_02_en.html#bluealsa3
#OPTIONS="-p a2dp-source -p a2dp-sink"
# but we only need the sink
OPTIONS="--profile=a2dp-sink"
# although using a USB Bluetooth radio you might need "--device=hciX"
# help - https://github.com/Arkq/bluez-alsa/blob/master/doc/bluealsa.8.rst
EOF!
sudo systemctl restart bluez-alsa.service
sudo systemctl status bluez-alsa.service
# These sections are derived from
# https://github.com/artmg/rpi-audio-receiver/blob/master/install-bluetooth.sh
# but original credit to https://github.com/nicokaiser/rpi-audio-receiver
##### bluealsa-aplay.service starts after bluez-alsa to pass A2DP to Alsa device
sudo tee /etc/systemd/system/bluealsa-aplay.service <<EOF!
[Unit]
Description=BlueALSA player
Requires=bluez-alsa.service
After=bluez-alsa.service
Wants=bluetooth.target sound.target
[Service]
Type=simple
User=root
ExecStartPre=/bin/sleep 2
ExecStart=/usr/bin/bluealsa-aplay --pcm=default --pcm-buffer-time=250000 00:00:00:00:00:00
# help - https://github.com/Arkq/bluez-alsa/blob/master/doc/bluealsa-aplay.1.rst
[Install]
WantedBy=graphical.target
EOF!
##### udev rule restarts the A2DP player when a device connects to bluetooth
sudo tee /etc/udev/rules.d/61-bluealsa-aplay.rules <<EOF!
# syntax generated by connecting to bluetooth whilst using
#udevadm monitor --environment --udev --kernel
ACTION=="add", SUBSYSTEM=="bluetooth", RUN+="/bin/systemctl restart bluealsa-aplay.service"
EOF!
# not bothered with udev script for connect/disconnect sounds from original
##### Bluetooth settings
sudo tee /etc/bluetooth/main.conf <<EOF!
[General]
# With bluetooth Class of Device (CoD), the Service Class (>0x2000) might be ignored
#Class = 0x200414
# Major device class: Audio/Video; Minor device class: Hifi audio device;
#Class = 0x0428
# Major device class: Audio/Video; Minor device class: Loudspeaker;
Class = 0x0414
DiscoverableTimeout = 0
[Policy]
AutoEnable=true
EOF!
sudo service bluetooth restart
sudo hciconfig hci0 piscan
sudo hciconfig hci0 sspmode 1
##### ALSA settings
sudo sed -i.orig 's/^options snd-usb-audio index=-2$/#options snd-usb-audio index=-2/' /lib/modprobe.d/aliases.conf
##### Bluetooth open pairing agent
# use modified version of BlueZ 'simple agent' python test script
wget https://raw.githubusercontent.com/bluez/bluez/master/test/simple-agent
# credit - https://github.com/RPi-Distro/repo/issues/291#issuecomment-1149427137
sed -i.orig 's/capability = "KeyboardDisplay"/capability = "NoInputNoOutput"/' simple-agent
sed -i.orig 's/return raw_input(prompt)/return "yes"/' simple-agent
sudo cp simple-agent /usr/local/bin/bluetooth-open-pairing
sudo chmod 755 /usr/local/bin/bluetooth-open-pairing
# and required library script
wget https://raw.githubusercontent.com/bluez/bluez/master/test/bluezutils.py
sudo cp bluezutils.py /usr/local/bin/
sudo chmod 755 /usr/local/bin/bluezutils.py
# I chose this script as it comes from the Bluez project itself.
# If it ever changes and you can't make it work, consider a simpler derivative like
# https://gist.github.com/mill1000/74c7473ee3b4a5b13f6325e9994ff84c#file-a2dp-agent
sudo tee /etc/systemd/system/bluetooth-open-pairing.service <<EOF!
# runs a modified copy of bluez simple-agent to silently allow all pairing requests
[Unit]
Description=Bluetooth open pairing agent
Requires=bluetooth.service
After=bluetooth.service
[Install]
WantedBy=bluetooth.target
[Service]
Type=simple
ExecStartPre=/bin/hciconfig hci0 piscan
ExecStartPre=/bin/hciconfig hci0 sspmode 1
ExecStart=/usr/local/bin/bluetooth-open-pairing
Restart=always
RestartSec=10
EOF!
# install pairing agent script dependencies
sudo apt install -y python3-gi python3-dbus
# now let's run it all
sudo systemctl daemon-reload
sudo systemctl enable --now bluealsa-aplay
sudo systemctl enable --now bluetooth-open-pairing.service
# Now you should be able to pair with your Pi and send sound
# that you can hear when you plug into the internal headphone jack
For any subsequent Snapcast configuration see Multi-room audio # Bluetooth sink
systemctl status bluetooth.service
systemctl status bluez-alsa.service
systemctl status bluealsa-aplay.service
systemctl status bluetooth-open-pairing.service
If you really want to go for simplicity and reliability, you might want to consider this fully scripted solution.
https://github.com/nicokaiser/rpi-audio-receiver/blob/main/install-bluetooth.sh
- It currently uses:
- bluez-tools
- pulseaudio-module-bluetooth
- yes that does mean more dependencies and 'potentially worse performance' (as yet unmeasured) but the Pi Foundation removed bluealsa from their repos for bullseye
- Currently there are some important parts of the PulseAudio configuration performed in the main 'install.sh' script, so you should follow the main instructions...
- https://github.com/nicokaiser/rpi-audio-receiver#installation
- Say Yes to run bluetooth script, but No to all the others
- Reboot to make ready
NB: If you later wish to alter the 'pretty name' that Bluetooth announces, then see https://github.com/artmg/lubuild/blob/master/help/diagnose/hardware.md#bluetooth-devices
Trying the version 19th July 2022 it worked on first connection. However after disconnecting it would not reconnect. I had to 1) remove the device on the client (e.g. android) 2) bluetoothctl / devices / remove to clear the device on the server (otherwise it would give me a 'PIN error')
This is not currently usable
It uses:
- python-dbus, unknown depends
- bluetoothctl, manage devices
- a2dp-agent, pair & auth, what source? bt-agent-a2dp.service
example solution: see https://gist.github.com/mill1000/74c7473ee3b4a5b13f6325e9994ff84c
According to this https://wiki.debian.org/Bluetooth/Alsa BlueZ5 no longer supports alsa
https://github.com/lukasjapan/bt-speaker
git bluez python python-gobject python-cffi python-dbus python-alsaaudio python-configparser sound-theme-freedesktop vorbis-tools
- BT-Speaker daemon written in Python
- uses Bluez5, via Bluez DBUS interface
- BT-Speaker registers itself as A2DP capable,
- then route the received audio fully decoded to ALSAs aplay command
Although it might be a slightly heavyweight implementation, there is some good detail in the docs that might help other design ideas
-
Use:
- the bash install script at https://github.com/lukasjapan/bt-speaker
- Install some pre-reqs (incl bluez and python-alsaaudio)
- Add a user so that the service does NOT need root privilege
- clone the bt-speaker scripts into /opt
- Set up the service
- the bash install script at https://github.com/lukasjapan/bt-speaker
-
Configure
- the configuration file is described here https://github.com/lukasjapan/bt-speaker#config
The scripts are python-based and include hooks for actions on device connect and disconnect.
- for unifying the solution see BTtoggle.py https://gist.github.com/User65k/8b14149ca1b00dc9c8c1a8c5f552d8f6
- user commands https://github.com/BaReinhard/Super-Simple-Raspberry-Pi-Audio-Receiver-Install/blob/master/bt_pa_install.sh
bluez pulseaudio-module-bluetooth python-gobject python-gobject-2 bluez-tools
- Uses udev 99-input.rules to launch script on BT connection
- shell script runs pactl to set loopback between BT source and local audio sink
- source is bluez_source device
https://www.instructables.com/id/Turn-your-Raspberry-Pi-into-a-Portable-Bluetooth-A/
alsa-utils bluez bluez-tools pulseaudio-module-bluetooth python-gobject python-gobject-2
https://gist.github.com/oleq/24e09112b07464acbda1
https://www.raspberrypi.org/forums/viewtopic.php?t=161944
Uses hacktooth
script
- for general advice on using the Bluetooth Audio Sink profile to receive audio, including pairing and connecting, see https://docs.google.com/document/d/12cK4heNd7kY3jYZI_sW1AD407dn_ds-9zZdX4qs-TJg/edit
COPY of info for snapcast Bluetooth sources:
- Bluetooth as snapserver source via pulse /tmp/fifobt
According to https://github.com/badaix/snapcast/blob/master/doc/player_setup.md#alsa the suggested input pipelines to snapserver's snapfifo pipe are:
- Alsa: alsa file plugin, configured in asound.conf using pcm.writeFile
- Pulse: pulseAudio pipe sink, configured by command pacmd load-module module-pipe-sink
You are at liberty to configure multiple pipe files, e.g. /tmp/snapMPDfifo
for MPD and /tmp/snapBluefifo
for Bluetooth.
Note: not checked that these are all needed or even work!
# Set Bluetooth Class of Device (for clients scanning)
hciconfig hci0 up
# Loudspeaker class
hciconfig hci0 class 0x240414
# could add Networking Service Class bit 0x260414
tee -a /etc/bluetooth/audio.conf << EOF!
[General]
Enable=Source
EOF!
# example code from python dbus
# detect connection
bus.add_signal_receiver(property_changed, bus_name="org.bluez", signal_name = "PropertyChanged", path_keyword="path", interface_keyword="interface")
# set up pulse loopback
pactl load-module module-loopback source=bluez_source.%s sink=alsa_output.pci-0000_00_1b.0.analog-stereo" % bt_addr
Buildroot components required
- rpi-bt-firmware
- bluez-tools bluez5-utils
- modules: bluetooth, bnep, btbcm, hci_uart
- based on https://gist.github.com/mill1000/74c7473ee3b4a5b13f6325e9994ff84c
- this was a popular gist and could merit it's own section above?
- also referred to by https://github.com/badaix/snapcast/issues/379
# credit https://gist.github.com/mill1000/74c7473ee3b4a5b13f6325e9994ff84c
sudo apt install -y bluealsa python-dbus
# install the agent from the gist
sudo wget --output-document=/usr/local/bin/a2dp-agent https://gist.githubusercontent.com/mill1000/74c7473ee3b4a5b13f6325e9994ff84c/raw/244a0acf2278569525c8577f4960cd552c81c697/a2dp-agent
sudo chmod +x /usr/local/bin/a2dp-agent
# set timeout and enable discovery
sudo sed -i 's/#DiscoverableTimeout = 0/DiscoverableTimeout = 0/' /etc/bluetooth/main.conf
sudo bluetoothctl
power on
discoverable on
exit
# make sure it appears as an audio sink
sudo sed -i 's|ExecStart=/usr/bin/bluealsa$|ExecStart=/usr/bin/bluealsa -p a2dp-sink|' /lib/systemd/system/bluealsa.service
sudo systemctl daemon-reload
sudo systemctl restart bluealsa.service
# test the agent
sudo /usr/local/bin/a2dp-agent
# install the agent as a service
sudo wget --output-document=/lib/systemd/system/bt-agent-a2dp.service https://gist.githubusercontent.com/mill1000/74c7473ee3b4a5b13f6325e9994ff84c/raw/244a0acf2278569525c8577f4960cd552c81c697/bt-agent-a2dp.service
# and create a playback service
sudo tee /lib/systemd/system/a2dp-playback.service <<EOF!
[Unit]
Description=A2DP Playback
After=bluealsa.service syslog.service
Requires=bluealsa.service
StartLimitInterval=200
StartLimitBurst=5
[Service]
ExecStartPre=/bin/sleep 3
ExecStart=/usr/bin/bluealsa-aplay --profile-a2dp 00:00:00:00:00:00
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=A2DP-Playback
User=pi
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF!
sudo systemctl daemon-reload
sudo systemctl enable bt-agent-a2dp.service
sudo systemctl enable a2dp-playback.service
sudo systemctl start bt-agent-a2dp.service
sudo systemctl start a2dp-playback.service
This failed at the point of testing the agent as it would not allow connections and not show up in device search lists
A single paired connection may set up multiple A2DP services in each direction. Each separate service is a uni-directional transfer of an audio stream in up to 2 channel stereo. The profile relies on AVDTP and GAVDP.
It includes mandatory support for the low-complexity SBC codec (not to be confused with Bluetooth's voice-signal codecs such as CVSDM) and supports optionally MPEG-1 Part 3/MPEG-2 Part 3 (MP2 and MP3), MPEG-2 Part 7/MPEG-4 Part 3 (AAC and HE-AAC), and ATRAC, and is extensible to support manufacturer-defined codecs
If a Bluetooth stack enforces the SCMS-T digital rights management (DRM) scheme, it may limit the transfer of high quality audio.
The A2DP standard operates in stereo and supports most of the standard audio compression codecs. The recommended sub-band coding (SBC) codec supports up to 345 kilobits per second at 48 kilohertz. That’s approximately one third the quality of standard CD audio—roughly the equivalent of a high-quality MP3 recording. Due to high “lossy” compression in the SBC codec, the reality of the audio quality is considerably lower, somewhere in the range of 256kbit/s.
The system also supports other popular methods of encoding and compressing audio, like MP3 itself. If the audio source is already compressed in a format like MP3, AAC, or ATRAC, then it doesn’t need to be re-encoded in SBC in order to be broadcast from the source device. With A2DP’s maximum audio bandwidth of 728kbit/s, it’s at least possible to start approaching what we’d call “high-quality audio” with the basic standard alone. (CD quality audio, uncompressed, is approximately 1400kbit/s.)
Unfortunately, very few hardware makers seem to be actually using this capability, and most A2DP-only devices are re-encoding audio to SBC and de-encoding on the receiver end. This makes the whole process more complicated, resulting in poorer audio quality.
—— <cite>[HowToGeek][1]</cite>
[1]:https://www.howtogeek.com/338750/whats-the-difference-between-bluetooth-a2dp-and-aptx/