[vlc-devel] [PATCH 3/5] upnp: add renderer discoverer

Shaleen Jain shaleen at jain.sh
Mon Oct 15 10:18:01 CEST 2018


---
 modules/services_discovery/upnp.cpp | 342 ++++++++++++++++++++++++++++
 modules/services_discovery/upnp.hpp |  39 ++++
 2 files changed, 381 insertions(+)

diff --git a/modules/services_discovery/upnp.cpp b/modules/services_discovery/upnp.cpp
index 2e9decee52..c1c2b947b2 100644
--- a/modules/services_discovery/upnp.cpp
+++ b/modules/services_discovery/upnp.cpp
@@ -8,6 +8,7 @@
  *          Mirsal Ennaime <mirsal dot ennaime at gmail dot com>
  *          Hugo Beauzée-Luyssen <hugo at beauzee.fr>
  *          Shaleen Jain <shaleen at jain.sh>
+ *          William Ung <william1.ung at epitech.eu>
  *
  * 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
@@ -34,6 +35,7 @@
 #include <vlc_plugin.h>
 #include <vlc_interrupt.h>
 #include <vlc_services_discovery.h>
+#include <vlc_renderer_discovery.h>
 
 #include <assert.h>
 #include <limits.h>
@@ -45,9 +47,11 @@
  * Constants
 */
 const char* MEDIA_SERVER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaServer:1";
+const char* MEDIA_RENDERER_DEVICE_TYPE = "urn:schemas-upnp-org:device:MediaRenderer:1";
 const char* CONTENT_DIRECTORY_SERVICE_TYPE = "urn:schemas-upnp-org:service:ContentDirectory:1";
 const char* SATIP_SERVER_DEVICE_TYPE = "urn:ses-com:device:SatIPServer:1";
 
+#define UPNP_SEARCH_TIMEOUT_SECONDS 15
 #define SATIP_CHANNEL_LIST N_("SAT>IP channel list")
 #define SATIP_CHANNEL_LIST_URL N_("Custom SAT>IP channel list URL")
 static const char *const ppsz_satip_channel_lists[] = {
@@ -69,6 +73,14 @@ struct services_discovery_sys_t
     vlc_thread_t         thread;
 };
 
+
+struct renderer_discovery_sys_t
+{
+    UpnpInstanceWrapper* p_upnp;
+    std::shared_ptr<RD::MediaRendererList> p_renderer_list;
+    vlc_thread_t thread;
+};
+
 struct access_sys_t
 {
     UpnpInstanceWrapper* p_upnp;
@@ -91,7 +103,14 @@ namespace Access
     static void CloseAccess( vlc_object_t* );
 }
 
+namespace RD
+{
+    static int OpenRD( vlc_object_t*);
+    static void CloseRD( vlc_object_t* );
+}
+
 VLC_SD_PROBE_HELPER( "upnp", N_("Universal Plug'n'Play"), SD_CAT_LAN )
+VLC_RD_PROBE_HELPER( "upnp_renderer", N_("UPnP Renderer Discovery") )
 
 /*
  * Module descriptor
@@ -117,6 +136,17 @@ vlc_module_begin()
         set_capability( "access", 0 )
 
     VLC_SD_PROBE_SUBMODULE
+
+    add_submodule()
+        set_description( N_( "UPnP Renderer Discovery" ) )
+        set_category( CAT_SOUT )
+        set_subcategory( SUBCAT_SOUT_RENDERER )
+        set_callbacks( RD::OpenRD, RD::CloseRD )
+        set_capability( "renderer_discovery", 0 )
+        add_shortcut( "upnp_renderer" )
+
+    VLC_RD_PROBE_SUBMODULE
+
 vlc_module_end()
 
 /*
@@ -172,6 +202,32 @@ IXML_Document* parseBrowseResult( IXML_Document* p_doc )
     return (IXML_Document*)p_node;
 }
 
+/**
+ * Reads the base URL from an XML device list
+ *
+ * \param services_discovery_t* p_sd This SD instance
+ * \param IXML_Document* p_desc an XML device list document
+ *
+ * \return const char* The base URL
+ */
+static const char *parseBaseUrl( IXML_Document *p_desc )
+{
+    const char    *psz_base_url = nullptr;
+    IXML_NodeList *p_url_list = nullptr;
+
+    if( ( p_url_list = ixmlDocument_getElementsByTagName( p_desc, "URLBase" ) ) )
+    {
+        if ( IXML_Node* p_url_node = ixmlNodeList_item( p_url_list, 0 ) )
+        {
+            IXML_Node* p_text_node = ixmlNode_getFirstChild( p_url_node );
+            if ( p_text_node )
+                psz_base_url = ixmlNode_getNodeValue( p_text_node );
+        }
+        ixmlNodeList_free( p_url_list );
+    }
+    return psz_base_url;
+}
+
 namespace SD
 {
 
@@ -1280,4 +1336,290 @@ static void CloseAccess( vlc_object_t* p_this )
     delete sys;
 }
 
