[vlc-devel] [PATCH v6 2/7] upnp: Add basic support for registering MediaRenderer service

Johan Gunnarsson johan.gunnarsson at gmail.com
Wed Dec 4 21:39:58 CET 2019


This introduces a UPnP device handle that is independent of the UPnP client
handle. With this, modules can enable the SOAP server, web server and register
the media renderer services only when they are in use. The web server will
serve UPnP service description XML files from VLC_PKG_DATA_DIR/upnp (which
is something like /usr/share/vlc/upnp on Linux systems).
---
 modules/services_discovery/upnp-wrapper.cpp | 183 ++++++++++++++++++++
 modules/services_discovery/upnp-wrapper.hpp |   7 +
 2 files changed, 190 insertions(+)

diff --git a/modules/services_discovery/upnp-wrapper.cpp b/modules/services_discovery/upnp-wrapper.cpp
index bbff2f3061..acdddd9b46 100644
--- a/modules/services_discovery/upnp-wrapper.cpp
+++ b/modules/services_discovery/upnp-wrapper.cpp
@@ -35,20 +35,100 @@
 
 #include "upnp-wrapper.hpp"
 #include <vlc_cxx_helpers.hpp>
+#include <vlc_md5.h>
+
+static const char *mediarenderer_desc_template   =
+    "<?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>%s</friendlyName>"
+        "<manufacturer>VideoLAN</manufacturer>"
+        "<modelName>" PACKAGE_NAME "</modelName>"
+        "<modelNumber>" PACKAGE_VERSION "</modelNumber>"
+        "<modelURL>https://www.videolan.org/vlc/</modelURL>"
+        "<UDN>%s</UDN>"
+        "<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;
 
+std::string generateUDN()
+{
+    struct md5_s hash;
+    InitMD5( &hash );
+
+    const char *ipv4addr = UpnpGetServerIpAddress();
+    if( ipv4addr )
+        AddMD5( &hash, ipv4addr, strlen( ipv4addr ) );
+    char ipv4port[7] = {};
+    snprintf( ipv4port, 7, ":%hu\n", UpnpGetServerPort() );
+    AddMD5( &hash, ipv4port, strlen( ipv4port ) );
+
+#ifdef UPNP_ENABLE_IPV6
+    const char *ipv6addr = UpnpGetServerIp6Address();
+    if( ipv6addr )
+        AddMD5( &hash, ipv6addr, strlen( ipv6addr ) );
+    char ipv6port[7] = {};
+    snprintf( ipv6port, 7, ":%hu\n", UpnpGetServerPort6() );
+    AddMD5( &hash, ipv6port, strlen( ipv6port ) );
+#endif
+
+    EndMD5( &hash );
+
+    std::unique_ptr<char> hexhash( psz_md5_hash( &hash ) );
+    std::string udn( hexhash.get() );
+    udn.insert( 20, "-" );
+    udn.insert( 16, "-" );
+    udn.insert( 12, "-" );
+    udn.insert( 8, "-" );
+    udn.insert( 0, "uuid:" );
+
+    return udn;
+}
+
 UpnpInstanceWrapper::UpnpInstanceWrapper()
     : m_client_handle( -1 )
+    , m_device_handle( -1 )
     , m_refcount( 0 )
+    , m_mediarenderer_refcount( 0 )
 {
 }
 
 UpnpInstanceWrapper::~UpnpInstanceWrapper()
 {
     UpnpUnRegisterClient( m_client_handle );
+    if( m_device_handle != -1 )
+        UpnpUnRegisterRootDevice( m_device_handle );
     UpnpFinish();
 }
 
@@ -83,6 +163,9 @@ UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj)
             return NULL;
         }
 
+        /* Build pseudo random UUID-like string based on listening addresses */
+        instance->m_udn = generateUDN();
+
         ixmlRelaxParser( 1 );
 
         /* Register a control point */
@@ -104,6 +187,19 @@ UpnpInstanceWrapper *UpnpInstanceWrapper::get(vlc_object_t *p_obj)
             delete instance;
             return NULL;
         }
+
+        char *root = config_GetSysPath( VLC_PKG_DATA_DIR, "upnp" );
+        if( root )
+        {
+            i_res = UpnpSetWebServerRootDir( root );
+            if( i_res != UPNP_E_SUCCESS)
+            {
+                msg_Warn( p_obj, "UpnpSetWebServerRootDir failed: %s",
+                          UpnpGetErrorMessage( i_res ));
+            }
+            free( root );
+        }
+
         s_instance = instance;
     }
     s_instance->m_refcount++;
@@ -127,6 +223,16 @@ UpnpClient_Handle UpnpInstanceWrapper::client_handle() const
     return m_client_handle;
 }
 
