[vlc-devel] [PATCH] chromecast: use libmicrodns to get the device IP/port based on its name

Steve Lhomme robux4 at videolabs.io
Thu Sep 24 17:36:35 CEST 2015


no name/port provided looks for the first Chromecast to reply to the mDNS request

--
replaces https://patches.videolan.org/patch/10124/ with missing
vlc_restorecancel() calls
also made i_status an atomic_int to avoid many locks (& potential errors)
---
 modules/stream_out/chromecast/cast.cpp | 208 +++++++++++++++++++++++----------
 1 file changed, 146 insertions(+), 62 deletions(-)

diff --git a/modules/stream_out/chromecast/cast.cpp b/modules/stream_out/chromecast/cast.cpp
index b8f2cb9..cb54060 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
@@ -39,6 +40,7 @@
 #include <vlc_tls.h>
 #include <vlc_url.h>
 #include <vlc_threads.h>
+#include <vlc_atomic.h>
 
 #include <cerrno>
 
@@ -51,6 +53,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 +76,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;
@@ -86,17 +100,20 @@ struct sout_stream_sys_t
 
     std::queue<castchannel::CastMessage> messagesToSend;
 
-    int i_status;
+    std::atomic_int i_status;
     vlc_mutex_t lock;
-    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
 
-#define CHROMECAST_CONTROL_PORT 8009
 #define HTTP_PORT               8010
 
 #define SOUT_CFG_PREFIX "sout-chromecast-"
@@ -114,7 +131,7 @@ struct sout_stream_sys_t
 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);
 
@@ -130,7 +147,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
 };
 
 /*****************************************************************************
@@ -158,6 +175,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", "", IP_TEXT, IP_LONGTEXT, false)
     add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT, HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false)
     add_string(SOUT_CFG_PREFIX "mux", "mp4stream", MUX_TEXT, MUX_LONGTEXT, false)
     add_string(SOUT_CFG_PREFIX "mime", "video/mp4", MIME_TEXT, MIME_LONGTEXT, false)
@@ -207,31 +225,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)
+#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;
 
     char *psz_mux = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mux");
     if (psz_mux == NULL)
@@ -259,7 +275,6 @@ static int Open(vlc_object_t *p_this)
     }
 
     vlc_mutex_init(&p_sys->lock);
-    vlc_cond_init(&p_sys->loadCommandCond);
 
     // Start the Chromecast event thread.
     if (vlc_clone(&p_sys->chromecastThread, chromecastThread, p_stream,
@@ -270,32 +285,6 @@ static int Open(vlc_object_t *p_this)
         return VLC_EGENERIC;
     }
 
-    /* Ugly part:
-     * We want to be sure that the Chromecast receives the first data packet sent by
-     * the HTTP server. */
-
-    // 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;
-    vlc_mutex_lock(&p_sys->lock);
-    while (p_sys->i_status != CHROMECAST_MEDIA_LOAD_SENT)
-    {
-        i_ret = vlc_cond_timedwait(&p_sys->loadCommandCond, &p_sys->lock, deadline);
-        if (i_ret == ETIMEDOUT)
-        {
-            msg_Err(p_stream, "Timeout reached before sending the media loading command");
-            vlc_mutex_unlock(&p_sys->lock);
-            vlc_cancel(p_sys->chromecastThread);
-            Clean(p_stream);
-            return VLC_EGENERIC;
-        }
-    }
-    vlc_mutex_unlock(&p_sys->lock);
-
-    /* Even uglier: sleep more to let to the Chromecast initiate the connection
-     * to the http server. */
-    msleep(2 * CLOCK_FREQ);
-
     // Set the sout callbacks.
     p_stream->pf_add    = Add;
     p_stream->pf_del    = Del;
@@ -346,12 +335,15 @@ static void Clean(sout_stream_t *p_stream)
     if (p_sys->p_out)
     {
         vlc_mutex_destroy(&p_sys->lock);
-        vlc_cond_destroy(&p_sys->loadCommandCond);
         sout_StreamChainDelete(p_sys->p_out, p_sys->p_out);
     }
 
     disconnectChromecast(p_stream);
 
+#ifdef HAVE_MICRODNS
+    mdns_cleanup(p_sys->microdns_ctx);
+#endif
+
     delete p_sys;
 }
 
@@ -361,10 +353,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;
 
