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

Hugo Beauzée-Luyssen hugo at beauzee.fr
Thu Jul 18 12:00:50 CEST 2019


Hi,

On Sat, Jul 13, 2019, at 11:38 AM, Johan Gunnarsson wrote:
> ---
>  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"

This should probably go in the .cpp file, since it's only used there

> +
>  #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;

This should be a UpnpDevice_Handle (even though both types are just int)

>      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")

Aside from those small remarks, I'd be in favor of splitting this patch in 2:
- Renaming the handle to client handle
- Adding the device handle

Regards,

-- 
  Hugo Beauzée-Luyssen
  hugo at beauzee.fr


More information about the vlc-devel mailing list