Banana Pi as UPnP music player

Last week I read a blog post by Stefan Kraus: Raspberry Pi als DLNA Renderer / Client mit Fernsteuerung which describes how to setup a Raspberry Pi mini computer as UPnP media renderer that can be controlled from any UPnP control point.

It uses gmrender-resurrect, a continuation of the gmediarender code that was published many years ago. It runs as daemon on a Linux machine and implements all the necessities to make it a usable part of the UPnP infrastructure.

Banana Pi

I have a Banana Pi which basically is a copy of the Raspberry with a bit better hardware specs - and which I didn't know what to use it for. On that hardware I run Bananian, a pre-configured image of Debian 8 made for the Banana Pi.

I followed the installation steps on Stefan Kraus' blog post, and at the end I remembered that gstreamer 0.10 is old, and version 1.0 is already available. So I uninstalled the gstreamer0.10 packages and installed the gstreamer1.0 ones.

ALSA

I decided that ALSA as sound system would suffice, and started gmediarender accordingly:

$ gmediarender -f Stereoanlage -u ... --gstout-audiosink=alsasink --gstout-audiodevice=sysdefault

Playing sound did not work; the log (--logfile=/dev/stdout) told this:

INFO  [date | gstreamer] Setting audio sink to alsasink; device=sysdefault
ERROR [date | gstreamer] Couldn't create sink 'alsasink'
INFO  [date | gstreamer] Set mute to off
INFO  [date | gstreamer] Set volume fraction to 0.316228

This error comes from the following code:

