Turning your Cubieboard/RaspberryPi into an A2DP capable media receiver

Few months ago I found out that there are a2dp media receivers out there, but the good ones that feature digital audio connectivity (TOSLINK or Electrical S/PDIF) are very expensive, so I decided to build one myself.

First I ordered a Bluetooth 4.0 USB dongle on Ebay – that was actually the longest part since I had to wait more than 2 weeks for it to arrive.

When the hardware is ready – we can move on to its configuration. The whole process is fairly simple if you know what you’re doing (it took me a while to debug why sink device wasn’t identified in PulseAudio 4.0).

Before we begin
We need to install the packages. I won’t get into too many details since this guide is kind of universal for all distros, but here’s what I installed on linaro:
apt-get install bluetooth bluez bluez-audio bluez-alsa:armhf bluez-tools bluez-utils pulseaudio pulseaudio-module-bluetooth pulseaudio-module-hal pulseaudio-module-udev pulseaudio-utils

Step 1: Set up bluetooth daemon
Herare are the contents of my /etc/bluetooth/audio.conf:




We also need to modify adapter name to something reasonable by setting Name = MediCenter in [general] section of /etc/bluetooth/main.conf, and device class to 0x20041C to make it discoverable as an audio sync. After editing my /etc/bluetooth/main.conf looks like this:

Name = MediaCenter
Class = 0x20041C
DiscoverableTimeout = 0
PairableTimeout = 0
PageTimeout = 8192
AutoConnectTimeout = 60
InitiallyPowered = true
RememberPowered = true
ReverseServiceDiscovery = true
NameResolving = true
DebugKeys = false
EnableGatt = false

