Building a Bluetooth DAC with Raspberry Pi Zero W

My car comes with a built-in Bluetooth hands-free but unfortunately it does not support audio streaming. Luckily there is an AUX input available which uses a regular 3,5 mm jack. Perfect opportunity for a DIY project. I built the Bluetooth DAC using Raspberry Pi Zero W and a DAC hat. This post depicts the details of this project.

For the software, I did the first iteration using Raspbian, and it was indeed fairly simple to get the device working. However, I wanted to see if it would be possible to create a small stripped down image just for this purpose. For this, I used Yocto. Both approaches are described below.

If you just want to get things working without the project details, you can skip directly to the end and grab a prebuilt ready-to-flash image with the Bluetooth DAC functionality.

Hardware

For the hardware I decided to use Raspberry Pi Zero W accompanied with pHAT DAC for the audio output. Linux has good support for Bluetooth audio, so the small and affordable Zero W fits this project nicely even though a full-blown Linux might be a bit excessive for this task. Some smaller microcontroller could handle these tasks as well, but would probably require much more implementation effort for the firmware.

Normally Raspberry Pi hats are mounted on top of the mainboard. However, I decided to mount the DAC hat under the Zero W so the 3,5 mm jack does not stick out so much. Moreover, I soldered the boards directly to each other using a single male header to make a compact package (not going to use this Zero W for anything else anyway). I also added a piece of electrical tape on top of the 3,5 mm jack so the metal parts are isolated from the test pads under the Zero W.

bt-dac-boards

Before the two boards were soldered together, I bent the debug serial port pins 90 degrees and did not solder them to the DAC hat. This way the debug serial was easily accessible and I did not need e.g. a network access to the Pi during development.

bt-dac-serial

Finally, I added clear acrylic case to finish the hardware.

bt-dac-final

Using Raspbian

