Donnerstag, 10. Januar 2013

OpenWRT UPnP/DLNA Renderer - diesmal wirklich!

Geht es in meinem Freundeskreis um das Thema Netzwerkaudio und Streaming, frage ich immer, ob überwiegend ein Apple Universum vorhanden ist oder eher viele verschiedene andere Geräte - also AirPlay oder UPNP/DLNA (meine EsounD, Pulseaudio-Lösung konnte ich bisher noch niemandem schmackhaft machen...). Mit Shairplay gibt es eine relativ schlanke Lösung für die AirPlay-Ausgabe auf beliebigen OpenWRT Geräten mit USB-Soundkarte.

In einem der alten Posts habe ich mich bereits beklagt, dass es jedoch keinen UPNP/DLNA-Renderer für Linux geben würde. Es gibt scheinbar Millionen von UPNP-Servern aber die Welt der Controlpoints und Renderer ist recht dünn gesät. Aus diesem Grund habe ich, vor gut einem Jahr, einem guten Freund den "alten" Fritz!Repeater empfohlen - nicht, um in seiner 3-Zimmer Wohnung das ohnehin gute WLAN zu verstärken, sondern als UPNP-Renderer für's Netz. Nach anfänglichen Erfolgen hält sich die Zufriedenheit in Grenzen und eine nennenswerte Weiterentwicklung scheint es auch nicht zu geben.

Diese kleine Niederlage wollte ich ungern auf mir sitzen lassen, so dass mir meine neuen PogoPlugs wie gerufen kamen, um damit Rygel mit PlayBin als Renderer auszuprobieren.


Achtung: Da Rygel nicht Bestandteil der OpenWRT Packages ist, muss das Paket selbst erstellt werden (Binaries ohne Gewähr finden sich hier). Die Anleitung auf OpenWRT führt zu einem perfekt gepflegten GIT-Repository, welches ich in meine "feeds.conf" aufgenommen habe. Zunächst müssen noch zwei Patches am Build-System durchgeführt werden. Danach kann man die Pakete bauen, was bei mir absolut fehlerfrei funtionierte.

Da ich mich absolut nicht mit GStreamer auskenne (Rygel benutzt GStreamer zur Ausgabe und offensichtlich auch zum Rendering), war ich erstmal vollkommen in meiner Euphorie gebremst, als ein

opkg install http://loetzimmer.de/openwrt/attitude_adjustment/kirkwood/packages/rygel-playbin_0.14.1-1_kirkwood.ipk
/etc/init.d/rygel start

zwar einen UPNP-Renderer im Netz erscheinen lies, diese aber keinen Stream abspielte. Ich war davon ausgegangen, dass das Paket bereits alle Dependencies installieren würde. Nach einem kurzen Blick auf die "gst-mod-..." Paketliste wurde mir klar, dass eigentlich noch nichts funktionieren kann.
Also habe ich ein paar Module nachinstalliert und plötzlich kamen Töne aus meinen Lautsprechern - wie in einem Traum... ;) (Ich versuche seit ca. vier Jahren aus bestehender Software einen UPNP/DLNA-Renderer mit OpenWRT zu bauen!)

rygel - 0.14.1-1
rygel-playbin - 0.14.1-1
gst-mod-alsa - 0.10.36-1
gst-mod-audioconvert - 0.10.36-1
gst-mod-audioparsers - 0.10.31-1
gst-mod-audioresample - 0.10.36-1
gst-mod-autodetect - 0.10.31-1
gst-mod-decodebin2 - 0.10.36-1
gst-mod-faad - 0.10.23-1
gst-mod-flac - 0.10.31-1
gst-mod-icydemux - 0.10.31-1
gst-mod-id3demux - 0.10.31-1
gst-mod-isomp4 - 0.10.31-1
gst-mod-mad - 0.10.19-1
gst-mod-ogg - 0.10.36-1
gst-mod-playbin - 0.10.36-1
gst-mod-souphttpsrc - 0.10.31-1
gst-mod-typefindfunctions - 0.10.36-1
gst-mod-volume - 0.10.36-1
gst-mod-vorbis - 0.10.36-1
gstreamer - 0.10.36-1
libgstaudio - 0.10.36-1
libgstcheck - 0.10.36-1
libgstcontroller - 0.10.36-1
libgstdataprotocol - 0.10.36-1
libgstinterfaces - 0.10.36-1
libgstnet - 0.10.36-1
libgstpbutils - 0.10.36-1
libgstreamer - 0.10.36-1
libgstriff - 0.10.36-1
libgstrtp - 0.10.36-1
libgsttag - 0.10.36-1
libgstvideo - 0.10.36-1

Diese Module habe ich auf Attitude Adjustment (openWRT 12.09) installiert für den UPNP/DLNA Renderer installiert.

Da der PogoPlug über ausreichend Ressourcen verfügt (im Gegensatz zu meinen NSLU2), darf Alsa das Mixing übernehmen (DMIX). Um hässliche Stotter-Effekte zu vermeiden, musste ich den Puffer allerdings deutlich erhöhen. Da in meiner angepassten Shairport Version libao den Sound per EsounD ausgibt, muss die libao.conf entsprechend angepasst werden.

#/etc/modules.d/60-usb-audio
#

snd-usbmidi-lib
snd-usb-audio nrpacks=1

#/etc/asound.conf
#

pcm.!default {
 type plug
 slave.pcm "dmixer"
}

pcm.dmixer  {
 type dmix
 ipc_key 1024
 slave {
  pcm "hw:9,0"
  period_time 0
  buffer_time 0
  period_size 2048
  buffer_size 32768
  rate 44100
 }
 bindings {
  0 0
  1 1
 }
}

