[vlc-devel] [PATCH] VLC as UPnP/DLNA MediaRenderer

Johan Gunnarsson johan.gunnarsson at gmail.com
Sat Jul 6 17:13:05 CEST 2019


This adds support for VLC to act as a UPnP/DLNA MediaRenderer on the
LAN. This means a UPnP/DLNA control point can "cast" media to a VLC
instance and control playback.

Consider this patch a proof of concept.
---
 modules/control/dlna.cpp                    | 706 ++++++++++++++++++
 modules/control/dlna.hpp                    |  68 ++
 modules/services_discovery/Makefile.am      |   4 +-
 modules/services_discovery/upnp-wrapper.cpp | 117 ++-
 modules/services_discovery/upnp-wrapper.hpp |  11 +-
 modules/services_discovery/upnp.cpp         |  15 +-
 modules/services_discovery/upnp.hpp         |   1 +
 modules/stream_out/dlna/dlna.hpp            |   2 +-
 share/Makefile.am                           |   5 +
 share/upnp/AVTransportSCPD.xml              | 585 +++++++++++++++
 share/upnp/ConnectionManagerSCPD.xml        | 182 +++++
 share/upnp/RenderingControlSCPD.xml         | 762 ++++++++++++++++++++
 12 files changed, 2445 insertions(+), 13 deletions(-)
 create mode 100644 modules/control/dlna.cpp
 create mode 100644 modules/control/dlna.hpp
 create mode 100644 share/upnp/AVTransportSCPD.xml
 create mode 100644 share/upnp/ConnectionManagerSCPD.xml
 create mode 100644 share/upnp/RenderingControlSCPD.xml

