[vlc-devel] [PATCH 5/5] chromecast: add an experimental sout module that connects to a ChromeCast device and streams using the HTTP access output

Rémi Denis-Courmont remi at remlab.net
Wed Aug 20 11:48:38 CEST 2014


Le 2014-08-20 11:38, magsoft at videolan.org a écrit :
> From: Adrien Maglo <magsoft at videolan.org>
>
> ---
>  configure.ac                           |   2 +-
>  modules/stream_out/Modules.am          |   6 +
>  modules/stream_out/chromecast/cast.cpp | 931
> +++++++++++++++++++++++++++++++++
>  3 files changed, 938 insertions(+), 1 deletion(-)
>  create mode 100644 modules/stream_out/chromecast/cast.cpp
>
> diff --git a/configure.ac b/configure.ac
> index 4852f2c..9feefc9 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -637,7 +637,7 @@ AC_CHECK_FUNCS([if_nameindex if_nametoindex])
>  VLC_RESTORE_FLAGS
>
>  AS_IF([test -n "$SOCKET_LIBS"], [
> -  VLC_ADD_LIBS([access_rtmp access_output_shout sap
> stream_out_standard stream_out_rtp stream_out_raop vod_rtsp oldrc
> netsync gnutls ts remoteosd audiobargraph_a],[${SOCKET_LIBS}])
> +  VLC_ADD_LIBS([access_rtmp access_output_shout sap
> stream_out_standard stream_out_rtp stream_out_raop
> stream_out_chromecast vod_rtsp oldrc netsync gnutls ts remoteosd
> audiobargraph_a],[${SOCKET_LIBS}])
>  ])
>  AC_SUBST(SOCKET_LIBS)
>
> diff --git a/modules/stream_out/Modules.am 
> b/modules/stream_out/Modules.am
> index b581152..b05ce67 100644
> --- a/modules/stream_out/Modules.am
> +++ b/modules/stream_out/Modules.am
> @@ -64,6 +64,12 @@ if HAVE_GCRYPT
>  stream_out_LTLIBRARIES += libstream_out_raop_plugin.la
>  endif
>
> +libstream_out_chromecast_plugin_la_SOURCES = chromecast/cast.cpp
> chromecast/cast_channel.pb.cc
> +libstream_out_chromecast_plugin_la_CPPFLAGS = $(AM_CPPFLAGS)
> +libstream_out_chromecast_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath
> '$(stream_outdir)'
> +libstream_out_chromecast_plugin_la_LIBADD = -lprotobuf -ljsoncpp
> $(LIBS_stream_out_chromecast)
> +stream_out_LTLIBRARIES += libstream_out_chromecast_plugin.la
> +

This breaks the build. kthxbye.

