From c3e38bce3b30c6e0974ec5da47e22260ed5b7ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Br=C3=BCns?= Date: Tue, 25 Dec 2018 03:48:15 +0100 Subject: [PATCH 2/2] Add Avahi implementation for chromecast renderer discovery --- modules/services_discovery/avahi.c | 246 ++++++++++++++++++++++++----- 1 file changed, 209 insertions(+), 37 deletions(-) diff --git a/modules/services_discovery/avahi.c b/modules/services_discovery/avahi.c index aa58c7f673..527e08a2e9 100644 --- a/modules/services_discovery/avahi.c +++ b/modules/services_discovery/avahi.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -48,8 +49,11 @@ /* Callbacks */ static int Open ( vlc_object_t * ); static void Close( vlc_object_t * ); +static int OpenRD ( vlc_object_t * ); +static void CloseRD( vlc_object_t * ); VLC_SD_PROBE_HELPER("avahi", N_("Zeroconf network services"), SD_CAT_LAN) +VLC_RD_PROBE_HELPER( "avahi_renderer", "Avahi Zeroconf renderer Discovery" ) vlc_module_begin () set_shortname( "Avahi" ) @@ -61,45 +65,153 @@ vlc_module_begin () add_shortcut( "mdns", "avahi" ) VLC_SD_PROBE_SUBMODULE + add_submodule() \ + set_description( N_( "Avahi Renderer Discovery" ) ) + set_category( CAT_SOUT ) + set_subcategory( SUBCAT_SOUT_RENDERER ) + set_capability( "renderer_discovery", 0 ) + set_callbacks( OpenRD, CloseRD ) + add_shortcut( "mdns_renderer", "avahi_renderer" ) + VLC_RD_PROBE_SUBMODULE vlc_module_end () /***************************************************************************** * Local structures *****************************************************************************/ -struct services_discovery_sys_t +typedef +struct { AvahiThreadedPoll *poll; AvahiClient *client; vlc_dictionary_t services_name_to_input_item; -}; + vlc_object_t *parent; + bool renderer; +} discovery_sys_t; + +struct vlc_renderer_discovery_sys +{ + discovery_sys_t s; +}; + +struct services_discovery_sys_t +{ + discovery_sys_t s; +}; static const struct { const char *psz_protocol; const char *psz_service_name; + bool b_renderer; } protocols[] = { - { "ftp", "_ftp._tcp" }, - { "smb", "_smb._tcp" }, - { "nfs", "_nfs._tcp" }, - { "sftp", "_sftp-ssh._tcp" }, - { "rtsp", "_rtsp._tcp" }, + { "ftp", "_ftp._tcp", false }, + { "smb", "_smb._tcp", false }, + { "nfs", "_nfs._tcp", false }, + { "sftp", "_sftp-ssh._tcp", false }, + { "rtsp", "_rtsp._tcp", false }, + { "chromecast", "_googlecast._tcp", true }, }; #define NB_PROTOCOLS (sizeof(protocols) / sizeof(*protocols)) +/***************************************************************************** + * helpers + *****************************************************************************/ +static void add_renderer( const char *psz_protocol, const char *psz_name, + const char *psz_addr, uint16_t i_port, + AvahiStringList *txt, discovery_sys_t *p_sys ) +{ + vlc_renderer_discovery_t *p_rd = ( vlc_renderer_discovery_t* )(p_sys->parent); + AvahiStringList *asl = NULL; + if ( !strcmp( "chromecast", psz_protocol ) ) { + int renderer_flags = 0; + + /* Friendly name */ + asl = avahi_string_list_find( txt, "fn" ); + if( asl != NULL ) + { + char *key = NULL; + char *value = NULL; + if( avahi_string_list_get_pair( asl, &key, &value, NULL ) == 0 && + value != NULL ) + { + + } + msg_Info( p_rd, "key: '%s' value: '%s'", key, value ); + + if( key != NULL ) + avahi_free( (void *)key ); + if( value != NULL ) + avahi_free( (void *)value ); + } + + /* capabilities */ + asl = avahi_string_list_find( txt, "ca" ); + if( asl != NULL ) { + char *key = NULL; + char *value = NULL; + if( avahi_string_list_get_pair( asl, &key, &value, NULL ) == 0 && + value != NULL ) + { + int ca = atoi( value ); + + if ( ( ca & 0x01 ) != 0 ) + renderer_flags |= VLC_RENDERER_CAN_VIDEO; + if ( ( ca & 0x04 ) != 0 ) + renderer_flags |= VLC_RENDERER_CAN_AUDIO; + } + msg_Info( p_rd, "key: '%s' value: '%s'", key, value ); + + if( key != NULL ) + avahi_free( (void *)key ); + if( value != NULL ) + avahi_free( (void *)value ); + } + /* + * "md": model + * "id": uuid + * "ic": icon URL, relative to http://:8008/ + */ + + const char *extra_uri = renderer_flags & VLC_RENDERER_CAN_VIDEO ? NULL : "no-video"; + char *uri = NULL; + char *icon_uri = NULL; + if( asprintf( &uri, "%s://%s:%u", psz_protocol, psz_addr, i_port ) < 0 ) + return; + if( asprintf( &icon_uri, "http://%s:8008/setup/icon.png", psz_addr) < 0 ) + { + free( uri ); + return; + } + + vlc_renderer_item_t *p_renderer_item = + vlc_renderer_item_new( "chromecast", psz_name, uri, extra_uri, + "cc_demux", icon_uri, renderer_flags ); + if( p_renderer_item == NULL ) { + free( uri ); + free( icon_uri ); + return; + } + + vlc_dictionary_insert( &p_sys->services_name_to_input_item, + psz_name, p_renderer_item); + vlc_rd_add_item( p_rd, p_renderer_item ); + vlc_renderer_item_release( p_renderer_item ); + } +} + /***************************************************************************** * client_callback *****************************************************************************/ static void client_callback( AvahiClient *c, AvahiClientState state, void * userdata ) { - services_discovery_t *p_sd = ( services_discovery_t* )userdata; - services_discovery_sys_t *p_sys = p_sd->p_sys; + discovery_sys_t *p_sys = userdata; if( state == AVAHI_CLIENT_FAILURE && (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) ) { - msg_Err( p_sd, "avahi client disconnected" ); + msg_Err( p_sys->parent, "avahi client disconnected" ); avahi_threaded_poll_quit( p_sys->poll ); } } @@ -122,15 +234,14 @@ static void resolve_callback( AvahiLookupResultFlags flags, void* userdata ) { - services_discovery_t *p_sd = ( services_discovery_t* )userdata; - services_discovery_sys_t *p_sys = p_sd->p_sys; + discovery_sys_t *p_sys = userdata; VLC_UNUSED(interface); VLC_UNUSED(host_name); VLC_UNUSED(flags); if( event == AVAHI_RESOLVER_FAILURE ) { - msg_Err( p_sd, + msg_Err( p_sys->parent, "failed to resolve service '%s' of type '%s' in domain '%s'", name, type, domain ); } @@ -142,7 +253,7 @@ static void resolve_callback( AvahiStringList *asl = NULL; input_item_t *p_input = NULL; - msg_Info( p_sd, "service '%s' of type '%s' in domain '%s' port %i", + msg_Info( p_sys->parent, "service '%s' of type '%s' in domain '%s' port %i", name, type, domain, port ); avahi_address_snprint(a, (sizeof(a)/sizeof(a[0]))-1, address); @@ -154,10 +265,15 @@ static void resolve_callback( } const char *psz_protocol = NULL; + bool is_renderer = false; for( unsigned int i = 0; i < NB_PROTOCOLS; i++ ) { if( !strcmp(type, protocols[i].psz_service_name) ) + { psz_protocol = protocols[i].psz_protocol; + is_renderer = protocols[i].b_renderer; + break; + } } if( psz_protocol == NULL ) { @@ -166,6 +282,15 @@ static void resolve_callback( return; } + if( txt != NULL && is_renderer ) + { + const char* addr_v4v6 = psz_addr != NULL ? psz_addr : a; + add_renderer( psz_protocol, name, addr_v4v6, port, txt, p_sys ); + free( psz_addr ); + avahi_service_resolver_free( r ); + return; + } + if( txt != NULL ) asl = avahi_string_list_find( txt, "path" ); if( asl != NULL ) @@ -212,6 +337,7 @@ static void resolve_callback( } if( p_input != NULL ) { + services_discovery_t *p_sd = ( services_discovery_t* )(p_sys->parent); vlc_dictionary_insert( &p_sys->services_name_to_input_item, name, p_input ); services_discovery_AddItem( p_sd, p_input ); @@ -238,8 +364,8 @@ static void browse_callback( { VLC_UNUSED(b); VLC_UNUSED(flags); - services_discovery_t *p_sd = ( services_discovery_t* )userdata; - services_discovery_sys_t *p_sys = p_sd->p_sys; + discovery_sys_t *p_sys = userdata; + msg_Info( p_sys->parent, "browse_callback - event: %d, name: %s, type: %s", event, name, type); if( event == AVAHI_BROWSER_NEW ) { if( avahi_service_resolver_new( p_sys->client, interface, protocol, @@ -247,22 +373,32 @@ static void browse_callback( 0, resolve_callback, userdata ) == NULL ) { - msg_Err( p_sd, "failed to resolve service '%s': %s", name, + msg_Err( p_sys->parent, "failed to resolve service '%s': %s", name, avahi_strerror( avahi_client_errno( p_sys->client ) ) ); } } else if( name ) { /** \todo Store the input id and search it, rather than searching the items */ - input_item_t *p_item; + void *p_item; p_item = vlc_dictionary_value_for_key( &p_sys->services_name_to_input_item, name ); if( !p_item ) - msg_Err( p_sd, "failed to find service '%s' in playlist", name ); + msg_Err( p_sys->parent, "failed to find service '%s' in playlist", name ); else { - services_discovery_RemoveItem( p_sd, p_item ); + if( p_sys->renderer ) + { + vlc_renderer_discovery_t *p_rd = ( vlc_renderer_discovery_t* )(p_sys->parent); + vlc_rd_remove_item( p_rd, p_item ); + } + else + { + //input_item_t *p_item; + services_discovery_t *p_sd = ( services_discovery_t* )(p_sys->parent); + services_discovery_RemoveItem( p_sd, p_item ); + } vlc_dictionary_remove_value_for_key( &p_sys->services_name_to_input_item, name, NULL, NULL ); @@ -273,46 +409,41 @@ static void browse_callback( /***************************************************************************** * Open: initialize and create stuff *****************************************************************************/ -static int Open( vlc_object_t *p_this ) +static int OpenCommon( discovery_sys_t *p_sys ) { - services_discovery_t *p_sd = ( services_discovery_t* )p_this; - services_discovery_sys_t *p_sys; int err; - p_sd->p_sys = p_sys = calloc( 1, sizeof( services_discovery_sys_t ) ); - if( !p_sys ) - return VLC_ENOMEM; - - p_sd->description = _("Zeroconf network services"); - vlc_dictionary_init( &p_sys->services_name_to_input_item, 1 ); p_sys->poll = avahi_threaded_poll_new(); if( p_sys->poll == NULL ) { - msg_Err( p_sd, "failed to create Avahi threaded poll" ); + msg_Err( p_sys->parent, "failed to create Avahi threaded poll" ); goto error; } p_sys->client = avahi_client_new( avahi_threaded_poll_get(p_sys->poll), - 0, client_callback, p_sd, &err ); + 0, client_callback, p_sys, &err ); if( p_sys->client == NULL ) { - msg_Err( p_sd, "failed to create avahi client: %s", + msg_Err( p_sys->parent, "failed to create avahi client: %s", avahi_strerror( err ) ); goto error; } for( unsigned i = 0; i < NB_PROTOCOLS; i++ ) { + if ( protocols[i].b_renderer != p_sys->renderer ) + continue; + AvahiServiceBrowser *sb; sb = avahi_service_browser_new( p_sys->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, protocols[i].psz_service_name, NULL, - 0, browse_callback, p_sd ); + 0, browse_callback, p_sys ); if( sb == NULL ) { - msg_Err( p_sd, "failed to create avahi service browser %s", avahi_strerror( avahi_client_errno(p_sys->client) ) ); + msg_Err( p_sys->parent, "failed to create avahi service browser %s", avahi_strerror( avahi_client_errno(p_sys->client) ) ); goto error; } } @@ -333,13 +464,39 @@ error: return VLC_EGENERIC; } +static int Open( vlc_object_t *p_this ) +{ + services_discovery_t *p_sd = ( services_discovery_t* )p_this; + p_sd->description = _("Zeroconf network services"); + + p_sd->p_sys = calloc( 1, sizeof( services_discovery_sys_t ) ); + if( !p_sd->p_sys ) + return VLC_ENOMEM; + p_sd->p_sys->s.parent = p_this; + p_sd->p_sys->s.renderer = false; + + return OpenCommon( &p_sd->p_sys->s ); +} + +static int OpenRD( vlc_object_t *p_this ) +{ + vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this; + + p_rd->p_sys = calloc( 1, sizeof( vlc_renderer_discovery_sys ) ); + if( !p_rd->p_sys ) + return VLC_ENOMEM; + p_rd->p_sys->s.parent = p_this; + p_rd->p_sys->s.renderer = true; + + msg_Info( p_this, "Opening Avahi Renderer SD"); + return OpenCommon( &p_rd->p_sys->s ); +} + /***************************************************************************** * Close: cleanup *****************************************************************************/ -static void Close( vlc_object_t *p_this ) +static void CloseCommon( discovery_sys_t *p_sys ) { - services_discovery_t *p_sd = ( services_discovery_t* )p_this; - services_discovery_sys_t *p_sys = p_sd->p_sys; avahi_threaded_poll_stop( p_sys->poll ); avahi_client_free( p_sys->client ); @@ -348,3 +505,18 @@ static void Close( vlc_object_t *p_this ) vlc_dictionary_clear( &p_sys->services_name_to_input_item, NULL, NULL ); free( p_sys ); } + +static void Close( vlc_object_t *p_this ) +{ + services_discovery_t *p_sd = ( services_discovery_t* )p_this; + services_discovery_sys_t *p_sys = p_sd->p_sys; + CloseCommon( &p_sys->s ); +} + +static void CloseRD( vlc_object_t *p_this ) +{ + msg_Info( p_this, "Closing Avahi Renderer SD"); + vlc_renderer_discovery_t *p_rd = (vlc_renderer_discovery_t *)p_this; + vlc_renderer_discovery_sys *p_sys = p_rd->p_sys; + CloseCommon( &p_sys->s ); +} -- 2.20.1