+} // namespace Access
+
+namespace RD
+{
+
+/**
+ * Crafts an MRL with the 'dlna' stream out
+ * containing the host and port.
+ *
+ * \param psz_location URL to the MediaRenderer device description doc
+ */
+const char *getUrl(const char *psz_location)
+{
+    char *psz_res;
+    vlc_url_t url;
+
+    vlc_UrlParse(&url, psz_location);
+    if (asprintf(&psz_res, "dlna://%s:%d", url.psz_host, url.i_port) < 0)
+    {
+        vlc_UrlClean(&url);
+        return NULL;
+    }
+    vlc_UrlClean(&url);
+    return psz_res;
+}
+
+MediaRendererDesc::MediaRendererDesc( const std::string& udn,
+                                      const std::string& fName,
+                                      const std::string& base,
+                                      const std::string& loc )
+    : UDN( udn )
+    , friendlyName( fName )
+    , base_url( base )
+    , location( loc )
+    , inputItem( NULL )
+{
+}
+
+MediaRendererDesc::~MediaRendererDesc()
+{
+    if (inputItem)
+        vlc_renderer_item_release(inputItem);
+}
+
+MediaRendererList::MediaRendererList(vlc_renderer_discovery_t *p_rd)
+    : m_rd( p_rd )
+{
+}
+
+MediaRendererList::~MediaRendererList()
+{
+    vlc_delete_all(m_list);
+}
+
+bool MediaRendererList::addRenderer(MediaRendererDesc *desc)
+{
+    const char* psz_url = getUrl(desc->location.c_str());
+
+    char *extra_sout;
+
+    if (asprintf(&extra_sout, "base_url=%s,url=%s", desc->base_url.c_str(),
+                        desc->location.c_str()) < 0)
+        return false;
+    desc->inputItem = vlc_renderer_item_new("stream_out_dlna",
+                                            desc->friendlyName.c_str(),
+                                            psz_url,
+                                            extra_sout,
+                                            NULL, "", 3);
+    free(extra_sout);
+    if ( !desc->inputItem )
+        return false;
+    msg_Dbg( m_rd, "Adding renderer '%s' with uuid %s",
+                        desc->friendlyName.c_str(),
+                        desc->UDN.c_str() );
+    vlc_rd_add_item(m_rd, desc->inputItem);
+    m_list.push_back(desc);
+    return true;
+}
+
+MediaRendererDesc* MediaRendererList::getRenderer( const std::string& udn )
+{
+    std::vector<MediaRendererDesc*>::const_iterator it = m_list.begin();
+    std::vector<MediaRendererDesc*>::const_iterator ite = m_list.end();
+
+    for ( ; it != ite; ++it )
+    {
+        if( udn == (*it)->UDN )
+            return *it;
+    }
+    return NULL;
+}
+
+void MediaRendererList::removeRenderer( const std::string& udn )
+{
+    MediaRendererDesc* p_renderer = getRenderer( udn );
+    if ( !p_renderer )
+        return;
+
+    assert( p_renderer->inputItem );
+
+    std::vector<MediaRendererDesc*>::iterator it =
+                        std::find( m_list.begin(),
+                                   m_list.end(),
+                                   p_renderer );
+    if( it != m_list.end() )
+    {
+        msg_Dbg( m_rd, "Removing renderer '%s' with uuid %s",
+                            p_renderer->friendlyName.c_str(),
+                            p_renderer->UDN.c_str() );
+        m_list.erase( it );
+    }
+    delete p_renderer;
+}
+
+void MediaRendererList::parseNewRenderer( IXML_Document* doc,
+                                          const std::string& location)
+{
+    assert(!location.empty());
+    msg_Dbg( m_rd , "Got device desc doc:\n%s", ixmlPrintDocument( doc ));
+
+    const char* psz_base_url = nullptr;
+    IXML_NodeList* p_device_nodes = nullptr;
+
+    /* Fallback to the Device description URL basename
+    * if no base URL is advertised */
+    psz_base_url = parseBaseUrl( doc );
+    if( !psz_base_url && !location.empty() )
+    {
+        psz_base_url = location.c_str();
+    }
+
+    p_device_nodes = ixmlDocument_getElementsByTagName( doc, "device" );
+    if ( !p_device_nodes )
+        return;
+
+    for ( unsigned int i = 0; i < ixmlNodeList_length( p_device_nodes ); i++ )
+    {
+        IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_nodes, i );
+        const char* psz_device_name = nullptr;
+        const char* psz_udn = nullptr;
+
+        if( !p_device_element )
+            continue;
+
+        psz_device_name = xml_getChildElementValue( p_device_element, "friendlyName");
+        if (psz_device_name == nullptr)
+            msg_Dbg( m_rd, "No friendlyName!" );
+
+        psz_udn = xml_getChildElementValue( p_device_element, "UDN");
+        if (psz_udn == nullptr)
+        {
+            msg_Err( m_rd, "No UDN" );
+            continue;
+        }
+
+        /* Check if renderer is already added */
+        if (getRenderer( psz_udn ))
+        {
+            msg_Warn( m_rd, "Renderer with UDN '%s' already exists.", psz_udn );
+            continue;
+        }
+
+        MediaRendererDesc *p_renderer = new MediaRendererDesc(psz_udn,
+                                                psz_device_name,
+                                                psz_base_url,
+                                                location);
+        if (!addRenderer( p_renderer ))
+            delete p_renderer;
+    }
+    ixmlNodeList_free( p_device_nodes );
+}
+
+int MediaRendererList::onEvent( Upnp_EventType event_type,
+                                UpnpEventPtr Event,
+                                void *p_user_data )
+{
+    if (p_user_data != MEDIA_RENDERER_DEVICE_TYPE)
+        return 0;
+
+    switch (event_type)
+    {
+        case UPNP_DISCOVERY_SEARCH_RESULT:
+        {
+            struct Upnp_Discovery *p_discovery = (struct Upnp_Discovery*)Event;
+            IXML_Document *p_doc = NULL;
+            int i_res;
+
+            i_res = UpnpDownloadXmlDoc( UpnpDiscovery_get_Location_cstr( p_discovery ), &p_doc);
+            if (i_res != UPNP_E_SUCCESS)
+            {
+                fprintf(stderr, "%s\n", UpnpDiscovery_get_Location_cstr( p_discovery ));
+                return i_res;
+            }
+            parseNewRenderer(p_doc, UpnpDiscovery_get_Location_cstr( p_discovery ) );
+            ixmlDocument_free(p_doc);
+        }
+        break;
+
+        case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
+        {
+            struct Upnp_Discovery* p_discovery = ( struct Upnp_Discovery* )Event;
+
+            removeRenderer( p_discovery->DeviceId );
+        }
+        break;
+
+        case UPNP_DISCOVERY_SEARCH_TIMEOUT:
+        {
+                msg_Warn( m_rd, "search timeout" );
+        }
+        break;
+
+        default:
+        break;
+    }
+    return UPNP_E_SUCCESS;
+}
+
+void *SearchThread(void *data)
+{
+    vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t*)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,
+            MEDIA_RENDERER_DEVICE_TYPE, MEDIA_RENDERER_DEVICE_TYPE);
+    if( i_res != UPNP_E_SUCCESS )
+    {
+        msg_Err( p_rd, "Error sending search request: %s", UpnpGetErrorMessage( i_res ) );
+        return NULL;
+    }
+    return data;
+}
+
+static int OpenRD( vlc_object_t *p_this )
+{
+    vlc_renderer_discovery_t *p_rd = ( vlc_renderer_discovery_t* )p_this;
+    renderer_discovery_sys_t *p_sys  = new(std::nothrow) renderer_discovery_sys_t;
+
+    if ( !p_sys )
+        return VLC_ENOMEM;
+    p_rd->p_sys = ( vlc_renderer_discovery_sys* ) p_sys;
+    p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
+
+    if ( !p_sys->p_upnp )
+    {
+        delete p_sys;
+        return VLC_EGENERIC;
+    }
+
+    try
+    {
+        p_sys->p_renderer_list = std::make_shared<RD::MediaRendererList>( p_rd );
+    }
+    catch ( const std::bad_alloc& )
+    {
+        msg_Err( p_rd, "Failed to create a MediaRendererList");
+        p_sys->p_upnp->release();
+        free(p_sys);
+        return VLC_EGENERIC;
+    }
+    p_sys->p_upnp->addListener( p_sys->p_renderer_list );
+
+    if( vlc_clone( &p_sys->thread, SearchThread, (void*)p_rd,
+                    VLC_THREAD_PRIORITY_LOW ) )
+        {
+            msg_Err( p_rd, "Can't run the lookup thread" );
+            p_sys->p_upnp->removeListener( p_sys->p_renderer_list );
+            p_sys->p_upnp->release();
+            delete p_sys;
+            return VLC_EGENERIC;
+        }
+    return VLC_SUCCESS;
+}
+
+static void CloseRD( vlc_object_t *p_this )
+{
+    vlc_renderer_discovery_t *p_rd = ( vlc_renderer_discovery_t* )p_this;
+    renderer_discovery_sys_t *p_sys = (renderer_discovery_sys_t*)p_rd->p_sys;
+
+    vlc_join(p_sys->thread, NULL);
+    p_sys->p_upnp->removeListener( p_sys->p_renderer_list );
+    p_sys->p_upnp->release();
+    delete p_sys;
 }
