Skip to content

Instantly share code, notes, and snippets.

@joleuger
Last active December 30, 2020 23:00
Show Gist options
  • Save joleuger/3a56cbb15d95fbc5b127 to your computer and use it in GitHub Desktop.
Save joleuger/3a56cbb15d95fbc5b127 to your computer and use it in GitHub Desktop.
Audioserver with Pulseaudio and SystemD/LoginD User Lingering (allows bluetooth)

For a tool, which executes a configurable command when a bluetooth device connects, see repository https://github.com/joleuger/bluetooth-manager. It can be used to play an icecast stream.

> apt-get install libpam-systemd
> apt-get install pulseaudio gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-tools mpv
> apt-get install bluetooth bluez bluez-tools bluez-firmware pulseaudio-module-bluetooth 
> adduser audioclient
> vim /etc/shadow (remove password)
> usermod -a -G bluetooth audioclient
> usermod -a -G audio audioclient
> udevadm control --reload
> udevadm trigger

Reason: The file /etc/dbus-1/system.d/bluetooth.conf requires it.

Quote:

  <!-- allow users of bluetooth group to communicate with hcid -->
  <policy group="bluetooth">
    <allow send_destination="org.bluez"/>
  </policy>
> ssh-keygen -t rsa -b 4096 -C "audioclientx"
> mkdir /home/audioclient/.ssh
> cat id_rsa.pub >> /home/audioclient/.ssh/authorized_keys
> chown -R audioclient:audioclient /home/audioclient/.ssh
> ssh -l audioclient localhost
> cd 
> mkdir .config/
> mkdir .config/systemd/
> mkdir .config/systemd/user
> mkdir .config/pulse
> cp /etc/pulse/default.pa /home/audioclient/.config/pulse/default.pa
> cp /etc/pulse/daemon.conf /home/audioclient/.config/pulse/daemon.conf

test
> mpv http://$$$IP-ADDRESS$$$:8000/egofm.mp3

Add to file /home/audioclient/.config/pulse/daemon.conf

default-script-file = /home/audioclient/.config/pulse/default.pa

Add to file /home/audioclient/.config/pulse/default.pa

load-module module-null-sink rate=44100 channels=2 sink_name=every_local_output
set-default-sink every_local_output
load-module module-loopback source=every_local_output.monitor sink=alsa_output.platform-bcm2835_AUD0.0.analog-stereo

#disable: load-module module-suspend-on-idle

Add files dbus.service dbus.socket audioserver_pulseaudio.service to .config/systemd/user (adapt audioserver->audioclient)

> systemctl --user enable dbus.socket
> systemctl --user enable audioclient_pulseaudio.service
> systemctl --user start audioclient_pulseaudio.service
> exit
> loginctl enable-linger audioclient
> systemctl disable lightdm
> systemctl disable mpd.socket
> systemctl disable mpd.service
> mkdir /home/audioclient/.config/mpd

Mdp

> cp /etc/mpd.conf /home/audioclient/.config/mpd/mpd.conf
> chown -R audioclient:audioclient /home/audioclient/.config
> mkdir /var/log/mpd
> chown -R audioclient:audioclient /var/lib/mpd
> chown -R audioclient:audioclient /var/log/mpd
> ssh -l audioclient localhost
> vim /home/audioclient/.config/mpd/mpd.conf (disable line user; pulse:disable localhost; disable log_file; disable pid_file)
> systemctl --user enable audioclient_mpd
> reboot

Snapclient (currently without pulse and bluetooth)

x64:
> apt-get install libasound2 libvorbis0a libogg0 libflac8 alsa-utils libavahi-client3 avahi-daemon
> wget https://github.com/badaix/snapcast/releases/download/v0.10.0/snapclient_0.10.0_amd64.deb
> dpkg-deb -x snapclient_0.10.0_amd64.deb snapclient_extracted
> cp snapclient_extracted/usr/sbin/snapclient /usr/local/bin/
> rm -rf snapclient_extracted
raspberry
> apt-get install libasound2 libvorbis0a libogg0 libflac8 alsa-utils libavahi-client3 avahi-daemon
> wget https://github.com/badaix/snapcast/releases/download/v0.10.0/snapclient_0.10.0_armhf.deb
> dpkg-deb -x snapclient_0.10.0_armhf.deb snapclient_extracted
> cp snapclient_extracted/usr/sbin/snapclient /usr/local/bin/
> rm -rf snapclient_extracted

