Friday, January 29, 2016

Multiroom Audio using MPD, Pulseaudio and Bluetooth


Almost all my activities (for example cooking, working on projects or playing games) have one thing in common: Theres music running in the background.
Usually this is some sort of FM radio running a local radio channel. The drawbacks are obvious: You have no control over what music is runing and the audio quality is far from great.
I started to ponder what to do about it. My requirements were simple:
  • Choose the music myself: These days, most of what i listen to is either internet radio, or a music streaming service like Spotify or Google Play Music
  • Reasonable quality: I am not an audiophile, but at least i do not want to have audible artifacts or noise when i walk past an antenna or something
  • Easy installation: I have no intent to run speaker wires to every room
  • Price: I dont want to spend hundreds of euros and for example buy Sonos speakers for all my rooms. At 230 Euros a piece, this would become really expsive really fast

Hardware

My system is built on the basis of bluetooth speakers.
I chose to use the Xiaomi Square Box for this, because it is quite cheap. But any bluetooth speaker would do. Of course the Xiaomi Box can not compete with expensive speakers like the Sonos or Bose systems, but at around 20 Euros incl. shipping, it is not bad. I even got one of these on a sale for 13 Euros incl. shipping.
The speakers have a builtin battery, so you could carry it around the house. But since i planned on just throwing this on a cupboard, i run it of a cheap phone charger.
Next thing i bought was a bluetooth dongle, which i plugged into my home server. In my case, the home server is an HP Microserver Gen8. But you could easily run this of a Rasperry Pi.

Software

For this to work, you need any current linux distribution. I amrunning Archlinux, because i like to tinker with new stuff, receive updates fast and take care of my system.
If you run some form of Ubuntu or Debian, for example Raspbian on a Raspi, the steps will be roughly the same. But since Ubuntu uses upstart where archlinux is based on systemd, you will have to improvise on that point.
To play my music, i currently use the Music Player Deamon, short MPD. The get support for spotify and the likes, i plan to change to mopidy. I just have not gotten around to it yet.
I use openHAB for my home automation, so the controls for the music will also be implemented there.

Steps to make it happen

Connecting bluetooth devices

Use bluetoothctl to turn on your bluetooth controller, pair, trust and connect to the speakers. Make sure to replace the mac address of your speakers.
% bluetoothctl
[bluetooth]# power on
Changing power on succeeded
[bluetooth]# scan on
Device A0:E9:DB:51:3B:C5 Mi Bluetooth Speaker
[bluetooth]# pair A0:E9:DB:51:3B:C5
...
[bluetooth]# trust A0:E9:DB:51:3B:C5
...
[bluetooth]# pair A0:E9:DB:51:3B:C5
...

Make sure your bluetooth controller is powered on after a reboot. On archlinux, this is done using a simple udev rule in /etc/udev/rules.d/20-bluetooth.rules containing this:
# Set bluetooth power up
ACTION=="add", SUBSYSTEM=="bluetooth", KERNEL=="hci[0-9]*", RUN+="/usr/bin/hciconfig %k up"
Make sure everything after ACTION is one line.

Pulseaudio

Enable pulseaudio as a permanently running service (not socket activated) and start it.
% systemctl --user enable pulseaudio.service
% systemctl --user start pulseaudio.service
Also make sure your user session is started on boot.
% sudo systemctl enable user@1000.service
Where 1000 is the user id of your user. You can get this id by running the program “id” as the corresponding user:
% id
uid=1000(tlan) gid=1000(tlan) groups=1000(tlan),7(lp),10(wheel),91(video),92(audio),1001(download)

(If groups does not contain audio)
% sudo gpasswd -a <username> audio
This is also the time to make sure your user is part of the group “audio” and if not, add him.
If everything went according to plan, pulseaudio should have automatically added your bluetooth devices as sinks. You can check using “pacmd”
% pacmd list-sinks
1 sink(s) available.
  * index: 1
    name: <bluez_sink.A0_E9_DB_51_3B_C5>
    driver: <module-bluez5-device.c>
    flags: HARDWARE DECIBEL_VOLUME LATENCY FLAT_VOLUME
    state: SUSPENDED
    suspend cause: IDLE
    priority: 9030
    volume: front-left: 51773 / 79% / -6.14 dB, front-right: 51773 / 79% / -6.14 dB
    balance 0.00
    base volume: 65536 / 100% / 0.00 dB
    volume steps: 65537
    muted: no
    ...
    properties:
      bluetooth.protocol = "a2dp_sink"
      device.description = "Mi Bluetooth Speaker"
      device.string = "A0:E9:DB:51:3B:C5"
      ...

(Optional, to set an upper limit for the volume)
% pacmd set-sink-volume bluez_sink.A0_E9_DB_51_3B_C5 50000
By setting the sink volume a bit under the maximum value of 65k and controlling the volume in my player software independently from pulseaudio, i effectively limit the maximum volume that can be set to not overload my speakers.
Add the line “load-module module-switch-on-connect” to /etc/pulse/default.pa to make sure pulseaudio automatically connects to your speakers after a reboot.
You should now be able to play music and hear it out of your bluetooth speakers. If you do not specify a sink, pulseaudio will output everything using the default sink.

MPD