>  BUILT_SOURCES += dummy.cpp
>
>  dummy.cpp:
> diff --git a/modules/stream_out/chromecast/cast.cpp
> b/modules/stream_out/chromecast/cast.cpp
> new file mode 100644
> index 0000000..1fff0e2
> --- /dev/null
> +++ b/modules/stream_out/chromecast/cast.cpp
> @@ -0,0 +1,931 @@
> 
> +/*****************************************************************************
> + * cast.cpp: Chromecast module for vlc
> +
> 
> *****************************************************************************
> + * Copyright © 2014 VideoLAN
> + *
> + * Authors: Jean-Baptiste Kempf <jb at videolan.org>
> + *          Adrien Maglo <magsoft at videolan.org>
> + *
> + * 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
> + * the Free Software Foundation; either version 2.1 of the License, 
> or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public 
> License
> + * along with this program; if not, write to the Free Software 
> Foundation,
> + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
> +
> 
> *****************************************************************************/
> +
> 
> +/*****************************************************************************
> + * Preamble
> +
> 
> *****************************************************************************/
> +
> +#ifdef HAVE_CONFIG_H
> +# include "config.h"
> +#endif
> +
> +#ifdef HAVE_POLL
> +# include <poll.h>
> +#endif
> +
> +#include <vlc_common.h>
> +#include <vlc_plugin.h>
> +#include <vlc_sout.h>
> +#include <vlc_tls.h>
> +#include <vlc_url.h>
> +#include <vlc_threads.h>
> +#include <vlc_atomic.h>
> +
> +#include <errno.h>
> +
> +#include <iostream>
> +#include <sstream>
> +#include <queue>
> +
> +#include <google/protobuf/io/zero_copy_stream_impl.h>
> +#include <google/protobuf/io/coded_stream.h>
> +#include "cast_channel.pb.h"
> +
> +#include <json/json.h>
> +
> +using namespace google::protobuf::io;
> +using namespace std;
> +using namespace castchannel;
> +
> +// Status
> +enum
> +{
> +    CHROMECAST_DISCONNECTED,
> +    CHROMECAST_TLS_CONNECTED,
> +    CHROMECAST_AUTHENTICATED,
> +    CHROMECAST_APP_STARTED,
> +    CHROMECAST_CONNECTION_DEAD,
> +};
> +
> +#define PACKET_MAX_LEN 10 * 1024
> +#define PACKET_HEADER_LEN 4
> +
> +struct sout_stream_sys_t
> +{
> +    sout_stream_sys_t()
> +        : i_received(0), i_status(CHROMECAST_DISCONNECTED),
> +          b_threadStarted(false), b_pingSent(false),
> +          p_mux(NULL), p_access(NULL)
> +    {
> +        atomic_init(&ab_error, false);
> +    }
> +
> +    vlc_url_t url;
> +    string serverIP;
> +
> +    int i_sock_fd;
> +    vlc_tls_creds_t *p_creds;
> +    vlc_tls_t *p_tls;
> +
> +    vlc_thread_t chromecastThread;
> +
> +    unsigned i_received;
> +    char p_packet[PACKET_MAX_LEN];
> +    unsigned i_requestId;
> +    string appTransportId;
> +
> +    queue<CastMessage> messagesToSend;
> +
> +    int i_status;
> +    bool b_threadStarted;
> +    bool b_pingSent;
> +    atomic_bool ab_error;
> +    vlc_mutex_t loadCommandMutex;
> +    vlc_cond_t loadCommandCond;
> +
> +    char *psz_mux;
> +    sout_mux_t* p_mux;
> +    sout_access_out_t *p_access;

There is the standard output if you want to hook a muxer to an access 
out.

And while I have not checked in depth, that seems like a surprising lot 
of parameters that need to be retained.

Indeed at least b_threadStarted is always true across entry points, so 
useless.

> +};
> +
> +// Media player Chromecast app id
> +#define APP_ID "CC1AD845" // Default media player
> +//#define APP_ID "698A9477"
> +
> +#define SOUT_CFG_PREFIX "sout-chromecast-"
> +
> +
> 
> +/*****************************************************************************
> + * Local prototypes
> +
> 
> *****************************************************************************/
> +static int Open(vlc_object_t *);
> +static void Close(vlc_object_t *);
> +static int connectChromecast(sout_stream_t *p_stream);
> +static void disconnectChromecast(sout_stream_t *p_stream);
> +static int sendMessages(sout_stream_t *p_stream);
> +
> +static void msgAuth(sout_stream_t *p_stream);
> +static void msgPing(sout_stream_t *p_stream);
> +static void msgPong(sout_stream_t *p_stream);
> +static void msgConnect(sout_stream_t *p_stream, string 
> destinationId);
> +static void msgClose(sout_stream_t *p_stream, string destinationId);
> +static void msgLaunch(sout_stream_t *p_stream);
> +static void msgLoad(sout_stream_t *p_stream);
> +
> +static void *chromecastThread(void *data);
> +
> +static string JsonToString(Json::Value data);
> +static Json::Value StringToJson(string str);
> +
> +static const char *const ppsz_sout_options[] = {
> +    "receiver-ip", "sender-ip", "mux", "mime", NULL
> +};
> +
> 
> +/*****************************************************************************
> + * Module descriptor
> +
> 
> *****************************************************************************/
> +
> +#define RECEIVER_IP_TEXT N_("Chromecast IP address")
> +#define RECEIVER_IP_LONGTEXT N_("This allows to define the ip adress
> of the Chromecast receiver.")

Heavy.

> +#define SENDER_IP_TEXT N_("Sender IP address")
> +#define SENDER_IP_LONGTEXT N_("This allows to define the ip address
> that the Chromecast" \
> +                              "will use to connect to HTTP stream.")

Ditto.

> +#define MUX_TEXT N_("Muxer")
> +#define MUX_LONGTEXT N_("This set the muxer used to stream to the
> Chromecast.")

Grammar.

> +#define MIME_TEXT N_("Mime type")
> +#define MIME_LONGTEXT N_("This set the media mine type sent to the
> Chromecast.")

Grammar.

