Seeking in Transcoded Streams with Rygel

When looking at various UPnP media servers, one of the features I wanted was the ability to play back my music collection through my PlayStation 3.  The complicating factor is that most of my collection is encoded in Vorbis format, which is not yet supported by the PS3 (at this point, it doesn't seem likely that it ever will). Both MediaTomb and Rygel could handle this to an extent, transcoding the audio to raw LPCM data to send over the network.  This doesn't require much CPU power on the server side, and only requires 1.4 Mbit/s of bandwidth, which is manageable on most home networks.  Unfortunately the only playback controls enabled in this mode are play and stop: if you want to pause, fast forward or rewind then you're out of luck. Given that Rygel has a fairly simple code base, I thought I'd have a go at fixing this.  The first solution I tried was the one I've mentioned a few times before: with uncompressed PCM data file offsets can be easily converted to sample numbers, so if the source format allows time based seeking, we can easily satisfy byte range requests. I got a basic implementation of this working, but it was a little bit jumpy and not as stable as I'd like.  Before fully debugging it, I started looking at the mysterious DLNA options I'd copied over to get things working.  One of those was the "DLNA operation", which was set to "range" mode.  Looking at the GUPnP header files, I noticed there was another value named "timeseek".  When I picked this option, the HTTP requests from the PS3 changed: GET /... HTTP/1.1 Host: ... User-Agent: PLAYSTATION 3 Connection: Keep-Alive Accept-Encoding: identity TimeSeekRange.dlna.org: npt=0.00- transferMode.dlna.org: Streaming The pause, rewind and fast forward controls were now active, although only the pause control actually worked properly. After fast forwarding or rewinding, the PS3 would issue another HTTP request with the TimeSeekRange.dlna.org header specifying the new offset, but the playback position would reset to the start of the track when the operation completed. After a little more experimentation, I found that the playback position didn't reset if I included TimeSeekRange.dlna.org in the response headers. Of course, I was still sending back the beginning of the track at this point but the PS3 acted as though it was playing from the new point in the song. It wasn't much more work to update the GStreamer calls to seek to the requested offset before playback and things worked pretty much as well as for non-transcoded files.  And since this solution didn't involve byte offsets, it also worked for Rygel's other transcoders.  It even worked to an extent with video files, but the delay before playback was a bit too high to make it usable -- fixing that would probably require caching the GStreamer pipeline between HTTP requests. Thoughts on DLNA While it can be fun to reverse engineer things like this, it was a bit annoying to only be able to find…

Watching iView with Rygel

One of the features of Rygel that I found most interesting was the external media server support.  It looked like an easy way to publish information on the network without implementing a full UPnP/DLNA media server (i.e. handling the UPnP multicast traffic, transcoding to a format that the remote system can handle, etc). As a small test, I put together a server that exposes the ABC's iView service to UPnP media renderers.  The result is a bit rough around the edges, but the basic functionality works.  The source can be grabbed using Bazaar: bzr branch lp:~jamesh/+junk/rygel-iview It needs Python, Twisted, the Python bindings for D-Bus and rtmpdump to run.  The program exports the guide via D-Bus, and uses rtmpdump to stream the shows via HTTP.  Rygel then publishes the guide via the UPnP media server protocol and provides MPEG2 versions of the streams if clients need them. There are still a few rough edges though.  The video from iView comes as 640x480 with a 16:9 aspect ratio so has a 4:3 pixel aspect ratio, but there is nothing in the video file to indicate this (I am not sure if flash video supports this metadata). Getting Twisted and D-Bus to cooperate Since I'd decided to use Twisted, I needed to get it to cooperate with the D-Bus bindings for Python.  The first step here was to get both libraries using the same event loop.  This can be achieved by setting Twisted to use the glib2 reactor, and enabling the glib mainloop integration in the D-Bus bindings. Next was enabling asynchronous D-Bus method implementations.  There is support for this in the D-Bus bindings, but has quite a different (and less convenient) API compared to Twisted.  A small decorator was enough to overcome this impedence: from functools import wraps import dbus.service from twisted.internet import defer def dbus_deferred_method(*args, **kwargs): def decorator(function): function = dbus.service.method(*args, **kwargs)(function) @wraps(function) def wrapper(*args, **kwargs): dbus_callback = kwargs.pop('_dbus_callback') dbus_errback = kwargs.pop('_dbus_errback') d = defer.maybeDeferred(function, *args, **kwargs) d.addCallbacks( dbus_callback, lambda failure: dbus_errback(failure.value)) wrapper._dbus_async_callbacks = ('_dbus_callback', '_dbus_errback') return wrapper return decorator This decorator could then be applied to methods in the same way as the @dbus.service.method method, but it would correctly handle the case where the method returns a Deferred. Unfortunately it can't be used in conjunction with @defer.inlineCallbacks, since the D-Bus bindings don't handle varargs functions properly. You can of course call another function or method that uses @defer.inlineCallbacks though. The iView Guide After coding this, it became pretty obvious why it takes so long to load up the iView flash player: it splits the guide data over almost 300 XML files.  This might make sense if it relied on most of these files remaining unchanged and stored in cache, however it also uses a cache-busting technique when requesting them (adding a random query component to the URL). Most of these files are series description files (some for finished series with no published programs).  These files contain a title, a short description, the URL for a thumbnail image and the…