[vlc-devel] [PATCH 6/6] chromecast: use libmicrodns to get the device IP/port based on its name
Steve Lhomme
robux4 at videolabs.io
Wed Oct 7 15:36:42 CEST 2015
no name/port provided looks for the first Chromecast to reply to the mDNS request
--
replaces https://patches.videolan.org/patch/10124/
---
modules/stream_out/chromecast/cast.cpp | 178 ++++++++++++++++++++++++++++-----
1 file changed, 151 insertions(+), 27 deletions(-)
diff --git a/modules/stream_out/chromecast/cast.cpp b/modules/stream_out/chromecast/cast.cpp
index f97902b..7f58b60 100644
--- a/modules/stream_out/chromecast/cast.cpp
+++ b/modules/stream_out/chromecast/cast.cpp
@@ -5,6 +5,7 @@
*
* Authors: Adrien Maglo <magsoft at videolan.org>
* Jean-Baptiste Kempf <jb at videolan.org>
+ * Steve Lhomme <robux4 at videolabs.io>
*
* 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
@@ -51,6 +52,12 @@
#include "../../misc/webservices/json.h"
+#ifdef HAVE_MICRODNS
+# include <microdns/microdns.h>
+
+static const char MDNS_CHROMECAST[] = "._googlecast._tcp.local";
+#endif
+
// Status
enum
{
@@ -68,11 +75,17 @@ enum
struct sout_stream_sys_t
{
sout_stream_sys_t()
- : p_tls(NULL), i_requestId(0),
+ : devicePort(8009)
+ ,p_tls(NULL), i_requestId(0),
i_status(CHROMECAST_DISCONNECTED), p_out(NULL)
+#ifdef HAVE_MICRODNS
+ ,microdns_ctx(NULL)
+#endif
{
}
+ uint16_t devicePort;
+ std::string deviceIP;
std::string serverIP;
int i_sock_fd;
@@ -92,12 +105,16 @@ struct sout_stream_sys_t
vlc_cond_t loadCommandCond;
sout_stream_t *p_out;
+#ifdef HAVE_MICRODNS
+ struct mdns_ctx *microdns_ctx;
+ mtime_t i_timeout;
+ std::string nameChromecast; /* name we're looking for */
+#endif
};
// Media player Chromecast app id
#define APP_ID "CC1AD845" // Default media player aka DEFAULT_MEDIA_RECEIVER_APPLICATION_ID
-#define CHROMECAST_CONTROL_PORT 8009
#define HTTP_PORT 8010
#define SOUT_CFG_PREFIX "sout-chromecast-"
@@ -108,6 +125,8 @@ struct sout_stream_sys_t
/* deadline regarding pong we expect after pinging the receiver */
#define PONG_WAIT_TIME 500
#define PONG_WAIT_RETRIES 2
+#define TIMEOUT_LOAD_CMD (6 * CLOCK_FREQ)
+#define TIMEOUT_MDNS_IP (4 * CLOCK_FREQ)
static const char NAMESPACE_DEVICEAUTH[] = "urn:x-cast:com.google.cast.tp.deviceauth";
static const char NAMESPACE_CONNECTION[] = "urn:x-cast:com.google.cast.tp.connection";
@@ -122,7 +141,7 @@ static const char NAMESPACE_MEDIA[] = "urn:x-cast:com.google.cast.media";
static int Open(vlc_object_t *);
static void Close(vlc_object_t *);
static void Clean(sout_stream_t *p_stream);
-static int connectChromecast(sout_stream_t *p_stream, char *psz_ipChromecast);
+static int connectChromecast(sout_stream_t *p_stream);
static void disconnectChromecast(sout_stream_t *p_stream);
static int sendMessages(sout_stream_t *p_stream);
@@ -138,7 +157,7 @@ static void msgStatus(sout_stream_t *p_stream);
static void *chromecastThread(void *data);
static const char *const ppsz_sout_options[] = {
- "ip", "http-port", "mux", "mime", NULL
+ "ip", "target", "http-port", "mux", "mime", NULL
};
/*****************************************************************************
@@ -147,6 +166,8 @@ static const char *const ppsz_sout_options[] = {
#define IP_TEXT N_("Chromecast IP address")
#define IP_LONGTEXT N_("This sets the IP adress of the Chromecast receiver.")
+#define TARGET_TEXT N_("Chromecast Name")
+#define TARGET_LONGTEXT N_("This sets the name of the Chromecast receiver.")
#define HTTP_PORT_TEXT N_("HTTP port")
#define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the server " \
"used to stream the media to the Chromecast.")
@@ -166,6 +187,7 @@ vlc_module_begin ()
set_callbacks(Open, Close)
add_string(SOUT_CFG_PREFIX "ip", "", IP_TEXT, IP_LONGTEXT, false)
+ add_string(SOUT_CFG_PREFIX "target", "", TARGET_TEXT, TARGET_LONGTEXT, false)
add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT, HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false)
add_string(SOUT_CFG_PREFIX "mux", "avformat{mux=matroska}", MUX_TEXT, MUX_LONGTEXT, false)
add_string(SOUT_CFG_PREFIX "mime", "video/x-matroska", MIME_TEXT, MIME_LONGTEXT, false)
@@ -215,31 +237,29 @@ static int Open(vlc_object_t *p_this)
config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg);
char *psz_ipChromecast = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "ip");
- if (psz_ipChromecast == NULL)
+ if (psz_ipChromecast != NULL)
{
- msg_Err(p_stream, "No Chromecast receiver IP provided");
- Clean(p_stream);
- return VLC_EGENERIC;
+ p_sys->deviceIP = psz_ipChromecast;
+ free(psz_ipChromecast);
}
-
- p_sys->i_sock_fd = connectChromecast(p_stream, psz_ipChromecast);
- free(psz_ipChromecast);
- if (p_sys->i_sock_fd < 0)
+ else
+#ifdef HAVE_MICRODNS
+ if (mdns_init(&p_sys->microdns_ctx, MDNS_ADDR_IPV4, MDNS_PORT) >= 0)
{
- msg_Err(p_stream, "Could not connect the Chromecast");
- Clean(p_stream);
- return VLC_EGENERIC;
+ char *psz_nameChromecast = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "target");
+ if (psz_nameChromecast != NULL)
+ {
+ p_sys->nameChromecast = psz_nameChromecast;
+ free(psz_nameChromecast);
+ }
}
- p_sys->i_status = CHROMECAST_TLS_CONNECTED;
-
- char psz_localIP[NI_MAXNUMERICHOST];
- if (net_GetSockAddress(p_sys->i_sock_fd, psz_localIP, NULL))
+ if (p_sys->microdns_ctx == NULL && p_sys->deviceIP.empty())
+#endif /* HAVE_MICRODNS */
{
- msg_Err(p_this, "Cannot get local IP address");
+ msg_Err(p_stream, "No Chromecast receiver IP/Name provided");
Clean(p_stream);
return VLC_EGENERIC;
}
- p_sys->serverIP = psz_localIP;
p_sys->psz_mime = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mime");
if (p_sys->psz_mime == NULL)
@@ -291,7 +311,7 @@ static int Open(vlc_object_t *p_this)
// Lock the sout thread until we have sent the media loading command to the Chromecast.
int i_ret = 0;
- const mtime_t deadline = mdate() + 6 * CLOCK_FREQ;
+ const mtime_t deadline = mdate() + TIMEOUT_LOAD_CMD + (p_sys->deviceIP.empty() ? TIMEOUT_MDNS_IP : 0);
vlc_mutex_lock(&p_sys->lock);
while (p_sys->i_status != CHROMECAST_MEDIA_LOAD_SENT && p_sys->i_status != CHROMECAST_CONNECTION_DEAD)
{
@@ -376,6 +396,9 @@ static void Clean(sout_stream_t *p_stream)
disconnectChromecast(p_stream);
+#ifdef HAVE_MICRODNS
+ mdns_cleanup(p_sys->microdns_ctx);
+#endif
free(p_sys->psz_mime);
delete p_sys;
}
@@ -386,10 +409,10 @@ static void Clean(sout_stream_t *p_stream)
* @param p_stream the sout_stream_t structure
* @return the opened socket file descriptor or -1 on error
*/
-static int connectChromecast(sout_stream_t *p_stream, char *psz_ipChromecast)
+static int connectChromecast(sout_stream_t *p_stream)
{
sout_stream_sys_t *p_sys = p_stream->p_sys;
- int fd = net_ConnectTCP(p_stream, psz_ipChromecast, CHROMECAST_CONTROL_PORT);
+ int fd = net_ConnectTCP(p_stream, p_sys->deviceIP.c_str(), p_sys->devicePort);
if (fd < 0)
return -1;
@@ -400,7 +423,7 @@ static int connectChromecast(sout_stream_t *p_stream, char *psz_ipChromecast)
return -1;
}
- p_sys->p_tls = vlc_tls_ClientSessionCreate(p_sys->p_creds, fd, psz_ipChromecast,
+ p_sys->p_tls = vlc_tls_ClientSessionCreate(p_sys->p_creds, fd, p_sys->deviceIP.c_str(),
"tcps", NULL, NULL);
if (p_sys->p_tls == NULL)
@@ -928,13 +951,62 @@ static void msgLoad(sout_stream_t *p_stream)
}
+#ifdef HAVE_MICRODNS
+static bool mdnsShouldStop(void *p_callback_cookie)
+{
+ sout_stream_t* p_stream = (sout_stream_t*)p_callback_cookie;
+ sout_stream_sys_t* p_sys = p_stream->p_sys;
+
+ vlc_testcancel();
+
+ return !p_sys->deviceIP.empty() || mdate() > p_sys->i_timeout;
+}
+
+static void mdnsCallback(void *p_callback_cookie, int i_status, const struct rr_entry *p_entry)
+{
+ sout_stream_t* p_stream = (sout_stream_t*)p_callback_cookie;
+ sout_stream_sys_t* p_sys = p_stream->p_sys;
+
+ if (i_status < 0)
+ {
+ char err_str[128];
+ if (mdns_strerror(i_status, err_str, sizeof(err_str)) == 0)
+ msg_Dbg(p_stream, "mDNS lookup error: %s", err_str);
+ }
+ else if (p_entry != NULL && p_entry->next != NULL)
+ {
+ std::string deviceName = p_entry->next->name;
+ if (deviceName.length() >= strlen(MDNS_CHROMECAST))
+ {
+ std::string serviceName = deviceName.substr(deviceName.length() - strlen(MDNS_CHROMECAST));
+ deviceName = deviceName.substr(0, deviceName.length() - strlen(MDNS_CHROMECAST));
+ if (serviceName == MDNS_CHROMECAST &&
+ (p_sys->nameChromecast.empty() ||
+ !strncasecmp(deviceName.c_str(), p_sys->nameChromecast.c_str(), p_sys->nameChromecast.length())))
+ {
+ while (p_entry != NULL)
+ {
+ if (p_entry->type == RR_A)
+ p_sys->deviceIP = p_entry->data.A.addr_str;
+ else if (p_entry->type == RR_AAAA)
+ p_sys->deviceIP = p_entry->data.AAAA.addr_str;
+ else if (p_entry->type == RR_SRV)
+ p_sys->devicePort = p_entry->data.SRV.port;
+ p_entry = p_entry->next;
+ }
+ msg_Dbg(p_stream, "Found %s:%d for target %s", p_sys->deviceIP.c_str(), p_sys->devicePort, deviceName.c_str());
+ }
+ }
+ }
+}
+#endif /* HAVE_MICRODNS */
+
/*****************************************************************************
* Chromecast thread
*****************************************************************************/
static void* chromecastThread(void* p_data)
{
- int canc = vlc_savecancel();
- // Not cancellation-safe part.
+ int canc;
sout_stream_t* p_stream = (sout_stream_t*)p_data;
sout_stream_sys_t* p_sys = p_stream->p_sys;
@@ -945,6 +1017,58 @@ static void* chromecastThread(void* p_data)
int i_waitdelay = PING_WAIT_TIME;
int i_retries = PING_WAIT_RETRIES;
+#ifdef HAVE_MICRODNS
+ if (p_sys->microdns_ctx != NULL)
+ {
+ int err;
+ p_sys->i_timeout = mdate() + TIMEOUT_MDNS_IP;
+ if ((err = mdns_listen(p_sys->microdns_ctx, MDNS_CHROMECAST+1, 2, &mdnsShouldStop, &mdnsCallback, p_stream)) < 0)
+ {
+ char err_str[128];
+ canc = vlc_savecancel();
+ if (mdns_strerror(err, err_str, sizeof(err_str)) == 0)
+ msg_Err(p_stream, "Failed to look for the target Name: %s", err_str);
+ vlc_mutex_lock(&p_sys->lock);
+ p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
+ vlc_cond_signal(&p_sys->loadCommandCond);
+ vlc_mutex_unlock(&p_sys->lock);
+ vlc_restorecancel(canc);
+ return NULL;
+ }
+ }
+#endif /* HAVE_MICRODNS */
+
+ p_sys->i_sock_fd = connectChromecast(p_stream);
+ if (p_sys->i_sock_fd < 0)
+ {
+ canc = vlc_savecancel();
+ msg_Err(p_stream, "Could not connect the Chromecast");
+ vlc_mutex_lock(&p_sys->lock);
+ p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
+ vlc_cond_signal(&p_sys->loadCommandCond);
+ vlc_mutex_unlock(&p_sys->lock);
+ vlc_restorecancel(canc);
+ return NULL;
+ }
+
+ char psz_localIP[NI_MAXNUMERICHOST];
+ if (net_GetSockAddress(p_sys->i_sock_fd, psz_localIP, NULL))
+ {
+ canc = vlc_savecancel();
+ msg_Err(p_stream, "Cannot get local IP address");
+ vlc_mutex_lock(&p_sys->lock);
+ p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
+ vlc_cond_signal(&p_sys->loadCommandCond);
+ vlc_mutex_unlock(&p_sys->lock);
+ vlc_restorecancel(canc);
+ return NULL;
+ }
+
+ canc = vlc_savecancel();
+ p_sys->serverIP = psz_localIP;
+
+ p_sys->i_status = CHROMECAST_TLS_CONNECTED;
+
msgAuth(p_stream);
sendMessages(p_stream);
vlc_restorecancel(canc);
--
2.5.2
More information about the vlc-devel
mailing list