> +
> +vlc_module_begin ()
> +
> +    set_shortname(N_("Chromecast"))
> +    set_description(N_("Chromecast stream output"))
> +    set_capability("sout stream", 50)
> +    add_shortcut("chromecast")
> +    set_category(CAT_SOUT)
> +    set_subcategory(SUBCAT_SOUT_STREAM)
> +    set_callbacks(Open, Close)
> +
> +    add_string(SOUT_CFG_PREFIX "receiver-ip", "", RECEIVER_IP_TEXT,
> RECEIVER_IP_LONGTEXT, false)
> +    add_string(SOUT_CFG_PREFIX "sender-ip", "", SENDER_IP_TEXT,
> SENDER_IP_LONGTEXT, false)
> +    add_string(SOUT_CFG_PREFIX "mux", "avformat{mux=mp4}", MUX_TEXT,
> MUX_LONGTEXT, false)
> +    add_string(SOUT_CFG_PREFIX "mime", "video/mp4", MIME_TEXT,
> MIME_LONGTEXT, false)
> +
> +vlc_module_end ()
> +
> +
> 
> +/*****************************************************************************
> + * Sout callbacks
> +
> 
> *****************************************************************************/
> +struct sout_stream_id_sys_t
> +{
> +};

Hmm??

> +
> +
> +static sout_stream_id_sys_t *Add(sout_stream_t *p_stream,
> es_format_t *p_fmt)
> +{
> +    return
> (sout_stream_id_sys_t*)sout_MuxAddStream(p_stream->p_sys->p_mux,
> p_fmt);
> +}
> +
> +
> +static int Del(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
> +{
> +    sout_MuxDeleteStream(p_stream->p_sys->p_mux, (sout_input_t*)id);
> +    return VLC_SUCCESS;
> +}
> +
> +
> +static int Send(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
> +                block_t *p_buffer)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +    if (atomic_load(&p_sys->ab_error))
> +        return VLC_EGENERIC;
> +
> +    return sout_MuxSendBuffer(p_stream->p_sys->p_mux,
> (sout_input_t*)id, p_buffer);
> +}
> +
> +
> 
> +/*****************************************************************************
> + * Open: connect to the Chromecast and initialize the sout
> +
> 
> *****************************************************************************/
> +static int Open(vlc_object_t *p_this)
> +{
> +    sout_stream_t *p_stream = (sout_stream_t*)p_this;
> +    sout_stream_sys_t *p_sys;
> +    p_sys = new(nothrow) sout_stream_sys_t();
> +    if (p_sys == NULL)
> +        return VLC_ENOMEM;
> +    p_stream->p_sys = p_sys;
> +
> +    config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options,
> p_stream->p_cfg);
> +
> +    char *psz_ipChromecast = var_GetNonEmptyString(p_stream,
> SOUT_CFG_PREFIX "receiver-ip");
> +    if (psz_ipChromecast == NULL)
> +    {
> +        msg_Err(p_stream, "No Chromecast receiver IP provided");
> +        Close(p_this);
> +        return VLC_EGENERIC;
> +    }
> +
> +    vlc_url_t url;

Uh? Why?

> +    url.psz_host = strdup(psz_ipChromecast);
> +    url.i_port = 8009;
> +    p_sys->url = url;
> +    free(psz_ipChromecast);
> +
> +    char *psz_ipLocal = var_GetNonEmptyString(p_stream,
> SOUT_CFG_PREFIX "sender-ip");
> +    if (psz_ipLocal == NULL)
> +    {
> +        msg_Err(p_stream, "No sender IP provided");
> +        Close(p_this);
> +        return VLC_EGENERIC;
> +    }
> +    p_sys->serverIP = psz_ipLocal;
> +    free(psz_ipLocal);
> +
> +    p_sys->i_sock_fd = connectChromecast(p_stream);
> +    if (p_sys->i_sock_fd < 0)
> +    {
> +        msg_Err(p_stream, "Could not connect the Chromecast");
> +        Close(p_this);
> +        return VLC_EGENERIC;
> +    }
> +
> +    char psz_access[] = "http";
> +    char psz_url[] = ":8009/stream";

Useless string copies.