I installed MPD and created the configuration files for each room in ~/.config/mpd:
% touch kitchen.{conf,database,log,pid}
Set up everything needed in the conf file. Make sure the files, especially the pid file, are unique for each configuration.  Change the paths to music and playlists to your needs
music_directory "/media/virtual/Music/mp3" 
playlist_directory "/media/virtual/Music/playlist"
db_file "~/.config/mpd/kitchen.database" 
log_file "~/.config/mpd/kitchen.log" 
pid_file "~/.config/mpd/kitchen.pid" 
 
port "6600" 
 
audio_output { 
 type "pulse" 
 name "Kitchen" 
 sink "bluez_sink.A0_E9_DB_51_3B_C5" 
}
Every instance of MPD has its own database. If you dont want this, see the Archwiki: Music Player Daemon – Satellite_setup.
The I created a special parameterized systemd unit in ~/.config/systemd/user/mpd@.service to run multiple MPDs with a custom configuration each
Description=Music Player Daemon
After=network.target sound.target

[Service]
ExecStart=/usr/bin/mpd --no-daemon %h/.config/mpd/%I.conf

[Install]
WantedBy=default.target
Then, have systemd reload config files, enable an instance of this configuration and start it
% systemctl --user daemon-reload
% systemctl --user enable mpd@kitchen
% systemctl --user start mpd@kitchen
You should now be able to play music using mpd to your bluetooth speakers. Use any MPD client to test this.

OpenHAB

OpenHAB comes with a binding for MPD. Bindings are plugins, that extend openHABs features for IoT devices. I use the binding to start/stop the music and control the volume.
Unfortunately, the plugin does not have features builtin to control playlists. So i decided just to use the exec-Binding, which allows you to run system commands, and the mpc client to load playlists.
Alternatively, i could have used a WebView, to display some sort of Webinterface for MPD in an iframe. But since right now, i only need to choose between two different playlists, i did not bother.
OpenHAB users will be familiar with the configuration layout, so i will just post the config files here and not go into much detail:
 % cat Music.items configurations/items raspberrypi Switch Mpd_Kitchen_StartStop "Start/Stop" (Kitchen,Radio) { mpd="ON:kitchen:play, OFF:kitchen:stop" }
Dimmer Mpd_Kitchen_VolumeControl "Volume [%d%%]" (Kitchen,Radio) { mpd="INCREASE:kitchen:volume_increase, DECREASE:kitchen:volume_decrease, PERCENT:kitchen:volume" }
String Mpd_Kitchen_Channel_Selection "Sender" (Kitchen,Radio) { autoupdate="false" }
String Mpd_Kitchen_Channel (Kitchen,Radio) { exec=">[CLEAR:mpc -h tlan-server.local -p 6600 clear] >[1LIVE:mpc -h tlan-server.local -p 6600 load 1live] >[WMW:mpc -h tlan-server.local -p 6600 load wmw]" }

 % cat default.sitemap
....
Frame label="Musik" { 
 Switch item=Mpd_Kitchen_StartStop 
 Slider item=Mpd_Kitchen_VolumeControl 
 Switch item=Mpd_Kitchen_Channel_Selection mappings=[ "1live"="Eins Live", "wmw"="WMW"]
}
....
 
 % cat Musik.rules configurations/rules raspberrypi import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import org.joda.time.DateTime
import java.lang.Thread

rule "Change channel"
when
 Item Mpd_Kitchen_Channel_Selection received command
then
 var channel = receivedCommand

 sendCommand( "Mpd_Kitchen_StartStop", "OFF")
 Thread::sleep(50)
 sendCommand( "Mpd_Kitchen_Channel", "CLEAR")
 Thread::sleep(50)

 switch channel {
 case '1live': sendCommand( "Mpd_Kitchen_Channel", "1LIVE")
 case 'wmw': sendCommand( "Mpd_Kitchen_Channel", "WMW")
 }

 Thread::sleep(50)
 sendCommand( "Mpd_Kitchen_StartStop", "ON")

end
Some notes:
  • Since (i think) openHAB processes events asynchronously, i had to add some Thread::sleep between commands to MPD
  • MPD behaved kind of strange when just loading playlists. So to make sure i dont mix up my playlists, i first stop music playback, then clear the current playlist, load the new playlist, and turn playback back on. This happens fast enough so you do not notice it, but fixed a lot of weirdness when controlling it.

Conclusion

Right now, i only have one speaker connected. But adding more is just a matter of connecting, copying configuration files and starting services. Controlling it is easy enough, everybody on my house can handle it.
Everything works great!

Further steps

As mentioned before, to support streaming services like spotify etc. i am going to switch to mopidy, although i will have to check how different the configuration will be.
Since the server is available as a bluetooth device, you can connect your smartphone to it and have sound from your phone come out of your speaker in any room. I am still looking nito what it would take to have an easy interface to choose which input to send to which output.
Since the bluetooth speakers are just regular pulseaudio sinks, you can send other stuff to them. For example, i installed espeak, a text-to-speech software. With it, you can have the server tell you “Hey, you’ve got mail!’ whenever a new email arrives.
Or, going further down the home automation route: You could for example use the openHAB weather binding check for rain and if rain is expected, warn you to close the windows in rooms where windows are open.
If you used bluetooth speakers which also offer handsfree features and have a microphone, you could even built something like an always listening voice assistant.

No comments:

Post a Comment