Headless A2DP Audio Streaming on Raspbian Stretch with a Raspberry Pi Zero W and a HiFiBerry DAC+ Zero
This gist will show how to setup Raspberry Pi OS Buster running on a Raspberry Pi Zero HW with a HiFiBerry DAC+ Zero, connected to a regular set of computer speakers for a DIY Streaming Bluetooth Speaker. You can use any device with a A2DP capability like a phone, a tablet or your laptop to stream audio. Towards the bottom, I added some bonus features like low latency optimization so audio played has minimal lag.
There are a million and one guides and gists out there but none of them actually does what I wanted to do "out of the box" - there are always bigger or smaller hacks involved. I wanted a turnkey solution that would not relay on any external scripts of extra code. It is up to date as of June 2021.
All examples below are from a Mac OS X laptop.
- A Raspberry Pi Zero HW (the one with WiFi and headers) (you can use a Zero W and either solder or hammer the headers to it)
- HiFiBerry DAC+ Zero HAT (for audio output)
- Min. 4GB micro SD card
- Technically, you can get by with 2GB card since the image is less than 1.9GB in size, but you won't be able to, for example, update the install.
- A PC (Windows, Mac or Linux)
-
Download and install Raspberry Pi OS Lite to a micro SD card using balenaEtcher or similar software.
-
After a successful install, mount the boot partition and prepare the OS to work headlessly (without a keyboard or a screen):
- Open a terminal window and cd into the mounted /boot partition (
cd /Volumes/boot
) and configure wifi networking:
$ cat <<EOF > wpa_supplicant.conf country=US ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 network={ ssid="YOUR_SSID" psk="YOUR_SSID_PASSWORD" } EOF
- Create an empty file
ssh
to enable ssh at boot:
$ touch ssh
- While there, we might as well enable the HiFiBerry DAC+ Zero HAT: inside config.txt
[all] ... start_x=0 gpu_mem=16 dtoverlay=hifiberry-dac ...
or just use the script (for Mac OS X use gsed instead):
$ sed -i '/\[all\]/a start_x=0\ngpu_mem=16\ndtoverlay=hifiberry-dac\n' config.txt
- Unmount and remove the card, put it into the Raspberry Pi and boot.
- Open a terminal window and cd into the mounted /boot partition (
-
Find out the zero's IP address and ssh to it with a user
pi
and passwordraspberry
. -
Change the default password!
$ passwd
-
Make sure the system is up to date using the following commands.
$ sudo apt update && sudo apt upgrade
-
Reboot.
-
Log back in and configure your fresh install with raspi config:
$ sudo raspi-config
-
Reboot as required.
-
Check if your HiFiBerry DAC+ Zero is properly seen by the system:
$ aplay -l **** List of PLAYBACK Hardware Devices **** card 0: sndrpihifiberry [snd_rpi_hifiberry_dac], device 0: HifiBerry DAC HiFi pcm5102a-hifi-0 [HifiBerry DAC HiFi pcm5102a-hifi-0] Subdevices: 1/1 Subdevice #0: subdevice #0
-
OPTIONAL Disable WiFi powersave to increase performance and decrease latency:
$ sudo systemctl --full --force edit wifi_powersave@.service
In the empty editor insert these statements, save them and quit the editor:
[Unit] Description=Set WiFi power save %i After=sys-subsystem-net-devices-wlan0.device [Service] Type=oneshot RemainAfterExit=yes ExecStart=/sbin/iw dev wlan0 set power_save %i [Install] WantedBy=sys-subsystem-net-devices-wlan0.device
Now enable just what you want on boot up:
$ sudo systemctl disable wifi_powersave@on.service $ sudo systemctl enable wifi_powersave@off.service
-
OPTIONAL Use zram as a swap space to save the card. One way is to use this repo.
$ sudo systemctl stop dphys-swapfile.service $ sudo systemctl disable dphys-swapfile.service $ sudo rm /var/swap
-
Reboot
-
Finally, log back in again and install the required packages.
$ sudo apt install bluealsa bluez-tools --no-install-recommends
Normally a Bluetooth device is only discoverable for a limited amount of time. Since this is a headless setup we want the device to always be discoverable.
-
Set the DiscoverableTimeout in
/etc/bluetooth/main.conf
to 0# How long to stay in discoverable mode before going back to non-discoverable # The value is in seconds. Default is 180, i.e. 3 minutes. # 0 = disable timer, i.e. stay discoverable forever DiscoverableTimeout = 0
-
Restart bluetooth service:
$ sudo systemctl restart bluetooth.service`
-
Enable discovery on the Bluetooth controller
$ sudo bluetoothctl power on discoverable on exit
Create, enable and start bt-agent service:
$ sudo systemctl --full --force edit bt-agent.service
add:
[Unit]
Description=Bluetooth Auth Agent
After=bluetooth.service
PartOf=bluetooth.service
[Service]
Type=simple
ExecStart=/usr/bin/bt-agent -c NoInputNoOutput
[Install]
WantedBy=bluetooth.target
$ sudo systemctl enable bt-agent.service
$ sudo systemctl start bt-agent.service
Run:
$ sudo systemctl --full --force edit bluealsa.service
and add -p a2dp-sink
to the ExecStart
line, so it looks like this:
...
ExecStart=/usr/bin/bluealsa -p a2dp-sink
...
Run:
$ sudo systemctl --full --force edit bluealsa-aplay.service
and add this to the file:
[Unit]
Description=BlueALSA aplay service
After=bluetooth.service
Requires=bluetooth.service
[Service]
ExecStart=/usr/bin/bluealsa-aplay -vv --profile-a2dp --pcm-buffer-time=24000 --pcm-period-time=6000 00:00:00:00:00:00
[Install]
WantedBy=multi-user.target
Then enable the service:
$ sudo systemctl enable bluealsa-aplay.service
Reboot.
Before testing with a device, log in to the Raspberry Pi and make sure all services started properly.
Run:
$ sudo ps auxw | grep bluealsa
root 309 0.0 0.6 15536 3276 ? Ssl 20:41 0:03 /usr/bin/bluealsa-aplay -vv --profile-a2dp --pcm-buffer-time=24000 --pcm-period-time=6000 00:00:00:00:00:00
root 404 0.2 1.0 35012 5012 ? Ssl 20:41 0:16 /usr/bin/bluealsa -p a2dp-sink
If you see the above processes, there is a big chance there will be sound!
Start a tail on /var/log/syslog
like that:
$ sudo tail -f /var/log/syslog
and try to search for your device in the bluetooth pairing section. Once connected and paired, try streaming!
It might happen that the you won't be able to adjust the volume from your device. Try this:
$ sudo systemctl --full --force edit bluetooth.service
and add --noplugin=avrcp
to the systemd service file so it looks like this:
[Unit]
...
[Service]
...
ExecStart=/usr/lib/bluetooth/bluetoothd --noplugin=avrcp
...
[Install]
...
Restart your bluetooth.service and try again.