sink = gst_element_factory_make (audio_sink, "sink");
if (sink == NULL) {
    Log_error("gstreamer", "Couldn't create sink '%s'", audio_sink);

Search engines did not really help here, but then I had an idea and looked for the gstreamer packages I had installed. In my uninstall and reinstall orgy I forgot gstreamer1.0-alsa. After installing that package I got sound.

ogg/vorbis

Most of my music library is encoded as ogg/vorbis files, and that is the source of the next trouble: Playing .mp3 files works fine, but playing .ogg files results in an error:

** (gmediarender:2418): WARNING **: conv: size 121 is not a multiple of unit size 4
ERROR [date | gstreamer] source: Error: Internal data flow error.
 (Debug: gstbasesrc.c(2933): gst_base_src_loop ():
 /GstPlayBin:play/GstURIDecodeBin:uridecodebin1/GstSoupHTTPSrc:source:

It turns out that my MediaTomb UPnP server gives out non-mp3 files as audio/L16. (I don't know yet why; my suspicion is that gupnp-av-cp does not send MIME types supported by the selected renderer along with the requests, so that MediTomb falls back to the basics) See a helping hand.

Another gmediarender user had the same problem, and this leads deep down to a bug in gstreamer on ARM machines..

So I'm stuck here for now until the gstreamer developers fix that bug. Or I transcode all my .ogg files to .mp3, which I won't do.

A helping hand

Two weeks later I joined the #gstreamer IRC channel and asked whom I should give money to get the bug fixed. Tim-Philipp Müller encouraged me to add a nice "me, too" comment to the bug report since most of the gstreamer devs work by mail.

One of the people who noticed that was Jens Georg of the Rygel UPnP project. He wrote me that gupnp-av-cp tries to match the offered streams of the media server and the supported stream types of the media renderer as good as possible, and that this probably failed - thus gmediarender would get the transcoded audio/L16 data instead of the original .ogg file.

Collecting information

At first I used gupnp-universal-cp to obtain the list of formats supported by the media renderer: Open the renderer, open urn:upnp-org:serviceId:urn:schemas-upnp-org:service:ConnectionManager then double-click GetProtocolInfo and run the command. In out-arguments, the Sink field contained the following list:

http-get:*:audio/x-private1-lpcm:*,http-get:*:raw/x-pcap:*,http-get:*:application/x-3gp:*,http-get:*:audio/mp4:*,http-get:*:audio/m4a:*,http-get:*:audio/x-m4a:*,http-get:*:video/mj2:*,http-get:*:video/quicktime:*,http-get:*:video/x-qt-part:*,http-get:*:video/x-mp4-part:*,http-get:*:application/vnd.ms-sstr+xml:*,http-get:*:audio/x-smpte-302m:*,http-get:*:audio/alac:*,http-get:*:audio/x-alac:*,http-get:*:audio/x-ffmpeg-parsed-ape:*,http-get:*:audio/x-vnd.sony.atrac1:*,http-get:*:audio/x-imc:*,http-get:*:audio/x-mlp:*,http-get:*:audio/x-gst-av-mp3adu:*,http-get:*:audio/x-gst-av-mp3on4:*,http-get:*:audio/x-ffmpeg-parsed-musepack:*,http-get:*:audio/qcelp:*,http-get:*:audio/x-qdm2:*,http-get:*:audio/x-shorten:*,http-get:*:audio/x-sipro:*,http-get:*:audio/x-true-hd:*,http-get:*:audio/x-truespeech:*,http-get:*:audio/x-twin-vq:*,http-get:*:audio/x-gst-av-vmdaudio:*,http-get:*:audio/x-wms:*,http-get:*:audio/x-gst-av-ws_snd1:*,http-get:*:audio/x-dpcm:*,http-get:*:video/x-aasc:*,http-get:*:video/x-apple-intermediate-codec:*,http-get:*:video/x-amv:*,http-get:*:video/x-asus:*,http-get:*:video/x-gst-av-avs:*,http-get:*:video/x-gst-av-cavs:*,http-get:*:video/x-cinepak:*,http-get:*:video/x-cirrus-logic-accupak:*,http-get:*:video/x-camstudio:*,http-get:*:video/x-compressed-yuv:*,http-get:*:video/x-dnxhd:*,http-get:*:video/x-gst-av-8bps:*,http-get:*:video/x-ffv:*,http-get:*:video/x-gst-av-ffvhuff:*,http-get:*:video/x-gst-av-flic:*,http-get:*:video/x-fraps:*,http-get:*:video/x-h261:*,http-get:*:video/x-intel-h263:*,http-get:*:video/x-gst-av-idcinvideo:*,http-get:*:video/x-indeo:*,http-get:*:video/x-gst-av-interplayvideo:*,http-get:*:video/x-kmvc:*,http-get:*:video/x-lagarith:*,http-get:*:video/x-loco:*,http-get:*:video/x-gst-av-mdec:*,http-get:*:video/x-mjpeg-b:*,http-get:*:video/x-gst-av-mmvideo:*,http-get:*:video/x-msvideocodec:*,http-get:*:video/x-mszh:*,http-get:*:video/x-nuv:*,http-get:*:image/pbm:*,http-get:*:video/x-gst-av-pgmyuv:*,http-get:*:image/ppm:*,http-get:*:video/x-prores:*,http-get:*:video/x-qdrw:*,http-get:*:video/x-gst-av-qpeg:*,http-get:*:video/x-rle:*,http-get:*:video/x-gst-av-roqvideo:*,http-get:*:video/x-apple-video:*,http-get:*:image/x-sgi:*,http-get:*:video/x-smc:*,http-get:*:video/sp5x:*,http-get:*:video/x-truemotion:*,http-get:*:video/x-camtasia:*,http-get:*:video/x-tscc:*,http-get:*:video/x-ultimotion:*,http-get:*:video/x-ati-vcr:*,http-get:*:video/x-gst-av-vmdvideo:*,http-get:*:video/x-vmnc:*,http-get:*:video/x-vp3:*,http-get:*:video/x-vp5:*,http-get:*:video/x-vp6:*,http-get:*:video/x-gst-av-vqavideo:*,http-get:*:video/x-gst-av-wnv1:*,http-get:*:video/x-xan:*,http-get:*:video/x-gst-av-xl:*,http-get:*:unknown/unknown:*,http-get:*:video/x-zlib:*,http-get:*:video/x-zmbv:*,http-get:*:application/x-ape:*,http-get:*:application/x-gst-av-avs:*,http-get:*:application/x-gst-av-daud:*,http-get:*:application/x-gst-av-ea:*,http-get:*:video/x-4xm:*,http-get:*:application/gxf:*,http-get:*:application/x-gst-av-idcin:*,http-get:*:application/x-gst-av-ipmovie:*,http-get:*:application/x-gst-av-mm:*,http-get:*:application/x-gst-av-mmf:*,http-get:*:audio/x-musepack:*,http-get:*:video/x-nsv:*,http-get:*:application/x-gst-av-nut:*,http-get:*:application/x-gst-av-nuv:*,http-get:*:video/x-pva:*,http-get:*:application/x-gst-av-film_cpk:*,http-get:*:application/x-gst-av-smk:*,http-get:*:application/x-gst-av-sol:*,http-get:*:application/x-gst-av-psxstr:*,http-get:*:audio/x-ttafile:*,http-get:*:application/x-gst-av-vmd:*,http-get:*:application/x-gst-av-wc3movie:*,http-get:*:application/x-gst-av-wsaud:*,http-get:*:application/x-gst-av-wsvqa:*,http-get:*:video/x-svq:*,http-get:*:audio/x-mace:*,http-get:*:audio/x-vnd.sony.atrac3:*,http-get:*:audio/x-vgm:*,http-get:*:audio/x-spc:*,http-get:*:audio/x-sap:*,http-get:*:audio/x-nsf:*,http-get:*:audio/x-kss:*,http-get:*:audio/x-hes:*,http-get:*:audio/x-gym:*,http-get:*:audio/x-gbs:*,http-get:*:audio/x-ay:*,http-get:*:application/vnd.rn-realmedia:*,http-get:*:application/x-pn-realaudio:*,http-get:*:application/x-rdt:*,http-get:*:application/mxf:*,http-get:*:video/x-cdxa:*,http-get:*:audio/x-stm:*,http-get:*:audio/x-s3m:*,http-get:*:audio/x-it:*,http-get:*:audio/x-xm:*,http-get:*:audio/x-mod:*,http-get:*:application/x-id3:*,http-get:*:application/sdp:*,http-get:*:application/x-gdp:*,http-get:*:audio/riff-midi:*,http-get:*:audio/midi:*,http-get:*:application/x-hls:*,http-get:*:application/kate:*,http-get:*:text/x-cmml:*,http-get:*:video/x-daala:*,http-get:*:video/x-smoke:*,http-get:*:application/x-ogm-audio:*,http-get:*:application/x-ogm-video:*,http-get:*:application/x-ogm-text:*,http-get:*:application/x-ogg-avi:*,http-get:*:application/x-subtitle-lrc:*,http-get:*:application/x-subtitle-qttext:*,http-get:*:application/x-subtitle-dks:*,http-get:*:application/x-subtitle-mpl2:*,http-get:*:application/x-subtitle-tmplayer:*,http-get:*:application/x-subtitle-sami:*,http-get:*:application/x-subtitle:*,http-get:*:video/x-sonix:*,http-get:*:video/x-pwc2:*,http-get:*:video/x-pwc1:*,http-get:*:video/x-bayer:*,http-get:*:image/x-exr:*,http-get:*:image/webp:*,http-get:*:video/x-ivf:*,http-get:*:video/x-matroska-3d:*,http-get:*:video/x-matroska:*,http-get:*:audio/x-matroska:*,http-get:*:audio/x-pn-realaudio:*,http-get:*:audio/x-tta:*,http-get:*:video/x-pn-realvideo:*,http-get:*:application/x-subtitle-unknown:*,http-get:*:application/x-usf:*,http-get:*:application/x-ass:*,http-get:*:application/x-ssa:*,http-get:*:audio/x-midi-event:*,http-get:*:audio/ms-gsm:*,http-get:*:image/x-pcx:*,http-get:*:image/x-tga:*,http-get:*:image/x-bitmap:*,http-get:*:image/vnd.wap.wbmp:*,http-get:*:image/x-MS-bmp:*,http-get:*:image/x-bmp:*,http-get:*:image/bmp:*,http-get:*:image/x-portable-pixmap:*,http-get:*:image/x-portable-graymap:*,http-get:*:image/x-portable-bitmap:*,http-get:*:image/x-portable-anymap:*,http-get:*:image/tiff:*,http-get:*:image/x-pixmap:*,http-get:*:image/x-sun-raster:*,http-get:*:image/x-cmu-raster:*,http-get:*:application/x-navi-animation:*,http-get:*:image/x-icon:*,http-get:*:image/gif:*,http-get:*:text/plain:*,http-get:*:image/svg:*,http-get:*:image/svg+xml:*,http-get:*:video/x-mimic:*,http-get:*:application/x-kate:*,http-get:*:subtitle/x-kate:*,http-get:*:audio/x-sid:*,http-get:*:video/x-vp9:*,http-get:*:text/x-raw:*,http-get:*:video/x-msvideo:*,http-get:*:video/x-huffyuv:*,http-get:*:video/x-msmpeg:*,http-get:*:application/x-subtitle-avi:*,http-get:*:image/jp2:*,http-get:*:image/x-j2c:*,http-get:*:subpicture/x-pgs:*,http-get:*:subpicture/x-dvd:*,http-get:*:application/x-apetag:*,http-get:*:audio/x-xi:*,http-get:*:audio/x-w64:*,http-get:*:audio/x-voc:*,http-get:*:audio/x-svx:*,http-get:*:audio/x-sds:*,http-get:*:audio/x-rf64:*,http-get:*:audio/x-paris:*,http-get:*:audio/x-nist:*,http-get:*:audio/x-ircam:*,http-get:*:audio/x-au:*,http-get:*:audio/aiff:*,http-get:*:audio/x-aiff:*,http-get:*:application/x-bzip:*,http-get:*:video/x-fli:*,http-get:*:application/x-yuv4mpeg:*,http-get:*:video/x-h265:*,http-get:*:image/png:*,http-get:*:application/dash+xml:*,http-get:*:video/x-wmv:*,http-get:*:audio/x-wma:*,http-get:*:video/x-ms-asf:*,http-get:*:multipart/x-mixed-replace:*,http-get:*:audio/x-amr-wb-sh:*,http-get:*:audio/x-amr-nb-sh:*,http-get:*:audio/x-private1-ac3:*,http-get:*:audio/x-eac3:*,http-get:*:audio/x-private1-dts:*,http-get:*:audio/x-flac:*,http-get:*:audio/x-wavpack:*,http-get:*:audio/x-wav:*,http-get:*:video/x-flv:*,http-get:*:audio/x-nellymoser:*,http-get:*:video/x-vp6-alpha:*,http-get:*:video/x-vp6-flash:*,http-get:*:video/x-flash-screen:*,http-get:*:video/x-flash-video:*,http-get:*:audio/ac3:*,http-get:*:audio/x-bv:*,http-get:*:audio/x-celt:*,http-get:*:video/x-dv:*,http-get:*:audio/x-iLBC:*,http-get:*:audio/G722:*,http-get:*:audio/G723:*,http-get:*:audio/x-adpcm:*,http-get:*:audio/G729:*,http-get:*:audio/x-gsm:*,http-get:*:audio/AMR-WB:*,http-get:*:audio/AMR:*,http-get:*:video/x-h263:*,http-get:*:image/x-jpc:*,http-get:*:video/x-jpeg:*,http-get:*:image/jpeg:*,http-get:*:video/mpegts:*,http-get:*:video/x-divx:*,http-get:*:audio/x-sbc:*,http-get:*:audio/x-siren:*,http-get:*:audio/x-speex:*,http-get:*:audio/x-vorbis:*,http-get:*:video/x-vp8:*,http-get:*:application/x-srtcp:*,http-get:*:application/x-srtp:*,http-get:*:application/x-rtcp:*,http-get:*:application/x-srtcp-stream:*,http-get:*:application/x-srtp-stream:*,http-get:*:application/x-rtcp-stream:*,http-get:*:application/x-rtp-stream:*,http-get:*:application/x-teletext:*,http-get:*:subpicture/x-dvb:*,http-get:*:audio/x-dts:*,http-get:*:audio/x-ac3:*,http-get:*:audio/x-lpcm:*,http-get:*:video/x-h264:*,http-get:*:video/x-dirac:*,http-get:*:video/mpeg:*,http-get:*:application/x-rtp:*,http-get:*:audio/x-opus:*,http-get:*:audio/x-mulaw:*,http-get:*:video/x-theora:*,http-get:*:audio/webm:*,http-get:*:video/webm:*,http-get:*:video/ogg:*,http-get:*:audio/ogg:*,http-get:*:application/ogg:*,http-get:*:application/x-icy:*,http-get:*:audio/L16;rate=44100;channels=2:*,http-get:*:audio/x-scpls:*,http-get:*:audio/x-mpeg:*,http-get:*:audio/mpeg:*,http-get:*:video/x-raw:*,http-get:*:audio/x-raw:*,http-get:*:audio/*:*,http-get:*:audio/x-alaw:*

What I saw there was that the audio/ogg MIME type came before audio/L16 and thus is preferred by the player.

Then I used gupnp-av-cp to get the DIDL-Lite information of a song offered by the server: Browse to a song, then right-click and select Fetch DIDL-Lite. It looked like that:

<?xml version="1.0" encoding="UTF-8"?>
<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/">
  <item id="20177" parentID="20140" restricted="1">
    <dc:title>Feelin Blue</dc:title>
    <upnp:class>object.item.audioItem.musicTrack</upnp:class>
    <upnp:artist>Wonderwall</upnp:artist>
    <upnp:album>Witchcraft</upnp:album>
    <dc:date>2002-01-01</dc:date>
    <upnp:genre>Pop</upnp:genre>
    <res duration="00:03:17" sampleFrequency="44100" nrAudioChannels="2" protocolInfo="http-get:*:audio/L16;rate=44100;channels=2:*">http://192.168.3.3:49152/content/media/object_id/20177/res_id/none/pr_name/audio2pcm/tr/1</res>
  </item>
</DIDL-Lite>

Missing ogg

Apparent in the DIDL-Lite is that there is no mention of audio/ogg at all. I looked up the UPnP specification, the XSD in UPnP-av-ContentDirectory-v1-Service.pdf told me that the <res> tag is allowed several times.

My MediaTomb 0.12.1 server had the following transcoding configuration for ogg/vorbis files:

<!-- ogg to wav #2 -->
<profile name="audio2pcm" enabled="yes" type="external">
  <mimetype>audio/L16</mimetype>
  <accept-url>no</accept-url>
  <first-resource>yes</first-resource>
  <hide-original-resource>yes</hide-original-resource>
  <accept-ogg-theora>no</accept-ogg-theora>
  <sample-frequency>44100</sample-frequency>
  <audio-channels>2</audio-channels>
  <agent command="ffmpeg" arguments="-i %in -acodec pcm_s16be -ab 192k -ar 44100 -ac 2 -f s16be -y %out"/>
  <buffer size="1048576" chunk-size="131072" fill-size="262144"/>
</profile>

hide-original-resource caused the original .ogg file to not to be listed at all, so I changed it to no and got two resources now:

<res duration="00:03:17" sampleFrequency="44100" nrAudioChannels="2" protocolInfo="http-get:*:audio/L16;rate=44100;channels=2:*">http://192.168.3.3:49152/content/media/object_id/20177/res_id/none/pr_name/audio2pcm/tr/1</res>
<res protocolInfo="http-get:*:audio/ogg:*" size="4940995" bitrate="24576" duration="00:03:17" sampleFrequency="44100" nrAudioChannels="2">http://192.168.3.3:49152/content/media/object_id/20177/res_id/0/ext/file.ogg</res>

Transcoding flag

Unfortunately the L16 stream was still handed to the player. Jens Georg explained to me that gupnp-av-cp used the first resource since the order of appearance is important when streams are equal. Resources listed first are seen as preferred by the server.

Apart from reordering, the audio/L16 resources can be marked as transcoded, and in this case gupnp-av-cp would actually prefer the original stream. DLNA provides a <res> attribute to indicate a transcoded stream:

7.4.1.3.23 MM ci-param (conversion indicator flag)

7.4.1.3.23.1

[GUIDELINE] The syntax definition of ci-param shall be as follows:

  • ci-param = ci-param-delim "DLNA.ORG_CI=" ci-value
  • ci-param-delim = ";"
  • ci-value = Boolean
  • Boolean = "1" | "0"

If the context of the protocolInfo involves a content binary that is converted from a different content binary, then ci-value is "1". Otherwise, the ci-value is "0". (See 7.4.1.3.12.5 for determining an appropriate context.)

DLNA Guidelines March 2014 - Part 1-1 Architectures and Protocols 201412....pdf

MediaTomb's source code contains a reference to the attribute in common.h , but it is wrapped in an #ifdef:

D_CONVERSION_INDICATOR "DLNA.ORG_CI"

I guess on Debian 8 this is no defined, and thus this attribute does not get added to transcoded resources.

Jin^ELD, one of the previous maintainers, told me that MediaTomb is realistically dead; nobody had been doing any work on it since 7 years.

Reordering

Now that I could not get the transcoding flag, the only option left was to change the order of the res tags. The transcoding configuration has a first-resource tag for renderers that don't do stream matching, and it was activated on my server.

Deactivating it made the audio/ogg resource appear before the audio/L16 one, and gupnp-av-cp finally handed the ogg file to gmediarender which played it fine.

Written by Christian Weiske.

Comments? Please send an e-mail.