[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