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:
[General]
Enable=Source
Disable=Socket
[Headset]
HFP=false
[A2DP]
SBCSources=1
MPEG12Sources=0
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:
[General]
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):
### BEGIN INIT INFO
# 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.
### END INIT INFO
#! /bin/sh
# /etc/init.d/bluetooth-agent
USER=root
HOME=/root
export USER HOME
case "$1" in
start)
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"
;;
stop)
echo "Stopping bluetooth-agent"
start-stop-daemon -K -x /usr/bin/bluetooth-agent
;;
*)
echo "Usage: /etc/init.d/bluetooth-agent {start|stop}"
exit 1
;;
esac
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"/>
</policy>
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
.endif
.ifexists module-bluetooth-discover.so
load-module module-bluetooth-discover
.endif
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=127.0.0.1;192.168.0.0/16
# 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:
#!/bin/bash
AUDIOSINK="alsa_output.platform-sunxi-sndhdmi.0.analog-stereo"
echo "Executing bluetooth script...|$ACTION|" | logger
ACTION=$(expr “$ACTION” : “([a-zA-Z]+).*”)
if [ “$ACTION” = “add” ]
then
# 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*)
do
if [ -f “$dev/name” ]
then
mac=$(cat “$dev/name” | sed ‘s/:/_/g’)
bluez_dev=bluez_source.$mac
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” ]
then
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
else
echo “Device $bluez_dev not found” | logger
fi
fi
done
fi
if [ “$ACTION” = “remove” ]
then
# Turn on bluetooth discovery if device disconnects
sudo hciconfig hci0 piscan
fi
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.
Troubleshooting:
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.