+
+} // namespace RD
diff --git a/modules/services_discovery/upnp.hpp b/modules/services_discovery/upnp.hpp
index 8b6f947b9e..5a0de1f284 100644
--- a/modules/services_discovery/upnp.hpp
+++ b/modules/services_discovery/upnp.hpp
@@ -128,3 +128,42 @@ private:
 };
 
 }
+
+namespace RD
+{
+
+struct MediaRendererDesc
+{
+    MediaRendererDesc( const std::string& udn, const std::string& fName,
+                    const std::string& base, const std::string& loc );
+    ~MediaRendererDesc();
+    std::string UDN;
+    std::string friendlyName;
+    std::string base_url;               // base url of the renderer
+    std::string location;               // device description url
+    vlc_renderer_item_t *inputItem;
+};
+
+class MediaRendererList : public UpnpInstanceWrapper::Listener
+{
+public:
+    MediaRendererList( vlc_renderer_discovery_t *p_rd );
+    ~MediaRendererList();
+
+    bool addRenderer(MediaRendererDesc *desc );
+    void removeRenderer(const std::string &udn );
+    MediaRendererDesc* getRenderer( const std::string& udn );
+    int onEvent( Upnp_EventType event_type,
+                 UpnpEventPtr p_event,
+                 void* p_user_data ) override;
+
+private:
+    void parseNewRenderer( IXML_Document* doc, const std::string& location );
+
+private:
+    vlc_renderer_discovery_t* const m_rd;
+    std::vector<MediaRendererDesc*> m_list;
+
+};
+
+}
-- 
2.19.1


More information about the vlc-devel mailing list