glueing together GStreamers playbin and Coherences UPnP A/V MediaRenderer
In an attempt to work on the documentation and as a practice finding the words to explain how Coherence ticks I’ll try to throw some light on how the GStreamer playbin backend and Coherence are glued together to form an UPnP A/V MediaRenderer device.
A device in UPnP speak is a logical something - a grouping of services. A MediaRenderer as an example needs to support
- a RenderingControl service, which is responsible for adjustment of volume, loudness, brightness,…
- a ConnectionManager service, used to enumerate and select the transfer protocol (http, rtsp/rtp,…) and
data format (mp3, ogg,…) to be used for transferring the content - an AVTransport service, optional - depending on the transfer protocols the ConnectionManager supports. But if available used to specify the content to be played, start/stop/pause a playback, seek in the content stream,…
These services can be controlled and queried with so called Actions, e.g. SetVolume() and GetVolume(), and hold their current state in so called StateVariables, e.g. Volume. A call to SetVolume() sets the volume value on the actual rendering engine and reflects that change in the variable Volume. And a call to GetVolume() retrieves the value from that variable. Furthermore a service is supposed to propagate the values of these variables on change to interested parties, e.g. to an UPnP ControlPoint.
Now one of the basic principles of Coherence is to seal off the backend – the actual rendering engine, in this case the GStreamer playbin – from the UPnP related tasks (and later from the D*AP ones) as much as possible.
This means that a backend should only need to
- tell Coherence what kind of device(s) it wants to resemble, e.g. a MediaRenderer
- define specific conditions, e.g. that is supports only decoding of mp3 content
- provide methods Coherence can access when an action is called that has effects on the backend engine, e.g. a volume change
- inform Coherence about state changes, e.g. the end of the media content is reached and playback has stopped
Everything else is the job of Coherence:
- propagate the device on the network (SSDP/MSEARCH)
- generate the necessary device and service descriptions with reasonable defaults, corresponding to the backends capabilities
- provide the control interfaces to the actions the services have
- handle all event subscription and propagation tasks (GENA)
This means also, that all actions with no effects on the backend engine are handled completely within Coherence, like an action that retrieves only a variable value. Of course a backend can override this if it wants to, but basically there is no need to do it.
In our GStreamer playbin case this (simplified) looks like this:
class Player: """ see item 1. above """ implements = ['MediaRenderer'] """ see item 2. above """ vendor_value_defaults = {'RenderingControl': {'A_ARG_TYPE_Channel':'Master'}, 'ConnectionManager': {'SinkProtocolInfo', 'http-get:*:audio/mpeg:*'}} vendor_range_defaults = {'RenderingControl': {'Volume': {'maximum':100}}} def __init__(self, device): self.player = gst.element_factory_make("playbin", "myplayer") self.device= device def set_volume(self, volume): """ here the actual volume change takes place """ """ gstreamer playbin has a volume range from 0.0-10.0 """ self.player.set_property('volume', float(volume)/10) """ feed back the new state - our item 4. """ rcs_id = self.device.connection_manager_server.lookup_rcs_id(self.current_connection_id) self.device.rendering_control_server.set_variable(rcs_id, 'Volume', volume) def upnp_SetVolume(self, *args, **kwargs): """ that's a method referred in item 3. """ InstanceID = int(kwargs['InstanceID']) Channel = kwargs['Channel'] DesiredVolume = int(kwargs['DesiredVolume']) self.set_volume(DesiredVolume) return {}
This is not perfect and not final yet and there will be some changes especially in the way how item 4. is handled. But the purpose of that backend is among other things to straighten out the bumpiness of these glue points.
And btw., in this backend the engine is embedded, but as stated in an earlier post, there is absolutly no reason why instead of calling self.player.set_property to set the volume this couldn’t be some ipc call (xml-rpc, dbus, Twisted PB,…) or an IR/ZigBee/ZWave signal emitted.