Dependencies of packacke: > dpkg -I snapclient_0.9.0_armhf.deb

systemctl --user enable audioclient_snapcast.service
systemctl --user start audioclient_snapcast.service

Bluetooth (initial)

> ssh -l audioclient localhost
> bluetoothctl
# scan on
# pairable on
# connect 00:1D:DF:A1:45:03 (address of bluetooth hw)
# exit
> pactl list
> pactl load-module module-loopback source=every_local_output.monitor sink=bluez_sink.00_1D_DF_A1_45_03

Note to Bluetooth: Works, but "connect 00:1D:DF:A1:45:03" must be called every time and "pactl load-module module-loopback...bluez_sink.00_1D_DF_A1_45_03". On disconnect module-loopback is remapped to different sink (thus echo). Module must be removed. TODO: Automate, see https://gist.github.com/joergschiller/1673341/) https://gist.github.com/Toasty27/8406352

TODO

enable bluetooth clients

pulsesrc device=broadcast_on_lan.monitor ! pulsesink device=bluetooth...

For a script, which performs every action automatically, see file audioserver/install-files/install/install.sh in the repository https://github.com/joleuger/audioserver

Add user audioserver Do same stuff for ssh and optionally add bluetooth

> apt-get install pulseaudio libpam-systemd
> cp /etc/pulse/default.pa /home/audioserver/.config/pulse/default.pa
> cp /etc/pulse/daemon.conf /home/audioserver/.config/pulse/daemon.conf

Add to file /home/audioserver/.config/pulse/daemon.conf

default-script-file = /home/audioserver/.config/pulse/default.pa

Add to file /home/audioserver/.config/pulse/default.pa

load-module module-native-protocol-unix # (must be enabled)
# load-module module-suspend-on-idle (must be disabled)
load-module module-null-sink rate=48000 channels=2 sink_name=broadcast_on_lan
set-default-sink broadcast_on_lan

## And disable everything not needed

There are two ways to connect pulseaudio with snapcast. Either use audioserver_to_snapcast.service or append following lines to /home/audioserver/.config/pulse/default.pa.

load-module module-pipe-sink channels=2 rate=48000 format=s16 file=/run/user/1002/snapfifo sink_name=snapcast   #here you need to change 1002 to the uid of the audioserver user
update-sink-proplist snapcast device.description=Snapcast
load-module module-loopback source=broadcast_on_lan.monitor sink=snapcast rate=48000
#remix=false adjust_time=0 latency_msec=100

Add services below to /home/audioserver/.config/systemd/user/ Don't forget to edit $$$ipaddress$$$