If your bluetooth dongle was connected to the system before you modified your bluetooth configuration files, you’ll have to also modify it’s profile in /var/lib/bluetooth/*/config. It should look like this:

name MediaCenter
pairable yes
class 0x20041C

After editing it make sure you restart bluetooth service by issuing a /etc/init.d/bluetooth restart

Step 2: configure bluetooth daemon to accept incoming connections
For this we need to create a custom script that will daemonize bluetooth agent on system startup. Mine looks like this (credits to instructables.com):

# Provides: bluetooth-agent
# Required-Start: $remote_fs $syslog bluetooth pulseaudio
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Makes Bluetooth discoverable and connectable to 0000
# Description: Start Bluetooth-Agent at boot time.
#! /bin/sh
# /etc/init.d/bluetooth-agent
export USER HOME
case "$1" in
echo "setting bluetooth discoverable"
sudo hciconfig hci0 piscan
start-stop-daemon -c pulse -S -b -x /usr/bin/bluetooth-agent -- 0000
echo "bluetooth-agent startet pw: 0000"
echo "Stopping bluetooth-agent"
start-stop-daemon -K -x /usr/bin/bluetooth-agent
echo "Usage: /etc/init.d/bluetooth-agent {start|stop}"
exit 1
exit 0

Once the script is installed in /etc/init.d/bluetooth-agent you need to make it executable an make sure it runs on startup:

chmod 775 /etc/init.d/bluetooth-agent
update-rc.d bluetooth-agent defaults
update-rc.d bluetooth-agent enable

Two things marked as bold in the script is the used under which bluetooth agent runs (pulse) and the pin used to connect to it (0000). Feel free to change the second to whatever you want.

Next, we need to add user pulse to bluetooth group to grant it control over your dongle:
usermod -a -G bluetooth pulse

For some of the agent will still fail to start complaining about dbus not allowing method calls. If you experience this problem, you need to change a policy group in /etc/dbus-1/system.d/bluetooth.conf. Find the the section and add the line in bold:

<!-- allow users of bluetooth group to communicate with hcid -->
<policy group="bluetooth">
<allow send_destination="org.bluez"/>
<allow send_type="method_call"/>

Step 3: configure pulse audio
I am running a headless system so I had to run pulseaudio as a daemon. My daemon runs under user pulse (default) which after completing step 2 should be a member of bluetooth group. I’m sure you’ll find a guide out there on how to do that, so I will skip instructions to configure it.

On a non-realtime kernel my /etc/pulse/daemon.conf looks like this:

allow-module-loading = yes
high-priority = yes
nice-level = -11
realtime-scheduling = no
load-default-script-file = yes
default-script-file = /etc/pulse/default.pa
log-target = auto
log-level = notice
resample-method = speex-float-1
flat-volumes = no
default-fragments = 8
default-fragment-size-msec = 10
deferred-volume-safety-margin-usec = 1

If you don’t include default script then you’ll have to add the following lines to your system.pa:

### Automatically load driver modules for Bluetooth hardware
.ifexists module-bluetooth-policy.so
load-module module-bluetooth-policy

.ifexists module-bluetooth-discover.so
load-module module-bluetooth-discover

Note: depending on your os and pulseaudio version the above may be disabled in defaults.pa by default, so make sure you enable bluetooth-policy and bluetooth-discover modules. I also have the following lines at the en of my system.pa to enable pulseaudio accept incoming tcp and rtp connections and announce the available sinks over zeroconf for streaming over network:

# Make local subsistem available over TCP
load-module module-native-protocol-tcp auth-ip-acl=;
# Also accept RTP traffic
load-module module-rtp-recv
# Publish to zeroconf
load-module module-zeroconf-publish

Final step: configure a loopback between a2dp input and the output of your choice on device connect
For that we’ll need an udev rule that will trigger a script once new input devices are connected. Mine looks like this:
cat /etc/udev/rules.d/99-input.rules

SUBSYSTEM=="input", GROUP="input", MODE="0660"
KERNEL=="input[0-9]*", RUN+="/usr/lib/udev/bluetooth"

It will run /usr/lib/udev/bluetooth every time new device is connected or disconnects.

Later you need to create /usr/lib/udev/bluetooth and make it executable:

chmod 754 /usr/lib/udev/bluetooth

Contents of mine are:

echo "Executing bluetooth script...|$ACTION|" | logger

ACTION=$(expr “$ACTION” : “([a-zA-Z]+).*”)
if [ “$ACTION” = “add” ]
# Turn off BT discover mode before connecting existing BT device to audio
hciconfig hci0 noscan

# Set volume level to 100 percent
amixer set Master 100%

for dev in $(find /sys/devices/virtual/input/* -name input*)
if [ -f “$dev/name” ]
mac=$(cat “$dev/name” | sed ‘s/:/_/g’)

sleep 1
cardid=`sudo -u pulse pactl list cards short | grep bluez_card | cut -f1`
echo “sudo -u pulse pactl set-card-profile $cardid a2dp_source” | logger
sudo -u pulse pactl set-card-profile $cardid a2dp_source 2>&1 | logger

sleep 3
CONFIRM=`sudo -u pulse pactl list short | grep $bluez_dev`
if [ ! -z “$CONFIRM” ]
echo “Setting bluez_source to: $bluez_dev” | logger
sudo -u pulse pactl load-module module-loopback source=”$bluez_dev” sink=”$AUDIOSINK” source_dont_move=”true” sink_input_properties=”media.role=music” 2>&1| logger
echo “Device $bluez_dev not found” | logger

if [ “$ACTION” = “remove” ]
# Turn on bluetooth discovery if device disconnects
sudo hciconfig hci0 piscan

The one linke you will have to change is the bold one. In my case I’m redirecting all incoming audio to hdmi stereo sink, your device name most probably be different so you’ll have to get it by running:
sudo -u pulse pactl list sinks short

Here’s my output:

0 alsa_output.platform-sunxi-codec.analog-stereo module-alsa-card.c s16le 2ch 44100Hz RUNNING
1 alsa_output.platform-sunxi-sndhdmi.0.analog-stereo module-alsa-card.c s16le 2ch 44100Hz RUNNING

Second column is what we need.

Depending on your syslog configuration, logs may be written in different places. Most probably it’s /var/log/messages or /var/log/syslog. Try to connect a device and watch the logs as you connect. Make sure bluetoothd, bluetooth-agent and pulseaudio are running.

Feel free to comment here if you have issues.

Leave a comment

Your email address will not be published. Required fields are marked *