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

Steve Lhomme robux4 at ycbcr.xyz
Mon Oct 15 15:55:46 CEST 2018


On 15/10/2018 10:18, Shaleen Jain wrote:
> ---
>   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>

What part of this code did this author contribute to ? Is s·he OK with 
the license of the code ?

>    *
>    * 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());

getUrl creates a string that needs to be free'd.

> +
> +    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,

It will assert if you pass a NULL 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 ));

No fprintf.

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

you may use vlc_obj_malloc() so you don't have to free it manually.

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

delete instead of free ?

> +        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 ) )
> +        {

odd identation

> +            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
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel



More information about the vlc-devel mailing list