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

Hugo Beauzee-Luyssen hugo at beauzee.fr
Fri Sep 12 05:29:43 CEST 2014


On 09/11/2014 12:48 PM, Felix Abecassis wrote:
> 2014-09-11 17:41 GMT+02:00 Adrien Maglo <magsoft at videolan.org>:
>> ---
>>  configure.ac                           |  26 +-
>>  modules/stream_out/Modules.am          |  15 +
>>  modules/stream_out/chromecast/cast.cpp | 867 +++++++++++++++++++++++++++++++++
>>  3 files changed, 907 insertions(+), 1 deletion(-)
>>  create mode 100644 modules/stream_out/chromecast/cast.cpp
>>
>> diff --git a/configure.ac b/configure.ac
>> index d3d5517..9a69718 100644
>> --- a/configure.ac
>> +++ b/configure.ac
>> @@ -636,7 +636,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 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 ts remoteosd audiobargraph_a],[${SOCKET_LIBS}])
>>  ])
>>  AC_SUBST(SOCKET_LIBS)
>>
>> @@ -3718,6 +3718,30 @@ PKG_WITH_MODULES([CHROMAPRINT],[libchromaprint >= libchromaprint_version],
>>  m4_popdef([libchromaprint_version])
>>
>>  dnl
>> +dnl  Chromecast streaming support
>> +dnl
>> +m4_pushdef([protobuf_lite_version], 2.5.0)
>> +PKG_WITH_MODULES([CHROMECAST],[protobuf-lite >= protobuf_lite_version], [
>> +    AC_CHECK_PROGS(PROTOC, protoc)
>> +    AS_IF([test "${PROTOC}" = "protoc"], [
>> +        VLC_ADD_PLUGIN([stream_out_chromecast])
>> +        VLC_ADD_CXXFLAGS([stream_out_chromecast],[${CHROMECAST_CFLAGS}] [-I./chromecast])
>> +        VLC_ADD_LIBS([stream_out_chromecast],[${CHROMECAST_LIBS}])
>> +        build_chromecast="yes"
>> +    ], [
>> +    AC_MSG_ERROR(protoc compiler needed for [chromecast] was not found)
>> +    ])
>> +], [
>> +    AS_IF([test "${enable_chromecast}" = "yes"],
>> +        AC_MSG_ERROR(Library [protobuf-lite >= protobuf_lite_version] needed for [chromecast] was not found),
>> +        AC_MSG_WARN(Library [protobuf-lite >= protobuf_lite_version] needed for [chromecast] was not found)
>> +    )
>> +    enable_chromecast="no"
>> +], [(Chromecast streaming support)], [auto])
>> +AM_CONDITIONAL([BUILD_CHROMECAST], [test "${build_chromecast}" = "yes"])
>> +m4_popdef([protobuf_lite_version])
>> +
>> +dnl
>>  dnl  Interface plugins
>>  dnl
>>
>> diff --git a/modules/stream_out/Modules.am b/modules/stream_out/Modules.am
>> index d307c34..4d78f08 100644
>> --- a/modules/stream_out/Modules.am
>> +++ b/modules/stream_out/Modules.am
>> @@ -69,3 +69,18 @@ BUILT_SOURCES += dummy.cpp
>>
>>  dummy.cpp:
>>         touch dummy.cpp
>> +
>> +# Chromecast plugin
>> +SUFFIXES += .proto .pb.cc
>> +BUILT_SOURCES += chromecast/cast_channel.pb.cc
>> +
>> +%.pb.cc: %.proto
>> +       $(PROTOC) --cpp_out=. -I$(srcdir) $<
>> +
>> +SOURCES_stream_out_chromecast = chromecast/cast_channel.proto chromecast/cast.cpp \
>> +                                ../misc/webservices/json.h ../misc/webservices/json.c
>> +nodist_libstream_out_chromecast_plugin_la_SOURCES = chromecast/cast_channel.pb.cc
>> +
>> +if BUILD_CHROMECAST
>> +stream_out_LTLIBRARIES += libstream_out_chromecast_plugin.la
>> +endif
>> diff --git a/modules/stream_out/chromecast/cast.cpp b/modules/stream_out/chromecast/cast.cpp
>> new file mode 100644
>> index 0000000..b23cf93
>> --- /dev/null
>> +++ b/modules/stream_out/chromecast/cast.cpp
>> @@ -0,0 +1,867 @@
>> +/*****************************************************************************
>> + * 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 "../../misc/webservices/json.h"
>> +
>> +using namespace google::protobuf::io;
>> +using namespace std;
>> +using namespace castchannel;
> 
> I'm not a big fan of "using namespace", especially since you're using
> atomic_store which exists in C++11 as std::atomic_store, so this is
> confusing.
> 
>> +
>> +// 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_out(NULL)
>> +    {
>> +        atomic_init(&ab_error, false);
>> +    }
>> +
>> +    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;
>> +
>> +    sout_stream_t *p_out;
>> +};
>> +
>> +// Media player Chromecast app id
>> +#define APP_ID "CC1AD845" // Default media player
>> +
>> +#define CHROMECAST_CONTROL_PORT 8009 // Chromecast control port
>> +#define SENDER_SOUT_PORT        8010
>> +
>> +#define SOUT_CFG_PREFIX "sout-chromecast-"
>> +
>> +
>> +/*****************************************************************************
>> + * Local prototypes
>> + *****************************************************************************/
>> +static int Open(vlc_object_t *);
>> +static void Close(vlc_object_t *);
>> +static void clean(sout_stream_sys_t *p_sys);
> 
> "Clean" might make more sense with "Open" and "Close".
> 
>> +static int connectChromecast(sout_stream_t *p_stream, char *psz_ipChromecast);
>> +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 const char *const ppsz_sout_options[] = {
>> +    "receiver-ip", "sender-ip", "sender-port", "mux", "mime", NULL
>> +};
>> +
>> +/*****************************************************************************
>> + * Module descriptor
>> + *****************************************************************************/
>> +
>> +#define RECEIVER_IP_TEXT N_("Chromecast IP address")
>> +#define RECEIVER_IP_LONGTEXT N_("This sets the ip adress of the Chromecast receiver.")
>> +#define SENDER_IP_TEXT N_("Sender IP address")
>> +#define SENDER_IP_LONGTEXT N_("This sets the ip address that the Chromecast" \
>> +                              "will use to connect to HTTP stream.")
>> +#define SENDERPORT_TEXT N_("Sender port to use for stream data")
>> +#define MUX_TEXT N_("Muxer")
>> +#define MUX_LONGTEXT N_("This sets the muxer used to stream to the Chromecast.")
>> +#define MIME_TEXT N_("Mime type")
>> +#define MIME_LONGTEXT N_("This sets the media mine type sent to the Chromecast.")
>> +
>> +vlc_module_begin ()
>> +
>> +    set_shortname(N_("Chromecast"))
>> +    set_description(N_("Chromecast stream output"))
>> +    set_capability("sout stream", 0)
>> +    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_integer(SOUT_CFG_PREFIX "sender-port", SENDER_SOUT_PORT, SENDERPORT_TEXT, NULL, 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)
>> +
>> +vlc_module_end ()
>> +
>> +
>> +/*****************************************************************************
>> + * Sout callbacks
>> + *****************************************************************************/
>> +static sout_stream_id_sys_t *Add(sout_stream_t *p_stream, es_format_t *p_fmt)
>> +{
>> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
>> +    return p_sys->p_out->pf_add(p_sys->p_out, p_fmt);
>> +}
>> +
>> +
>> +static int Del(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
>> +{
>> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
>> +    return p_sys->p_out->pf_del(p_sys->p_out, id);
>> +}
>> +
>> +
>> +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 p_sys->p_out->pf_send(p_sys->p_out, 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();
> 
> You don't need the "()" I think.
> 
>> +    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_ipLocal = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "sender-ip");
>> +    if (psz_ipLocal == NULL)
>> +    {
>> +        msg_Err(p_stream, "No sender IP provided");
>> +        clean(p_sys);
>> +        return VLC_EGENERIC;
>> +    }
>> +    p_sys->serverIP = psz_ipLocal;
>> +    free(psz_ipLocal);
>> +
>> +    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");
>> +        clean(p_sys);
>> +        return VLC_EGENERIC;
>> +    }
>> +
>> +    p_sys->i_sock_fd = connectChromecast(p_stream, psz_ipChromecast);
>> +    free(psz_ipChromecast);
>> +    if (p_sys->i_sock_fd < 0)
>> +    {
>> +        msg_Err(p_stream, "Could not connect the Chromecast");
>> +        clean(p_sys);
>> +        return VLC_EGENERIC;
>> +    }
>> +
>> +    char *psz_mux = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mux");
>> +    char *psz_chain = NULL;
>> +    int i_bytes = asprintf(&psz_chain, "http{dst=:%u/stream,mux=%s}",
>> +                           (unsigned)var_InheritInteger(p_stream, SOUT_CFG_PREFIX"sender-port"),
>> +                           psz_mux);
>> +    free(psz_mux);
>> +    if (i_bytes < 0)
>> +    {
>> +        clean(p_sys);
>> +        return VLC_EGENERIC;
>> +    }
>> +
>> +    p_sys->p_out = sout_StreamChainNew(p_stream->p_sout, psz_chain, NULL, NULL);
>> +    free(psz_chain);
>> +    if (p_sys->p_out == NULL)
>> +    {
>> +        clean(p_sys);
>> +        return VLC_EGENERIC;
>> +    }
>> +
>> +    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");
>> +        clean(p_sys);
>> +        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);