For the first iteration I used Raspbian Buster Lite (https://www.raspberrypi.org/downloads/raspbian/) as the base. I started with the DAC and followed the official tutorial (https://learn.pimoroni.com/tutorial/phat/raspberry-pi-phat-dac-install). When these steps were done, I tested the audio output using aplay utility with headphones connected to the audio output.

For the Bluetooth audio part I found an existing guide that describes how to set up the Advanced Audio Distribution Profile (A2DP) and how to handle automatic pairing. The guide targeted a bit older Rasbian version (Stretch), but it worked more or less directly for Raspbian Buster also. Though, I did have to manually set the bluealsa profile to sink as described in this comment. Otherwise the device was not recognized as audio device.

After going through these guides and with a bit of problem solving the device was working as a Bluetooth DAC. Still, I wanted to see if it would be possible to implement a more lightweight image for this purpose. One that would also be useful for others who are trying to get the Bluetooth DAC functionality working.

Custom image with Yocto

Yocto is a system building tool-set that allows to build customized Linux distributions. Large variety of hardware and devices are supported, including the different Raspberry Pi variants. Raspberry Pi support is implemented in a Board Support Package (BSP) layer, meta-raspberrypi, that extends the generic Yocto layers and provides hardware specific specializations (e.g. bootloaders and kernel configurations). This layer, with its dependencies, allows to build generic Linux images for different Raspberry Pis.

Following the footmarks of the Raspbian specific guide and the DAC guide, I implemented meta-rpi-bt-dac layer that adds the Bluetooth audio functionality and DAC support on top of the generic meta-raspberrypi layer. The project readme provides instructions to build the custom image.

The most important parts of the layer are:

  • bluez-alsa_1.4.0.bb recipe that provides build instructions for the Bluetooth ALSA backend
  • a2dp-agent which is C implementation for the dbus auto-pairing agent (no need for python dependency as in Raspbian guide)
  • rpi-config_git.bbappend which appends the generated config.txt with DAC device tree overlay
  • dac-config init script to configure the Bluetooth functionality

The default Yocto configuration for Raspberry Pi uses SysVinit as the init manager. Therefore the systemd service files from the Raspbian guides were converted to init scripts.

The project builds a ready-to-flash Linux image with all the necessary Bluetooth and DAC configurations in place. When the Pi is powered on, it will become discoverable with name “RPi-DAC”. The device accepts all pairings and does not require a pin. The device is recognized as Bluetooth audio device and the audio from the connecting device is streamed to the DAC output.

For this use case only the Bluetooth and audio parts are needed. Therefore the default Yocto image still contains a lot of unnecessary components. I added the following configuration to Yocto local.conf to disable many of the unnecessary features:

MACHINE_FEATURES_remove = "apm wifi screen touchscreen"
DISTRO_FEATURES_remove = "ipv4 ipv6 irda usbgadget usbhost wifi nfs zeroconf 3g nfc x11 wayland vulkan"

Furthermore, I also disabled networking init scripts and removed some unnecessary kernel features to improve the boot time. Measured from dmesg, the start-up time for the device and Bluetooth (excluding the low level bootloader) is about 5 seconds.

...
[    3.962021] Bluetooth: Core ver 2.22
[    3.962246] NET: Registered protocol family 31
[    3.962263] Bluetooth: HCI device and connection manager initialized
[    3.969311] Bluetooth: HCI socket layer initialized
[    3.969361] Bluetooth: L2CAP socket layer initialized
[    3.969384] Bluetooth: SCO socket layer initialized
[    4.748779] Bluetooth: HCI UART driver ver 2.3
[    4.748816] Bluetooth: HCI UART protocol H4 registered
[    4.748828] Bluetooth: HCI UART protocol Three-wire (H5) registered
[    4.751038] Bluetooth: HCI UART protocol Broadcom registered

These optimizations were quite easy and the boot time could certainly be optimized further. Though, I feel that the performance is sufficient for this case and I probably won’t spend much more time optimizing it.

Prebuilt image

The Yocto image with Bluetooth DAC functionality can be build with the instructions in the project README. Though, the full Yocto build can take several hours so a prebuilt image is also provided in the Releases section.

The core-image-base-raspberrypi0-wifi.rootfs.rpi-sdimg file can be flashed directly to a SD card. The image is meant for Raspberry Pi Zero W hardware.

14 thoughts on “Building a Bluetooth DAC with Raspberry Pi Zero W

  1. Built it last night in under 5 minutes, and most of that time was attaching the header pin to the PI Zero W. Thanks for taking the time to share!

    Liked by 1 person

  2. Thanks – name changed.

    But that’s made me aware that it has a filesystem mounted rw, which means that I should do clean shutdowns rather than just disconnect the power? I was hoping to use this in my car…

    Like

      1. Thanks again. After switching to your rebuilt image I just had to “mount -o remount,rw /”, edit the /etc/bluetooth/main.conf to change the device name then “reboot” to get back to a ro filesystem.

        For me, the first attempt to connect a device after booting the Pi seems to fail every time. The second attempt is always fine. The same problem repeats with a second device (without rebooting the Pi first). Do you see the same?

        My phone connects to bluetooth for phone calls in my car automatically, but I have to manually connect to the Pi – any idea what causes that difference?

        Like

  3. Regarding /var/log/messages entries such as:
    Aug 11 09:15:45 raspberrypi0-wifi daemon.err bluetoothd[242]: AVRCP: failed to init uinput for 12:34:56:78:90:AB

    I saw this:
    https://bugs.launchpad.net/blueman/+bug/439166

    and tried this:
    modprobe uinput
    (via serial console)

    and now the logs show then when a device connects:
    Aug 11 09:18:23 raspberrypi0-wifi user.info kernel: [ 412.225764] input: 12:34:56:78:90:AB as /devices/virtual/input/input0

    I’m not sure what having uinput enables, but it’s one less error message in the logs.

    I made it repeatable by putting “uinput” in the file /etc/modules

    Like

    1. I haven’t encountered the connection issue you mentioned in previous message (I’ve tested with Android 9). Though I do have to connect each time.

      I think the uinput is related to audio/video remote control profile (avrcp) which would allow sending play/pause etc. commands from the Pi.

      I’ll apply the fix later this week. Currently pretty busy with work.

      Like

  4. OK, thanks. There’s no hurry for me as I’ve applied the changes after booting the image.

    I’m finding a few issues with bluetooth behaviour (e.g. connecting and disconnecting multiple devices can cause playback to stop altogether, requiring a restart of bluealsa-aplay (or a reboot, if no terminal connected to the serial console). But these are Bluez problems, not problems with your Pi image build.

    Thanks again for your efforts.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s