> +
> +    p_sys->p_access = sout_AccessOutNew(p_stream, psz_access, 
> psz_url);
> +    if (p_sys->p_access == NULL)
> +    {
> +        msg_Err(p_stream, "No suitable sout access module");
> +        Close(p_this);
> +        return VLC_EGENERIC;
> +    }
> +
> +    char *psz_mux = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX 
> "mux");
> +    p_sys->p_mux = sout_MuxNew(p_stream->p_sout, psz_mux, 
> p_sys->p_access);
> +    free(psz_mux);
> +    if (!p_sys->p_mux)
> +    {
> +        msg_Err(p_stream, "No suitable sout mux module");
> +        Close(p_this);
> +        return VLC_EGENERIC;
> +    }
> +
> +    // Set the sout callbacks.
> +    p_stream->pf_add    = Add;
> +    p_stream->pf_del    = Del;
> +    p_stream->pf_send   = Send;
> +
> +    if (!sout_AccessOutCanControlPace(p_sys->p_access))
> +        p_stream->pace_nocontrol = true;
> +
> +    vlc_mutex_init(&p_sys->loadCommandMutex);
> +    vlc_cond_init(&p_sys->loadCommandCond);
> +
> +    // Start the Chromecast event thread.
> +    if (vlc_clone(&p_sys->chromecastThread, chromecastThread, 
> p_stream,
> +                  VLC_THREAD_PRIORITY_LOW))
> +    {
> +        msg_Err(p_stream, "Could not start the Chromecast talking 
> thread");
> +        Close(p_this);
> +        return VLC_EGENERIC;
> +    }
> +
> +    p_sys->b_threadStarted = true;
> +
> +    /* 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.
> +    vlc_mutex_lock(&p_sys->loadCommandMutex);
> +    const mtime_t deadline = mdate() + 6000 * 1000;
> +    int i_ret = vlc_cond_timedwait(&p_sys->loadCommandCond,
> &p_sys->loadCommandMutex, deadline);
> +    if (i_ret == ETIMEDOUT)
> +    {
> +        msg_Err(p_stream, "Timeout reached before sending the media
> loading command");
> +        vlc_mutex_unlock(&p_sys->loadCommandMutex);
> +        Close(p_this);
> +        return VLC_EGENERIC;
> +    }
> +    vlc_mutex_unlock(&p_sys->loadCommandMutex);
> +
> +    // Sleep more to let to the Chromecast initiate the connection
> to the http server.
> +    sleep(2);

Not thread-safe.

And extremely dubious anyway.

> +
> +    return VLC_SUCCESS;
> +}
> +
> +
> 
> +/*****************************************************************************
> + * Close: destroy interface
> +
> 
> *****************************************************************************/
> +static void Close(vlc_object_t *p_this)
> +{
> +    sout_stream_t *p_stream = (sout_stream_t *)p_this;
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    if (p_sys->b_threadStarted)
> +    {
> +        vlc_cancel(p_sys->chromecastThread);

glibc supports it but generally cancel is not portably defined for C++ 
functions. I believe you need to stick to C linkage and POD at all 
potential cancel points.

> +        vlc_join(p_sys->chromecastThread, NULL);
> +
> +        vlc_cond_destroy(&p_sys->loadCommandCond);
> +        vlc_mutex_destroy(&p_sys->loadCommandMutex);

Leaked on false branch.

> +    }
> +
> +    // Generate the close messages.
> +    if (p_sys->i_status == CHROMECAST_APP_STARTED)
> +    {
> +        msgClose(p_stream, p_sys->appTransportId);
> +        p_sys->i_status = CHROMECAST_AUTHENTICATED;
> +    }
> +
> +    if (p_sys->i_status == CHROMECAST_AUTHENTICATED)
> +    {
> +        msgClose(p_stream, "receiver-0");
> +        p_sys->i_status = CHROMECAST_TLS_CONNECTED;
> +    }
> +
> +    // Send the just added close messages.
> +    sendMessages(p_stream);
> +
> +    if (p_sys->i_status == CHROMECAST_TLS_CONNECTED)
> +    {
> +        disconnectChromecast(p_stream);
> +        p_sys->i_status = CHROMECAST_DISCONNECTED;
> +    }
> +
> +    if (p_sys->p_mux != NULL)
> +        sout_MuxDelete(p_sys->p_mux);
> +    if (p_sys->p_access != NULL)
> +        sout_AccessOutDelete(p_sys->p_access);
> +
> +    delete p_sys;
> +}
> +
> +
> +vlc_tls_t *chromecast_ClientSessionCreate(vlc_tls_creds_t *crd, int 
> fd,
> +                                          const char *host, const
> char *service)
> +{
> +    vlc_tls_t *session = vlc_tls_SessionCreate(crd, fd, host);
> +    if (session == NULL)
> +        return NULL;
> +
> +    mtime_t deadline = mdate();
> +    deadline += var_InheritInteger(crd, "ipv4-timeout") * 1000;
> +
> +    struct pollfd ufd[1];
> +    ufd[0].fd = fd;
> +
> +    int val;
> +    while ((val = vlc_tls_SessionHandshake(session, host, service)) 
> > 0)
> +    {
> +        mtime_t now = mdate ();
> +        if( now > deadline )
> +            now = deadline;
> +
> +        ufd[0] .events = (val == 1) ? POLLIN : POLLOUT;
> +
> +        if (poll(ufd, 1, (deadline - now) / 1000) == 0)
> +        {
> +            msg_Err( session, "TLS client session handshake timeout" 
> );
> +            val = -1;
> +            break;
> +        }
> +    }
> +
> +    msg_Warn(session, "TLS client session is NOT trusted (%i)", 
> val);
> +
> +    return session;
> +}

This looks like cut&paste of tls.c...

> +
> +
> +/**
> + * @brief Connect to the Chromecast
> + * @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)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +    int fd = net_ConnectTCP(p_stream, p_sys->url.psz_host,
> p_sys->url.i_port);
> +    if (fd < 0)
> +        return -1;
> +
> +    p_sys->p_creds = vlc_tls_ClientCreate(VLC_OBJECT(p_stream));
> +    if (p_sys->p_creds == NULL)
> +        return -1;
> +
> +    p_sys->p_tls = chromecast_ClientSessionCreate(p_sys->p_creds, 
> fd,
> +                                                  
> p_sys->url.psz_host,
> +                                                  "tcps");
> +
> +    if (p_sys->p_tls == NULL)
> +    {
> +        vlc_tls_Delete(p_sys->p_creds);
> +        return -1;
> +    }
> +
> +    p_sys->i_status = CHROMECAST_TLS_CONNECTED;
> +    return fd;
> +}
> +
> +
> +/**
> + * @brief Disconnect from the Chromecast
> + */
> +static void disconnectChromecast(sout_stream_t *p_stream)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    vlc_tls_SessionDelete(p_sys->p_tls);
> +    vlc_tls_Delete(p_sys->p_creds);
> +}
> +
> +
> +/**
> + * @brief Send a message to the Chromecast
> + * @param p_stream the sout_stream_t structure
> + * @param msg the CastMessage to send
> + * @return the number of bytes sent or -1 on error
> + */
> +static int sendMessage(sout_stream_t *p_stream, CastMessage &msg)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    uint32_t i_size = msg.ByteSize();
> +    uint32_t i_sizeNetwork = hton32(i_size);
> +
> +    char p_data[PACKET_HEADER_LEN + i_size];
> +    memcpy(p_data, (char *)&i_sizeNetwork, PACKET_HEADER_LEN);
> +    msg.SerializeWithCachedSizesToArray((uint8_t *)(p_data +
> PACKET_HEADER_LEN));
> +
> +    return tls_Send(p_sys->p_tls, p_data, sizeof(p_data));
> +}
> +
> +
> +/**
> + * @brief Send all the messages in the pending queue to the 
> Chromecast
> + * @param p_stream the sout_stream_t structure
> + * @param msg the CastMessage to send
> + * @return the number of bytes sent or -1 on error
> + */
> +static int sendMessages(sout_stream_t *p_stream)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    int i_ret = 0;
> +    while (!p_sys->messagesToSend.empty())
> +    {
> +        unsigned i_retSend = sendMessage(p_stream,
> p_sys->messagesToSend.front());
> +        if (i_retSend <= 0)
> +            return i_retSend;
> +
> +        p_sys->messagesToSend.pop();
> +        i_ret += i_retSend;
> +    }
> +
> +    return i_ret;
> +}
> +
> +
> +/**
> + * @brief Receive a data packet from the Chromecast
> + * @param p_stream the sout_stream_t structure
> + * @param b_msgReceived returns true if a message has been entirely
> received else false
> + * @param i_payloadSize returns the payload size of the message 
> received
> + * @return the number of bytes received of -1 on error
> + */
> +static int recvPacket(sout_stream_t *p_stream, bool &b_msgReceived,
> +                      uint32_t &i_payloadSize)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +    struct pollfd ufd[1];
> +    ufd[0].fd = p_sys->i_sock_fd;
> +    ufd[0].events = POLLIN;
> +
> +    mtime_t deadline = mdate();
> +    deadline += 6 * 1000 * 1000; // 6 seconds
> +
> +    mtime_t now = mdate();
> +    if (now > deadline)
> +        now = deadline;
> +
> +    /* The Chromecast normally sends a PING command every 5 seconds 
> or so.
> +     * If we do not receive one after 6 seconds, we send a PING.
> +     * If after this PING, we do not receive a PONG, then we 
> consider the
> +     * connection as dead. */
> +    if (poll(ufd, 1, (deadline - now) / 1000) == 0)
> +    {
> +        if (p_sys->b_pingSent)
> +        {
> +            msg_Err(p_stream, "No PONG answer received from the
> Chromecast");
> +            return 0; // Connection died
> +        }
> +        msg_Warn(p_stream, "No PING received from the Chromecast,
> sending a PING");
> +        msgPing(p_stream);
> +        p_sys->b_pingSent = true;
> +        return -1;
> +    }
> +
> +    p_sys->b_pingSent = false;
> +    char *p_data = p_sys->p_packet;
> +    int i_ret;
> +
> +    /* Packet structure:
> +     * 
> +------------------------------------+------------------------------+
> +     * | Payload size (uint32_t big endian) |         Payload data   
>      |
> +     *
> +------------------------------------+------------------------------+
> */
> +    if (p_sys->i_received < PACKET_HEADER_LEN)
> +    {
> +        // We receive the header.
> +        i_ret = tls_Recv(p_sys->p_tls, p_data, PACKET_HEADER_LEN -
> p_sys->i_received);
> +        if (i_ret <= 0)
> +            return i_ret;
> +        p_sys->i_received += i_ret;
> +    }
> +    else
> +    {
> +        // We receive the payload.
> +
> +        // Get the size of the payload
> +        memcpy(&i_payloadSize, p_data, PACKET_HEADER_LEN);
> +        i_payloadSize = hton32(i_payloadSize);
> +        const uint32_t i_maxPayloadSize = PACKET_MAX_LEN -
> PACKET_HEADER_LEN;
> +
> +        if (i_payloadSize > i_maxPayloadSize)
> +        {
> +            // Error case: the packet sent by the Chromecast is too
> long: we drop it.
> +            msg_Err(p_stream, "Packet too long: droping its data");
> +
> +            uint32_t i_size = i_payloadSize - (p_sys->i_received -
> PACKET_HEADER_LEN);
> +            if (i_size > i_maxPayloadSize)
> +                i_size = i_maxPayloadSize;
> +
> +            i_ret = tls_Recv(p_sys->p_tls, p_data +
> PACKET_HEADER_LEN, i_size);
> +            if (i_ret <= 0)
> +                return i_ret;
> +            p_sys->i_received += i_ret;
> +
> +            if (p_sys->i_received < i_payloadSize + 
> PACKET_HEADER_LEN)
> +                return i_ret;
> +
> +            p_sys->i_received = 0;
> +            return -1;
> +        }
> +
> +        // Normal case
> +        i_ret = tls_Recv(p_sys->p_tls, p_data + p_sys->i_received,
> +                         i_payloadSize - (p_sys->i_received -
> PACKET_HEADER_LEN));
> +        if (i_ret <= 0)
> +            return i_ret;
> +        p_sys->i_received += i_ret;
> +
> +        if (p_sys->i_received < i_payloadSize + PACKET_HEADER_LEN)
> +            return i_ret;
> +
> +        assert(p_sys->i_received == i_payloadSize + 
> PACKET_HEADER_LEN);
> +        p_sys->i_received = 0;
> +        b_msgReceived = true;
> +        return i_ret;
> +    }
> +
> +    return i_ret;
> +}
> +
> +
> +/**
> + * @brief Process a message received from the Chromecast
> + * @param p_stream the sout_stream_t structure
> + * @param msg the CastMessage to process
> + * @return 0 if the message has been successfuly processed else -1
> + */
> +static int processMessage(sout_stream_t *p_stream, CastMessage &msg)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +    string namespace_ = msg.namespace_();
> +
> +    if (namespace_ == "urn:x-cast:com.google.cast.tp.deviceauth")
> +    {
> +        DeviceAuthMessage authMessage;
> +        authMessage.ParseFromString(msg.payload_binary());
> +
> +        if (authMessage.has_error())
> +        {
> +            msg_Err(p_stream, "Authentification error: %d",
> authMessage.error().error_type());
> +            return -1;
> +        }
> +
> +        if (!authMessage.has_response()) {
> +            msg_Err(p_stream, "Authentification message has no
> response field");
> +            return -1;
> +        }
> +
> +        p_sys->i_status = CHROMECAST_AUTHENTICATED;
> +        msgConnect(p_stream, "receiver-0");
> +        msgLaunch(p_stream);
> +    }
> +    else if (namespace_ == 
> "urn:x-cast:com.google.cast.tp.heartbeat")
> +    {
> +        Json::Value data = StringToJson(msg.payload_utf8());
> +        string type = data.get("type", "none").asString();
> +
> +        if (type == "PING")
> +        {
> +            msg_Dbg(p_stream, "PING received from the Chromecast");
> +            msgPong(p_stream);
> +        }
> +        else if (type == "PONG")
> +        {
> +            msg_Dbg(p_stream, "PONG received from the Chromecast");
> +        }
> +        else
> +        {
> +            msg_Err(p_stream, "Heartbeat command not supported");
> +            return -1;
> +        }
> +    }
> +    else if (namespace_ == "urn:x-cast:com.google.cast.receiver")
> +    {
> +        Json::Value data = StringToJson(msg.payload_utf8());
> +        string type = data.get("type", "none").asString();
> +
> +        if (type == "RECEIVER_STATUS")
> +        {
> +            Json::Value status = data["status"];
> +            Json::Value applications = status["applications"];
> +            for (unsigned i = 0; i < applications.size(); ++i)
> +            {
> +                string appId = applications[i]["appId"].asString();
> +                if (appId == APP_ID)
> +                    p_sys->appTransportId =
> applications[i]["transportId"].asString();
> +            }
> +            if (p_sys->appTransportId != ""
> +                && p_sys->i_status == CHROMECAST_AUTHENTICATED)
> +            {
> +                p_sys->i_status = CHROMECAST_APP_STARTED;
> +                msgConnect(p_stream, p_sys->appTransportId);
> +                msgLoad(p_stream);
> +            }
> +        }
> +        else
> +        {
> +            msg_Err(p_stream, "Receiver command not supported: %s",
> +                    msg.payload_utf8().c_str());
> +            return -1;
> +        }
> +    }
> +    else if (namespace_ == "urn:x-cast:com.google.cast.media")
> +    {
> +        Json::Value data = StringToJson(msg.payload_utf8());
> +        string type = data.get("type", "none").asString();
> +
> +        if (type == "MEDIA_STATUS")
> +        {
> +            Json::Value status = data["status"];
> +            msg_Dbg(p_stream, "Player state: %s",
> +                    status[0]["playerState"].asString().c_str());
> +        }
> +        else if (type == "LOAD_FAILED")
> +        {
> +            msg_Err(p_stream, "Media load failed");
> +            atomic_store(&p_sys->ab_error, true);
> +        }
> +        else
> +        {
> +            msg_Err(p_stream, "Media command not supported: %s",
> +                    msg.payload_utf8().c_str());
> +            return -1;
> +        }
> +    }
> +    else
> +    {
> +        msg_Err(p_stream, "Unknown namespace: %s",
> msg.namespace_().c_str());
> +        return -1;
> +    }
> +
> +    return 0;
> +}
> +
> +
> +/**
> + * @brief Build a CastMessage to send to the Chromecast
> + * @param namespace_ the message namespace
> + * @param payloadType the payload type 
> (CastMessage_PayloadType_STRING or
> + * CastMessage_PayloadType_BINARY
> + * @param payload the payload
> + * @param destinationId the destination idenifier
> + * @return the generated CastMessage
> + */
> +static CastMessage buildMessage(string namespace_,
> +                                castchannel::CastMessage_PayloadType
> payloadType,
> +                                string payload, string destinationId
> = "receiver-0")
> +{
> +    CastMessage msg;
> +
> +    
> msg.set_protocol_version(CastMessage_ProtocolVersion_CASTV2_1_0);
> +    msg.set_namespace_(namespace_);
> +    msg.set_payload_type(payloadType);
> +    msg.set_source_id("sender-vlc");
> +    msg.set_destination_id(destinationId);
> +    if (payloadType == CastMessage_PayloadType_STRING)
> +        msg.set_payload_utf8(payload);
> +    else // CastMessage_PayloadType_BINARY
> +        msg.set_payload_binary(payload);
> +
> +    return msg;
> +}
> +
> +
> 
> +/*****************************************************************************
> + * String-JSON conversion
> +
> 
> *****************************************************************************/
> +static string JsonToString(Json::Value data)
> +{
> +    Json::FastWriter writer;
> +    return writer.write(data);
> +}
> +
> +
> +static Json::Value StringToJson(string str)
> +{
> +    Json::Reader reader;
> +    Json::Value data;
> +    reader.parse(str, data);
> +    return data;
> +}
> +
> +
> 
> +/*****************************************************************************
> + * Message preparation
> +
> 
> *****************************************************************************/
> +static void msgAuth(sout_stream_t *p_stream)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    DeviceAuthMessage authMessage;
> +    authMessage.mutable_challenge();
> +    std::string authMessageString;
> +    authMessage.SerializeToString(&authMessageString);
> +
> +    CastMessage msg =
> buildMessage("urn:x-cast:com.google.cast.tp.deviceauth",
> +        CastMessage_PayloadType_BINARY, authMessageString);
> +
> +    p_sys->messagesToSend.push(msg);
> +}
> +
> +
> +static void msgPing(sout_stream_t *p_stream)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    Json::Value data;
> +    data["type"] = "PING";
> +
> +    CastMessage msg =
> buildMessage("urn:x-cast:com.google.cast.tp.heartbeat",
> +        CastMessage_PayloadType_STRING, JsonToString(data));
> +
> +    p_sys->messagesToSend.push(msg);
> +}
> +
> +
> +static void msgPong(sout_stream_t *p_stream)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    Json::Value data;
> +    data["type"] = "PONG";
> +
> +    CastMessage msg =
> buildMessage("urn:x-cast:com.google.cast.tp.heartbeat",
> +        CastMessage_PayloadType_STRING, JsonToString(data));
> +
> +    p_sys->messagesToSend.push(msg);
> +}
> +
> +
> +static void msgConnect(sout_stream_t *p_stream, string 
> destinationId)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    Json::Value data;
> +    data["type"] = "CONNECT";
> +
> +    CastMessage msg =
> buildMessage("urn:x-cast:com.google.cast.tp.connection",
> +        CastMessage_PayloadType_STRING, JsonToString(data), 
> destinationId);
> +
> +    p_sys->messagesToSend.push(msg);
> +}
> +
> +
> +static void msgClose(sout_stream_t *p_stream, string destinationId)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    Json::Value data;
> +    data["type"] = "CLOSE";
> +
> +    CastMessage msg =
> buildMessage("urn:x-cast:com.google.cast.tp.connection",
> +        CastMessage_PayloadType_STRING, JsonToString(data), 
> destinationId);
> +
> +    p_sys->messagesToSend.push(msg);
> +}
> +
> +
> +static void msgLaunch(sout_stream_t *p_stream)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    Json::Value data;
> +    data["type"] = "LAUNCH";
> +    data["appId"] = APP_ID;
> +    data["requestId"] = p_stream->p_sys->i_requestId++;
> +
> +    CastMessage msg = 
> buildMessage("urn:x-cast:com.google.cast.receiver",
> +        CastMessage_PayloadType_STRING, JsonToString(data));
> +
> +    p_sys->messagesToSend.push(msg);
> +}
> +
> +
> +static void msgLoad(sout_stream_t *p_stream)
> +{
> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
> +
> +    char *psz_mime = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX
> "mime");
> +
> +    Json::Value data;
> +    data["type"] = "LOAD";
> +    Json::Value media;
> +    media["contentId"] = "http://" + p_sys->serverIP + 
> ":8009/stream";
> +    media["streamType"] = "LIVE";
> +    media["contentType"] = psz_mime;
> +    data["media"] = media;
> +    data["requestId"] = p_sys->i_requestId++;
> +
> +    free(psz_mime);
> +
> +    CastMessage msg = 
> buildMessage("urn:x-cast:com.google.cast.media",
> +        CastMessage_PayloadType_STRING, JsonToString(data),
> +        p_sys->appTransportId);
> +
> +    p_sys->messagesToSend.push(msg);
> +
> +    // Unlock the sout tread.
> +    vlc_mutex_lock(&p_sys->loadCommandMutex);
> +    vlc_cond_signal(&p_sys->loadCommandCond);
> +    vlc_mutex_unlock(&p_sys->loadCommandMutex);
> +}
> +
> +
> 
> +/*****************************************************************************
> + * Chromecast thread
> +
> 
> *****************************************************************************/
> +static void* chromecastThread(void* p_data)
> +{
> +    sout_stream_t* p_stream = (sout_stream_t*)p_data;
> +    sout_stream_sys_t* p_sys = p_stream->p_sys;
> +
> +    msgAuth(p_stream);
> +    sendMessages(p_stream);
> +
> +    while (1)
> +    {
> +        bool b_msgReceived = false;
> +        uint32_t i_payloadSize = 0;
> +        int i_ret = recvPacket(p_stream, b_msgReceived, 
> i_payloadSize);
> +
> +        int canc = vlc_savecancel();
> +
> +        if ((i_ret < 0 && errno != EAGAIN) || i_ret == 0)
> +        {
> +            msg_Err(p_stream, "The connection to the Chromecast 
> died.");
> +            p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
> +            atomic_store(&p_sys->ab_error, true);
> +            break;
> +        }
> +
> +        if (b_msgReceived)
> +        {
> +            CastMessage msg;
> +            msg.ParseFromArray(p_sys->p_packet + PACKET_HEADER_LEN,
> i_payloadSize);
> +            processMessage(p_stream, msg);
> +            // Send the answer messages if there is any.
> +            if (!p_sys->messagesToSend.empty())
> +            {
> +                i_ret = sendMessages(p_stream);
> +                if ((i_ret < 0 && errno != EAGAIN) || i_ret == 0)
> +                {
> +                    msg_Err(p_stream, "The connection to the
> Chromecast died.");
> +                    p_sys->i_status = CHROMECAST_CONNECTION_DEAD;
> +                    atomic_store(&p_sys->ab_error, true);
> +                    break;
> +                }
> +            }
> +        }
> +        vlc_restorecancel(canc);
> +    }
> +
> +    return NULL;
> +}

-- 
Rémi Denis-Courmont



More information about the vlc-devel mailing list