@@ -375,7 +367,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)
@@ -601,7 +593,6 @@ static int processMessage(sout_stream_t *p_stream, const castchannel::CastMessag
         }
         else
         {
-            vlc_mutex_locker locker(&p_sys->lock);
             p_sys->i_status = CHROMECAST_AUTHENTICATED;
             msgConnect(p_stream, "receiver-0");
             msgLaunch(p_stream);
@@ -662,7 +653,6 @@ static int processMessage(sout_stream_t *p_stream, const castchannel::CastMessag
                     msgConnect(p_stream, p_sys->appTransportId);
                     msgLoad(p_stream);
                     p_sys->i_status = CHROMECAST_MEDIA_LOAD_SENT;
-                    vlc_cond_signal(&p_sys->loadCommandCond);
                 }
             }
             else
@@ -707,9 +697,7 @@ static int processMessage(sout_stream_t *p_stream, const castchannel::CastMessag
         {
             msg_Err(p_stream, "Media load failed");
             msgClose(p_stream, p_sys->appTransportId);
-            vlc_mutex_lock(&p_sys->lock);
             p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
-            vlc_mutex_unlock(&p_sys->lock);
         }
         else
         {
@@ -729,9 +717,7 @@ static int processMessage(sout_stream_t *p_stream, const castchannel::CastMessag
         if (type == "CLOSE")
         {
             msg_Warn(p_stream, "received close message");
-            vlc_mutex_lock(&p_sys->lock);
             p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
-            vlc_mutex_unlock(&p_sys->lock);
         }
         else
         {
@@ -900,6 +886,54 @@ 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;
+
+    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
  *****************************************************************************/
@@ -917,6 +951,50 @@ 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() + 5 * CLOCK_FREQ;
+        if ((err = mdns_listen(p_sys->microdns_ctx, MDNS_CHROMECAST+1, 2, &mdnsShouldStop, &mdnsCallback, p_stream)) < 0)
+        {
+            char err_str[128];
+            if (mdns_strerror(err, err_str, sizeof(err_str)) == 0)
+                msg_Err(p_stream, "Failed to look for the target Name: %s", err_str);
+            p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
+            vlc_restorecancel(canc);
+            return NULL;
+        }
+    }
+#endif /* HAVE_MICRODNS */
+
+    p_sys->i_sock_fd = connectChromecast(p_stream);
+    if (p_sys->i_sock_fd < 0)
+    {
+        msg_Err(p_stream, "Could not connect the Chromecast");
+        p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
+        vlc_restorecancel(canc);
+        return NULL;
+    }
+
+    char psz_localIP[NI_MAXNUMERICHOST];
+    if (net_GetSockAddress(p_sys->i_sock_fd, psz_localIP, NULL))
+    {
+        msg_Err(p_stream, "Cannot get local IP address");
+        p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
+        vlc_restorecancel(canc);
+        return NULL;
+    }
+    p_sys->serverIP = psz_localIP;
+
+    p_sys->i_status = CHROMECAST_TLS_CONNECTED;
+
+    /* Ugly part:
+     * We want to be sure that the Chromecast receives the first data packet sent by
+     * the HTTP server. */
+
+    mtime_t deadline = mdate() + 6 * CLOCK_FREQ;
+
     msgAuth(p_stream);
     sendMessages(p_stream);
     vlc_restorecancel(canc);
@@ -939,7 +1017,6 @@ static void* chromecastThread(void* p_data)
 #endif
         {
             msg_Err(p_stream, "The connection to the Chromecast died.");
-            vlc_mutex_locker locker(&p_sys->lock);
             p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
             break;
         }
@@ -968,11 +1045,18 @@ static void* chromecastThread(void* p_data)
 #endif
             {
                 msg_Err(p_stream, "The connection to the Chromecast died.");
-                vlc_mutex_locker locker(&p_sys->lock);
                 p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
             }
         }
 
+        if (p_sys->i_status == CHROMECAST_MEDIA_LOAD_SENT)
+            deadline = 0;
+        else if (mdate() > deadline)
+        {
+            msg_Err(p_stream, "Timeout reached before sending the media loading command");
+            p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
+        }
+
         vlc_mutex_lock(&p_sys->lock);
         if ( p_sys->i_status == CHROMECAST_CONNECTION_DEAD )
         {
-- 
2.5.1



More information about the vlc-devel mailing list