+UpnpDevice_Handle UpnpInstanceWrapper::device_handle() const
+{
+    return m_device_handle;
+}
+
+std::string UpnpInstanceWrapper::udn() const
+{
+    return m_udn;
+}
+
 int UpnpInstanceWrapper::Callback(Upnp_EventType event_type, UpnpEventPtr p_event, void *p_user_data)
 {
     vlc::threads::mutex_locker lock( &s_lock );
@@ -152,3 +258,80 @@ void UpnpInstanceWrapper::removeListener(ListenerPtr listener)
     if ( iter != s_listeners.end() )
         s_listeners.erase( iter );
 }
+
+bool UpnpInstanceWrapper::startMediaRenderer( vlc_object_t *p_obj )
+{
+    vlc::threads::mutex_locker lock( &s_lock );
+    if( m_mediarenderer_refcount == 0 )
+    {
+        std::string friendly_name( "VLC media player" );
+        char hostname[128] = "";
+        if( gethostname( hostname, sizeof( hostname ) ) == 0 &&
+            strlen( hostname ) > 0 )
+        {
+            friendly_name = friendly_name + ": " + hostname;
+        }
+
+        char mediarenderer_desc[4096] = {};
+        int i_res;
+        i_res = snprintf( mediarenderer_desc, sizeof( mediarenderer_desc ),
+                          mediarenderer_desc_template, friendly_name.c_str(),
+                          udn().c_str() );
+        if( i_res < 0 || i_res >= (int) sizeof( mediarenderer_desc ) )
+        {
+            msg_Err( p_obj, "Failed to build MediaRenderer XML description" );
+            return false;
+        }
+
+        if( ( i_res = UpnpEnableWebserver( TRUE ) ) != UPNP_E_SUCCESS )
+        {
+            msg_Err( p_obj, "Failed to enable webserver: %s", UpnpGetErrorMessage( i_res ) );
+            return false;
+        }
+
+        i_res = UpnpRegisterRootDevice2( UPNPREG_BUF_DESC,
+                                         mediarenderer_desc,
+                                         strlen( mediarenderer_desc ),
+                                         1,
+                                         Callback,
+                                         nullptr,
+                                         &m_device_handle );
+        if( i_res != UPNP_E_SUCCESS )
+        {
+            msg_Err( p_obj, "Device registration failed: %s", UpnpGetErrorMessage( i_res ) );
+            /* Disable web server again in case register device failed */
+            UpnpEnableWebserver( FALSE );
+            return false;
+        }
+    }
+    m_mediarenderer_refcount++;
+
+    return true;
+}
+
+bool UpnpInstanceWrapper::stopMediaRenderer( vlc_object_t *p_obj )
+{
+    vlc::threads::mutex_locker lock( &s_lock_lock );
+    if( m_mediarenderer_refcount == 1 )
+    {
+        int i_res;
+        if( ( i_res = UpnpEnableWebserver( FALSE ) ) != UPNP_E_SUCCESS )
+        {
+            msg_Err( p_obj, "Failed to disable webserver: %s", UpnpGetErrorMessage( i_res ) );
+            return false;
+        }
+
+        i_res = UpnpUnRegisterRootDevice( m_device_handle );
+        if( i_res != UPNP_E_SUCCESS )
+        {
+            msg_Err( p_obj, "Device unregistration failed: %s", UpnpGetErrorMessage( i_res ) );
+            /* Enable web server again in case unregister device failed */
+            UpnpEnableWebserver( TRUE );
+            return false;
+        }
+        m_device_handle = -1;
+    }
+    m_mediarenderer_refcount--;
+
+    return true;
+}
diff --git a/modules/services_discovery/upnp-wrapper.hpp b/modules/services_discovery/upnp-wrapper.hpp
index 03116c14a1..a831a93632 100644
--- a/modules/services_discovery/upnp-wrapper.hpp
+++ b/modules/services_discovery/upnp-wrapper.hpp
@@ -71,7 +71,10 @@ private:
     static UpnpInstanceWrapper* s_instance;
     static vlc_mutex_t s_lock;
     UpnpClient_Handle m_client_handle;
+    UpnpDevice_Handle m_device_handle;
+    std::string m_udn;
     int m_refcount;
+    int m_mediarenderer_refcount;
     typedef std::shared_ptr<Listener> ListenerPtr;
     typedef std::vector<ListenerPtr> Listeners;
     static Listeners s_listeners;
@@ -81,8 +84,12 @@ public:
     static UpnpInstanceWrapper* get( vlc_object_t* p_obj );
     void release();
     UpnpClient_Handle client_handle() const;
+    UpnpDevice_Handle device_handle() const;
+    std::string udn() const;
     void addListener(ListenerPtr listener);
     void removeListener(ListenerPtr listener);
+    bool startMediaRenderer( vlc_object_t *p_obj );
+    bool stopMediaRenderer( vlc_object_t *p_obj );
 
 private:
     static int Callback( Upnp_EventType event_type, UpnpEventPtr p_event, void* p_user_data );
-- 
2.17.1



More information about the vlc-devel mailing list