diff --git a/modules/control/dlna.cpp b/modules/control/dlna.cpp
new file mode 100644
index 0000000000..3f41ca515a
--- /dev/null
+++ b/modules/control/dlna.cpp
@@ -0,0 +1,706 @@
+/*****************************************************************************
+ * dlna.cpp :  DLNA MediaRenderer module
+ *****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * Authors: Johan Gunnarsson <johan.gunnarsson at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <string>
+#include <map>
+#include <iostream>
+#include <regex>
+
+#include "../services_discovery/upnp-wrapper.hpp"
+
+#include <vlc_plugin.h>
+#include <vlc_interface.h>
+#include <vlc_playlist.h>
+#include <vlc_player.h>
+#include <vlc_input.h>
+
+#include "dlna.hpp"
+
+#define SINK_PROTOCOL_INFO ("http-get:*:video/mpeg:*," \
+                            "http-get:*:video/mp4:*," \
+                            "http-get:*:video/vnd.dlna.mpeg-tts:*," \
+                            "http-get:*:video/avi:*," \
+                            "http-get:*:video/x-matroska:*," \
+                            "http-get:*:video/x-ms-wmv:*," \
+                            "http-get:*:video/wtv:*," \
+                            "http-get:*:audio/mpeg:*," \
+                            "http-get:*:audio/mp3:*," \
+                            "http-get:*:audio/mp4:*," \
+                            "http-get:*:audio/x-ms-wma*," \
+                            "http-get:*:audio/wav:*," \
+                            "http-get:*:audio/L16:*," \
+                            "http-get:*image/jpeg:*," \
+                            "http-get:*image/png:*," \
+                            "http-get:*image/gif:*," \
+                            "http-get:*image/tiff:*")
+
+struct intf_sys_t
+{
+    UpnpInstanceWrapper *p_upnp;
+    std::shared_ptr<DLNA::EventHandler> p_listener;
+    vlc_playlist_t *playlist;
+    vlc_player_t *player;
+    vlc_player_listener_id *p_player_listener;
+    vlc_player_aout_listener_id *p_player_aout_listener;
+};
+
+typedef std::map<std::string,std::string> parammap;
+
+typedef bool (*ActionRequestHandler)( parammap&, parammap&, intf_thread_t* );
+
+namespace DLNA
+{
+
+static bool
+handle_AVT_SetAVTransportURI( parammap& in_params, parammap& out_params,
+                              intf_thread_t *p_intf )
+{
+    VLC_UNUSED(out_params);
+
+    std::string s = in_params["CurrentURI"];
+
+    input_item_t *item = input_item_New(s.c_str(), NULL);
+    if( !item )
+    {
+        printf("failed to parse URL?");
+        return -1;
+    }
+
+    vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+    vlc_player_Lock( player );
+    vlc_player_SetCurrentMedia( player, item );
+    vlc_player_Start( player );
+    vlc_player_Unlock( player );
+
+    input_item_Release( item );
+
+    return true;
+}
+
+static bool
+handle_AVT_GetMediaInfo( parammap& in_params, parammap& out_params,
+                         intf_thread_t *p_intf )
+{
+    VLC_UNUSED(in_params);
+    VLC_UNUSED(p_intf);
+
+    out_params["MediaDuration"] = "00:00";
+
+    return true;
+}
+
+
+static bool
+handle_AVT_GetTransportInfo( parammap& in_params, parammap& out_params,
+                             intf_thread_t *p_intf )
+{
+    VLC_UNUSED(in_params);
+
+    vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+    vlc_player_Lock( player );
+    enum vlc_player_state state = vlc_player_GetState( player );
+    vlc_player_Unlock( player );
+
+    switch( state )
+    {
+    case VLC_PLAYER_STATE_STOPPED:
+        out_params["CurrentTransportState"] = "STOPPED";
+        break;
+    case VLC_PLAYER_STATE_PLAYING:
+        out_params["CurrentTransportState"] = "PLAYING";
+        break;
+    case VLC_PLAYER_STATE_PAUSED:
+        out_params["CurrentTransportState"] = "PAUSED_PLAYBACK";
+        break;
+    case VLC_PLAYER_STATE_STARTED: /* fall through */
+    case VLC_PLAYER_STATE_STOPPING:
+        out_params["CurrentTransportState"] = "TRANSITIONING";
+        break;
+    default:
+        out_params["CurrentTransportState"] = "UNKNOWN";
+        break;
+    }
+
+    out_params["CurrentTransportStatus"] = "";
+    out_params["CurrentSpeed"] = "";
+
+    return true;
+}
+
+static bool
+handle_AVT_Stop( parammap& in_params, parammap& out_params,
+                 intf_thread_t *p_intf )
+{
+    VLC_UNUSED(in_params);
+    VLC_UNUSED(out_params);
+
+    vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+    vlc_player_Lock( player );
+    vlc_player_Stop( player );
+    vlc_player_Unlock( player );
+
+    return true;
+}
+
+static bool
+handle_AVT_Play( parammap& in_params, parammap& out_params,
+                 intf_thread_t *p_intf )
+{
+    VLC_UNUSED(in_params);
+    VLC_UNUSED(out_params);
+
+    vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+    vlc_player_Lock( player );
+    vlc_player_Resume( player );
+    vlc_player_Unlock( player );
+
+    return true;
+}
+
+static bool
+handle_AVT_Pause( parammap& in_params, parammap& out_params,
+                  intf_thread_t *p_intf )
+{
+    VLC_UNUSED(in_params);
+    VLC_UNUSED(out_params);
+
+    vlc_player_t *player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+
+    vlc_player_Lock( player );
+    vlc_player_Pause( player );
+    vlc_player_Unlock( player );
+
+    return true;
+}
+
+static bool
+handle_CM_GetProtocolInfo( parammap& in_params, parammap& out_params,
+                           intf_thread_t *p_intf )
+{
+    VLC_UNUSED(p_intf);
+    VLC_UNUSED(in_params);
+
+    out_params["Source"] = "";
+    out_params["Sink"] = SINK_PROTOCOL_INFO;
+
+    return true;
+}
+
+static bool
+handle_RC_GetVolume( parammap& in_params, parammap& out_params,
+                     intf_thread_t *p_intf )
+{
+    VLC_UNUSED(in_params);
+
+    /* Volume as in range [0.0, 2.0] or -1.0 if no audio */
+    float volume = vlc_player_aout_GetVolume( p_intf->p_sys->player );
+    if( volume < 0.0 )
+        volume = 0.0;
+    else if( volume > 2.0 )
+        volume = 2.0;
+
+    std::string v = std::to_string( std::round( volume * 50 ) );
+
+    out_params["CurrentVolume"] = v;
+
+    return true;
+}
+
+static bool
+handle_RC_SetVolume( parammap& in_params, parammap& out_params,
+                     intf_thread_t *p_intf )
+{
+    VLC_UNUSED(out_params);
+
+    /* Volume in range [0, 100] */
+    unsigned long volume = std::stoul( in_params["DesiredVolume"] );
+    if( volume > 100 )
+        volume = 100;
+
+    int ret = vlc_player_aout_SetVolume( p_intf->p_sys->player, volume / 50.0 );
+    if ( ret != UPNP_E_SUCCESS )
+    {
+    }
+
+    return true;
+}
+
+#define SRV_AVT "urn:upnp-org:serviceId:AVTransport"
+#define SRV_RC  "urn:upnp-org:serviceId:RenderingControl"
+#define SRV_CM  "urn:upnp-org:serviceId:ConnectionManager"
+
+static struct {
+    const char *service;
+    const char *action;
+    ActionRequestHandler handler;
+} actions[] = {
+    { SRV_AVT, "SetAVTransportURI", handle_AVT_SetAVTransportURI },
+    { SRV_AVT, "GetMediaInfo", handle_AVT_GetMediaInfo },
+    { SRV_AVT, "GetTransportInfo", handle_AVT_GetTransportInfo },
+    { SRV_AVT, "Stop", handle_AVT_Stop },
+    { SRV_AVT, "Play", handle_AVT_Play },
+    { SRV_AVT, "Pause", handle_AVT_Pause },
+    { SRV_CM, "GetProtocolInfo", handle_CM_GetProtocolInfo },
+    { SRV_RC, "GetVolume", handle_RC_GetVolume },
+    { SRV_RC, "SetVolume", handle_RC_SetVolume },
+    { NULL, NULL, NULL }
+};
+
+static parammap
+build_param_map( IXML_Node *p_node )
+{
+    parammap params;
+
+    for( IXML_Node *param = ixmlNode_getFirstChild( p_node );
+         param != NULL;
+         param = ixmlNode_getNextSibling( param ) )
+    {
+        const DOMString key = ixmlNode_getNodeName(param);
+        if( !key )
+            continue;
+
+        IXML_Node *vnode = ixmlNode_getFirstChild( param );
+        if( !vnode )
+            continue;
+
+        const DOMString value = ixmlNode_getNodeValue( vnode );
+        if( !value )
+            continue;
+
+        params[std::string(key)] = std::string(value);
+    }
+
+    return params;
+}
+
+static std::string 
+build_event_xml( const char **keys, const char **values, int count )
+{
+    IXML_Document *doc = NULL;
+    IXML_Element *event = NULL;
+    IXML_Element *instance = NULL;
+    DOMString xmlcstr = NULL;
+    std::string xmlstr;
+
+    doc = ixmlDocument_createDocument();
+    if( !doc )
+        return xmlstr;
+
+    event = ixmlDocument_createElement( doc, "Event" );
+    if( !event )
+        goto err1;
+
+    if( ixmlNode_appendChild( (IXML_Node *)doc,
+                              (IXML_Node *)event ) != IXML_SUCCESS )
+    {
+        ixmlElement_free( event );
+        goto err1;
+    }
+
+    instance = ixmlDocument_createElement( doc, "InstanceID" );
+    if( !instance )
+        goto err1;
+
+    if( ixmlNode_appendChild( (IXML_Node *)event,
+                              (IXML_Node *)instance ) != IXML_SUCCESS )
+    {
+        ixmlElement_free( instance );
+        goto err1;
+    }
+
+    if( ixmlElement_setAttribute( instance, "val", "0") != IXML_SUCCESS )
+        goto err1;
+
+    for( int i = 0; i < count; i++ )
+    {
+        IXML_Element *arg = ixmlDocument_createElement( doc, keys[i] );
+        if( !arg )
+            goto err1;
+
+        if( ixmlNode_appendChild( (IXML_Node *)instance,
+                                  (IXML_Node *)arg ) != IXML_SUCCESS )
+        {
+            ixmlElement_free( arg );
+            goto err1;
+        }
+
+        if( ixmlElement_setAttribute( arg, "val", values[i]) != IXML_SUCCESS )
+            goto err1;
+    }
+
+    xmlcstr = ixmlNodetoString( (IXML_Node *)doc );
+    if( !xmlcstr )
+        goto err1;
+
+    xmlstr = std::string(xmlcstr);
+
+    free( xmlcstr );
+
+    ixmlDocument_free( doc );
+
+    xmlstr = std::regex_replace(xmlstr, std::regex("&"), "&");
+    xmlstr = std::regex_replace(xmlstr, std::regex("\""), """);
+    xmlstr = std::regex_replace(xmlstr, std::regex("\'"), "'");
+    xmlstr = std::regex_replace(xmlstr, std::regex("<"), "<");
+    xmlstr = std::regex_replace(xmlstr, std::regex(">"), ">");
+
+    return xmlstr;
+
+err1:
+    ixmlDocument_free( doc );
+
+    return xmlstr;
+}
+
+int EventHandler::onActionRequest( UpnpActionRequest *p_event,
+                                   void *p_user_data )
+{
+    (void) p_user_data;
+
+    /* For example urn:upnp-org:serviceId:AVTransport */
+    char *service_id = UpnpActionRequest_get_ServiceID_cstr( p_event );
+
+    /* For example SetAVTransportURI */
+    char *action_name = UpnpActionRequest_get_ActionName_cstr( p_event );
+
+    /* "Body" XML node in the request */
+    IXML_Document *body = UpnpActionRequest_get_ActionRequest( p_event );
+    if( !body )
+        return 0;
+
+    for( IXML_Node *action = ixmlNode_getFirstChild( (IXML_Node*) body );
+         action != NULL;
+         action = ixmlNode_getNextSibling( action ) )
+    {
+        parammap in_params = build_param_map( action );
+
+        for( size_t i = 0; actions[i].handler; i++ )
+        {
+            if( strcmp( actions[i].service, service_id ) ||
+                strcmp( actions[i].action, action_name ) )
+                continue;
+
+            parammap out_params;
+
+            p_event->ErrCode = UPNP_E_SUCCESS;
+            p_event->ActionResult = NULL;
+
+            int r = actions[i].handler( in_params, out_params, p_intf );
+            if( !r )
+                continue;
+
+            p_event->ActionResult = UpnpMakeActionResponse( action_name,
+                                                            service_id,
+                                                            0,
+                                                            NULL );
+            if( !p_event->ActionResult )
+                continue;
+
+            for( auto& x : out_params )
+            {
+                int r = UpnpAddToActionResponse( &p_event->ActionResult,
+                                                 action_name,
+                                                 service_id,
+                                                 x.first.c_str(),
+                                                 x.second.c_str() );
+                if( r != UPNP_E_SUCCESS )
+                {
+                    if( p_event->ActionResult != NULL )
+                        ixmlDocument_free( p_event->ActionResult );
+                    p_event->ActionResult = NULL;
+
+                    return r;
+                }
+            }
+
+            return UPNP_E_SUCCESS;
+        }
+    }
+
+    // TODO: return "not implemented"
+
+    return UPNP_E_INTERNAL_ERROR;
+}
+
+int EventHandler::onGetVarRequest( UpnpStateVarRequest *p_event,
+                                   void *p_user_data )
+{
+    (void) p_event;
+    (void) p_user_data;
+
+    // TODO
+
+    return -124;
+}
+
+int EventHandler::onSubscriptionRequest( UpnpSubscriptionRequest *p_event,
+                                         void *p_user_data )
+{
+    (void) p_user_data;
+
+    /* For example urn:upnp-org:serviceId:AVTransport */
+    char *service_id = UpnpSubscriptionRequest_get_ServiceId_cstr( p_event );
+
+    /* For example ... */
+    char *udn = UpnpSubscriptionRequest_get_UDN_cstr( p_event );
+
+    std::string event_xml = build_event_xml( NULL, NULL, 0 );
+std::cout << event_xml << std::endl;
+
+    const char *var_keys[1] = { "LastChange" };
+    const char *var_values[1] = { event_xml.c_str() };
+
+    int ret = UpnpAcceptSubscription( p_intf->p_sys->p_upnp->device_handle(),
+                                      udn,
+                                      service_id,
+                                      (const char **) &var_keys,
+                                      (const char **) &var_values,
+                                      1,
+                                      p_event->Sid );
+    if( ret != UPNP_E_SUCCESS )
+    {
+        printf("UpnpAcceptSubscription failed\n");
+    }
+
+    return ret;
+}
+
+int EventHandler::onEvent( Upnp_EventType event_type,
+                           UpnpEventPtr p_event,
+                           void* p_user_data )
+{
+    switch( event_type )
+    {
+    case UPNP_CONTROL_ACTION_REQUEST:
+        return onActionRequest( (UpnpActionRequest *) p_event, p_user_data );
+    case UPNP_CONTROL_GET_VAR_REQUEST:
+        return onGetVarRequest( (UpnpStateVarRequest *) p_event, p_user_data );
+    case UPNP_EVENT_SUBSCRIPTION_REQUEST:
+        return onSubscriptionRequest( (UpnpSubscriptionRequest *) p_event,
+                                      p_user_data );
+    default:
+        break;
+    }
+
+    return -123;
+}
+
+static void
+player_state_changed( vlc_player_t *player, enum vlc_player_state state,
+                      void *data )
+{
+    (void) player;
+
+    intf_thread_t *p_intf = (intf_thread_t *)data;
+
+    const char *new_state;
+
+    switch (state)
+    {
+    case VLC_PLAYER_STATE_STOPPED:
+        new_state = "STOPPED";
+        break;
+    case VLC_PLAYER_STATE_PLAYING:
+        new_state = "PLAYING";
+        break;
+    case VLC_PLAYER_STATE_PAUSED:
+        new_state = "PAUSED_PLAYBACK";
+        break;
+    case VLC_PLAYER_STATE_STARTED: /* fall through */
+    case VLC_PLAYER_STATE_STOPPING:
+        new_state = "TRANSITIONING";
+        break;
+    default:
+        new_state = "UNKNOWN";
+        break;
+    }
+
+    const char *event_keys[1] = { "TransportState" };
+    const char *event_values[1] = { new_state };
+
+    std::string event_xml = build_event_xml( event_keys, event_values, 1 );
+std::cout << event_xml << std::endl;
+
+    const char *var_keys[1] = { "LastChange" };
+    const char *var_values[1] = { event_xml.c_str() };
+
+    int ret = UpnpNotify( p_intf->p_sys->p_upnp->device_handle(),
+                          UPNP_UDN,
+                          SRV_AVT,
+                          (const char **) &var_keys,
+                          (const char **) &var_values,
+                          1 );
+    if ( ret != UPNP_E_SUCCESS )
+    {
+        printf("UpnpNotify failed\n");
+    }
+}
+
+static void
+player_aout_volume_changed( vlc_player_t *player, float new_volume,
+                            void *data )
+{
+    (void) player;
+
+    intf_thread_t *p_intf = (intf_thread_t *) data;
+
+    if( new_volume < 0.0 )
+        new_volume = 0.0;
+    else if( new_volume > 2.0 )
+        new_volume = 2.0;
+
+    /* Volume in range [0, 100] */
+    std::string v = std::to_string( std::round( new_volume * 50 ) );
+
+    const char *event_keys[1] = { "Volume" };
+    const char *event_values[1] = { v.c_str() };
+
+    std::string event_xml = build_event_xml( event_keys, event_values, 1 );
+std::cout << event_xml << std::endl;
+
+    const char *var_keys[1] = { "LastChange" };
+    const char *var_values[1] = { event_xml.c_str() };
+
+    int ret = UpnpNotify( p_intf->p_sys->p_upnp->device_handle(),
+                          UPNP_UDN,
+                          SRV_RC,
+                          (const char **) &var_keys,
+                          (const char **) &var_values,
+                          1 );
+    if ( ret != UPNP_E_SUCCESS )
+    {
+        printf("UpnpNotify failed\n");
+    }
+}
+
+int OpenControl( vlc_object_t *p_this )
+{
+    intf_thread_t *p_intf = (intf_thread_t *)p_this;
+
+    p_intf->p_sys = (intf_sys_t *)calloc ( 1, sizeof( intf_sys_t ) );
+    if( unlikely(p_intf->p_sys == NULL) )
+        return VLC_ENOMEM;
+
+    p_intf->p_sys->playlist = vlc_intf_GetMainPlaylist( p_intf );
+    if( !p_intf->p_sys->playlist )
+        goto error1;
+
+    p_intf->p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
+    if( !p_intf->p_sys->p_upnp )
+        goto error1;
+
+    /* Start the UPnP MediaRenderer service */
+    p_intf->p_sys->p_upnp->startMediaRenderer( p_this );
+    try
+    {
+        p_intf->p_sys->p_listener =
+            std::make_shared<DLNA::EventHandler>( p_intf );
+    }
+    catch ( const std::bad_alloc& )
+    {
+        msg_Err( p_this, "Failed to alloc");
+        goto error2;
+    }
+
+    p_intf->p_sys->p_upnp->addListener( p_intf->p_sys->p_listener );
+
+    static struct vlc_player_cbs player_cbs = {};
+    player_cbs.on_state_changed = player_state_changed;
+
+    static struct vlc_player_aout_cbs player_aout_cbs = {};
+    player_aout_cbs.on_volume_changed = player_aout_volume_changed;
+
+    p_intf->p_sys->player = vlc_playlist_GetPlayer( p_intf->p_sys->playlist );
+    if( !p_intf->p_sys->player )
+        goto error2;
+
+    vlc_playlist_Lock( p_intf->p_sys->playlist );
+
+    p_intf->p_sys->p_player_listener =
+        vlc_player_AddListener( p_intf->p_sys->player, &player_cbs, p_intf );
+    if ( !p_intf->p_sys->p_player_listener )
+        goto error3;
+
+    p_intf->p_sys->p_player_aout_listener =
+        vlc_player_aout_AddListener( p_intf->p_sys->player, &player_aout_cbs,
+                                     p_intf );
+    if ( !p_intf->p_sys->p_player_aout_listener )
+        goto error4;
+
+    vlc_playlist_Unlock( p_intf->p_sys->playlist );
+
+    msg_Info( p_this, "Started MediaRenderer service");
+
+    return VLC_SUCCESS;
+
+error4:
+    vlc_playlist_Lock( p_intf->p_sys->playlist );
+    vlc_player_RemoveListener( p_intf->p_sys->player,
+                               p_intf->p_sys->p_player_listener );
+    vlc_playlist_Unlock( p_intf->p_sys->playlist );
+
+error3:
+    p_intf->p_sys->p_upnp->removeListener( p_intf->p_sys->p_listener );
+
+error2:
+    p_intf->p_sys->p_upnp->release();
+
+error1:
+    free( p_intf->p_sys );
+
+    return VLC_EGENERIC;
+}
+
+void CloseControl( vlc_object_t *p_this )
+{
+    intf_thread_t *p_intf = (intf_thread_t *)p_this;
+
+    /* Remove player listeners */
+    vlc_playlist_Lock( p_intf->p_sys->playlist );
+    vlc_player_aout_RemoveListener( p_intf->p_sys->player,
+                                    p_intf->p_sys->p_player_aout_listener );
+    vlc_player_RemoveListener( p_intf->p_sys->player,
+                               p_intf->p_sys->p_player_listener );
+    vlc_playlist_Unlock( p_intf->p_sys->playlist );
+
+    /* Remove UPnP listener */
+    p_intf->p_sys->p_upnp->removeListener( p_intf->p_sys->p_listener );
+
+    /* Stop the UPnP MediaRenderer service */
+    p_intf->p_sys->p_upnp->stopMediaRenderer( p_this );
+
+    p_intf->p_sys->p_upnp->release();
+
+    free( p_intf->p_sys );
+
+    msg_Info( p_this, "Stopped MediaRenderer service");
+}
+
+} // namespace DLNA
diff --git a/modules/control/dlna.hpp b/modules/control/dlna.hpp
new file mode 100644
index 0000000000..c6ee6439a8
--- /dev/null
+++ b/modules/control/dlna.hpp
@@ -0,0 +1,68 @@
+/*****************************************************************************
+ * dlna.cpp :  DLNA MediaRenderer module
+ *****************************************************************************
+ * Copyright (C) 2019 VLC authors and VideoLAN
+ *
+ * Authors: Johan Gunnarsson <johan.gunnarsson at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef CONTROL_DLNA_H
+#define CONTROL_DLNA_H
+
+#include "../services_discovery/upnp-wrapper.hpp"
+
+#include <vlc_plugin.h>
+#include <vlc_interface.h>
+
+namespace DLNA
+{
+
+int OpenControl( vlc_object_t *p_this );
+void CloseControl( vlc_object_t *p_this );
+
+class EventHandler : public UpnpInstanceWrapper::Listener
+{
+public:
+    EventHandler( intf_thread_t *_p_intf )
+        : p_intf( _p_intf )
+    {
+    }
+
+    ~EventHandler()
+    {
+    }
+
+    int onEvent( Upnp_EventType event_type,
+                 UpnpEventPtr p_event,
+                 void *p_user_data ) override;
+
+private:
+    intf_thread_t *p_intf = NULL;
+
+    int onActionRequest( UpnpActionRequest *p_event,
+                         void *p_user_data );
+
+    int onGetVarRequest( UpnpStateVarRequest *p_event,
+                         void *p_user_data );
+
+    int onSubscriptionRequest( UpnpSubscriptionRequest *p_event,
+                               void *p_user_data );
+};
+
+} // namespace DLNA
+
+#endif
diff --git a/modules/services_discovery/Makefile.am b/modules/services_discovery/Makefile.am
index 72c21efb56..65af52edf8 100644
--- a/modules/services_discovery/Makefile.am
+++ b/modules/services_discovery/Makefile.am
@@ -31,7 +31,9 @@ libupnp_plugin_la_SOURCES = services_discovery/upnp.cpp services_discovery/upnp.
 			    stream_out/dlna/profile_names.hpp \
 			    stream_out/dlna/dlna_common.hpp \
 			    stream_out/dlna/dlna.hpp \
-			    stream_out/dlna/dlna.cpp
+			    stream_out/dlna/dlna.cpp \
+			    control/dlna.hpp \
+			    control/dlna.cpp
 libupnp_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(UPNP_CFLAGS)
 libupnp_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)'
 libupnp_plugin_la_LIBADD = $(UPNP_LIBS)
diff --git a/modules/services_discovery/upnp-wrapper.cpp b/modules/services_discovery/upnp-wrapper.cpp
index c001492f37..9f3748ec21 100644
--- a/modules/services_discovery/upnp-wrapper.cpp
+++ b/modules/services_discovery/upnp-wrapper.cpp
@@ -36,19 +36,65 @@
 #include "upnp-wrapper.hpp"
 #include <vlc_cxx_helpers.hpp>
 
+static const char *mediarenderer_desc =
+    "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+    "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">"
+      "<specVersion>"
+        "<major>1</major>"
+        "<minor>0</minor>"
+      "</specVersion>"
+      "<device>"
+        "<deviceType>urn:schemas-upnp-org:device:MediaRenderer:1</deviceType>"
+        "<friendlyName>VLC media player</friendlyName>" /* TODO: include hostname */
+        "<manufacturer>VideoLAN</manufacturer>"
+        "<modelName>" PACKAGE_NAME "</modelName>"
+        "<modelNumber>" PACKAGE_VERSION "</modelNumber>"
+        "<modelURL>https://www.videolan.org/vlc/</modelURL>"
+        "<UDN>" UPNP_UDN "</UDN>" /* TODO: generate at each startup */
+        "<serviceList>"
+          "<service>"
+            "<serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>"
+            "<serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>"
+            "<SCPDURL>/RenderingControlSCPD.xml</SCPDURL>"
+            "<controlURL>/upnp/control/RenderingControl</controlURL>"
+            "<eventSubURL>/upnp/event/RenderingControl</eventSubURL>"
+          "</service>"
+          "<service>"
+            "<serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>"
+            "<serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>"
+            "<SCPDURL>/ConnectionManagerSCPD.xml</SCPDURL>"
+            "<controlURL>/upnp/control/ConnectionManager</controlURL>"
+            "<eventSubURL>/upnp/event/ConnectionManager</eventSubURL>"
+          "</service>"
+          "<service>"
+            "<serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>"
+            "<serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>"
+            "<SCPDURL>/AVTransportSCPD.xml</SCPDURL>"
+            "<controlURL>/upnp/control/AVTransport</controlURL>"
+            "<eventSubURL>/upnp/event/AVTransport</eventSubURL>"
+          "</service>"
+        "</serviceList>"
+      "</device>"
+    "</root>";
+
 UpnpInstanceWrapper* UpnpInstanceWrapper::s_instance;
 UpnpInstanceWrapper::Listeners UpnpInstanceWrapper::s_listeners;
 vlc_mutex_t UpnpInstanceWrapper::s_lock = VLC_STATIC_MUTEX;
 
 UpnpInstanceWrapper::UpnpInstanceWrapper()
-    : m_handle( -1 )
+    : m_client_handle( -1 )
+    , m_device_handle( -1 )
     , m_refcount( 0 )
+    , m_mediarenderer_refcount( 0 )
 {
 }
 
 UpnpInstanceWrapper::~UpnpInstanceWrapper()
 {
-    UpnpUnRegisterClient( m_handle );
+    if( m_client_handle > 0 )
+        UpnpUnRegisterClient( m_client_handle );
+    if( m_device_handle > 0 )
+        UpnpUnRegisterRootDevice( m_device_handle );
     UpnpFinish();
 }
 
@@ -86,7 +132,7 @@ UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj)
         ixmlRelaxParser( 1 );
 
         /* Register a control point */
-        i_res = UpnpRegisterClient( Callback, NULL, &instance->m_handle );
+        i_res = UpnpRegisterClient( Callback, NULL, &instance->m_client_handle );
         if( i_res != UPNP_E_SUCCESS )
         {
             msg_Err( p_obj, "Client registration failed: %s", UpnpGetErrorMessage( i_res ) );
@@ -104,6 +150,15 @@ UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj)
             delete instance;
             return NULL;
         }
+
+        char *root = config_GetSysPath( VLC_PKG_DATA_DIR, "upnp" );
+        if( (i_res = UpnpSetWebServerRootDir( root )) != UPNP_E_SUCCESS)
+        {
+            msg_Warn( p_obj, "UpnpSetWebServerRootDir failed: %s",
+                      UpnpGetErrorMessage( i_res ));
+        }
+        free( root );
+
         s_instance = instance;
     }
     s_instance->m_refcount++;
@@ -123,9 +178,14 @@ void UpnpInstanceWrapper::release()
     delete p_delete;
 }
 
-UpnpClient_Handle UpnpInstanceWrapper::handle() const
+UpnpClient_Handle UpnpInstanceWrapper::client_handle() const
+{
+    return m_client_handle;
+}
+
+UpnpDevice_Handle UpnpInstanceWrapper::device_handle() const
 {
-    return m_handle;
+    return m_device_handle;
 }
 
 int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, UpnpEventPtr p_event, void *p_user_data)
@@ -153,3 +213,50 @@ void UpnpInstanceWrapper::removeListener(ListenerPtr listener)
     if ( iter != s_listeners.end() )
         s_listeners.erase( iter );
 }
+
+void UpnpInstanceWrapper::startMediaRenderer( vlc_object_t *p_obj )
+{
+    vlc::threads::mutex_locker lock( &s_lock );
+    if( m_mediarenderer_refcount == 0 )
+    {
+        int i_res;
+        if( (i_res = UpnpEnableWebserver( TRUE )) != UPNP_E_SUCCESS)
+        {
+            msg_Err( p_obj, "Failed to enable webserver: %s", UpnpGetErrorMessage( i_res ) );
+            return;
+        }
+        i_res = UpnpRegisterRootDevice2( UPNPREG_BUF_DESC,
+                                         mediarenderer_desc,
+                                         strlen(mediarenderer_desc),
+                                         1,
+                                         Callback,
+                                         NULL,
+                                         &m_device_handle );
+        if( i_res != UPNP_E_SUCCESS )
+        {
+            msg_Err( p_obj, "Device registration failed: %s", UpnpGetErrorMessage( i_res ) );
+        }
+    }
+    m_mediarenderer_refcount++;
+}
+
+void UpnpInstanceWrapper::stopMediaRenderer( vlc_object_t *p_obj )
+{
+    vlc::threads::mutex_locker lock( &s_lock );
+    m_mediarenderer_refcount--;
+    if( m_mediarenderer_refcount == 0 )
+    {
+        int i_res;
+        i_res = UpnpUnRegisterRootDevice( m_device_handle );
+        if( i_res != UPNP_E_SUCCESS )
+        {
+            msg_Err( p_obj, "Device unregistration failed: %s", UpnpGetErrorMessage( i_res ) );
+        }
+        m_device_handle = -1;
+        i_res = UpnpEnableWebserver( FALSE );
+        if( i_res != UPNP_E_SUCCESS )
+        {
+            msg_Warn( p_obj, "Failed to disable webserver: %s", UpnpGetErrorMessage( i_res ) );
+        }
+    }
+}
diff --git a/modules/services_discovery/upnp-wrapper.hpp b/modules/services_discovery/upnp-wrapper.hpp
index 3002599172..e0e4652b08 100644
--- a/modules/services_discovery/upnp-wrapper.hpp
+++ b/modules/services_discovery/upnp-wrapper.hpp
@@ -41,6 +41,8 @@
 #include <upnp.h>
 #include <upnptools.h>
 
+#define UPNP_UDN "uuid:034fc8dc-ec22-44e5-a79b-38c935f11663"
+
 #if UPNP_VERSION < 10800
 typedef void* UpnpEventPtr;
 #else
@@ -70,8 +72,10 @@ public:
 private:
     static UpnpInstanceWrapper* s_instance;
     static vlc_mutex_t s_lock;
-    UpnpClient_Handle m_handle;
+    UpnpClient_Handle m_client_handle;
+    UpnpClient_Handle m_device_handle;
     int m_refcount;
+    int m_mediarenderer_refcount;
     typedef std::shared_ptr<Listener> ListenerPtr;
     typedef std::vector<ListenerPtr> Listeners;
     static Listeners s_listeners;
@@ -80,9 +84,12 @@ public:
     // This increases the refcount before returning the instance
     static UpnpInstanceWrapper* get( vlc_object_t* p_obj );
     void release();
-    UpnpClient_Handle handle() const;
+    UpnpClient_Handle client_handle() const;
+    UpnpDevice_Handle device_handle() const;
     void addListener(ListenerPtr listener);
     void removeListener(ListenerPtr listener);
+    void startMediaRenderer( vlc_object_t *p_obj );
+    void stopMediaRenderer( vlc_object_t *p_obj );
 
 private:
     static int Callback( Upnp_EventType event_type, UpnpEventPtr p_event, void* p_user_data );
diff --git a/modules/services_discovery/upnp.cpp b/modules/services_discovery/upnp.cpp
index a467caaaa8..310969f908 100644
--- a/modules/services_discovery/upnp.cpp
+++ b/modules/services_discovery/upnp.cpp
@@ -180,6 +180,13 @@ vlc_module_begin()
         add_string(SOUT_CFG_PREFIX "base_url", NULL, BASE_URL_TEXT, BASE_URL_LONGTEXT, false)
         add_string(SOUT_CFG_PREFIX "url", NULL, URL_TEXT, URL_LONGTEXT, false)
         add_renderer_opts(SOUT_CFG_PREFIX)
+
+    add_submodule()
+        set_description( N_("UPnP/DLNA MediaRenderer") )
+        set_category( CAT_INTERFACE )
+        set_subcategory( SUBCAT_INTERFACE_CONTROL )
+        set_callbacks( DLNA::OpenControl, DLNA::CloseControl )
+        set_capability( "interface", 0 )
 vlc_module_end()
 
 /*
@@ -271,7 +278,7 @@ SearchThread( void *p_data )
     services_discovery_sys_t *p_sys = reinterpret_cast<services_discovery_sys_t *>( p_sd->p_sys );
 
     /* Search for media servers */
-    int i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
+    int i_res = UpnpSearchAsync( p_sys->p_upnp->client_handle(), 5,
             MEDIA_SERVER_DEVICE_TYPE, MEDIA_SERVER_DEVICE_TYPE );
     if( i_res != UPNP_E_SUCCESS )
     {
@@ -280,7 +287,7 @@ SearchThread( void *p_data )
     }
 
     /* Search for Sat Ip servers*/
-    i_res = UpnpSearchAsync( p_sys->p_upnp->handle(), 5,
+    i_res = UpnpSearchAsync( p_sys->p_upnp->client_handle(), 5,
             SATIP_SERVER_DEVICE_TYPE, MEDIA_SERVER_DEVICE_TYPE );
     if( i_res != UPNP_E_SUCCESS )
         msg_Err( p_sd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
@@ -1249,7 +1256,7 @@ IXML_Document* MediaServer::_browseAction( const char* psz_object_id_,
     /* Setup an interruptible callback that will call sendActionCb if not
      * interrupted by vlc_interrupt_kill */
     i11eCb = new Upnp_i11e_cb( sendActionCb, &p_response );
-    i_res = UpnpSendActionAsync( sys->p_upnp->handle(),
+    i_res = UpnpSendActionAsync( sys->p_upnp->client_handle(),
               m_psz_root,
               CONTENT_DIRECTORY_SERVICE_TYPE,
               NULL, /* ignored in SDK, must be NULL */
@@ -1587,7 +1594,7 @@ void *SearchThread(void *data)
     renderer_discovery_sys_t *p_sys = (renderer_discovery_sys_t*)p_rd->p_sys;
     int i_res;
 
-    i_res = UpnpSearchAsync(p_sys->p_upnp->handle(), UPNP_SEARCH_TIMEOUT_SECONDS,
+    i_res = UpnpSearchAsync(p_sys->p_upnp->client_handle(), UPNP_SEARCH_TIMEOUT_SECONDS,
             MEDIA_RENDERER_DEVICE_TYPE, MEDIA_RENDERER_DEVICE_TYPE);
     if( i_res != UPNP_E_SUCCESS )
     {
diff --git a/modules/services_discovery/upnp.hpp b/modules/services_discovery/upnp.hpp
index 4eef534f91..ca527af250 100644
--- a/modules/services_discovery/upnp.hpp
+++ b/modules/services_discovery/upnp.hpp
@@ -29,6 +29,7 @@
 
 #include "upnp-wrapper.hpp"
 #include "../stream_out/dlna/dlna_common.hpp"
+#include "../control/dlna.hpp"
 
 #include <vlc_url.h>
 #include <vlc_interrupt.h>
diff --git a/modules/stream_out/dlna/dlna.hpp b/modules/stream_out/dlna/dlna.hpp
index 128ee1c423..6bda6a7587 100644
--- a/modules/stream_out/dlna/dlna.hpp
+++ b/modules/stream_out/dlna/dlna.hpp
@@ -76,7 +76,7 @@ public:
         : parent(p_stream)
         , base_url(base_url)
         , device_url(device_url)
-        , handle(upnp->handle())
+        , handle(upnp->client_handle())
         , ConnectionID("0")
         , AVTransportID("0")
         , RcsID("0")
diff --git a/share/Makefile.am b/share/Makefile.am
index e5b801a5be..3c2b34641b 100644
--- a/share/Makefile.am
+++ b/share/Makefile.am
@@ -67,6 +67,11 @@ if KDE_SOLID
 soliddata_DATA = $(DIST_solid)
 endif
 
+nobase_dist_pkgdata_DATA += \
+	upnp/AVTransportSCPD.xml \
+	upnp/ConnectionManagerSCPD.xml \
+	upnp/RenderingControlSCPD.xml
+
 DIST_icons = \
 	vlc512x512.png
 
diff --git a/share/upnp/AVTransportSCPD.xml b/share/upnp/AVTransportSCPD.xml
new file mode 100644
index 0000000000..fbb75f9978
--- /dev/null
+++ b/share/upnp/AVTransportSCPD.xml
@@ -0,0 +1,585 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+   <specVersion>
+      <major>1</major>
+      <minor>0</minor>
+   </specVersion>
+   <actionList>
+      <action>
+         <name>SetAVTransportURI</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentURI</name>
+               <direction>in</direction>
+               <relatedStateVariable>AVTransportURI</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentURIMetaData</name>
+               <direction>in</direction>
+               <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetNextAVTransportURI</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>NextURI</name>
+               <direction>in</direction>
+               <relatedStateVariable>NextAVTransportURI</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>NextURIMetaData</name>
+               <direction>in</direction>
+               <relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetMediaInfo</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>NrTracks</name>
+               <direction>out</direction>
+               <relatedStateVariable>NumberOfTracks</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>MediaDuration</name>
+               <direction>out</direction>
+               <relatedStateVariable>CurrentMediaDuration</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentURI</name>
+               <direction>out</direction>
+               <relatedStateVariable>AVTransportURI</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentURIMetaData</name>
+               <direction>out</direction>
+               <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>NextURI</name>
+               <direction>out</direction>
+               <relatedStateVariable>NextAVTransportURI</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>NextURIMetaData</name>
+               <direction>out</direction>
+               <relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>PlayMedium</name>
+               <direction>out</direction>
+               <relatedStateVariable>PlaybackStorageMedium</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>RecordMedium</name>
+               <direction>out</direction>
+               <relatedStateVariable>RecordStorageMedium</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>WriteStatus</name>
+               <direction>out</direction>
+               <relatedStateVariable>RecordMediumWriteStatus</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetTransportInfo</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentTransportState</name>
+               <direction>out</direction>
+               <relatedStateVariable>TransportState</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentTransportStatus</name>
+               <direction>out</direction>
+               <relatedStateVariable>TransportStatus</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentSpeed</name>
+               <direction>out</direction>
+               <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetPositionInfo</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Track</name>
+               <direction>out</direction>
+               <relatedStateVariable>CurrentTrack</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>TrackDuration</name>
+               <direction>out</direction>
+               <relatedStateVariable>CurrentTrackDuration</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>TrackMetaData</name>
+               <direction>out</direction>
+               <relatedStateVariable>CurrentTrackMetaData</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>TrackURI</name>
+               <direction>out</direction>
+               <relatedStateVariable>CurrentTrackURI</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>RelTime</name>
+               <direction>out</direction>
+               <relatedStateVariable>RelativeTimePosition</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>AbsTime</name>
+               <direction>out</direction>
+               <relatedStateVariable>AbsoluteTimePosition</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>RelCount</name>
+               <direction>out</direction>
+               <relatedStateVariable>RelativeCounterPosition</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>AbsCount</name>
+               <direction>out</direction>
+               <relatedStateVariable>AbsoluteCounterPosition</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetDeviceCapabilities</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>PlayMedia</name>
+               <direction>out</direction>
+               <relatedStateVariable>PossiblePlaybackStorageMedia</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>RecMedia</name>
+               <direction>out</direction>
+               <relatedStateVariable>PossibleRecordStorageMedia</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>RecQualityModes</name>
+               <direction>out</direction>
+               <relatedStateVariable>PossibleRecordQualityModes</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetTransportSettings</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>PlayMode</name>
+               <direction>out</direction>
+               <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>RecQualityMode</name>
+               <direction>out</direction>
+               <relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>Stop</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>Play</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Speed</name>
+               <direction>in</direction>
+               <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>Pause</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>Seek</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Unit</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_SeekMode</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Target</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_SeekTarget</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>Next</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>Previous</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetPlayMode</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>NewPlayMode</name>
+               <direction>in</direction>
+               <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetCurrentTransportActions</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Actions</name>
+               <direction>out</direction>
+               <relatedStateVariable>CurrentTransportActions</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+   </actionList>
+   <serviceStateTable>
+      <stateVariable sendEvents="no">
+         <name>TransportState</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>STOPPED</allowedValue>
+            <allowedValue>PAUSED_PLAYBACK</allowedValue>
+            <allowedValue>PAUSED_RECORDING</allowedValue>
+            <allowedValue>PLAYING</allowedValue>
+            <allowedValue>RECORDING</allowedValue>
+            <allowedValue>TRANSITIONING</allowedValue>
+            <allowedValue>NO_MEDIA_PRESENT</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>TransportStatus</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>OK</allowedValue>
+            <allowedValue>ERROR_OCCURRED</allowedValue>
+            <allowedValue>vendor-defined</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>PlaybackStorageMedium</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>UNKNOWN</allowedValue>
+            <allowedValue>DV</allowedValue>
+            <allowedValue>MINI-DV</allowedValue>
+            <allowedValue>VHS</allowedValue>
+            <allowedValue>W-VHS</allowedValue>
+            <allowedValue>S-VHS</allowedValue>
+            <allowedValue>D-VHS</allowedValue>
+            <allowedValue>VHSC</allowedValue>
+            <allowedValue>VIDEO8</allowedValue>
+            <allowedValue>HI8</allowedValue>
+            <allowedValue>CD-ROM</allowedValue>
+            <allowedValue>CD-DA</allowedValue>
+            <allowedValue>CD-R</allowedValue>
+            <allowedValue>CD-RW</allowedValue>
+            <allowedValue>VIDEO-CD</allowedValue>
+            <allowedValue>SACD</allowedValue>
+            <allowedValue>MD-AUDIO</allowedValue>
+            <allowedValue>MD-PICTURE</allowedValue>
+            <allowedValue>DVD-ROM</allowedValue>
+            <allowedValue>DVD-VIDEO</allowedValue>
+            <allowedValue>DVD-R</allowedValue>
+            <allowedValue>DVD+RW</allowedValue>
+            <allowedValue>DVD-RW</allowedValue>
+            <allowedValue>DVD-RAM</allowedValue>
+            <allowedValue>DVD-AUDIO</allowedValue>
+            <allowedValue>DAT</allowedValue>
+            <allowedValue>LD</allowedValue>
+            <allowedValue>HDD</allowedValue>
+            <allowedValue>MICRO-MV</allowedValue>
+            <allowedValue>NETWORK</allowedValue>
+            <allowedValue>NONE</allowedValue>
+            <allowedValue>NOT_IMPLEMENTED</allowedValue>
+            <allowedValue>vendor-defined</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>RecordStorageMedium</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>UNKNOWN</allowedValue>
+            <allowedValue>DV</allowedValue>
+            <allowedValue>MINI-DV</allowedValue>
+            <allowedValue>VHS</allowedValue>
+            <allowedValue>W-VHS</allowedValue>
+            <allowedValue>S-VHS</allowedValue>
+            <allowedValue>D-VHS</allowedValue>
+            <allowedValue>VHSC</allowedValue>
+            <allowedValue>VIDEO8</allowedValue>
+            <allowedValue>HI8</allowedValue>
+            <allowedValue>CD-ROM</allowedValue>
+            <allowedValue>CD-DA</allowedValue>
+            <allowedValue>CD-R</allowedValue>
+            <allowedValue>CD-RW</allowedValue>
+            <allowedValue>VIDEO-CD</allowedValue>
+            <allowedValue>SACD</allowedValue>
+            <allowedValue>MD-AUDIO</allowedValue>
+            <allowedValue>MD-PICTURE</allowedValue>
+            <allowedValue>DVD-ROM</allowedValue>
+            <allowedValue>DVD-VIDEO</allowedValue>
+            <allowedValue>DVD-R</allowedValue>
+            <allowedValue>DVD+RW</allowedValue>
+            <allowedValue>DVD-RW</allowedValue>
+            <allowedValue>DVD-RAM</allowedValue>
+            <allowedValue>DVD-AUDIO</allowedValue>
+            <allowedValue>DAT</allowedValue>
+            <allowedValue>LD</allowedValue>
+            <allowedValue>HDD</allowedValue>
+            <allowedValue>MICRO-MV</allowedValue>
+            <allowedValue>NETWORK</allowedValue>
+            <allowedValue>NONE</allowedValue>
+            <allowedValue>NOT_IMPLEMENTED</allowedValue>
+            <allowedValue>vendor-defined</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>PossiblePlaybackStorageMedia</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>PossibleRecordStorageMedia</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>CurrentPlayMode</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>NORMAL</allowedValue>
+            <allowedValue>SHUFFLE</allowedValue>
+            <allowedValue>REPEAT_ONE</allowedValue>
+            <allowedValue>REPEAT_ALL</allowedValue>
+            <allowedValue>RANDOM</allowedValue>
+            <allowedValue>DIRECT_1</allowedValue>
+            <allowedValue>INTRO</allowedValue>
+         </allowedValueList>
+         <defaultValue>NORMAL</defaultValue>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>TransportPlaySpeed</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>1</allowedValue>
+            <allowedValue>vendor-defined</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>RecordMediumWriteStatus</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>WRITABLE</allowedValue>
+            <allowedValue>PROTECTED</allowedValue>
+            <allowedValue>NOT_WRITABLE</allowedValue>
+            <allowedValue>UNKNOWN</allowedValue>
+            <allowedValue>NOT_IMPLEMENTED</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>CurrentRecordQualityMode</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>0:EP</allowedValue>
+            <allowedValue>1:LP</allowedValue>
+            <allowedValue>2:SP</allowedValue>
+            <allowedValue>0:BASIC</allowedValue>
+            <allowedValue>1:MEDIUM</allowedValue>
+            <allowedValue>2:HIGH</allowedValue>
+            <allowedValue>NOT_IMPLEMENTED</allowedValue>
+            <allowedValue>vendor-defined</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>PossibleRecordQualityModes</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>NumberOfTracks</name>
+         <dataType>ui4</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>vendor-defined</maximum>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>CurrentTrack</name>
+         <dataType>ui4</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>vendor-defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>CurrentTrackDuration</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>CurrentMediaDuration</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>CurrentTrackMetaData</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>CurrentTrackURI</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>AVTransportURI</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>AVTransportURIMetaData</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>NextAVTransportURI</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>NextAVTransportURIMetaData</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>RelativeTimePosition</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>AbsoluteTimePosition</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>RelativeCounterPosition</name>
+         <dataType>i4</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>AbsoluteCounterPosition</name>
+         <dataType>i4</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>CurrentTransportActions</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="yes">
+         <name>LastChange</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_SeekMode</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>ABS_TIME</allowedValue>
+            <allowedValue>REL_TIME</allowedValue>
+            <allowedValue>ABS_COUNT</allowedValue>
+            <allowedValue>REL_COUNT</allowedValue>
+            <allowedValue>TRACK_NR</allowedValue>
+            <allowedValue>CHANNEL_FREQ</allowedValue>
+            <allowedValue>TAPE-INDEX</allowedValue>
+            <allowedValue>FRAME</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_SeekTarget</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_InstanceID</name>
+         <dataType>ui4</dataType>
+      </stateVariable>
+   </serviceStateTable>
+</scpd>
diff --git a/share/upnp/ConnectionManagerSCPD.xml b/share/upnp/ConnectionManagerSCPD.xml
new file mode 100644
index 0000000000..77c835bebe
--- /dev/null
+++ b/share/upnp/ConnectionManagerSCPD.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+   <specVersion>
+      <major>1</major>
+      <minor>0</minor>
+   </specVersion>
+   <actionList>
+      <action>
+         <name>GetProtocolInfo</name>
+         <argumentList>
+            <argument>
+               <name>Source</name>
+               <direction>out</direction>
+               <relatedStateVariable>SourceProtocolInfo</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Sink</name>
+               <direction>out</direction>
+               <relatedStateVariable>SinkProtocolInfo</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>PrepareForConnection</name>
+         <argumentList>
+            <argument>
+               <name>RemoteProtocolInfo</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>PeerConnectionManager</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>PeerConnectionID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Direction</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>ConnectionID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>AVTransportID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>RcsID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>ConnectionComplete</name>
+         <argumentList>
+            <argument>
+               <name>ConnectionID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetCurrentConnectionIDs</name>
+         <argumentList>
+            <argument>
+               <name>ConnectionIDs</name>
+               <direction>out</direction>
+               <relatedStateVariable>CurrentConnectionIDs</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetCurrentConnectionInfo</name>
+         <argumentList>
+            <argument>
+               <name>ConnectionID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>RcsID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>AVTransportID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>ProtocolInfo</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>PeerConnectionManager</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>PeerConnectionID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Direction</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Status</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_ConnectionStatus</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+   </actionList>
+   <serviceStateTable>
+      <stateVariable sendEvents="yes">
+         <name>SourceProtocolInfo</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="yes">
+         <name>SinkProtocolInfo</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="yes">
+         <name>CurrentConnectionIDs</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_ConnectionStatus</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>OK</allowedValue>
+            <allowedValue>ContentFormatMismatch</allowedValue>
+            <allowedValue>InsufficientBandwidth</allowedValue>
+            <allowedValue>UnreliableChannel</allowedValue>
+            <allowedValue>Unknown</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_ConnectionManager</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_Direction</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>Input</allowedValue>
+            <allowedValue>Output</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_ProtocolInfo</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_ConnectionID</name>
+         <dataType>i4</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_AVTransportID</name>
+         <dataType>i4</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_RcsID</name>
+         <dataType>i4</dataType>
+      </stateVariable>
+   </serviceStateTable>
+</scpd>
diff --git a/share/upnp/RenderingControlSCPD.xml b/share/upnp/RenderingControlSCPD.xml
new file mode 100644
index 0000000000..5c5c0f9a5e
--- /dev/null
+++ b/share/upnp/RenderingControlSCPD.xml
@@ -0,0 +1,762 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+   <specVersion>
+      <major>1</major>
+      <minor>0</minor>
+   </specVersion>
+   <actionList>
+      <action>
+         <name>ListPresets</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentPresetNameList</name>
+               <direction>out</direction>
+               <relatedStateVariable>PresetNameList</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SelectPreset</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>PresetName</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_PresetName</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetBrightness</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentBrightness</name>
+               <direction>out</direction>
+               <relatedStateVariable>Brightness</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetBrightness</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredBrightness</name>
+               <direction>in</direction>
+               <relatedStateVariable>Brightness</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetContrast</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentContrast</name>
+               <direction>out</direction>
+               <relatedStateVariable>Contrast</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetContrast</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredContrast</name>
+               <direction>in</direction>
+               <relatedStateVariable>Contrast</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetSharpness</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentSharpness</name>
+               <direction>out</direction>
+               <relatedStateVariable>Sharpness</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetSharpness</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredSharpness</name>
+               <direction>in</direction>
+               <relatedStateVariable>Sharpness</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetRedVideoGain</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentRedVideoGain</name>
+               <direction>out</direction>
+               <relatedStateVariable>RedVideoGain</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetRedVideoGain</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredRedVideoGain</name>
+               <direction>in</direction>
+               <relatedStateVariable>RedVideoGain</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetGreenVideoGain</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentGreenVideoGain</name>
+               <direction>out</direction>
+               <relatedStateVariable>GreenVideoGain</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetGreenVideoGain</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredGreenVideoGain</name>
+               <direction>in</direction>
+               <relatedStateVariable>GreenVideoGain</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetBlueVideoGain</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentBlueVideoGain</name>
+               <direction>out</direction>
+               <relatedStateVariable>BlueVideoGain</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetBlueVideoGain</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredBlueVideoGain</name>
+               <direction>in</direction>
+               <relatedStateVariable>BlueVideoGain</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetRedVideoBlackLevel</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentRedVideoBlackLevel</name>
+               <direction>out</direction>
+               <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetRedVideoBlackLevel</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredRedVideoBlackLevel</name>
+               <direction>in</direction>
+               <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetGreenVideoBlackLevel</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentGreenVideoBlackLevel</name>
+               <direction>out</direction>
+               <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetGreenVideoBlackLevel</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredGreenVideoBlackLevel</name>
+               <direction>in</direction>
+               <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetBlueVideoBlackLevel</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentBlueVideoBlackLevel</name>
+               <direction>out</direction>
+               <relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetBlueVideoBlackLevel</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredBlueVideoBlackLevel</name>
+               <direction>in</direction>
+               <relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetColorTemperature</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentColorTemperature</name>
+               <direction>out</direction>
+               <relatedStateVariable>ColorTemperature</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetColorTemperature</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredColorTemperature</name>
+               <direction>in</direction>
+               <relatedStateVariable>ColorTemperature</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetHorizontalKeystone</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentHorizontalKeystone</name>
+               <direction>out</direction>
+               <relatedStateVariable>HorizontalKeystone</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetHorizontalKeystone</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredHorizontalKeystone</name>
+               <direction>in</direction>
+               <relatedStateVariable>HorizontalKeystone</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetVerticalKeystone</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentVerticalKeystone</name>
+               <direction>out</direction>
+               <relatedStateVariable>VerticalKeystone</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetVerticalKeystone</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredVerticalKeystone</name>
+               <direction>in</direction>
+               <relatedStateVariable>VerticalKeystone</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetMute</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Channel</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentMute</name>
+               <direction>out</direction>
+               <relatedStateVariable>Mute</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetMute</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Channel</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredMute</name>
+               <direction>in</direction>
+               <relatedStateVariable>Mute</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetVolume</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Channel</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentVolume</name>
+               <direction>out</direction>
+               <relatedStateVariable>Volume</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetVolume</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Channel</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredVolume</name>
+               <direction>in</direction>
+               <relatedStateVariable>Volume</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetVolumeDB</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Channel</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentVolume</name>
+               <direction>out</direction>
+               <relatedStateVariable>VolumeDB</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetVolumeDB</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Channel</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredVolume</name>
+               <direction>in</direction>
+               <relatedStateVariable>VolumeDB</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetVolumeDBRange</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Channel</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>MinValue</name>
+               <direction>out</direction>
+               <relatedStateVariable>VolumeDB</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>MaxValue</name>
+               <direction>out</direction>
+               <relatedStateVariable>VolumeDB</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>GetLoudness</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Channel</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>CurrentLoudness</name>
+               <direction>out</direction>
+               <relatedStateVariable>Loudness</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+      <action>
+         <name>SetLoudness</name>
+         <argumentList>
+            <argument>
+               <name>InstanceID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Channel</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DesiredLoudness</name>
+               <direction>in</direction>
+               <relatedStateVariable>Loudness</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+   </actionList>
+   <serviceStateTable>
+      <stateVariable sendEvents="no">
+         <name>PresetNameList</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="yes">
+         <name>LastChange</name>
+         <dataType>string</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>Brightness</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>Contrast</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>Sharpness</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>RedVideoGain</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>GreenVideoGain</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>BlueVideoGain</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>RedVideoBlackLevel</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>GreenVideoBlackLevel</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>BlueVideoBlackLevel</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>ColorTemperature</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>HorizontalKeystone</name>
+         <dataType>i2</dataType>
+         <allowedValueRange>
+            <minimum>Vendor defined (Must be >= 0)</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>VerticalKeystone</name>
+         <dataType>i2</dataType>
+         <allowedValueRange>
+            <minimum>Vendor defined (Must be >= 0)</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>Mute</name>
+         <dataType>boolean</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>Volume</name>
+         <dataType>ui2</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>1</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>VolumeDB</name>
+         <dataType>i2</dataType>
+         <allowedValueRange>
+            <minimum>Vendor defined</minimum>
+            <maximum>Vendor defined</maximum>
+            <step>Vendor defined</step>
+         </allowedValueRange>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>Loudness</name>
+         <dataType>boolean</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_Channel</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>Master</allowedValue>
+            <allowedValue>LF</allowedValue>
+            <allowedValue>RF</allowedValue>
+            <allowedValue>CF</allowedValue>
+            <allowedValue>LFE</allowedValue>
+            <allowedValue>LS</allowedValue>
+            <allowedValue>RS</allowedValue>
+            <allowedValue>LFC</allowedValue>
+            <allowedValue>RFC</allowedValue>
+            <allowedValue>SD</allowedValue>
+            <allowedValue>SL</allowedValue>
+            <allowedValue>SR</allowedValue>
+            <allowedValue>T</allowedValue>
+            <allowedValue>B</allowedValue>
+            <allowedValue>Vendor defined</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_InstanceID</name>
+         <dataType>ui4</dataType>
+      </stateVariable>
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_PresetName</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>FactoryDefaults</allowedValue>
+            <allowedValue>InstallationDefaults</allowedValue>
+            <allowedValue>Vendor defined</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+   </serviceStateTable>
+</scpd>
-- 
2.17.1




More information about the vlc-devel mailing list