[vlc-devel] [PATCH 3/3] 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
Thu Sep 11 20:28:04 CEST 2014
Le jeudi 11 septembre 2014, 17:41:18 Adrien Maglo a écrit :
> ---
> 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;
> +
> +// 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;
I think we've already been through this.
> + bool b_pingSent;
> + atomic_bool ab_error;
Dubious. What if the error is flagged after the other thread checks the flag?
> + 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);
> +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")
Mime and MIME are different things.
> +#define MIME_LONGTEXT N_("This sets the media mine type sent to the
> Chromecast.")
Ditto.
> +
> +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)
I can see the point in setting the receiver, but why is the sender not
automatically selected by the INET stack?
> +
> add_integer(SOUT_CFG_PREFIX "sender-port", SENDER_SOUT_PORT,
> SENDERPORT_TEXT, NULL, false)
Ditto.
> + 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();
> + 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);
> + const mtime_t deadline = mdate() + 6 * CLOCK_FREQ;
> + int i_ret = vlc_cond_timedwait(&p_sys->loadCommandCond,
> &p_sys->loadCommandMutex, deadline);
This is an anti-pattern that has been brought up too many times on this very
list to be tolerated any further.
> + 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);
What is 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];
4GiB on the stack does not seem like a good idea.
> + memcpy(p_data, (char *)&i_sizeNetwork, PACKET_HEADER_LEN);
Why the cast?
> + 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.
The comment does not match the code, or vice versa.
> + 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)
> +{
> + 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;
Either a jump is missing or the nonobvious code lacks comments.
> + }
> +
> + if (!authMessage.has_response()) {
> + msg_Err(p_stream, "Authentification message has no response
> field");
> + i_ret = -1;
Ditto.
> + }
> +
> + 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 != ""
> + && 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;
> +}
> +
> +
> +/**************************************************************************
> *** + * 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\"}";
> +
> + 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);
The anti-pattern again.
> +}
> +
> +
> +/**************************************************************************
> *** + * 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;
> +}
--
Rémi Denis-Courmont
http://www.remlab.net/
More information about the vlc-devel
mailing list