[vlc-devel] [PATCH v2 1/5] upnp: Add basic support for registering MediaRenderer service

Johan Gunnarsson johan.gunnarsson at gmail.com
Sat Jul 13 11:37:42 CEST 2019


---
 modules/services_discovery/upnp-wrapper.cpp | 117 +++++++++++++++++++-
 modules/services_discovery/upnp-wrapper.hpp |  11 +-
 modules/services_discovery/upnp.cpp         |   8 +-
 modules/stream_out/dlna/dlna.hpp            |   2 +-
 4 files changed, 126 insertions(+), 12 deletions(-)

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..72644d0565 100644
--- a/modules/services_discovery/upnp.cpp
+++ b/modules/services_discovery/upnp.cpp
@@ -271,7 +271,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 +280,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 +1249,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 +1587,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/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")
-- 
2.17.1



More information about the vlc-devel mailing list