I'd suggest you use the vlc_mutex_locker RAII helper.
Same goes for all mutex locks.

>> +    const mtime_t deadline = mdate() + 6 * CLOCK_FREQ;
>> +    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);
>> +        vlc_cancel(p_sys->chromecastThread);
>> +        vlc_join(p_sys->chromecastThread, NULL);
>> +        clean(p_sys);
>> +        return VLC_EGENERIC;
>> +    }
>> +    vlc_mutex_unlock(&p_sys->loadCommandMutex);
>> +
>> +    /* 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;
>> +    p_stream->pf_send   = Send;
>> +
>> +    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);
>> +        vlc_join(p_sys->chromecastThread, NULL);
>> +    }
>> +
>> +    switch (p_sys->i_status)
>> +    {
>> +    case CHROMECAST_APP_STARTED:
>> +        // Generate the close messages.
>> +        msgClose(p_stream, p_sys->appTransportId);
>> +    case CHROMECAST_AUTHENTICATED:
>> +        msgClose(p_stream, "receiver-0");
>> +        // Send the just added close messages.
>> +        sendMessages(p_stream);
>> +    case CHROMECAST_TLS_CONNECTED:
>> +        disconnectChromecast(p_stream);
>> +        p_sys->i_status = CHROMECAST_DISCONNECTED;
>> +    default:
>> +        break;
>> +    }
>> +
>> +    clean(p_sys);
>> +}
>> +
>> +
>> +/**
>> + * @brief Clean and release the variables in a sout_stream_sys_t structure
>> + */
>> +static void clean(sout_stream_sys_t *p_sys)
>> +{
>> +    if (p_sys->p_out)
>> +    {
>> +        vlc_mutex_destroy(&p_sys->loadCommandMutex);
>> +        vlc_cond_destroy(&p_sys->loadCommandCond);
>> +        sout_StreamChainDelete(p_sys->p_out, p_sys->p_out);
>> +    }
>> +
>> +    delete p_sys;
>> +}
>> +
>> +
>> +/**
>> + * @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, char *psz_ipChromecast)
>> +{
>> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
>> +    int fd = net_ConnectTCP(p_stream, psz_ipChromecast, CHROMECAST_CONTROL_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 = vlc_tls_ClientSessionCreate(p_sys->p_creds, fd, psz_ipChromecast,
>> +                                               "tcps", NULL, NULL);
>> +
>> +    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)
>> +{
>> +    // Use here only C linkage and POD types as this function is a cancelation point.
>> +    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;
>> +
>> +    /* 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, 6000) == 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)

Could msg be const? It seems every operation is read only.

>> +{
>> +    int i_ret = 0;
>> +    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());
>> +            i_ret = -1;
>> +        }
>> +
>> +        if (!authMessage.has_response()) {
>> +            msg_Err(p_stream, "Authentification message has no response field");
>> +            i_ret = -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 *p_data = json_parse(msg.payload_utf8().c_str());
>> +        string type((*p_data)["type"]);
>> +
>> +        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");
>> +            i_ret = -1;
>> +        }
>> +
>> +        json_value_free(p_data);
>> +    }
>> +    else if (namespace_ == "urn:x-cast:com.google.cast.receiver")
>> +    {
>> +        json_value *p_data = json_parse(msg.payload_utf8().c_str());
>> +        string type((*p_data)["type"]);
>> +
>> +        if (type == "RECEIVER_STATUS")
>> +        {
>> +            json_value applications = (*p_data)["status"]["applications"];
>> +            for (unsigned i = 0; i < applications.u.array.length; ++i)
>> +            {
>> +                string appId(applications[i]["appId"]);
>> +                if (appId == APP_ID)
>> +                    p_sys->appTransportId = string(applications[i]["transportId"]);
>> +            }
>> +            if (p_sys->appTransportId != ""
> 
> Use .empty() maybe?
> 
>> +                && 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());
>> +            i_ret = -1;
>> +        }
>> +
>> +        json_value_free(p_data);
>> +    }
>> +    else if (namespace_ == "urn:x-cast:com.google.cast.media")
>> +    {
>> +        json_value *p_data = json_parse(msg.payload_utf8().c_str());
>> +        string type((*p_data)["type"]);
>> +
>> +        if (type == "MEDIA_STATUS")
>> +        {
>> +            json_value status = (*p_data)["status"];
>> +            msg_Dbg(p_stream, "Player state: %s",
>> +                    status[0]["playerState"].operator const char *());
>> +        }
>> +        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());
>> +            i_ret = -1;
>> +        }
>> +    }
>> +    else
>> +    {
>> +        msg_Err(p_stream, "Unknown namespace: %s", msg.namespace_().c_str());
>> +        i_ret = -1;
>> +    }
>> +
>> +    return i_ret;
>> +}
>> +
>> +
>> +/**
>> + * @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;
>> +}
> 
> Is it ok to copy return CastMessage? What's the size of the struct?

There's a very high probability that RVO will kick in, so this is not
something I'd worry too much about. Also, once we switch to C++11 (any
plan for that btw?) we'll be able to move it anyway.

> 
>> +
>> +
>> +/*****************************************************************************
>> + * 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;
>> +
>> +    stringstream ss;
>> +    ss << "{\"type\":\"PING\"}";

Do you need a stringstream for this?
Same question about later functions.

>> +
>> +    CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.heartbeat",
>> +        CastMessage_PayloadType_STRING, ss.str());
>> +
>> +    p_sys->messagesToSend.push(msg);
>> +}
>> +
>> +
>> +static void msgPong(sout_stream_t *p_stream)
>> +{
>> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
>> +
>> +    stringstream ss;
>> +    ss << "{\"type\":\"PONG\"}";
>> +
>> +    CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.heartbeat",
>> +        CastMessage_PayloadType_STRING, ss.str());
>> +
>> +    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;
>> +
>> +    stringstream ss;
>> +    ss << "{\"type\":\"CONNECT\"}";
>> +
>> +    CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.connection",
>> +        CastMessage_PayloadType_STRING, ss.str(), 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;
>> +
>> +    stringstream ss;
>> +    ss << "{\"type\":\"CLOSE\"}";
>> +
>> +    CastMessage msg = buildMessage("urn:x-cast:com.google.cast.tp.connection",
>> +        CastMessage_PayloadType_STRING, ss.str(), destinationId);
>> +
>> +    p_sys->messagesToSend.push(msg);
>> +}
>> +
>> +
>> +static void msgLaunch(sout_stream_t *p_stream)
>> +{
>> +    sout_stream_sys_t *p_sys = p_stream->p_sys;
>> +
>> +    stringstream ss;
>> +    ss << "{\"type\":\"LAUNCH\","
>> +       <<  "\"appId\":\"" << APP_ID << "\","
>> +       <<  "\"requestId\":" << p_stream->p_sys->i_requestId++ << "}";
>> +
>> +    CastMessage msg = buildMessage("urn:x-cast:com.google.cast.receiver",
>> +        CastMessage_PayloadType_STRING, ss.str());
>> +
>> +    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");
>> +
>> +    stringstream ss;
>> +    ss << "{\"type\":\"LOAD\","
>> +       <<  "\"media\":{\"contentId\":\"http://" << p_sys->serverIP << ":"
>> +           << var_InheritInteger(p_stream, SOUT_CFG_PREFIX"sender-port")
>> +           << "/stream\","
>> +       <<             "\"streamType\":\"BUFFERED\","
>> +       <<             "\"contentType\":\"" << string(psz_mime) << "\"},"
>> +       <<  "\"requestId\":" << p_stream->p_sys->i_requestId++ << "}";
>> +
>> +    free(psz_mime);
>> +
>> +    CastMessage msg = buildMessage("urn:x-cast:com.google.cast.media",
>> +        CastMessage_PayloadType_STRING, ss.str(), p_sys->appTransportId);
>> +
>> +    p_sys->messagesToSend.push(msg);
>> +
>> +    // Unlock the sout thread.
>> +    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;
>> +
>> +    int canc = vlc_savecancel();
>> +    // Not cancellation-safe part.
>> +    msgAuth(p_stream);
>> +    sendMessages(p_stream);
>> +    vlc_restorecancel(canc);
>> +
>> +    while (1)
>> +    {
>> +        bool b_msgReceived = false;
>> +        uint32_t i_payloadSize = 0;
>> +        int i_ret = recvPacket(p_stream, b_msgReceived, i_payloadSize);
>> +
>> +        canc = vlc_savecancel();
>> +        // Not cancellation-safe part.
>> +
>> +        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;
>> +}
>> --
>> 1.9.1
>>
>> _______________________________________________
>> vlc-devel mailing list
>> To unsubscribe or modify your subscription options:
>> https://mailman.videolan.org/listinfo/vlc-devel
> 
> 
> 

Regards,

-- 
Hugo Beauzée-Luyssen
hugo at beauzee.fr
+1-650-430-3672

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: OpenPGP digital signature
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20140911/42611afb/attachment.sig>


More information about the vlc-devel mailing list