ctl.dmixer {
 type hw
 card 0
}

#pcm.!default pcm.card0
#
#pcm.card0 {
# type plug
# slave {
#  pcm "hw:0,0"
#  rate 44100
#  channels 2
# }
#}

#/etc/libao.conf
#

default_driver=alsa
#default_driver=esd
#host=127.0.0.1:16000
#server=127.0.0.1:16000
#debug
#verbose

Bei meinen Tests fiel mir auf, dass ich häufig trotz gestartetem Rygel-Prozess keinen Zugriff auf den Renderer hatte. Ich vermute, dass Rygel startet, bevor das Netzwerk Interface eine DHCP-Adresse bekommen hat. Also habe ich die Gelegenheit genutzt und mich mit den Hotplug-Mechanismen von OpenWRT auseinander gesetzt. Ein kleines Script im Verzeichnis /etc/hotplug.d/iface startet Rygel einfach neu, sobald das Interface eingerichtet ist.

#/etc/hotplug.d/iface/99-rygel
#

grep -qs "^ *$DEVICE:" /proc/net/dev || exit 0

case "$ACTION" in
 ifup)
  if [ -f /etc/asound.conf ] ; then
   /etc/init.d/rygel restart
  fi
  ;;
esac

Da man bei USB-Soundkarten nicht vorhersehen kann, ob und wann ein Nutzer die Karte entfernt bzw. ansteckt, ist es eigentlich nur konsequent, die Sound-Dienste nur bei gesteckter Soundkarte starten zu lassen. Als kleine herausforderung stelle man sich ein System mit MJPG-Streamer USB-Videokamera vor, dessen Mikrofon ebenfalls eine Soundkarte darstellt - nur ohne Abspielmöglichkeit...

mv /etc/asound.conf /etc/asound.conf.orig
ln -s /tmp/asound.conf /etc/asound.conf

Zu diesem Zweck habe ich mir ein USB-Audio Hotplug Script geschrieben, welches beim Vorhandensein des ersten Playback-Gerätes die Sounddienste startet. Die Überprüfung ob der Link /etc/asound.conf auf eine existierende /tmp/asound.conf zeigt verhindert einen mehrfachen Start, da eine USB-Soundkarte aus mind. drei Geräten besteht, das Hotplug-Script also dementsprechend drei Mal ausgeführt würde.

#/etc/hotplug.d/usb/30-audio
#

I=`grep "audio playback" /proc/asound/devices | sed -e "s/.*\[ *\([0-9]\)- *\([0-9]\)\].*/\1,\2/g"`

case "$ACTION" in
 add)
  if [ -n "$I" -a ! -f /etc/asound.conf ] ; then
   sed -e "s/hw:[0-9],[0-9]/hw:$I/g" /etc/asound.conf.orig > /etc/asound.conf

   /etc/init.d/shairport start
   /etc/init.d/rygel start
   /etc/init.d/esound start
  fi
  ;;
 remove)
  if [ -z "$I" ] ; then
   if [ -f /tmp/asound.conf ] ; then rm -f /tmp/asound.conf ; fi

   /etc/init.d/esound stop
   /etc/init.d/shairport stop
   /etc/init.d/rygel stop
  fi
  ;;
esac

Inspiriert durch das Rygel Config-File habe ich bei der Gelegentheit auch meine Shairport Scripte angepasst, so kann man über das Setzen des Hostnames bequem im OpenWRT LUCI Webfrontend auch den Namen des Airplay Servers setzen (Hostname des OpenWRT Gerätes).

#!/bin/sh /etc/rc.common
#
#/etc/init.d/shairport
#
START=75

NAME=shairport
PROG=/usr/bin/$NAME

start_service() {
 local section="$1"
 local apname
 config_get_bool "enabled" "$section" "enabled" '1'
 config_get apname "$section" apname

 myap=`echo "$apname" | tr "a-z" "A-Z"`
 if [ $myap == "@HOSTNAME@" ] ; then
  apname=`cat /proc/sys/kernel/hostname`
 fi

 [ "$enabled" -gt 0 ] && $PROG -a $apname -d
}
start() {
 config_load "shairport"
 config_foreach start_service "shairport"
}

stop() {
 killall avahi-publish-service
 killall -9 $NAME
}

Ein Shairplay Config File sieht dann z.B. so aus:

#/etc/config/shairport
#

config 'shairport'
 option apname '@HOSTNAME@'
 option enabled '1'

Hat eigentlich nichts mehr mit dem Renderer-Thema zu tun, aber da ich meinen Home-Multimediaboxen noch ein kleines Zusatzfeature geben wollte, habe ich auch den mjpg-streamer installiert und (da ich mich ja jetzt mit den Hotplug-Scripten auskenne) das bestehende USB-Hotplug Script angepasst - schließlich soll mjpg-streamer nur bei einer eingesteckten Kamera aktiv werden.

#/etc/hotplug.d/usb/20-mjpg-streamer
#

case "$ACTION" in
 add)
  if [ -e /dev/video0 -a ! -h /dev/video ] ; then
   ln -s /dev/video0 /dev/video
   /etc/init.d/mjpg-streamer start
  fi
  ;;
 remove)
  if [ ! -e /dev/video -a -h /dev/video ] ; then
   rm -f /dev/video
   /etc/init.d/mjpg-streamer stop
  fi
  ;;
esac

Und ein passendes Config-File:

#/etc/config/mjpg-streamer
#

config mjpg-streamer core
 option enabled  "1"
 option device  "/dev/video"
 option resolution "800x600"
 option fps  "15"
 option www  "/www/webcam"
 option port  "8080"