Enable services. Login as audioserver. su audioserver does not work (see https://www.phoronix.com/scan.php?page=news_item&px=Systemd-Own-su-Command). Use "ssh -l audioserver localhost" or "machinectl --uid=audioserver shell" instead. Be sure libpam-systemd is used

apt-get install shairport-sync
systemctl disable shairport-sync
systemctl stop shairport-sync
#cp /etc/shairport-sync.conf /home/audioserver/
> loginctl enable-linger audioserver
> mkdir /home/audioserver/.ssh
> cat /root/.ssh/id_rsa.pub >> /home/audioserver/.ssh/authorized_keys
> chown -R audioserver:audioserver /home/audioserver/.config
> ssh -l audioserver localhost
> systemctl --user disable pulseaudio.socket
> systemctl --user enable dbus.socket
> systemctl --user enable audioserver_pulseaudio.service
> systemctl --user enable audioserver_shairport.service
> systemctl --user enable audioserver_to_icecast.service
> systemctl --user enable audioserver_to_snapcast.service
> systemctl --user enable audioserver_restart_broadcast.timer
> systemctl --user start audioserver_pulseaudio.service
> systemctl --user start audioserver_shairport.service
> systemctl --user start audioserver_to_icecast.service
> systemctl --user start audioserver_to_snapcast.service
> systemctl --user start audioserver_restart_broadcast.timer
> systemctl --user status
> exit
> journalctl /usr/bin/pulseaudio
> journalctl --user-unit audioserver_pulseaudio.service

For now DBus (see https://wiki.archlinux.org/index.php/Systemd/User#D-Bus):

> go get github.com/godbus/dbus
> export DBUS_SESSION_BUS_ADDRESS=unix:path=$XDG_RUNTIME_DIR/bus
> printenv
> qdbus org.PulseAudio1 /org/pulseaudio/server_lookup1 org.PulseAudio.ServerLookup1.Address
> qdbus --address unix:path=/run/user/1002/pulse/dbus-socket
> qdbus
Mopidy
apt-get install rygel rygel-playbin rygel-gst-launch
ssh -l audioserver localhost / machinectl shell --uid audioserver

Use Rygel for remote control with upnp over mpris
vim ~/.config/rygel.conf

> systemctl --user start audioserver_reload_mopidy.timer
> systemctl --user enable audioserver_reload_mopidy.timer
> systemctl --user start audioserver_mopidy.service
> systemctl --user enable audioserver_rygel.service
> systemctl --user start audioserver_rygel.service
> systemctl --user enable audioserver_mopidy.service
> sudo pip install -U mopidy Mopidy-MPRIS
Install Icecast
> apt-get install icecast2
Web-View
http://$$$ipaddress$$$:8000/
> ffmpeg -f lavfi -i aevalsrc=0 -t 5 /usr/share/icecast2/web/silence.mp3


    
> apt-get install gstreamer1.0-plugins-ugly lame gstreamer1.0-plugins-good

> cp snapserver /usr/local/bin
> ssh -l audioserver localhost
> systemctl --user enable audioserver_snapcast.service
> systemctl --user start audioserver_snapcast.service

mpv -ao pulse::broadcast_on_lan http://$$$IP-ADDRESS$$$:8000/egofm.mp3
mpv -ao pulse::broadcast_on_lan /usr/share/sounds/alsa/Noise.wav
For broadcast_on_lan in alsa:
Add home_audioserver_.asoundrc as /home/audioserver/.asoundrc

Test:
aplay /usr/share/sounds/alsa/Noise.wav

Spotify

apt-get install cargo
https://github.com/plietar/librespot.git
cd librespot
cargo build --release

Forked-Daapd

sudo apt-get install   build-essential git autotools-dev autoconf libtool gettext gawk gperf   antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev   libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev   libasound2-dev libmxml-dev libgcrypt11-dev libavahi-client-dev zlib1g-dev libjson-c-dev libpulse-dev  libevent-dev
git clone https://github.com/ejurgensen/forked-daapd.git
autoreconf -i
./configure --with-pulseaudio  --enable-spotify  --localstatedir=/var
make
make install
machinectl shell --uid audioserver
mkdir /home/audioserver/.config/forked-daapd/
cd /home/audioserver/.config/forked-daapd/
cp /usr/local/etc/forked-daapd.conf .
mkdir /var/run/user/1002/forked-daapd
mkdir /home/audioserver/.cache/forked-daapd
vim forked-daapd.conf
# change everything. Important: uid=audioserver

forked-daapd -f -c forked-daapd.conf

##TODO

[Unit]
Description=mpd audioclient
Requires=audioclient_pulseaudio.service
[Service]
Type=simple
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioclient/.config/pulse/daemon.conf
ExecStartPre=/bin/sleep 3
ExecStart=/usr/bin/mpd --no-daemon /home/audioclient/.config/mpd/mpd.conf
[Install]
WantedBy=default.target
[Unit]
Description=Snapcast client
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/snapclient
#PIDFile=/var/run/snapclient.pid
StandardOutput=null
[Install]
WantedBy=default.target
[Unit]
Description=pulseaudio audioserver, forked dappd
Requires=audioserver_pulseaudio.service
[Service]
Type=simple
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioserver/.config/pulse/daemon.conf
ExecStartPre=/bin/sleep 3
ExecStart=/usr/local/sbin/forked-daapd -f -c /home/audioserver/.config/forked-daapd/forked-daapd.conf
[Install]
WantedBy=default.target
[Unit]
Description=pulseaudio audioserver, soundclient mopidy
Requires=audioserver_pulseaudio.service
[Service]
Type=simple
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioserver/.config/pulse/daemon.conf
ExecStartPre=/bin/sleep 3
#ExecStartPre=/usr/local/bin/mopidy local scan #not necessary when using mopidy sqlite storage engine
ExecStart=/usr/local/bin/mopidy
[Install]
WantedBy=default.target
[Unit]
Description=pulseaudio audioserver
Requires=dbus.socket
[Service]
Type=dbus
BusName=org.PulseAudio1
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioserver/.config/pulse/daemon.conf
#Environment=PULSE_LATENCY_MSEC=60 #maybe possible to erase, maybe set to 30
StandardOutput=null
ExecStartPre=/bin/sh -c "( [ -e /run/user/1002/snapfifo ] || mkfifo /run/user/1002/snapfifo )"
ExecStart=/usr/bin/pulseaudio -vv
# more output: ExecStart=/usr/bin/pulseaudio -vvv
[Install]
WantedBy=default.target
[Unit]
Description=Reload mopidy
[Service]
Type=simple
#ExecStart=/bin/systemctl --user restart audioserver_mopidy
ExecStart=/usr/local/bin/mopidy local scan
[Unit]
Description=Reload mopidy database daily
[Timer]
OnCalendar=daily
Persistent=false
[Install]
WantedBy=timers.target
[Unit]
Description=Restart broadcast
[Service]
Type=simple
ExecStart=/bin/sh -c "/bin/systemctl --user stop audioserver_to_icecast.service audioserver_to_snapcast.service audioserver_snapcast.service ; dd if=/run/user/1002/snapfifo iflag=nonblock of=/dev/null ; /bin/systemctl --user start audioserver_to_icecast.service audioserver_to_snapcast.service audioserver_snapcast.service"
[Unit]
Description=Restart broadcast daily
[Timer]
OnCalendar=3:00
Persistent=false
[Install]
WantedBy=timers.target
[Unit]
Description=rygel uPnp
Requires=audioserver_pulseaudio.service
[Service]
Type=simple
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioserver/.config/pulse/daemon.conf
ExecStartPre=/bin/sleep 3
ExecStart=/usr/bin/rygel
[Install]
WantedBy=default.target
[Unit]
Description=pulseaudio audioserver, sound receiver for airplay requests
Requires=audioserver_pulseaudio.service
[Service]
Type=simple
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioserver/.config/pulse/daemon.conf
ExecStartPre=/bin/sleep 3
#ExecStartPre=/bin/sh -c "timeout 1 dd if=/run/user/1002/snapfifo of=/dev/null || true"
ExecStart=/usr/bin/shairport-sync --name Broadcast --output pulse -- -s broadcast_on_lan -n AirPlay
[Install]
WantedBy=default.target
[Unit]
Description=Snapcast server
[Service]
Type=simple
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioserver/.config/pulse/daemon.conf
ExecStartPre=/bin/sleep 3
StandardOutput=null
#way 1: the direct pulseaudio way (read) or pulseaudio.service creates fifo
ExecStart=/usr/local/bin/snapserver -s pipe:///run/user/1002/snapfifo?name=BroadcastOnLan&sampleformat=48000:16:2&mode=read #here you need to change 1002 to the uid of the audioserver user
#way 2: the gstreamer way (create), when pulseaudio.service does not create fifo
#ExecStart=/usr/local/bin/snapserver -s pipe:///run/user/1002/snapfifo?name=BroadcastOnLan&sampleformat=48000:16:2&mode=create #here you need to change 1002 to the uid of the audioserver user
[Install]
WantedBy=default.target
[Unit]
Description=pulseaudio audioserver, stream to icecast
Requires=audioserver_pulseaudio.service
[Service]
Type=simple
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioserver/.config/pulse/daemon.conf
ExecStartPre=/bin/sleep 5
ExecStart=/usr/bin/gst-launch-1.0 pulsesrc device=broadcast_on_lan.monitor ! audioconvert ! lamemp3enc target=1 bitrate=256 cbr=true ! shout2send ip=127.0.0.1 port=8000 password=PASSWORD mount=broadcast_on_lan.mp3
[Install]
WantedBy=default.target
# See http://hstuart.dk/2010/11/07/gstreamer-and-pulseaudio-to-a-squeezebox/
[Unit]
Description=pulseaudio audioserver, stream to snapcast
Requires=audioserver_pulseaudio.service
Requires=audioserver_snapcast.service
[Service]
Type=simple
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioserver/.config/pulse/daemon.conf
ExecStartPre=/bin/sleep 5
ExecStart=/usr/bin/gst-launch-1.0 pulsesrc device=broadcast_on_lan.monitor ! audioconvert ! audio/x-raw,rate=48000,channels=2,format=S16LE ! wavenc ! filesink location=/run/user/1002/snapfifo
[Install]
WantedBy=default.target
[Unit]
Description=pulseaudio audioserver, soundclient vlc
Requires=audioserver_pulseaudio.service
[Service]
Type=simple
Environment=DBUS_SESSION_BUS_ADDRESS=unix:path=%t/bus
Environment=PULSE_CONFIG=/home/audioserver/.config/pulse/daemon.conf
ExecStartPre=/bin/sleep 3
ExecStart=/usr/bin/cvlc pulse://broadcast_on_lan.monitor --http-port=5003 --sout-vorbis-quality=9 --sout '#transcode{acodec=vorb,aenc=vorbis}:standard{access=http,mux=ogg,dst=$$$ipaddress$$$:5003/broadcast_on_lan}' -vvv
#Alternatives:
#ExecStart=/usr/bin/cvlc pulse://broadcast_on_lan.monitor --http-port=5003 --sout '#transcode{acodec=mp3,ab=256}:rtp{dst=224.1.1.1,port=5000,sdp=http://$$$ipaddress$$$:5003/broadcast_on_lan.sdp}'
#ExecStart=/usr/bin/cvlc pulse://broadcast_on_lan.monitor --http-port=5003 --sout-vorbis-quality=9 --sout '#transcode{acodec=vorb,aenc=vorbis}:standard{access=http,mux=ogg,dst=$$$ipaddress$$$:5003/broadcast_on_lan}' -vvv
#Vlc audioserver client
#ExecStart=/usr/bin/vlc -vvv http://$$$ipaddress$$$:5003/broadcast_on_lan.sdp
#ExecStart=/usr/bin/vlc -vvv http://$$$ipaddress$$$:5003/broadcast_on_lan
[Install]
WantedBy=default.target
[Unit]
Description=D-Bus User Message Bus
Documentation=man:dbus-daemon(1)
Requires=dbus.socket
[Service]
ExecStart=/usr/bin/dbus-daemon --session --address=systemd: --nofork --nopidfile --systemd-activation
ExecReload=/usr/bin/dbus-send --print-reply --session --type=method_call --dest=org.freedesktop.DBus / org.freedesktop.DBus.ReloadConfig
[Install]
Also=dbus.socket
[Unit]
Description=D-Bus User Message Bus Socket
[Socket]
ListenStream=%t/bus
[Install]
WantedBy=sockets.target
Also=dbus.service
#EXTM3U
http://$$$IP-ADDRESS$$$:8000/egofm.mp3
# Derived from https://www.archlinux.org/packages/extra/any/pulseaudio-alsa/
# When you use it, you should set the alsa sink in /home/audioclient/.config/pulse/default.pa explicitly
# Use PulseAudio by default
pcm.!default {
type pulse
fallback "sysdefault"
hint {
show on
description "Default ALSA Output (currently PulseAudio Sound Server)"
}
}
ctl.!default {
type pulse
fallback "sysdefault"
}
# vim:set ft=alsaconf:
# See /usr/share/doc/libasound2-plugins/README-pulse
pcm.!default {
type pulse
device broadcast_on_lan
hint {
show on
description "broadcast_on_lan Output (PulseAudio)"
}
}
ctl.!default {
type pulse
device broadcast_on_lan
}
Config of /etc/icecast2/icecast.xml (excerpt)
<source-password>$$$changeme$$$</source-password>
<listen-socket>
<port>8000</port>
<!-- <bind-address>127.0.0.1</bind-address> -->
<!-- <shoutcast-mount>/stream</shoutcast-mount> -->
<shoutcast-mount>/mopidy.mp3</shoutcast-mount>
<shoutcast-mount>/silence.mp3</shoutcast-mount>
<shoutcast-mount>/broadcast_on_lan.mp3</shoutcast-mount>
</listen-socket>
<relays-on-demand>1</relays-on-demand>
<relay>
<server>mp3ad.egofm.c.nmdn.net</server>
<port>80</port>
<mount>/ps-egofm_192/livestream.mp3</mount>
<local-mount>/egofm.mp3</local-mount>
<on-demand>1</on-demand>
<relay-shoutcast-metadata>1</relay-shoutcast-metadata>
</relay>
<mount>
<mount-name>/broadcast_on_lan.mp3</mount-name>
<fallback-mount>/silence.mp3</fallback-mount>
<fallback-override>1</fallback-override>
</mount>
<mount>
<mount-name>/silence.mp3</mount-name>
<dump-file>silence.mp3</dump-file>
<burst-size>65536</burst-size>
</mount>
<mount>
<mount-name>/mopidy.mp3</mount-name>
<fallback-mount>/silence.mp3</fallback-mount>
<fallback-override>1</fallback-override>
</mount>
# download libspotify and install https://developer.spotify.com/technologies/libspotify/
# install mopidy (https://docs.mopidy.com/en/latest/installation/source/; pip install -U mopidy)
# install mopidy-spotify https://github.com/mopidy/mopidy-spotify
# pip install Mopidy-Mopify
# pip install --upgrade Mopidy-Mopify
#....
[audio]
#mixer = software
#mixer_volume =
#output = audioconvert ! lamemp3enc target=1 bitrate=256 cbr=true ! shout2send ip=127.0.0.1 port=8000 password=... mount=mopidy.mp3
output = pulsesink device=broadcast_on_lan
#EXTM3U
http://$$$IP-ADDRESS$$$:8000/mopidy.mp3
#EXTM3U
http://$$$IP-ADDRESS$$$:8000/broadcast_on_lan.mp3
[general]
upnp-enabled=true
enable-transcoding=true
media-engine=librygel-media-engine-gst.so
port=0
allow-upload=false
allow-deletion=false
[GstMediaEngine]
transcoders=mp3;lpcm;mp2ts;wmv;aac;avc
[MediaExport]
enabled=true
title=Medien auf Wohnzimmer-Server
uris=/media/usb-storage/usb-2000a/Musik;
extract-metadata=true
monitor-changes=true
monitor-grace-timeout=5
virtual-folders=true
[Playbin]
enabled=true
title=Broadcasts auf Wohnzimmer-Server
launch-items=broadcast_on_lan
broadcast_on_lan-title=Broadcast on LAN
broadcast_on_lan-mime=audio/mpeg
broadcast_on_lan-launch=pulsesrc device=broadcast_on_lan.monitor ! audio/x-raw,channels=2 ! lamemp3enc target=1 bitrate=256 cbr=true
[MPRIS]
enabled=true
[org.mpris.MediaPlayer2.mopidy]
enabled=true
title=Mopidy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment