Originally, I had been looking around online for a Bluetooth enabled FM transmitter for use in my car as I no longer have an AUX-IN connector on the radio to connect my iPhone. During my search, I came across some decent ones but they started at around 70$ USD. Given I had an old RPi lying around, I thought it might make for a fun project to see if I could create one for my own.
Note: It turns out, I ran into a number of problems with the audio transmitting at half-speed over FM so I re-purposed the idea to connect directly into my hi-fi stereo in the house which has surround sound so I can stream music from my iPhone, MacBok and iPad. I’ll detail the instructions on how to stream over FM at the end of this post. Give it a try and if you figure out how to resolve the half-speed issue, comment and let me know!
To start, I did some research and found the following blog post which essentially covered everything I needed: http://www.instructables.com/id/Raspberry-Pi-Wireless-Bluetooth-Audio-FM-Radio-Tra/?ALLSTEPS.
Note: The instructions provided here are heavily based on the above reference, including scripts.
Next, I needed to get the equipment:
- Raspberry Pi (B Model)
- Bluetooth Dongle
- AUX Cable
To begin, I installed Rasbian following the instructions on the RaspberryPi Foundation site: https://www.raspberrypi.org/help/noobs-setup/ Once booted into the fresh OS, I made the following changes to get the system up and running:
Firstly, I installed the necessary applications (bluetooth support, pulseaudio and their related dependencies):
$ sudo apt-get update $ sudo apt-get upgrade $ sudo apt-get install bluez pulseaudio-module-bluetooth python-gobject python-gobject-2 bluez-tools pulseaudio-utils
Next, I started by configuring bluetooth’s audio service by modifying /etc/bluetooth/audio.conf and adding the following under the [General] tag:
$ sudo nano /etc/bluetooth/audio.conf [General]: Enable=Source,Sink,Media,Socket
Then, I modified the bluetooth main configuration file /etc/bluetooth/main.conf to add my own bluetooth device name and Class ID:
Name = pifm Class = 0x20041C
You’ll also need to modify the configuration of your bluetooth dongle to match the above configuration. I did this by modifying the dongle’s configuration file /var/lib/bluetooth//config and adding the device name and class id:
name pifm class 0x20041C
At this point, I configured the pulseaudio daemon by modifying the configuration file /etc/pulse/daemon.conf to enable trivial re-sampling and disable idle-time. This was done by adding the following to the bottom of the file:
resample-method = trivial exit-idle-time = -1
Note: Next, I’m going to demonstrate how to connect your iPhone/bluetooth device manually, and link it to the pulseaudio sink to enable audio playback out via the jack. In the majority of details I found online, they merely provided a script but didn’t disucss what each line did. I’ll provide a break-down of what is necessary and then we’ll pull it togther into a udev script to automate the entire process when the iPhone attempts to pair with the RPi.
To connect your phone, enable bluetooth scanning and start the bluetooth-agent script by running the following command:
sudo hciconfig hci0 piscan start-stop-daemon -S -x /usr/bin/bluetooth-agent -c pi -b -- 0000
To print out the list of available devices you can use:
sudo hcitool scan
Note: To disable bluetooth scanning run:
susdo hcitool noscan
At this point, you can pair your iPhone with the RPi. If requested, enter in the pin ‘0000’.
At this point, we’ll need to initialise the audiopulse server. By default, an audiopulse instance is created on a per-user login basis. There is support for system-wide mode, but I couldn’t get this to work correctly. In order to get around this, we’ll need to set auto-login on ttyp1 for user pi. This way, we can control audiopulse using sudo -u pi in the future.
Let’s first add the user ‘pi’ to the audio group to ensure it can use pulseaudio’s services:
sudo usermod -Ga pulse-access pi
Next, we’ll need to modify /etc/inittab to enable auto-login for user ‘pi’. This will allow us to access the pulse-audio service when running the RPi headless later.
Comment out the following line:
1:2345:respawn:/sbin/getty --noclear 38400 tty1
Add this line directly below:
1:2345:respawn:/bin/login -f pi tty1 </dev/tty1 >/dev/tty1 2>&1
At this point, reboot the device.
sudo reboot
Once the system is back up, re-pair your phone to the RPi. We can now interact with the pulseaudio server and list any sinks (audio-output devices) and source devices (the iPhone conneced over bluetooth) that are available to us:
sudo -u pi pactl list sinks short 0 alsa_output.0.analog-stereo module-alsa-card.c s16le 2ch 44100Hz IDLE sudo -u pi pactl list sources short 0 alsa_output.0.analog-stereo.monitor module-alsa-card.c s16le 2ch 44100Hz IDLE 1 bluez_source.D4_F4_6F_B1_1F_FF module-bluetooth-device.c s16le 2ch 44100Hz RUNNING
To connect the sink to the source, you can run the following command:
sudo -u pi pactl load-module module-loopback source=bluez_source.D4_F4_6F_B1_1F_FF sink=aalsa_output.0.analog-stereo
Connect up some speakers via the 3.5 mm jack, and play a song on the iPhone. I tested this using Spotify and it worked like a charm. If the audio doesn’t play right-away, you can set the output to the 3.5 mm jack using the following command:
sudo -u pi amixer cset numid=3 1
The volume can also be increased (below, I’ve increased it to 125%) using the following commands. Note, 0 denotes the channel number (e.g. alsa_output.0.analog-stereo) as seen when running pactl list sinks short:
sudo -u pi amixer set Master 100% sudo -u pi pacmd set-sink-volume 0 0x12500
Automation is the key!
Once all tested successfully, our next step is to automate this entire process when the device pairs with the RPi. This can be achieved using a udev script.
You’ll want to create a udev rule to auto-execute your custom script(s) when the bluetooth dongle is detected/inserted. This can be be by adding the following line to /etc/udev/rules.d/99-input.rules. We’ll create the bluetooth script afterwards:**
KERNEL=="input[0-9]*", RUN+="/usr/lib/udev/bluetooth"
Next, we’ll create the following scripts which combines what we’ve used above to automate everything.
Firstly, let’s start with the bluetooth-agent script. Add this to /etc/init.d/bluetooth-agent:
#!/bin/sh
#/etc/init.d/bluetooth-agent
### 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
USER=root
HOME=/root
export USER HOME
case "$1" in
start)
echo "initializing pulseaudio"
sudo pactl info
echo "setting bluetooth discoverable"
sudo hciconfig hci0 piscan
start-stop-daemon -S -x /usr/bin/bluetooth-agent -c pi -b -- 0000
echo "bluetooth-agent started pw: 0000"
sudo /usr/lib/udev/bluetooth-auto &
echo "bluetooth-auto-discovery started"
;;
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
Save the file and change set the appropriate permissions using the following command:
sudo chmod 774 /etc/init.d/bluetooth-agent
Next, We’ll set up the bluetooth udev scripts.
Firstly, create the udev directory and some of the files we’ll need
sudo mkdir -p /usr/lib/udev sudo touch /usr/lib/udev/bluetooth-trust sudo touch /var/log/bluetooth_dev
Next, add the following script to /usr/lib/udev/bluetooth:
Note: You can change the $AUDIOSINK variable to your specified device. You can retrieve the list of available devices by running sudo -u pi pactl list sinks short.
#!/bin/bash AUDIOSINK="alsa_output.0.analog-stereo" ACTION=$(expr "$ACTION" : "\([a-zA-Z]\+\).*") echo "Executing bluetooth script...|$ACTION|" >> /var/log/bluetooth_dev if [ "$ACTION" = "add" ] then # Turn off BT discover mode before connecting existing BT device to audio hciconfig hci0 noscan # Turn off BT auto connect if it is still running sudo killall bluetooth-auto # set the audio output to the hdmi amixer cset numid=3 2 # Set volume level to 100 percent amixer set Master 100% # Set sink volume to 125% pacmd set-sink-volume 0 0x12500 for dev in $(find /sys/devices/virtual/input/ -name input*) do if [ -f "$dev/name" ] then mac=$(cat "$dev/name") # Add this mac address to list of trusted addresses TRUST=$(grep "$mac" /usr/lib/udev/bluetooth-trust) if [ -z "$TRUST" ] then echo "Adding $mac to trusted addresses" >> /var/log/bluetooth_dev echo $mac >> /usr/lib/udev/bluetooth-trust fi mac_underscore=$(cat "$dev/name" | sed 's/:/_/g') bluez_dev=bluez_source.$mac_underscore # Set source volume to 125% pacmd set-source-volume $bluez_dev 0x12500 sleep 1 CONFIRM=`sudo -u pi pactl list short | grep $bluez_dev` if [ ! -z "$CONFIRM" ] then echo "Setting bluez_source to: $bluez_dev" >> /var/log/bluetooth_dev echo pactl load-module module-loopback source=$bluez_dev sink=$AUDIOSINK rate=44100 adjust_time=0 >> /var/log/bluetooth_dev sudo -u pi pactl load-module module-loopback source=$bluez_dev sink=$AUDIOSINK rate=44100 adjust_time=0 >> /var/log/bluetooth_dev fi fi done fi if [ "$ACTION" = "remove" ] then # Turn on bluetooth discovery if device disconnects sudo hciconfig hci0 piscan # Turn on bluetooth auto discovery sudo /usr/lib/udev/bluetooth-auto & fi
Next, create the bluetooth auto discovery script by adding the following to /usr/lib/udev/bluetooth-auto:
#!/bin/bash
while [ true ]
do
sleep 1
echo "Scanning for trusted devices" >> /var/log/bluetooth_dev
for mac in $(sudo hcitool scan | grep ":" | awk '{print $1}')
do
trust=$(grep "$mac" /usr/lib/udev/bluetooth-trust)
if [ ! -z "$trust" ]
then
_BT_ADAPTER=`dbus-send --system --print-reply --dest=org.bluez / org.bluez.Manager.DefaultAdapter|awk '/object path/ {print $3}'`
BT_ADAPTER=${_BT_ADAPTER//\"/}
mac_underscore=$(echo "$mac" | sed 's/:/_/g')
echo "Connecting to device at: $mac" >> /var/log/bluetooth_dev
sudo dbus-send --print-reply --system --dest=org.bluez $BT_ADAPTER/dev_$mac_underscore org.bluez.AudioSource.Connect >> /var/log/bluetooth_dev
exit 0
fi
done
done
Finally, grant the appropiate permissions to these script files by issuing the following commands:
sudo chmod 774 /usr/lib/udev/bluetooth sudo chmod 774 /usr/lib/udev/bluetooth-auto
At this point, you can restart your pi and and pair the device to play music.
Streaming the audio over FM
Firstly, you'll need to enable HDMI audio output. You can do this by using executing sudo raspi-config at the command line and following the on-screen <a href="https://www.raspberrypi.org/documentation/configuration/raspi-config.md" title="instructions">instructions</a> or make the following changes to /boot/config.txt and reboot:
Uncomment the following files to /boot/config.txt:
hdmi_force_hotplug=1 hdmi_drive=2
Next, reboot the RPi.
At this stage, you'll need to download the <a title="RPi FM Transmitting Software" href="http://www.icrobotics.co.uk/wiki/index.php/Turning_the_Raspberry_Pi_Into_an_FM_Transmitter">FM transmitter Software</a>. You can do this by following these steps:
sudo apt-get install sox libsox-fmt-all cd ~ && mkdir -p fm && cd fm wget http://www.icrobotics.co.uk/wiki/images/c/c3/Pifm.tar.gz tar -zxvf Pifm.tar.gz
You can test the software by running the following. You will need to either connect a basic wire to act as an antenna to GPIO4 (pin 7) or just place the pi on top of a radio. Test the Pifm script by executing the following:
cd ~/fm && sudo ./pifm sound.wav 87.7
You can also test streaming an mp3 using the following (you'll need ffmpeg installed):
ffmpeg -i input.mp3 -f s16le -ar 22.05k -ac 1 - | sudo ./pifm -
At this stage, you'll need to modify the /usr/lib/udev/bluetooth script and replace it with the following:
#!/bin/bash AUDIOSINK="alsa_output.0.analog-stereo" ACTION=$(expr "$ACTION" : "\([a-zA-Z]\+\).*") echo "Executing bluetooth script...|$ACTION|" >> /var/log/bluetooth_dev if [ "$ACTION" = "add" ] then # Turn off BT discover mode before connecting existing BT device to audio hciconfig hci0 noscan # Turn off BT auto connect if it is still running sudo killall bluetooth-auto # set the audio output to the hdmi amixer cset numid=3 2 # Set volume level to 100 percent amixer set Master 100% # Set sink volume to 125% pacmd set-sink-volume 0 0x12500 for dev in $(find /sys/devices/virtual/input/ -name input*) do if [ -f "$dev/name" ] then mac=$(cat "$dev/name") # Add this mac address to list of trusted addresses TRUST=$(grep "$mac" /usr/lib/udev/bluetooth-trust) if [ -z "$TRUST" ] then echo "Adding $mac to trusted addresses" >> /var/log/bluetooth_dev echo $mac >> /usr/lib/udev/bluetooth-trust fi mac_underscore=$(cat "$dev/name" | sed 's/:/_/g') bluez_dev=bluez_source.$mac_underscore # Set source volume to 125% pacmd set-source-volume $bluez_dev 0x12500 sleep 1 CONFIRM=`sudo -u pi pactl list short | grep $bluez_dev` if [ ! -z "$CONFIRM" ] then echo "Setting bluez_source to: $bluez_dev" >> /var/log/bluetooth_dev echo pactl load-module module-loopback source=$bluez_dev sink=$AUDIOSINK rate=44100 adjust_time=0 >> /var/log/bluetooth_dev sudo -u pi pactl load-module module-loopback source=$bluez_dev sink=$AUDIOSINK rate=44100 adjust_time=0 >> /var/log/bluetooth_dev echo "Killing any existing radio connections" >> /var/log/bluetooth_dev sudo killall pifm >> /var/log/bluetooth_dev echo "Connecting bluetooth output to radio input, playing on 87.7" >> /var/log/bluetooth_dev # Using $AUDIOSINK instead of 0 here doesn't seem to work, not sure why echo pacat -r -d 0 --latency-msec=50 | sox -t raw -r 44100 -e signed-integer -b 16 -c 2 - -t wav - gain -l 10 | sudo /home/pi/fm/pifm - 87.7 44100 stereo >> /var/log/bluetooth_dev sudo -u pi pacat -r -d 0 --latency-msec=50 | sudo -u pi sox -t raw -r 44100 -e signed-integer -b 16 -c 2 - -t wav - gain -l 10 | sudo /home/pi/fm/pifm - 87.7 44100 stereo >> /var/log/bluetooth_dev fi fi done fi if [ "$ACTION" = "remove" ] then # Turn on bluetooth discovery if device disconnects sudo hciconfig hci0 piscan # Turn on bluetooth auto discovery sudo /usr/lib/udev/bluetooth-auto & fi
Note: You'll notice above, I attempted to stream over FM using sox to convert the signal. I've also tried ffmpeg as described earlier but both methods resulted in audio that was playing at roughly half-speed. I've attempted playing around with the frequency (changing from 44.1Hz to 44.8Hz above and below with no joy. IF you figure it out, do let me know.
Next, replace the /etc/init.d/bluetooth-agent script with the following. Note the additional lines to enable pifm at the bottom of the start case.
#!/bin/sh
#/etc/init.d/bluetooth-agent
### 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
USER=root
HOME=/root
export USER HOME
case "$1" in
start)
echo "initializing pulseaudio"
sudo pactl info
echo "setting bluetooth discoverable"
sudo hciconfig hci0 piscan
start-stop-daemon -S -x /usr/bin/bluetooth-agent -c pi -b -- 0000
echo "bluetooth-agent started pw: 0000"
sudo /usr/lib/udev/bluetooth-auto &
echo "bluetooth-auto-discovery started"
sudo /home/pi/fm/pifm /home/pi/fm/silence 87.7 44100 stereo
echo "pifm started at 87.7, playing silence"
;;
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
Set a static IP address
As my RPi won't be connected wirelessly to any network and it wasn't positioned physically close enough to the router to run a cable, I wanted to set a static IP so I could connect up an Ethernet cable and login over SSH if necessary.
To do this, I simply modified /etc/network/interfaces and made the following changes for eth0. Replace the eth0 auto line with the following:
iface eth0 inet static address 192.168.1.100 netmask 255.255.255.0 network 192.168.1.0 broadcast 192.168.1.255 gateway 192.168.1.254
You can reboot the RPi and connect up a cable directly. Don't forget to set your own machines network interface to some IP in the same range (e.g. 192.168.1.10).
Read only SD – stop disk corruption
To ensure that the disk wouldn't become corrupted when switching off the power, I attempted to make the root drive read-only. I also created a tmpfs partition for /tmp (around 30MB) in size as some processes need to write files to /tmp to operate correctly. To do this, I made the following changes to the /etc/fstab file:
/dev/mmcblk0p2 / ext4 defaults,noatime,ro 0 1 tmpfs /tmp tmpfs defaults,size=30M 0 0
If you do need to make a change to a file, you cna remount the / partition as write, make your necessary changes and reboot:
sudo mount / -o remount,rw
Note: This didn’t work with the audiopulse server. While I was able to connect my bluetooth device successfully, it failed to send the audio correctly. I’m still playing see if I can enable some sort of fuax-write for specific services such as audiopulse in /tmp.
