2016-01-17 14:59:16 -05:00
/*
2019-02-28 17:19:05 -05:00
* Copyright ( C ) 2015 - 2019 Espen Jürgensen < espenjurgensen @ gmail . com >
2016-01-17 14:59:16 -05:00
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 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 General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <errno.h>
# include <sys/types.h>
# include <sys/socket.h>
# include <arpa/inet.h>
2017-01-06 03:44:18 -05:00
# include <net/if.h>
# include <netinet/in.h>
2016-01-31 12:42:32 -05:00
# include <ifaddrs.h>
2016-01-17 14:59:16 -05:00
# include <unistd.h>
2016-01-26 15:49:32 -05:00
# include <fcntl.h>
2017-01-06 03:44:18 -05:00
# ifdef HAVE_ENDIAN_H
# include <endian.h>
# elif defined(HAVE_SYS_ENDIAN_H)
# include <sys / endian.h>
2017-01-14 00:56:43 -05:00
# elif defined(HAVE_LIBKERN_OSBYTEORDER_H)
# include <libkern/OSByteOrder.h>
# define htobe32(x) OSSwapHostToBigInt32(x)
# define be32toh(x) OSSwapBigToHostInt32(x)
2017-01-06 03:44:18 -05:00
# endif
2016-01-17 14:59:16 -05:00
# include <gnutls/gnutls.h>
# include <event2/event.h>
2017-01-06 03:44:18 -05:00
# include <json.h>
2016-03-06 15:33:49 -05:00
2016-01-30 17:01:01 -05:00
# include "conffile.h"
2016-01-26 15:49:32 -05:00
# include "mdns.h"
2019-02-28 17:19:05 -05:00
# include "transcode.h"
2016-01-17 14:59:16 -05:00
# include "logger.h"
2016-01-26 15:49:32 -05:00
# include "player.h"
2019-02-28 17:19:05 -05:00
# include "rtp_common.h"
2016-01-26 15:49:32 -05:00
# include "outputs.h"
2016-02-06 07:38:43 -05:00
# ifdef HAVE_PROTOBUF_OLD
# include "cast_channel.v0.pb-c.h"
# else
2016-01-17 14:59:16 -05:00
# include "cast_channel.pb-c.h"
2016-02-06 07:38:43 -05:00
# endif
2016-01-17 14:59:16 -05:00
// Number of bytes to request from TLS connection
# define MAX_BUF 4096
// CA file location (not very portable...?)
# define CAFILE " / etc / ssl / certs / ca-certificates.crt"
2016-02-03 16:54:18 -05:00
// Seconds without a heartbeat from the Chromecast before we close the session
2019-02-28 17:19:05 -05:00
//#define HEARTBEAT_TIMEOUT 30
2016-02-03 16:54:18 -05:00
// Seconds to wait for a reply before making the callback requested by caller
# define REPLY_TIMEOUT 5
2019-02-28 17:19:05 -05:00
// ID of the audio mirroring app used by Chrome (Google Home)
2019-03-26 18:13:09 -04:00
# define CAST_APP_ID "85CDB22F"
2019-02-28 17:19:05 -05:00
// Old mirroring app (Chromecast)
2019-03-26 18:13:09 -04:00
# define CAST_APP_ID_OLD "0F5096E8"
2016-02-02 06:02:14 -05:00
2016-01-17 14:59:16 -05:00
// Namespaces
# define NS_CONNECTION "urn:x-cast:com.google.cast.tp.connection"
# define NS_RECEIVER "urn:x-cast:com.google.cast.receiver"
# define NS_HEARTBEAT "urn:x-cast:com.google.cast.tp.heartbeat"
# define NS_MEDIA "urn:x-cast:com.google.cast.media"
2019-02-28 17:19:05 -05:00
# define NS_WEBRTC "urn:x-cast:com.google.cast.webrtc"
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
# define USE_TRANSPORT_ID (1 << 1)
# define USE_REQUEST_ID (1 << 2)
# define USE_REQUEST_ID_ONLY (1 << 3)
2016-01-30 17:01:01 -05:00
# define CALLBACK_REGISTER_SIZE 32
2016-01-26 15:49:32 -05:00
2019-02-28 17:19:05 -05:00
// Chromium will send OPUS encoded 10 ms packets (48kHz), about 120 bytes. We
// use a 20 ms packet, so 50 pkts/sec, because that's the default for ffmpeg.
// A 20 ms audio packet at 48000 kHz makes this number 48000 * (20 / 1000)
# define CAST_SAMPLES_PER_PACKET 960
# define CAST_QUALITY_SAMPLE_RATE_DEFAULT 48000
# define CAST_QUALITY_BITS_PER_SAMPLE_DEFAULT 16
# define CAST_QUALITY_CHANNELS_DEFAULT 2
/* Notes
* OFFER / ANSWER < - webrtc
* RTCP / RTP
* XR custom receiver report
* Control and data on same UDP connection
* OPUS encoded
*/
2016-02-03 16:54:18 -05:00
//#define DEBUG_CONNECTION 1
2016-01-26 15:49:32 -05:00
union sockaddr_all
{
struct sockaddr_in sin ;
struct sockaddr_in6 sin6 ;
struct sockaddr sa ;
struct sockaddr_storage ss ;
} ;
struct cast_session ;
struct cast_msg_payload ;
2019-02-28 17:19:05 -05:00
// See cast_packet_header_make()
# define CAST_HEADER_SIZE 11
# define CAST_PACKET_BUFFER_SIZE 1000
static struct encode_ctx * cast_encode_ctx ;
static struct evbuffer * cast_encoded_data ;
2016-01-26 15:49:32 -05:00
typedef void ( * cast_reply_cb ) ( struct cast_session * cs , struct cast_msg_payload * payload ) ;
2016-02-02 06:02:14 -05:00
// Session is starting up
2016-02-03 16:54:18 -05:00
# define CAST_STATE_F_STARTUP (1 << 13)
2016-02-02 06:02:14 -05:00
// The default receiver app is ready
2016-02-02 16:37:08 -05:00
# define CAST_STATE_F_MEDIA_CONNECTED (1 << 14)
2016-02-02 06:02:14 -05:00
// Media is loaded in the receiver app
2016-02-02 16:37:08 -05:00
# define CAST_STATE_F_MEDIA_LOADED (1 << 15)
// Media is playing in the receiver app
2019-02-28 17:19:05 -05:00
# define CAST_STATE_F_MEDIA_STREAMING (1 << 16)
2016-02-02 06:02:14 -05:00
// Beware, the order of this enum has meaning
enum cast_state
{
// Something bad happened during a session
2016-08-12 18:05:00 -04:00
CAST_STATE_FAILED = 0 ,
2016-02-02 06:02:14 -05:00
// No session allocated
2016-08-12 18:05:00 -04:00
CAST_STATE_NONE = 1 ,
2016-02-02 06:02:14 -05:00
// Session allocated, but no connection
CAST_STATE_DISCONNECTED = CAST_STATE_F_STARTUP | 0x01 ,
// TCP connect, TLS handshake, CONNECT and GET_STATUS request
CAST_STATE_CONNECTED = CAST_STATE_F_STARTUP | 0x02 ,
2019-02-28 17:19:05 -05:00
// Receiver app has been launched
2016-02-02 06:02:14 -05:00
CAST_STATE_MEDIA_LAUNCHED = CAST_STATE_F_STARTUP | 0x03 ,
2019-02-28 17:19:05 -05:00
// CONNECT, GET_STATUS and OFFER made to receiver app
2016-02-02 06:02:14 -05:00
CAST_STATE_MEDIA_CONNECTED = CAST_STATE_F_MEDIA_CONNECTED ,
2019-02-28 17:19:05 -05:00
// After OFFER
CAST_STATE_MEDIA_STREAMING = CAST_STATE_F_MEDIA_CONNECTED | CAST_STATE_F_MEDIA_STREAMING ,
} ;
struct cast_master_session
{
struct evbuffer * evbuf ;
int evbuf_samples ;
struct rtp_session * rtp_session ;
struct media_quality quality ;
uint8_t * rawbuf ;
size_t rawbuf_size ;
int samples_per_packet ;
// Number of samples that we tell the output to buffer (this will mean that
// the position that we send in the sync packages are offset by this amount
// compared to the rtptimes of the corresponding RTP packages we are sending)
int output_buffer_samples ;
2016-02-02 06:02:14 -05:00
} ;
2016-01-17 14:59:16 -05:00
struct cast_session
{
2019-02-28 17:19:05 -05:00
uint64_t device_id ;
int callback_id ;
struct cast_master_session * master_session ;
2016-02-02 06:02:14 -05:00
// Current state
enum cast_state state ;
// Used to register a target state if we are transitioning from one to another
enum cast_state wanted_state ;
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
// Connection fd and session, and listener event
2019-02-28 17:19:05 -05:00
int64_t server_fd ; // Use int64 so we can cast in gnutls_transport_set_ptr()
2016-01-17 14:59:16 -05:00
gnutls_session_t tls_session ;
2016-01-26 15:49:32 -05:00
struct event * ev ;
2016-01-17 14:59:16 -05:00
char * devname ;
char * address ;
2019-02-28 17:19:05 -05:00
int family ;
2016-01-17 14:59:16 -05:00
unsigned short port ;
2016-02-02 06:02:14 -05:00
// ChromeCast uses a float between 0 - 1
2016-01-29 18:36:05 -05:00
float volume ;
2016-01-17 14:59:16 -05:00
2019-02-28 17:19:05 -05:00
uint32_t ssrc_id ;
2016-01-31 12:42:32 -05:00
// IP address URL of forked-daapd's mp3 stream
char stream_url [ 128 ] ;
2016-01-26 15:49:32 -05:00
// Outgoing request which have the USE_REQUEST_ID flag get a new id, and a
// callback is registered. The callback is called when an incoming message
2016-02-03 16:54:18 -05:00
// from the peer with that request id arrives. If nothing arrives within
// REPLY_TIMEOUT we make the callback with a NULL payload pointer.
2016-01-26 15:49:32 -05:00
int request_id ;
cast_reply_cb callback_register [ CALLBACK_REGISTER_SIZE ] ;
2016-02-03 16:54:18 -05:00
struct event * reply_timeout ;
2016-01-26 15:49:32 -05:00
2016-08-01 03:05:09 -04:00
// This is used to work around a bug where no response is given by the device.
// For certain requests, we will then retry, e.g. by checking status. We
// register our retry so that we on only retry once.
int retry ;
2019-02-28 17:19:05 -05:00
// Session info from the Chromecast
2016-01-26 15:49:32 -05:00
char * transport_id ;
char * session_id ;
2016-01-31 12:42:32 -05:00
int media_session_id ;
2016-01-17 14:59:16 -05:00
2019-02-28 17:19:05 -05:00
int udp_fd ;
unsigned short udp_port ;
2016-01-17 14:59:16 -05:00
struct cast_session * next ;
} ;
2016-01-26 15:49:32 -05:00
enum cast_msg_types
2016-01-17 14:59:16 -05:00
{
UNKNOWN ,
PING ,
PONG ,
CONNECT ,
CLOSE ,
GET_STATUS ,
2016-01-26 15:49:32 -05:00
RECEIVER_STATUS ,
2016-01-17 14:59:16 -05:00
LAUNCH ,
2019-03-26 18:13:09 -04:00
LAUNCH_OLD ,
LAUNCH_ERROR ,
2016-02-02 06:02:14 -05:00
STOP ,
2016-01-17 14:59:16 -05:00
MEDIA_CONNECT ,
2016-02-02 06:02:14 -05:00
MEDIA_CLOSE ,
2019-02-28 17:19:05 -05:00
OFFER ,
ANSWER ,
2016-01-17 14:59:16 -05:00
MEDIA_GET_STATUS ,
2016-01-26 15:49:32 -05:00
MEDIA_STATUS ,
MEDIA_LOAD ,
2016-01-31 12:42:32 -05:00
MEDIA_PLAY ,
MEDIA_PAUSE ,
2016-01-17 14:59:16 -05:00
MEDIA_STOP ,
2016-01-26 15:49:32 -05:00
MEDIA_LOAD_FAILED ,
2016-02-02 06:02:14 -05:00
MEDIA_LOAD_CANCELLED ,
2016-01-29 18:36:05 -05:00
SET_VOLUME ,
2019-03-26 18:13:09 -04:00
PRESENTATION ,
2016-01-26 15:49:32 -05:00
} ;
struct cast_msg_basic
{
enum cast_msg_types type ;
char * tag ; // Used for looking up incoming message type
char * namespace ;
char * payload ;
int flags ;
} ;
struct cast_msg_payload
{
enum cast_msg_types type ;
int request_id ;
2016-02-02 06:02:14 -05:00
const char * app_id ;
2016-01-26 15:49:32 -05:00
const char * session_id ;
const char * transport_id ;
2016-02-02 16:37:08 -05:00
const char * player_state ;
2019-02-28 17:19:05 -05:00
const char * result ;
2016-01-31 12:42:32 -05:00
int media_session_id ;
2019-02-28 17:19:05 -05:00
unsigned short udp_port ;
2016-01-17 14:59:16 -05:00
} ;
2016-01-26 15:49:32 -05:00
// Array of the cast messages that we use. Must be in sync with cast_msg_types.
struct cast_msg_basic cast_msg [ ] =
{
{
. type = UNKNOWN ,
. namespace = " " ,
. payload = " " ,
} ,
{
. type = PING ,
. tag = " PING " ,
. namespace = NS_HEARTBEAT ,
. payload = " {'type':'PING'} " ,
} ,
{
. type = PONG ,
. tag = " PONG " ,
. namespace = NS_HEARTBEAT ,
. payload = " {'type':'PONG'} " ,
} ,
{
. type = CONNECT ,
. namespace = NS_CONNECTION ,
. payload = " {'type':'CONNECT'} " ,
// msg.payload_utf8 = "{\"origin\":{},\"userAgent\":\"forked-daapd\",\"type\":\"CONNECT\",\"senderInfo\":{\"browserVersion\":\"44.0.2403.30\",\"version\":\"15.605.1.3\",\"connectionType\":1,\"platform\":4,\"sdkType\":2,\"systemVersion\":\"Macintosh; Intel Mac OS X10_10_3\"}}";
} ,
{
. type = CLOSE ,
. tag = " CLOSE " ,
. namespace = NS_CONNECTION ,
. payload = " {'type':'CLOSE'} " ,
} ,
{
. type = GET_STATUS ,
. namespace = NS_RECEIVER ,
. payload = " {'type':'GET_STATUS','requestId':%d} " ,
. flags = USE_REQUEST_ID_ONLY ,
} ,
{
. type = RECEIVER_STATUS ,
. tag = " RECEIVER_STATUS " ,
} ,
{
. type = LAUNCH ,
. namespace = NS_RECEIVER ,
2016-02-02 06:02:14 -05:00
. payload = " {'type':'LAUNCH','requestId':%d,'appId':' " CAST_APP_ID " '} " ,
2016-01-26 15:49:32 -05:00
. flags = USE_REQUEST_ID_ONLY ,
} ,
2019-03-26 18:13:09 -04:00
{
. type = LAUNCH_OLD ,
. namespace = NS_RECEIVER ,
. payload = " {'type':'LAUNCH','requestId':%d,'appId':' " CAST_APP_ID_OLD " '} " ,
. flags = USE_REQUEST_ID_ONLY ,
} ,
{
. type = LAUNCH_ERROR ,
. tag = " LAUNCH_ERROR " ,
} ,
2016-02-02 06:02:14 -05:00
{
. type = STOP ,
. namespace = NS_RECEIVER ,
. payload = " {'type':'STOP','sessionId':'%s','requestId':%d} " ,
. flags = USE_REQUEST_ID ,
} ,
2016-01-26 15:49:32 -05:00
{
. type = MEDIA_CONNECT ,
. namespace = NS_CONNECTION ,
. payload = " {'type':'CONNECT'} " ,
. flags = USE_TRANSPORT_ID ,
} ,
2016-02-02 06:02:14 -05:00
{
. type = MEDIA_CLOSE ,
. namespace = NS_CONNECTION ,
. payload = " {'type':'CLOSE'} " ,
. flags = USE_TRANSPORT_ID ,
} ,
2019-02-28 17:19:05 -05:00
{
. type = OFFER ,
. namespace = NS_WEBRTC ,
// codecName can be aac or opus, ssrc should be random
// We don't set 'aesKey' and 'aesIvMask'
// sampleRate seems to be ignored
2019-03-01 18:30:43 -05:00
// storeTime unknown meaning - perhaps size of buffer?
// targetDelay - should be RTP delay in ms, but doesn't seem to change anything?
2019-02-28 17:19:05 -05:00
. payload = " {'type':'OFFER','seqNum':%d,'offer':{'castMode':'mirroring','supportedStreams':[{'index':0,'type':'audio_source','codecName':'opus','rtpProfile':'cast','rtpPayloadType':127,'ssrc':%d,'storeTime':400,'targetDelay':400,'bitRate':128000,'sampleRate':48000,'timeBase':'1/48000','channels':2,'receiverRtcpEventLog':false}]}} " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
{
. type = ANSWER ,
. tag = " ANSWER " ,
} ,
2016-01-26 15:49:32 -05:00
{
. type = MEDIA_GET_STATUS ,
. namespace = NS_MEDIA ,
. payload = " {'type':'GET_STATUS','requestId':%d} " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID_ONLY ,
} ,
{
. type = MEDIA_STATUS ,
. tag = " MEDIA_STATUS " ,
} ,
{
. type = MEDIA_LOAD ,
. namespace = NS_MEDIA ,
. payload = " {'currentTime':0,'media':{'contentId':'%s','streamType':'LIVE','contentType':'audio/mp3'},'customData':{},'sessionId':'%s','requestId':%d,'type':'LOAD','autoplay':1} " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
2016-01-31 12:42:32 -05:00
{
. type = MEDIA_PLAY ,
. namespace = NS_MEDIA ,
. payload = " {'mediaSessionId':%d,'sessionId':'%s','type':'PLAY','requestId':%d} " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
{
. type = MEDIA_PAUSE ,
. namespace = NS_MEDIA ,
. payload = " {'mediaSessionId':%d,'sessionId':'%s','type':'PAUSE','requestId':%d} " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
2016-01-26 15:49:32 -05:00
{
. type = MEDIA_STOP ,
2016-01-31 12:42:32 -05:00
. namespace = NS_MEDIA ,
. payload = " {'mediaSessionId':%d,'sessionId':'%s','type':'STOP','requestId':%d} " ,
2016-01-26 15:49:32 -05:00
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
{
. type = MEDIA_LOAD_FAILED ,
. tag = " LOAD_FAILED " ,
} ,
2016-02-02 06:02:14 -05:00
{
. type = MEDIA_LOAD_CANCELLED ,
. tag = " LOAD_CANCELLED " ,
} ,
2016-01-29 18:36:05 -05:00
{
. type = SET_VOLUME ,
. namespace = NS_RECEIVER ,
. payload = " {'type':'SET_VOLUME','volume':{'level':%.2f,'muted':0},'requestId':%d} " ,
. flags = USE_REQUEST_ID ,
} ,
2019-03-26 18:13:09 -04:00
{
. type = PRESENTATION ,
. namespace = NS_WEBRTC ,
. payload = " {'type':'PRESENTATION','sessionId':'%s',seqnum:%d,'title':'forked-daapd','icons':[{'url':'http://www.gyfgafguf.dk/images/fugl.jpg'}] } " ,
. flags = USE_TRANSPORT_ID | USE_REQUEST_ID ,
} ,
2016-01-26 15:49:32 -05:00
{
. type = 0 ,
} ,
} ;
2016-01-17 14:59:16 -05:00
/* From player.c */
extern struct event_base * evbase_player ;
/* Globals */
static gnutls_certificate_credentials_t tls_credentials ;
2019-02-28 17:19:05 -05:00
static struct cast_session * cast_sessions ;
static struct cast_master_session * cast_master_session ;
//static struct timeval heartbeat_timeout = { HEARTBEAT_TIMEOUT, 0 };
2016-02-03 16:54:18 -05:00
static struct timeval reply_timeout = { REPLY_TIMEOUT , 0 } ;
2019-02-28 17:19:05 -05:00
static struct media_quality cast_quality_default = { CAST_QUALITY_SAMPLE_RATE_DEFAULT , CAST_QUALITY_BITS_PER_SAMPLE_DEFAULT , CAST_QUALITY_CHANNELS_DEFAULT } ;
2016-02-03 16:54:18 -05:00
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
/* ------------------------------- MISC HELPERS ----------------------------- */
2016-01-17 14:59:16 -05:00
static int
2019-02-28 17:19:05 -05:00
cast_connect ( const char * address , unsigned short port , int family , int type )
2016-01-17 14:59:16 -05:00
{
union sockaddr_all sa ;
int fd ;
int len ;
int ret ;
2019-02-28 17:19:05 -05:00
DPRINTF ( E_DBG , L_CAST , " Connecting to %s (family=%d), port %u \n " , address , family , port ) ;
2016-01-26 15:49:32 -05:00
// TODO Open non-block right away so we don't block the player while connecting
// and during TLS handshake (we would probably need to introduce a deferredev)
2017-01-06 03:44:18 -05:00
# ifdef SOCK_CLOEXEC
2019-02-28 17:19:05 -05:00
fd = socket ( family , type | SOCK_CLOEXEC , 0 ) ;
2017-01-06 03:44:18 -05:00
# else
2019-02-28 17:19:05 -05:00
fd = socket ( family , type , 0 ) ;
2017-01-06 03:44:18 -05:00
# endif
2016-01-17 14:59:16 -05:00
if ( fd < 0 )
{
DPRINTF ( E_LOG , L_CAST , " Could not create socket: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
switch ( family )
{
case AF_INET :
sa . sin . sin_port = htons ( port ) ;
ret = inet_pton ( AF_INET , address , & sa . sin . sin_addr ) ;
len = sizeof ( sa . sin ) ;
break ;
case AF_INET6 :
sa . sin6 . sin6_port = htons ( port ) ;
ret = inet_pton ( AF_INET6 , address , & sa . sin6 . sin6_addr ) ;
len = sizeof ( sa . sin6 ) ;
break ;
default :
DPRINTF ( E_WARN , L_CAST , " Unknown family %d \n " , family ) ;
close ( fd ) ;
return - 1 ;
}
if ( ret < = 0 )
{
DPRINTF ( E_LOG , L_CAST , " Device address not valid (%s) \n " , address ) ;
close ( fd ) ;
return - 1 ;
}
sa . ss . ss_family = family ;
ret = connect ( fd , & sa . sa , len ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_CAST , " connect() to [%s]:%u failed: %s \n " , address , port , strerror ( errno ) ) ;
close ( fd ) ;
return - 1 ;
}
return fd ;
}
static void
2019-02-28 17:19:05 -05:00
cast_disconnect ( int fd )
2016-01-17 14:59:16 -05:00
{
/* no more receptions */
shutdown ( fd , SHUT_RDWR ) ;
close ( fd ) ;
}
2016-01-31 12:42:32 -05:00
static int
stream_url_make ( char * out , size_t len , const char * peer_addr , int family )
{
struct ifaddrs * ifap ;
struct ifaddrs * ifa ;
union sockaddr_all haddr ;
union sockaddr_all hmask ;
union sockaddr_all paddr ;
char host_addr [ 128 ] ;
unsigned short port ;
int found ;
int ret ;
if ( family = = AF_INET )
ret = inet_pton ( AF_INET , peer_addr , & paddr . sin . sin_addr ) ;
else
ret = inet_pton ( AF_INET6 , peer_addr , & paddr . sin6 . sin6_addr ) ;
if ( ret ! = 1 )
return - 1 ;
found = 0 ;
2017-12-10 13:50:57 -05:00
ret = getifaddrs ( & ifap ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_CAST , " Could not get interface address: %s \n " , strerror ( errno ) ) ;
return - 1 ;
}
2016-01-31 12:42:32 -05:00
for ( ifa = ifap ; ! found & & ifa ; ifa = ifa - > ifa_next )
{
2017-12-10 13:50:57 -05:00
if ( ! ifa - > ifa_addr )
{
DPRINTF ( E_LOG , L_CAST , " Skipping null address from getifaddrs() \n " ) ;
continue ;
}
2016-01-31 12:42:32 -05:00
if ( ifa - > ifa_addr - > sa_family ! = family )
continue ;
if ( family = = AF_INET )
{
memcpy ( & haddr . sin , ifa - > ifa_addr , sizeof ( struct sockaddr_in ) ) ;
memcpy ( & hmask . sin , ifa - > ifa_netmask , sizeof ( struct sockaddr_in ) ) ;
found = ( ( haddr . sin . sin_addr . s_addr & hmask . sin . sin_addr . s_addr ) = =
( paddr . sin . sin_addr . s_addr & hmask . sin . sin_addr . s_addr ) ) ;
if ( found )
inet_ntop ( family , & haddr . sin . sin_addr , host_addr , sizeof ( host_addr ) ) ;
}
else if ( family = = AF_INET6 )
{
memcpy ( & haddr . sin6 , ifa - > ifa_addr , sizeof ( struct sockaddr_in6 ) ) ;
found = ( memcmp ( & haddr . sin6 . sin6_addr . s6_addr , & paddr . sin6 . sin6_addr . s6_addr , 8 ) = = 0 ) ;
if ( found )
inet_ntop ( family , & haddr . sin6 . sin6_addr , host_addr , sizeof ( host_addr ) ) ;
}
}
freeifaddrs ( ifap ) ;
if ( ! found )
return - 1 ;
port = cfg_getint ( cfg_getsec ( cfg , " library " ) , " port " ) ;
2016-02-02 06:02:14 -05:00
if ( family = = AF_INET )
snprintf ( out , len , " http://%s:%d/stream.mp3 " , host_addr , port ) ;
else
snprintf ( out , len , " http://[%s]:%d/stream.mp3 " , host_addr , port ) ;
2016-01-31 12:42:32 -05:00
return 0 ;
}
2016-01-26 15:49:32 -05:00
static char *
squote_to_dquote ( char * buf )
{
char * ptr ;
for ( ptr = buf ; * ptr ! = ' \0 ' ; ptr + + )
if ( * ptr = = ' \' ' )
* ptr = ' " ' ;
return buf ;
}
2016-01-17 14:59:16 -05:00
2016-02-02 06:02:14 -05:00
/* ----------------------------- SESSION CLEANUP ---------------------------- */
2019-02-28 17:19:05 -05:00
static void
master_session_free ( struct cast_master_session * cms )
{
if ( ! cms )
return ;
outputs_quality_unsubscribe ( & cms - > rtp_session - > quality ) ;
rtp_session_free ( cms - > rtp_session ) ;
evbuffer_free ( cms - > evbuf ) ;
free ( cms - > rawbuf ) ;
free ( cms ) ;
}
static void
master_session_cleanup ( struct cast_master_session * cms )
{
struct cast_session * cs ;
// First check if any other session is using the master session
for ( cs = cast_sessions ; cs ; cs = cs - > next )
{
if ( cs - > master_session = = cms )
return ;
}
2019-03-01 18:30:43 -05:00
if ( cms = = cast_master_session )
cast_master_session = NULL ;
2019-02-28 17:19:05 -05:00
master_session_free ( cms ) ;
}
2016-02-02 06:02:14 -05:00
static void
cast_session_free ( struct cast_session * cs )
2016-01-26 15:49:32 -05:00
{
2017-10-05 16:13:01 -04:00
if ( ! cs )
return ;
2019-02-28 17:19:05 -05:00
master_session_cleanup ( cs - > master_session ) ;
2016-02-03 16:54:18 -05:00
event_free ( cs - > reply_timeout ) ;
2016-02-02 06:02:14 -05:00
event_free ( cs - > ev ) ;
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
if ( cs - > server_fd > = 0 )
2019-02-28 17:19:05 -05:00
cast_disconnect ( cs - > server_fd ) ;
if ( cs - > udp_fd > = 0 )
cast_disconnect ( cs - > udp_fd ) ;
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
gnutls_deinit ( cs - > tls_session ) ;
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
if ( cs - > address )
free ( cs - > address ) ;
if ( cs - > devname )
free ( cs - > devname ) ;
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
if ( cs - > session_id )
free ( cs - > session_id ) ;
if ( cs - > transport_id )
free ( cs - > transport_id ) ;
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
free ( cs ) ;
}
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
static void
cast_session_cleanup ( struct cast_session * cs )
{
struct cast_session * s ;
2016-01-26 15:49:32 -05:00
2019-02-28 17:19:05 -05:00
if ( cs = = cast_sessions )
cast_sessions = cast_sessions - > next ;
2016-02-02 06:02:14 -05:00
else
{
2019-02-28 17:19:05 -05:00
for ( s = cast_sessions ; s & & ( s - > next ! = cs ) ; s = s - > next )
2016-02-02 06:02:14 -05:00
; /* EMPTY */
if ( ! s )
DPRINTF ( E_WARN , L_CAST , " WARNING: struct cast_session not found in list; BUG! \n " ) ;
else
s - > next = cs - > next ;
}
2019-02-28 17:19:05 -05:00
outputs_device_session_remove ( cs - > device_id ) ;
2016-02-02 06:02:14 -05:00
cast_session_free ( cs ) ;
}
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
// Forward
static void
cast_session_shutdown ( struct cast_session * cs , enum cast_state wanted_state ) ;
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
/* --------------------------- CAST MESSAGE HANDLING ------------------------ */
2016-01-26 15:49:32 -05:00
static int
cast_msg_send ( struct cast_session * cs , enum cast_msg_types type , cast_reply_cb reply_cb )
{
Extensions__CoreApi__CastChannel__CastMessage msg = EXTENSIONS__CORE_API__CAST_CHANNEL__CAST_MESSAGE__INIT ;
char msg_buf [ MAX_BUF ] ;
uint8_t buf [ MAX_BUF ] ;
uint32_t be ;
size_t len ;
int ret ;
2016-02-03 16:54:18 -05:00
# ifdef DEBUG_CONNECTION
2016-02-02 16:37:08 -05:00
DPRINTF ( E_DBG , L_CAST , " Preparing to send message type %d to '%s' \n " , type , cs - > devname ) ;
2016-02-02 06:02:14 -05:00
# endif
2016-01-26 15:49:32 -05:00
msg . source_id = " sender-0 " ;
msg . namespace_ = cast_msg [ type ] . namespace ;
if ( ( cast_msg [ type ] . flags & USE_TRANSPORT_ID ) & & ! cs - > transport_id )
{
DPRINTF ( E_LOG , L_CAST , " Error, didn't get transportId for message (type %d) to '%s' \n " , type , cs - > devname ) ;
return - 1 ;
}
if ( cast_msg [ type ] . flags & USE_TRANSPORT_ID )
msg . destination_id = cs - > transport_id ;
else
msg . destination_id = " receiver-0 " ;
if ( cast_msg [ type ] . flags & ( USE_REQUEST_ID | USE_REQUEST_ID_ONLY ) )
{
cs - > request_id + + ;
if ( reply_cb )
2016-02-03 16:54:18 -05:00
{
cs - > callback_register [ cs - > request_id % CALLBACK_REGISTER_SIZE ] = reply_cb ;
event_add ( cs - > reply_timeout , & reply_timeout ) ;
}
2016-01-26 15:49:32 -05:00
}
2016-01-29 18:36:05 -05:00
// Special handling of some message types
2016-01-26 15:49:32 -05:00
if ( cast_msg [ type ] . flags & USE_REQUEST_ID_ONLY )
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > request_id ) ;
2016-02-02 06:02:14 -05:00
else if ( type = = STOP )
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > session_id , cs - > request_id ) ;
2019-02-28 17:19:05 -05:00
else if ( type = = OFFER )
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > request_id , cs - > ssrc_id ) ;
2019-03-26 18:13:09 -04:00
else if ( type = = PRESENTATION )
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > session_id , cs - > request_id ) ;
2016-01-26 15:49:32 -05:00
else if ( type = = MEDIA_LOAD )
2016-01-31 12:42:32 -05:00
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > stream_url , cs - > session_id , cs - > request_id ) ;
else if ( ( type = = MEDIA_PLAY ) | | ( type = = MEDIA_PAUSE ) | | ( type = = MEDIA_STOP ) )
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > media_session_id , cs - > session_id , cs - > request_id ) ;
2016-01-29 18:36:05 -05:00
else if ( type = = SET_VOLUME )
snprintf ( msg_buf , sizeof ( msg_buf ) , cast_msg [ type ] . payload , cs - > volume , cs - > request_id ) ;
2016-01-26 15:49:32 -05:00
else
snprintf ( msg_buf , sizeof ( msg_buf ) , " %s " , cast_msg [ type ] . payload ) ;
squote_to_dquote ( msg_buf ) ;
msg . payload_utf8 = msg_buf ;
len = extensions__core_api__cast_channel__cast_message__get_packed_size ( & msg ) ;
if ( len < = 0 )
{
2016-08-22 15:18:24 -04:00
DPRINTF ( E_LOG , L_CAST , " Could not send message (type %d), invalid length: %zu \n " , type , len ) ;
2016-01-26 15:49:32 -05:00
return - 1 ;
}
// The message must be prefixed with Big-Endian 32 bit length
be = htobe32 ( len ) ;
memcpy ( buf , & be , 4 ) ;
// Now add the packed message and send it
extensions__core_api__cast_channel__cast_message__pack ( & msg , buf + 4 ) ;
ret = gnutls_record_send ( cs - > tls_session , buf , len + 4 ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_CAST , " Could not send message, TLS error \n " ) ;
return - 1 ;
}
else if ( ret ! = len + 4 )
{
DPRINTF ( E_LOG , L_CAST , " BUG! Message partially sent, and we are not able to send the rest \n " ) ;
return - 1 ;
}
2016-02-03 16:54:18 -05:00
if ( type ! = PONG )
2016-08-22 15:18:24 -04:00
DPRINTF ( E_DBG , L_CAST , " TX %zu %s %s %s %s \n " , len , msg . source_id , msg . destination_id , msg . namespace_ , msg . payload_utf8 ) ;
2016-01-26 15:49:32 -05:00
return 0 ;
}
static void *
cast_msg_parse ( struct cast_msg_payload * payload , char * s )
{
json_object * haystack ;
json_object * somehay ;
json_object * needle ;
const char * val ;
int i ;
haystack = json_tokener_parse ( s ) ;
if ( ! haystack )
{
DPRINTF ( E_LOG , L_CAST , " JSON parser returned an error \n " ) ;
return NULL ;
}
payload - > type = UNKNOWN ;
if ( json_object_object_get_ex ( haystack , " type " , & needle ) )
{
val = json_object_get_string ( needle ) ;
for ( i = 1 ; cast_msg [ i ] . type ; i + + )
{
if ( cast_msg [ i ] . tag & & ( strcmp ( val , cast_msg [ i ] . tag ) = = 0 ) )
{
payload - > type = cast_msg [ i ] . type ;
break ;
}
}
}
if ( json_object_object_get_ex ( haystack , " requestId " , & needle ) )
payload - > request_id = json_object_get_int ( needle ) ;
2019-02-28 17:19:05 -05:00
else if ( json_object_object_get_ex ( haystack , " seqNum " , & needle ) )
payload - > request_id = json_object_get_int ( needle ) ;
if ( json_object_object_get_ex ( haystack , " answer " , & somehay ) & &
json_object_object_get_ex ( somehay , " udpPort " , & needle ) & &
json_object_get_type ( needle ) = = json_type_int )
payload - > udp_port = json_object_get_int ( needle ) ;
if ( json_object_object_get_ex ( haystack , " result " , & needle ) & &
json_object_get_type ( needle ) = = json_type_string )
payload - > result = json_object_get_string ( needle ) ;
2016-01-26 15:49:32 -05:00
// Might be done now
2016-01-31 12:42:32 -05:00
if ( ( payload - > type ! = RECEIVER_STATUS ) & & ( payload - > type ! = MEDIA_STATUS ) )
2016-01-26 15:49:32 -05:00
return haystack ;
// Isn't this marvelous
2016-01-31 12:42:32 -05:00
if ( json_object_object_get_ex ( haystack , " status " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_array ) & &
2016-02-02 16:37:08 -05:00
( somehay = json_object_array_get_idx ( needle , 0 ) ) )
{
if ( json_object_object_get_ex ( somehay , " mediaSessionId " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_int ) )
payload - > media_session_id = json_object_get_int ( needle ) ;
2016-01-26 15:49:32 -05:00
2016-02-02 16:37:08 -05:00
if ( json_object_object_get_ex ( somehay , " playerState " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_string ) )
payload - > player_state = json_object_get_string ( needle ) ;
}
2016-02-02 06:02:14 -05:00
2016-01-26 15:49:32 -05:00
2016-02-02 16:37:08 -05:00
if ( json_object_object_get_ex ( haystack , " status " , & somehay ) & &
json_object_object_get_ex ( somehay , " applications " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_array ) & &
( somehay = json_object_array_get_idx ( needle , 0 ) ) )
{
if ( json_object_object_get_ex ( somehay , " appId " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_string ) )
payload - > app_id = json_object_get_string ( needle ) ;
if ( json_object_object_get_ex ( somehay , " sessionId " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_string ) )
payload - > session_id = json_object_get_string ( needle ) ;
if ( json_object_object_get_ex ( somehay , " transportId " , & needle ) & &
( json_object_get_type ( needle ) = = json_type_string ) )
payload - > transport_id = json_object_get_string ( needle ) ;
}
2016-01-26 15:49:32 -05:00
return haystack ;
}
static void
cast_msg_parse_free ( void * haystack )
{
2016-03-06 15:33:49 -05:00
# ifdef HAVE_JSON_C_OLD
json_object_put ( ( json_object * ) haystack ) ;
# else
2016-01-26 15:49:32 -05:00
if ( json_object_put ( ( json_object * ) haystack ) ! = 1 )
DPRINTF ( E_LOG , L_CAST , " Memleak: JSON parser did not free object \n " ) ;
2016-03-06 15:33:49 -05:00
# endif
2016-01-26 15:49:32 -05:00
}
static void
cast_msg_process ( struct cast_session * cs , const uint8_t * data , size_t len )
2016-01-17 14:59:16 -05:00
{
Extensions__CoreApi__CastChannel__CastMessage * reply ;
2016-02-03 16:54:18 -05:00
cast_reply_cb reply_cb ;
2016-01-26 15:49:32 -05:00
struct cast_msg_payload payload = { 0 } ;
void * hdl ;
2016-02-02 06:02:14 -05:00
int unknown_session_id ;
2016-01-26 15:49:32 -05:00
int i ;
2016-01-17 14:59:16 -05:00
2016-07-31 17:36:27 -04:00
# ifdef DEBUG_CONNECTION
char * b64 = b64_encode ( data , len ) ;
if ( b64 )
{
2016-08-22 15:18:24 -04:00
DPRINTF ( E_DBG , L_CAST , " Reply dump (len %zu): %s \n " , len , b64 ) ;
2016-07-31 17:36:27 -04:00
free ( b64 ) ;
}
# endif
2016-01-17 14:59:16 -05:00
reply = extensions__core_api__cast_channel__cast_message__unpack ( NULL , len , data ) ;
2016-01-26 15:49:32 -05:00
if ( ! reply )
{
DPRINTF ( E_LOG , L_CAST , " Could not unpack message! \n " ) ;
return ;
}
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
hdl = cast_msg_parse ( & payload , reply - > payload_utf8 ) ;
if ( ! hdl )
{
DPRINTF ( E_DBG , L_CAST , " Could not parse message: %s \n " , reply - > payload_utf8 ) ;
goto out_free_unpacked ;
}
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
if ( payload . type = = PING )
2016-01-17 14:59:16 -05:00
{
2016-01-26 15:49:32 -05:00
cast_msg_send ( cs , PONG , NULL ) ;
goto out_free_parsed ;
2016-01-17 14:59:16 -05:00
}
2016-08-22 15:18:24 -04:00
DPRINTF ( E_DBG , L_CAST , " RX %zu %s %s %s %s \n " , len , reply - > source_id , reply - > destination_id , reply - > namespace_ , reply - > payload_utf8 ) ;
2016-02-03 16:54:18 -05:00
2016-01-26 15:49:32 -05:00
if ( payload . type = = UNKNOWN )
goto out_free_parsed ;
i = payload . request_id % CALLBACK_REGISTER_SIZE ;
2016-01-30 17:01:01 -05:00
if ( payload . request_id & & cs - > callback_register [ i ] )
2016-01-17 14:59:16 -05:00
{
2016-02-03 16:54:18 -05:00
reply_cb = cs - > callback_register [ i ] ;
2016-01-26 15:49:32 -05:00
cs - > callback_register [ i ] = NULL ;
2016-02-03 16:54:18 -05:00
// Cancel the timeout if no pending callbacks
for ( i = 0 ; ( i < CALLBACK_REGISTER_SIZE ) & & ( ! cs - > callback_register [ i ] ) ; i + + ) ;
if ( i = = CALLBACK_REGISTER_SIZE )
evtimer_del ( cs - > reply_timeout ) ;
reply_cb ( cs , & payload ) ;
2016-01-26 15:49:32 -05:00
goto out_free_parsed ;
2016-01-17 14:59:16 -05:00
}
2016-02-03 16:54:18 -05:00
// TODO Should we read volume and playerstate changes from the Chromecast?
2016-02-02 16:37:08 -05:00
2016-02-02 06:02:14 -05:00
if ( payload . type = = RECEIVER_STATUS & & ( cs - > state & CAST_STATE_F_MEDIA_CONNECTED ) )
{
unknown_session_id = payload . session_id & & ( strcmp ( payload . session_id , cs - > session_id ) ! = 0 ) ;
2019-04-04 15:20:39 -04:00
if ( unknown_session_id )
2016-02-02 06:02:14 -05:00
{
2019-04-04 15:20:39 -04:00
DPRINTF ( E_LOG , L_CAST , " Our session '%s' on '%s' was lost to session '%s' \n " , cs - > session_id , cs - > devname , payload . session_id ) ;
2016-02-02 06:02:14 -05:00
// Downgrade state, we don't have the receiver app any more
cs - > state = CAST_STATE_CONNECTED ;
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
goto out_free_parsed ;
}
}
2019-02-28 17:19:05 -05:00
if ( payload . type = = CLOSE & & ( cs - > state & CAST_STATE_F_MEDIA_CONNECTED ) )
{
// Downgrade state, we can't write any more
cs - > state = CAST_STATE_CONNECTED ;
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
goto out_free_parsed ;
}
if ( payload . type = = MEDIA_STATUS & & ( cs - > state & CAST_STATE_F_MEDIA_STREAMING ) )
2016-02-02 16:37:08 -05:00
{
if ( payload . player_state & & ( strcmp ( payload . player_state , " PAUSED " ) = = 0 ) )
{
DPRINTF ( E_WARN , L_CAST , " Something paused our session on '%s' \n " , cs - > devname ) ;
/* cs->state = CAST_STATE_MEDIA_CONNECTED;
// Kill the session, the player will need to restart it
2016-08-12 18:05:00 -04:00
cast_session_shutdown ( cs , CAST_STATE_NONE ) ;
2016-02-02 16:37:08 -05:00
goto out_free_parsed ;
*/ }
}
2016-01-26 15:49:32 -05:00
out_free_parsed :
cast_msg_parse_free ( hdl ) ;
out_free_unpacked :
extensions__core_api__cast_channel__cast_message__free_unpacked ( reply , NULL ) ;
2016-01-17 14:59:16 -05:00
}
2016-02-02 06:02:14 -05:00
/* -------------------------------- CALLBACKS ------------------------------- */
2016-01-17 14:59:16 -05:00
2016-02-02 06:02:14 -05:00
/* Maps our internal state to the generic output state and then makes a callback
* to the player to tell that state
*/
static void
cast_status ( struct cast_session * cs )
{
enum output_device_state state ;
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
switch ( cs - > state )
{
case CAST_STATE_FAILED :
state = OUTPUT_STATE_FAILED ;
break ;
2016-08-12 18:05:00 -04:00
case CAST_STATE_NONE :
2016-02-02 06:02:14 -05:00
state = OUTPUT_STATE_STOPPED ;
break ;
case CAST_STATE_DISCONNECTED . . . CAST_STATE_MEDIA_LAUNCHED :
state = OUTPUT_STATE_STARTUP ;
break ;
case CAST_STATE_MEDIA_CONNECTED :
state = OUTPUT_STATE_CONNECTED ;
break ;
2019-02-28 17:19:05 -05:00
case CAST_STATE_MEDIA_STREAMING :
2016-02-02 06:02:14 -05:00
state = OUTPUT_STATE_STREAMING ;
break ;
default :
DPRINTF ( E_LOG , L_CAST , " Bug! Unhandled state in cast_status() \n " ) ;
state = OUTPUT_STATE_FAILED ;
}
2016-01-26 15:49:32 -05:00
2019-02-28 17:19:05 -05:00
outputs_cb ( cs - > callback_id , cs - > device_id , state ) ;
cs - > callback_id = - 1 ;
2016-01-17 14:59:16 -05:00
}
2016-02-02 06:02:14 -05:00
/* cast_cb_stop*: Callback chain for shutting down a session */
2016-01-17 14:59:16 -05:00
static void
2016-02-02 06:02:14 -05:00
cast_cb_stop ( struct cast_session * cs , struct cast_msg_payload * payload )
2016-01-17 14:59:16 -05:00
{
2016-02-08 16:34:29 -05:00
if ( ! payload )
DPRINTF ( E_LOG , L_CAST , " No RECEIVER_STATUS reply to our STOP - will continue anyway \n " ) ;
else if ( payload - > type ! = RECEIVER_STATUS )
DPRINTF ( E_LOG , L_CAST , " No RECEIVER_STATUS reply to our STOP (got type: %d) - will continue anyway \n " , payload - > type ) ;
2016-01-17 14:59:16 -05:00
2016-02-02 06:02:14 -05:00
cs - > state = CAST_STATE_CONNECTED ;
if ( cs - > state = = cs - > wanted_state )
cast_status ( cs ) ;
else
cast_session_shutdown ( cs , cs - > wanted_state ) ;
2016-01-17 14:59:16 -05:00
}
static void
2016-02-02 06:02:14 -05:00
cast_cb_stop_media ( struct cast_session * cs , struct cast_msg_payload * payload )
2016-01-17 14:59:16 -05:00
{
2016-02-08 16:34:29 -05:00
if ( ! payload )
DPRINTF ( E_LOG , L_CAST , " No MEDIA_STATUS reply to our STOP - will continue anyway \n " ) ;
else if ( payload - > type ! = MEDIA_STATUS )
DPRINTF ( E_LOG , L_CAST , " No MEDIA_STATUS reply to our STOP (got type: %d) - will continue anyway \n " , payload - > type ) ;
2016-02-02 06:02:14 -05:00
cs - > state = CAST_STATE_MEDIA_CONNECTED ;
2016-01-17 14:59:16 -05:00
2016-02-02 06:02:14 -05:00
if ( cs - > state = = cs - > wanted_state )
cast_status ( cs ) ;
else
cast_session_shutdown ( cs , cs - > wanted_state ) ;
2016-01-17 14:59:16 -05:00
}
2016-02-02 06:02:14 -05:00
/* cast_cb_startup*: Callback chain for starting a session */
2016-01-26 15:49:32 -05:00
static void
cast_cb_startup_volume ( struct cast_session * cs , struct cast_msg_payload * payload )
{
/* Session startup and setup is done, tell our user */
DPRINTF ( E_DBG , L_CAST , " Session ready \n " ) ;
2016-02-02 06:02:14 -05:00
cast_status ( cs ) ;
2016-01-26 15:49:32 -05:00
}
2019-02-28 17:19:05 -05:00
static void
cast_cb_startup_offer ( struct cast_session * cs , struct cast_msg_payload * payload )
{
int ret ;
if ( ! payload )
{
DPRINTF ( E_LOG , L_CAST , " No reply from '%s' to our OFFER request \n " , cs - > devname ) ;
goto error ;
}
else if ( payload - > type ! = ANSWER )
{
DPRINTF ( E_LOG , L_CAST , " The device '%s' did not give us an ANSWER to our OFFER \n " , cs - > devname ) ;
goto error ;
}
else if ( ! payload - > udp_port | | strcmp ( payload - > result , " ok " ) ! = 0 )
{
DPRINTF ( E_LOG , L_CAST , " Missing UDP port (or unexpected result '%s') in ANSWER - aborting \n " , payload - > result ) ;
goto error ;
}
DPRINTF ( E_INFO , L_CAST , " UDP port in ANSWER is %d \n " , payload - > udp_port ) ;
cs - > udp_port = payload - > udp_port ;
cs - > udp_fd = cast_connect ( cs - > address , cs - > udp_port , cs - > family , SOCK_DGRAM ) ;
if ( cs - > udp_fd < 0 )
goto error ;
ret = cast_msg_send ( cs , SET_VOLUME , cast_cb_startup_volume ) ;
if ( ret < 0 )
goto error ;
cs - > state = CAST_STATE_MEDIA_CONNECTED ;
return ;
error :
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
}
2016-01-26 15:49:32 -05:00
static void
cast_cb_startup_media ( struct cast_session * cs , struct cast_msg_payload * payload )
{
2016-01-29 18:36:05 -05:00
int ret ;
2016-01-26 15:49:32 -05:00
2016-02-08 16:34:29 -05:00
if ( ! payload )
{
DPRINTF ( E_LOG , L_CAST , " No MEDIA_STATUS reply to our GET_STATUS - aborting \n " ) ;
goto error ;
}
else if ( payload - > type ! = MEDIA_STATUS )
2016-01-26 15:49:32 -05:00
{
2016-02-02 06:02:14 -05:00
DPRINTF ( E_LOG , L_CAST , " No MEDIA_STATUS reply to our GET_STATUS (got type: %d) - aborting \n " , payload - > type ) ;
2016-02-08 16:34:29 -05:00
goto error ;
2016-01-26 15:49:32 -05:00
}
2019-02-28 17:19:05 -05:00
ret = cast_msg_send ( cs , OFFER , cast_cb_startup_offer ) ;
2016-01-29 18:36:05 -05:00
if ( ret < 0 )
2016-02-08 16:34:29 -05:00
goto error ;
2016-02-02 06:02:14 -05:00
2016-02-08 16:34:29 -05:00
return ;
error :
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
2016-01-26 15:49:32 -05:00
}
static void
cast_cb_startup_launch ( struct cast_session * cs , struct cast_msg_payload * payload )
{
int ret ;
2016-08-01 03:05:09 -04:00
// Sometimes the response to a LAUNCH is just a broadcast RECEIVER_STATUS
// without our requestId. That won't be registered by our response handler,
// and we get an empty callback due to timeout. In this case we send a
// GET_STATUS to see if we are good to go anyway.
if ( ! payload & & ! cs - > retry )
{
DPRINTF ( E_LOG , L_CAST , " No RECEIVER_STATUS reply to our LAUNCH - trying GET_STATUS instead \n " ) ;
cs - > retry + + ;
ret = cast_msg_send ( cs , GET_STATUS , cast_cb_startup_launch ) ;
if ( ret ! = 0 )
goto error ;
return ;
}
2016-02-08 16:34:29 -05:00
if ( ! payload )
{
DPRINTF ( E_LOG , L_CAST , " No RECEIVER_STATUS reply to our LAUNCH - aborting \n " ) ;
goto error ;
}
2016-08-01 03:05:09 -04:00
2019-03-26 18:13:09 -04:00
if ( payload - > type = = LAUNCH_ERROR & & ! cs - > retry )
{
DPRINTF ( E_WARN , L_CAST , " Device '%s' does not support app id '%s', trying '%s' instead \n " , cs - > devname , CAST_APP_ID , CAST_APP_ID_OLD ) ;
cs - > retry + + ;
ret = cast_msg_send ( cs , LAUNCH_OLD , cast_cb_startup_launch ) ;
if ( ret < 0 )
goto error ;
return ;
}
2016-08-01 03:05:09 -04:00
if ( payload - > type ! = RECEIVER_STATUS )
2016-01-26 15:49:32 -05:00
{
2016-02-02 06:02:14 -05:00
DPRINTF ( E_LOG , L_CAST , " No RECEIVER_STATUS reply to our LAUNCH (got type: %d) - aborting \n " , payload - > type ) ;
2016-02-08 16:34:29 -05:00
goto error ;
2016-01-26 15:49:32 -05:00
}
2016-08-01 03:05:09 -04:00
if ( ! payload - > transport_id | | ! payload - > session_id )
2016-02-02 06:02:14 -05:00
{
DPRINTF ( E_LOG , L_CAST , " Missing session id or transport id in RECEIVER_STATUS - aborting \n " ) ;
2016-02-08 16:34:29 -05:00
goto error ;
2016-02-02 06:02:14 -05:00
}
if ( cs - > session_id | | cs - > transport_id )
DPRINTF ( E_LOG , L_CAST , " Bug! Memleaking... \n " ) ;
2016-01-26 15:49:32 -05:00
2016-02-02 06:02:14 -05:00
cs - > session_id = strdup ( payload - > session_id ) ;
2016-01-26 15:49:32 -05:00
cs - > transport_id = strdup ( payload - > transport_id ) ;
2016-08-01 03:05:09 -04:00
cs - > retry = 0 ;
2016-01-26 15:49:32 -05:00
ret = cast_msg_send ( cs , MEDIA_CONNECT , NULL ) ;
if ( ret = = 0 )
ret = cast_msg_send ( cs , MEDIA_GET_STATUS , cast_cb_startup_media ) ;
if ( ret < 0 )
2016-02-08 16:34:29 -05:00
goto error ;
2016-02-02 06:02:14 -05:00
cs - > state = CAST_STATE_MEDIA_LAUNCHED ;
2016-02-08 16:34:29 -05:00
return ;
error :
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
2016-01-26 15:49:32 -05:00
}
static void
cast_cb_startup_connect ( struct cast_session * cs , struct cast_msg_payload * payload )
{
int ret ;
2016-02-08 16:34:29 -05:00
if ( ! payload )
{
DPRINTF ( E_LOG , L_CAST , " No RECEIVER_STATUS reply to our GET_STATUS - aborting \n " ) ;
goto error ;
}
else if ( payload - > type ! = RECEIVER_STATUS )
2016-01-26 15:49:32 -05:00
{
2016-02-02 06:02:14 -05:00
DPRINTF ( E_LOG , L_CAST , " No RECEIVER_STATUS reply to our GET_STATUS (got type: %d) - aborting \n " , payload - > type ) ;
2016-02-08 16:34:29 -05:00
goto error ;
2016-01-26 15:49:32 -05:00
}
ret = cast_msg_send ( cs , LAUNCH , cast_cb_startup_launch ) ;
if ( ret < 0 )
2016-02-08 16:34:29 -05:00
goto error ;
2016-02-02 06:02:14 -05:00
cs - > state = CAST_STATE_CONNECTED ;
2016-02-08 16:34:29 -05:00
return ;
error :
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
2016-01-26 15:49:32 -05:00
}
2016-02-02 06:02:14 -05:00
/* cast_cb_probe: Callback from cast_device_probe */
2016-01-26 15:49:32 -05:00
static void
2016-02-02 06:02:14 -05:00
cast_cb_probe ( struct cast_session * cs , struct cast_msg_payload * payload )
2016-01-26 15:49:32 -05:00
{
2016-02-08 16:34:29 -05:00
if ( ! payload )
{
DPRINTF ( E_LOG , L_CAST , " No RECEIVER_STATUS reply to our GET_STATUS - aborting \n " ) ;
goto error ;
}
else if ( payload - > type ! = RECEIVER_STATUS )
2016-02-02 06:02:14 -05:00
{
DPRINTF ( E_LOG , L_CAST , " No RECEIVER_STATUS reply to our GET_STATUS (got type: %d) - aborting \n " , payload - > type ) ;
2016-02-08 16:34:29 -05:00
goto error ;
2016-02-02 06:02:14 -05:00
}
2016-01-31 12:42:32 -05:00
2016-02-02 06:02:14 -05:00
cs - > state = CAST_STATE_CONNECTED ;
2016-01-31 12:42:32 -05:00
2016-02-02 06:02:14 -05:00
cast_status ( cs ) ;
2016-01-31 12:42:32 -05:00
2016-08-12 18:05:00 -04:00
cast_session_shutdown ( cs , CAST_STATE_NONE ) ;
2016-02-08 16:34:29 -05:00
return ;
error :
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
2016-01-26 15:49:32 -05:00
}
2016-01-29 18:36:05 -05:00
static void
cast_cb_volume ( struct cast_session * cs , struct cast_msg_payload * payload )
{
2016-02-02 06:02:14 -05:00
cast_status ( cs ) ;
2016-01-29 18:36:05 -05:00
}
2019-04-09 15:03:14 -04:00
/*
2019-03-26 18:13:09 -04:00
static void
cast_cb_presentation ( struct cast_session * cs , struct cast_msg_payload * payload )
{
if ( ! payload )
DPRINTF ( E_LOG , L_CAST , " No reply to PRESENTATION request from '%s' - will continue \n " , cs - > devname ) ;
else if ( payload - > type ! = MEDIA_STATUS )
DPRINTF ( E_LOG , L_CAST , " Unexpected reply to PRESENTATION request from '%s' - will continue \n " , cs - > devname ) ;
}
2019-04-09 15:03:14 -04:00
*/
2019-03-26 18:13:09 -04:00
2016-02-02 06:02:14 -05:00
/* The core of this module. Libevent makes a callback to this function whenever
* there is new data to be read on the fd from the ChromeCast . If everything is
* good then the data will be passed to cast_msg_process ( ) that will then
* parse and make callbacks , if relevant .
*/
2016-01-17 14:59:16 -05:00
static void
cast_listen_cb ( int fd , short what , void * arg )
{
struct cast_session * cs ;
uint8_t buffer [ MAX_BUF + 1 ] ; // Not sure about the +1, but is copied from gnutls examples
2016-08-12 18:05:00 -04:00
uint32_t be ;
size_t len ;
2016-01-30 17:01:01 -05:00
int received ;
2016-01-17 14:59:16 -05:00
int ret ;
2019-02-28 17:19:05 -05:00
for ( cs = cast_sessions ; cs ; cs = cs - > next )
2016-08-12 18:05:00 -04:00
{
if ( cs = = ( struct cast_session * ) arg )
break ;
}
if ( ! cs )
{
DPRINTF ( E_INFO , L_CAST , " Callback on dead session, ignoring \n " ) ;
return ;
}
2016-01-17 14:59:16 -05:00
2016-02-02 16:37:08 -05:00
if ( what = = EV_TIMEOUT )
{
DPRINTF ( E_LOG , L_CAST , " No heartbeat from '%s', shutting down \n " , cs - > devname ) ;
2016-08-12 18:05:00 -04:00
goto fail ;
2016-02-02 16:37:08 -05:00
}
2016-02-03 16:54:18 -05:00
# ifdef DEBUG_CONNECTION
2016-02-02 16:37:08 -05:00
DPRINTF ( E_DBG , L_CAST , " New data from '%s' \n " , cs - > devname ) ;
2016-02-02 06:02:14 -05:00
# endif
2016-01-17 14:59:16 -05:00
2016-08-12 18:05:00 -04:00
// We first read the 4 byte header and then the actual message. The header
// will be the length of the message.
ret = gnutls_record_recv ( cs - > tls_session , buffer , 4 ) ;
if ( ret ! = 4 )
goto no_read ;
memcpy ( & be , buffer , 4 ) ;
len = be32toh ( be ) ;
if ( ( len = = 0 ) | | ( len > MAX_BUF ) )
{
2016-08-22 15:18:24 -04:00
DPRINTF ( E_LOG , L_CAST , " Bad length of incoming message, aborting (len=%zu, size=%d) \n " , len , MAX_BUF ) ;
2016-08-12 18:05:00 -04:00
goto fail ;
}
2016-01-30 17:01:01 -05:00
received = 0 ;
2016-08-12 18:05:00 -04:00
while ( received < len )
2016-01-17 14:59:16 -05:00
{
2016-08-12 18:05:00 -04:00
ret = gnutls_record_recv ( cs - > tls_session , buffer + received , len - received ) ;
if ( ret < = 0 )
goto no_read ;
received + = ret ;
2016-02-03 16:54:18 -05:00
# ifdef DEBUG_CONNECTION
2016-08-22 15:22:47 -04:00
DPRINTF ( E_DBG , L_CAST , " Received %d bytes out of expected %zu bytes \n " , received , len ) ;
2016-07-31 18:12:08 -04:00
# endif
2016-08-12 18:05:00 -04:00
}
2016-01-29 18:36:05 -05:00
2016-08-12 18:05:00 -04:00
ret = gnutls_record_check_pending ( cs - > tls_session ) ;
// Process the message - note that this may result in cs being invalidated
cast_msg_process ( cs , buffer , len ) ;
// In the event there was more data waiting for us we go again
if ( ret > 0 )
{
DPRINTF ( E_INFO , L_CAST , " More data pending from device (%d bytes) \n " , ret ) ;
cast_listen_cb ( fd , what , arg ) ;
2016-01-17 14:59:16 -05:00
}
2016-08-12 18:05:00 -04:00
return ;
no_read :
2016-01-26 15:49:32 -05:00
if ( ( ret ! = GNUTLS_E_INTERRUPTED ) & & ( ret ! = GNUTLS_E_AGAIN ) )
{
DPRINTF ( E_LOG , L_CAST , " Session error: %s \n " , gnutls_strerror ( ret ) ) ;
2016-08-12 18:05:00 -04:00
goto fail ;
2016-01-26 15:49:32 -05:00
}
2016-01-29 18:36:05 -05:00
2016-08-12 18:05:00 -04:00
DPRINTF ( E_DBG , L_CAST , " Return value from tls is %d (GNUTLS_E_AGAIN is %d) \n " , ret , GNUTLS_E_AGAIN ) ;
return ;
fail :
// Downgrade state to make cast_session_shutdown perform an exit which is
// quick and won't require a reponse from the device
cs - > state = CAST_STATE_CONNECTED ;
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
2016-01-17 14:59:16 -05:00
}
2016-02-03 16:54:18 -05:00
static void
cast_reply_timeout_cb ( int fd , short what , void * arg )
{
struct cast_session * cs ;
int i ;
cs = ( struct cast_session * ) arg ;
2016-08-12 18:29:34 -04:00
i = cs - > request_id % CALLBACK_REGISTER_SIZE ;
2016-02-03 16:54:18 -05:00
2016-08-12 18:29:34 -04:00
DPRINTF ( E_LOG , L_CAST , " Request %d timed out, will run empty callback \n " , i ) ;
2016-02-03 16:54:18 -05:00
2016-08-12 18:29:34 -04:00
if ( cs - > callback_register [ i ] )
{
cs - > callback_register [ i ] ( cs , NULL ) ;
cs - > callback_register [ i ] = NULL ;
}
2016-02-03 16:54:18 -05:00
}
2016-02-02 06:02:14 -05:00
static void
cast_device_cb ( const char * name , const char * type , const char * domain , const char * hostname , int family , const char * address , int port , struct keyval * txt )
{
struct output_device * device ;
2017-02-10 13:27:52 -05:00
const char * friendly_name ;
2016-02-02 06:02:14 -05:00
uint32_t id ;
2016-02-02 16:37:08 -05:00
id = djb_hash ( name , strlen ( name ) ) ;
2016-02-02 06:02:14 -05:00
if ( ! id )
{
2016-02-02 16:37:08 -05:00
DPRINTF ( E_LOG , L_CAST , " Could not hash ChromeCast device name (%s) \n " , name ) ;
2016-02-02 06:02:14 -05:00
return ;
}
2017-02-10 13:27:52 -05:00
friendly_name = keyval_get ( txt , " fn " ) ;
if ( friendly_name )
name = friendly_name ;
2016-02-02 16:37:08 -05:00
DPRINTF ( E_DBG , L_CAST , " Event for Chromecast device '%s' (port %d, id % " PRIu32 " ) \n " , name , port , id ) ;
2016-02-02 06:02:14 -05:00
device = calloc ( 1 , sizeof ( struct output_device ) ) ;
if ( ! device )
{
2016-02-02 16:37:08 -05:00
DPRINTF ( E_LOG , L_CAST , " Out of memory for new Chromecast device \n " ) ;
2016-02-02 06:02:14 -05:00
return ;
}
device - > id = id ;
device - > name = strdup ( name ) ;
device - > type = OUTPUT_TYPE_CAST ;
device - > type_name = outputs_name ( device - > type ) ;
if ( port < 0 )
{
/* Device stopped advertising */
switch ( family )
{
case AF_INET :
device - > v4_port = 1 ;
break ;
case AF_INET6 :
device - > v6_port = 1 ;
break ;
}
player_device_remove ( device ) ;
return ;
}
2016-02-02 16:37:08 -05:00
DPRINTF ( E_INFO , L_CAST , " Adding Chromecast device '%s' \n " , name ) ;
2016-02-02 06:02:14 -05:00
2019-02-28 17:19:05 -05:00
device - > advertised = 1 ;
2016-02-02 06:02:14 -05:00
switch ( family )
{
case AF_INET :
device - > v4_address = strdup ( address ) ;
device - > v4_port = port ;
break ;
case AF_INET6 :
device - > v6_address = strdup ( address ) ;
device - > v6_port = port ;
break ;
}
player_device_add ( device ) ;
}
2019-03-26 18:13:09 -04:00
/* --------------------------------- METADATA ------------------------------- */
2019-04-09 15:03:14 -04:00
/*
2019-03-26 18:13:09 -04:00
static void
metadata_send ( struct cast_session * cs )
{
cast_msg_send ( cs , PRESENTATION , cast_cb_presentation ) ;
}
2019-04-09 15:03:14 -04:00
*/
2019-03-26 18:13:09 -04:00
2016-02-02 06:02:14 -05:00
/* --------------------- SESSION CONSTRUCTION AND SHUTDOWN ------------------ */
2019-02-28 17:19:05 -05:00
static struct cast_master_session *
master_session_make ( struct media_quality * quality )
{
struct cast_master_session * cms ;
int ret ;
// First check if we already have a master session, then just use that
if ( cast_master_session )
return cast_master_session ;
// Let's create a master session
ret = outputs_quality_subscribe ( quality ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_CAST , " Could not subscribe to required audio quality (%d/%d/%d) \n " , quality - > sample_rate , quality - > bits_per_sample , quality - > channels ) ;
return NULL ;
}
CHECK_NULL ( L_CAST , cms = calloc ( 1 , sizeof ( struct cast_master_session ) ) ) ;
cms - > rtp_session = rtp_session_new ( quality , CAST_PACKET_BUFFER_SIZE , 0 ) ;
if ( ! cms - > rtp_session )
{
outputs_quality_unsubscribe ( quality ) ;
free ( cms ) ;
return NULL ;
}
cms - > quality = * quality ;
cms - > samples_per_packet = CAST_SAMPLES_PER_PACKET ;
cms - > rawbuf_size = STOB ( cms - > samples_per_packet , quality - > bits_per_sample , quality - > channels ) ;
cms - > output_buffer_samples = OUTPUTS_BUFFER_DURATION * quality - > sample_rate ;
CHECK_NULL ( L_CAST , cms - > rawbuf = malloc ( cms - > rawbuf_size ) ) ;
CHECK_NULL ( L_CAST , cms - > evbuf = evbuffer_new ( ) ) ;
cast_master_session = cms ;
return cms ;
}
2016-01-17 14:59:16 -05:00
static struct cast_session *
2019-02-28 17:19:05 -05:00
cast_session_make ( struct output_device * device , int family , int callback_id )
2016-01-17 14:59:16 -05:00
{
struct cast_session * cs ;
const char * proto ;
const char * err ;
char * address ;
unsigned short port ;
2016-01-26 15:49:32 -05:00
int flags ;
2016-01-17 14:59:16 -05:00
int ret ;
switch ( family )
{
case AF_INET :
/* We always have the v4 services, so no need to check */
2016-01-26 15:49:32 -05:00
if ( ! device - > v4_address )
2016-01-17 14:59:16 -05:00
return NULL ;
2016-01-26 15:49:32 -05:00
address = device - > v4_address ;
port = device - > v4_port ;
2016-01-17 14:59:16 -05:00
break ;
case AF_INET6 :
2016-01-26 15:49:32 -05:00
if ( ! device - > v6_address )
2016-01-17 14:59:16 -05:00
return NULL ;
2016-01-26 15:49:32 -05:00
address = device - > v6_address ;
port = device - > v6_port ;
2016-01-17 14:59:16 -05:00
break ;
default :
return NULL ;
}
2019-02-10 17:27:29 -05:00
CHECK_NULL ( L_CAST , cs = calloc ( 1 , sizeof ( struct cast_session ) ) ) ;
2016-01-17 14:59:16 -05:00
2016-02-02 06:02:14 -05:00
cs - > state = CAST_STATE_DISCONNECTED ;
2019-02-28 17:19:05 -05:00
cs - > device_id = device - > id ;
cs - > callback_id = callback_id ;
cs - > master_session = master_session_make ( & cast_quality_default ) ;
if ( ! cs - > master_session )
{
DPRINTF ( E_LOG , L_CAST , " Could not attach a master session for device '%s' \n " , device - > name ) ;
goto out_free_session ;
}
cs - > ssrc_id = cs - > master_session - > rtp_session - > ssrc_id ;
2016-01-17 14:59:16 -05:00
/* Init TLS session, use default priorities and put the x509 credentials to the current session */
if ( ( ( ret = gnutls_init ( & cs - > tls_session , GNUTLS_CLIENT ) ) ! = GNUTLS_E_SUCCESS ) | |
( ( ret = gnutls_priority_set_direct ( cs - > tls_session , " PERFORMANCE " , & err ) ) ! = GNUTLS_E_SUCCESS ) | |
( ( ret = gnutls_credentials_set ( cs - > tls_session , GNUTLS_CRD_CERTIFICATE , tls_credentials ) ) ! = GNUTLS_E_SUCCESS ) )
{
DPRINTF ( E_LOG , L_CAST , " Could not initialize GNUTLS session: %s \n " , gnutls_strerror ( ret ) ) ;
2019-02-28 17:19:05 -05:00
goto out_free_master_session ;
2016-01-17 14:59:16 -05:00
}
2019-02-28 17:19:05 -05:00
cs - > server_fd = cast_connect ( address , port , family , SOCK_STREAM ) ;
2016-01-17 14:59:16 -05:00
if ( cs - > server_fd < 0 )
2016-01-31 12:42:32 -05:00
{
DPRINTF ( E_LOG , L_CAST , " Could not connect to %s \n " , device - > name ) ;
goto out_deinit_gnutls ;
}
ret = stream_url_make ( cs - > stream_url , sizeof ( cs - > stream_url ) , address , family ) ;
if ( ret < 0 )
{
DPRINTF ( E_LOG , L_CAST , " Bug! Could find a network interface on same subnet as %s \n " , device - > name ) ;
goto out_close_connection ;
}
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
cs - > ev = event_new ( evbase_player , cs - > server_fd , EV_READ | EV_PERSIST , cast_listen_cb , cs ) ;
2016-01-17 14:59:16 -05:00
if ( ! cs - > ev )
{
DPRINTF ( E_LOG , L_CAST , " Out of memory for listener event \n " ) ;
goto out_close_connection ;
}
2016-02-03 16:54:18 -05:00
cs - > reply_timeout = evtimer_new ( evbase_player , cast_reply_timeout_cb , cs ) ;
if ( ! cs - > reply_timeout )
{
DPRINTF ( E_LOG , L_CAST , " Out of memory for reply_timeout \n " ) ;
goto out_close_connection ;
}
2016-01-17 14:59:16 -05:00
gnutls_transport_set_ptr ( cs - > tls_session , ( gnutls_transport_ptr_t ) cs - > server_fd ) ;
ret = gnutls_handshake ( cs - > tls_session ) ;
if ( ret ! = GNUTLS_E_SUCCESS )
{
DPRINTF ( E_LOG , L_CAST , " Could not attach TLS to TCP connection: %s \n " , gnutls_strerror ( ret ) ) ;
goto out_free_ev ;
}
2016-01-26 15:49:32 -05:00
flags = fcntl ( cs - > server_fd , F_GETFL , 0 ) ;
fcntl ( cs - > server_fd , F_SETFL , flags | O_NONBLOCK ) ;
2019-02-28 17:19:05 -05:00
event_add ( cs - > ev , NULL ) ; // &heartbeat_timeout
2016-01-17 14:59:16 -05:00
2016-01-26 15:49:32 -05:00
cs - > devname = strdup ( device - > name ) ;
2016-01-17 14:59:16 -05:00
cs - > address = strdup ( address ) ;
2019-02-28 17:19:05 -05:00
cs - > family = family ;
cs - > udp_fd = - 1 ;
2016-01-17 14:59:16 -05:00
2016-01-29 18:36:05 -05:00
cs - > volume = 0.01 * device - > volume ;
2016-01-17 14:59:16 -05:00
2019-02-28 17:19:05 -05:00
cs - > next = cast_sessions ;
cast_sessions = cs ;
2016-01-17 14:59:16 -05:00
2019-02-28 17:19:05 -05:00
// cs is now the official device session
outputs_device_session_add ( device - > id , cs ) ;
2019-02-10 17:27:29 -05:00
2016-01-17 14:59:16 -05:00
proto = gnutls_protocol_get_name ( gnutls_protocol_get_version ( cs - > tls_session ) ) ;
2016-02-02 16:37:08 -05:00
DPRINTF ( E_INFO , L_CAST , " Connection to '%s' established using %s \n " , cs - > devname , proto ) ;
2016-01-17 14:59:16 -05:00
return cs ;
out_free_ev :
2016-02-03 16:54:18 -05:00
event_free ( cs - > reply_timeout ) ;
2016-01-17 14:59:16 -05:00
event_free ( cs - > ev ) ;
out_close_connection :
2019-02-28 17:19:05 -05:00
cast_disconnect ( cs - > server_fd ) ;
2016-01-17 14:59:16 -05:00
out_deinit_gnutls :
gnutls_deinit ( cs - > tls_session ) ;
2019-02-28 17:19:05 -05:00
out_free_master_session :
master_session_cleanup ( cs - > master_session ) ;
2016-01-17 14:59:16 -05:00
out_free_session :
free ( cs ) ;
return NULL ;
}
2016-02-02 06:02:14 -05:00
// Attempts to "nicely" bring down a session to wanted_state, and then issues
2016-08-12 18:05:00 -04:00
// the callback. If wanted_state is CAST_STATE_NONE/FAILED then the session is purged.
2016-01-23 19:14:07 -05:00
static void
2016-02-02 06:02:14 -05:00
cast_session_shutdown ( struct cast_session * cs , enum cast_state wanted_state )
2016-01-23 19:14:07 -05:00
{
2016-02-02 06:02:14 -05:00
int pending ;
int ret ;
2016-01-23 19:14:07 -05:00
2016-02-02 16:37:08 -05:00
if ( cs - > state = = wanted_state )
{
cast_status ( cs ) ;
return ;
}
else if ( cs - > state < wanted_state )
2016-01-23 19:14:07 -05:00
{
2016-08-12 18:05:00 -04:00
DPRINTF ( E_LOG , L_CAST , " Bug! Shutdown request got wanted_state (%d) that is higher than current state (%d) \n " , wanted_state , cs - > state ) ;
2016-01-23 19:14:07 -05:00
return ;
}
2016-02-02 06:02:14 -05:00
cs - > wanted_state = wanted_state ;
2016-01-23 19:14:07 -05:00
2016-02-02 06:02:14 -05:00
pending = 0 ;
switch ( cs - > state )
2016-01-23 19:14:07 -05:00
{
2019-02-28 17:19:05 -05:00
case CAST_STATE_MEDIA_STREAMING :
2016-02-02 06:02:14 -05:00
ret = cast_msg_send ( cs , MEDIA_STOP , cast_cb_stop_media ) ;
pending = 1 ;
break ;
2016-01-23 19:14:07 -05:00
2016-02-02 06:02:14 -05:00
case CAST_STATE_MEDIA_CONNECTED :
2019-02-28 17:19:05 -05:00
cast_disconnect ( cs - > udp_fd ) ;
cs - > udp_fd = - 1 ;
2016-02-02 06:02:14 -05:00
ret = cast_msg_send ( cs , MEDIA_CLOSE , NULL ) ;
cs - > state = CAST_STATE_MEDIA_LAUNCHED ;
if ( ( ret < 0 ) | | ( wanted_state > = CAST_STATE_MEDIA_LAUNCHED ) )
break ;
2016-01-23 19:14:07 -05:00
2017-02-13 13:09:01 -05:00
/* FALLTHROUGH */
2016-02-02 06:02:14 -05:00
case CAST_STATE_MEDIA_LAUNCHED :
ret = cast_msg_send ( cs , STOP , cast_cb_stop ) ;
pending = 1 ;
break ;
2016-01-23 19:14:07 -05:00
2016-02-02 06:02:14 -05:00
case CAST_STATE_CONNECTED :
ret = cast_msg_send ( cs , CLOSE , NULL ) ;
if ( ret = = 0 )
gnutls_bye ( cs - > tls_session , GNUTLS_SHUT_RDWR ) ;
2019-02-28 17:19:05 -05:00
cast_disconnect ( cs - > server_fd ) ;
2016-02-02 06:02:14 -05:00
cs - > server_fd = - 1 ;
cs - > state = CAST_STATE_DISCONNECTED ;
break ;
2016-01-23 19:14:07 -05:00
2016-02-02 06:02:14 -05:00
case CAST_STATE_DISCONNECTED :
ret = 0 ;
break ;
2016-01-23 19:14:07 -05:00
2016-02-02 06:02:14 -05:00
default :
DPRINTF ( E_LOG , L_CAST , " Bug! Shutdown doesn't know how to handle current state \n " ) ;
ret = - 1 ;
}
2016-01-23 19:14:07 -05:00
2016-02-02 06:02:14 -05:00
// We couldn't talk to the device, tell the user and clean up
if ( ret < 0 )
{
cs - > state = CAST_STATE_FAILED ;
cast_status ( cs ) ;
cast_session_cleanup ( cs ) ;
2016-01-23 19:14:07 -05:00
return ;
}
2016-02-02 06:02:14 -05:00
// If pending callbacks then we let them take care of the rest
if ( pending )
return ;
2016-01-23 19:14:07 -05:00
2016-02-02 06:02:14 -05:00
// Asked to destroy the session
2016-08-12 18:05:00 -04:00
if ( wanted_state = = CAST_STATE_NONE | | wanted_state = = CAST_STATE_FAILED )
2016-01-23 19:14:07 -05:00
{
2016-02-02 06:02:14 -05:00
cs - > state = wanted_state ;
cast_status ( cs ) ;
cast_session_cleanup ( cs ) ;
return ;
2016-01-23 19:14:07 -05:00
}
2016-02-02 06:02:14 -05:00
cast_status ( cs ) ;
2016-01-23 19:14:07 -05:00
}
2016-02-02 06:02:14 -05:00
2019-02-28 17:19:05 -05:00
/* ------------------ PREPARING AND SENDING CAST RTP PACKETS ---------------- */
// Makes a Cast RTP packet (source: Chromium's media/cast/net/rtp/rtp_packetizer.cc)
//
// A Cast RTP packet is made of:
// RTP header (12 bytes)
// Cast header (7 bytes)
// Extension data (4 bytes)
// Packet data
//
// The Cast header + extension (optional?) consists of:
// 0 1 2 3
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |k|r| n_ext | frame_id | packet id |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | max_packet_id | ref_frame_id | ext_type |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | ext_size | new_playout_delay_ms |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// k: Is the frame a key frame?
// r: Is there a reference frame id?
// n_ext: Number of Cast extensions (Chromium uses 1: Adaptive Latency)
// ext_type: 0x04 Adaptive Latency extension
// ext_size: 0x02 -> 2 bytes
// new_playout_delay_ms: ??
// OPUS encodes the rawbuf payload
static int
payload_encode ( struct evbuffer * evbuf , uint8_t * rawbuf , size_t rawbuf_size , int nsamples , struct media_quality * quality )
{
transcode_frame * frame ;
int len ;
2019-04-02 16:47:11 -04:00
frame = transcode_frame_new ( rawbuf , rawbuf_size , nsamples , quality ) ;
2019-02-28 17:19:05 -05:00
if ( ! frame )
{
2019-03-28 18:22:49 -04:00
DPRINTF ( E_LOG , L_CAST , " Could not convert raw PCM to frame (bufsize=%zu) \n " , rawbuf_size ) ;
2019-02-28 17:19:05 -05:00
return - 1 ;
}
len = transcode_encode ( evbuf , cast_encode_ctx , frame , 0 ) ;
transcode_frame_free ( frame ) ;
if ( len < 0 )
{
DPRINTF ( E_LOG , L_CAST , " Could not Opus encode frame \n " ) ;
return - 1 ;
}
return len ;
}
2016-02-02 06:02:14 -05:00
2016-01-26 15:49:32 -05:00
static int
2019-02-28 17:19:05 -05:00
packet_prepare ( struct rtp_packet * pkt , struct evbuffer * evbuf )
{
// Cast header
memset ( pkt - > payload , 0 , CAST_HEADER_SIZE ) ;
pkt - > payload [ 0 ] = 0xc1 ; // k = 1, r = 1 and one extension
pkt - > payload [ 1 ] = ( char ) pkt - > seqnum ;
// packet_id and max_packet_id don't seem to be used, so leave them at 0
pkt - > payload [ 6 ] = ( char ) pkt - > seqnum ;
pkt - > payload [ 7 ] = 0x04 ; // kCastRtpExtensionAdaptiveLatency has id (1 << 2)
pkt - > payload [ 8 ] = 0x02 ; // Extension will use two bytes
// leave extension values at 0, but Chromium sets them to:
// (frame.new_playout_delay_ms >> 8) and frame.new_playout_delay_ms (normal byte values are 0x03 0x20)
// Copy payload
return evbuffer_remove ( evbuf , pkt - > payload + CAST_HEADER_SIZE , pkt - > payload_len - CAST_HEADER_SIZE ) ;
}
static int
packet_send ( struct cast_session * cs , struct rtp_packet * pkt )
2016-01-17 14:59:16 -05:00
{
int ret ;
2019-02-28 17:19:05 -05:00
ret = send ( cs - > udp_fd , pkt - > data , pkt - > data_len , 0 ) ;
if ( ret < 0 )
2016-01-17 14:59:16 -05:00
{
2019-02-28 17:19:05 -05:00
DPRINTF ( E_LOG , L_CAST , " Send error for '%s': %s \n " , cs - > devname , strerror ( errno ) ) ;
return - 1 ;
}
else if ( ret ! = pkt - > data_len )
{
DPRINTF ( E_WARN , L_CAST , " Partial send (%d) for '%s' \n " , ret , cs - > devname ) ;
return 0 ;
2016-01-17 14:59:16 -05:00
}
2019-02-28 17:19:05 -05:00
/* DPRINTF(E_DBG, L_PLAYER, "RTP PACKET seqnum %u, rtptime %u, payload 0x%x, pktbuf_s %zu\n",
cs - > master_session - > rtp_session - > seqnum ,
cs - > master_session - > rtp_session - > pos ,
pkt - > header [ 1 ] ,
cs - > master_session - > rtp_session - > pktbuf_len
) ;
*/
return 0 ;
}
static int
packets_send ( struct cast_master_session * cms )
{
struct rtp_packet * pkt ;
struct cast_session * cs ;
struct cast_session * next ;
int len ;
int ret ;
// Encode payload into cast_encoded_data
len = payload_encode ( cast_encoded_data , cms - > rawbuf , cms - > rawbuf_size , cms - > samples_per_packet , & cms - > quality ) ;
if ( len < 0 )
2016-01-17 14:59:16 -05:00
return - 1 ;
2019-02-28 17:19:05 -05:00
// Chromium uses a RTP payload type that is 0xff
pkt = rtp_packet_next ( cms - > rtp_session , CAST_HEADER_SIZE + len , cms - > samples_per_packet , 0xff ) ;
2016-02-02 06:02:14 -05:00
2019-02-28 17:19:05 -05:00
// Creates Cast header + adds payload
ret = packet_prepare ( pkt , cast_encoded_data ) ;
2016-01-17 14:59:16 -05:00
if ( ret < 0 )
2019-02-28 17:19:05 -05:00
return - 1 ;
for ( cs = cast_sessions ; cs ; cs = next )
2016-01-17 14:59:16 -05:00
{
2019-02-28 17:19:05 -05:00
next = cs - > next ;
if ( cs - > master_session ! = cms | | ! ( cs - > state & CAST_STATE_F_MEDIA_CONNECTED ) )
continue ;
ret = packet_send ( cs , pkt ) ;
if ( ret < 0 )
{
// Downgrade state immediately to avoid further write attempts
cs - > state = CAST_STATE_MEDIA_LAUNCHED ;
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
}
2016-01-17 14:59:16 -05:00
}
2016-01-26 15:49:32 -05:00
2019-02-28 17:19:05 -05:00
// Commits packet to retransmit buffer, and prepares the session for the next packet
rtp_packet_commit ( cms - > rtp_session , pkt ) ;
2016-01-17 14:59:16 -05:00
return 0 ;
}
2019-02-28 17:19:05 -05:00
/* TODO This does not currently work - need to investigate what sync the devices support
2016-01-26 15:49:32 -05:00
static void
2019-02-28 17:19:05 -05:00
packets_sync_send ( struct cast_master_session * cms , struct timespec pts )
2016-01-26 15:49:32 -05:00
{
2019-02-28 17:19:05 -05:00
struct rtp_packet * sync_pkt ;
struct cast_session * cs ;
struct rtcp_timestamp cur_stamp ;
struct timespec ts ;
bool is_sync_time ;
2016-01-26 15:49:32 -05:00
2019-02-28 17:19:05 -05:00
// Check if it is time send a sync packet to sessions that are already running
is_sync_time = rtp_sync_is_time ( cms - > rtp_session ) ;
// (See raop.c for more comments on sync packets)
cur_stamp . ts . tv_sec = pts . tv_sec ;
cur_stamp . ts . tv_nsec = pts . tv_nsec ;
clock_gettime ( CLOCK_MONOTONIC , & ts ) ;
cur_stamp . pos = cms - > rtp_session - > pos + cms - > evbuf_samples - cms - > output_buffer_samples ;
for ( cs = cast_sessions ; cs ; cs = cs - > next )
{
if ( cs - > master_session ! = cms )
continue ;
// A device has joined and should get an init sync packet
if ( cs - > state = = CAST_STATE_MEDIA_CONNECTED )
{
sync_pkt = rtp_sync_packet_next ( cms - > rtp_session , & cur_stamp , 0x80 ) ;
packet_send ( cs , sync_pkt ) ;
DPRINTF ( E_DBG , L_PLAYER , " Start sync packet sent to '%s': cur_pos=% " PRIu32 " , cur_ts=%lu:%lu, now=%lu:%lu, rtptime=% " PRIu32 " , \n " ,
cs - > devname , cur_stamp . pos , cur_stamp . ts . tv_sec , cur_stamp . ts . tv_nsec , ts . tv_sec , ts . tv_nsec , cms - > rtp_session - > pos ) ;
}
else if ( is_sync_time & & cs - > state = = CAST_STATE_MEDIA_STREAMING )
{
sync_pkt = rtp_sync_packet_next ( cms - > rtp_session , & cur_stamp , 0x80 ) ;
packet_send ( cs , sync_pkt ) ;
}
}
2016-02-02 06:02:14 -05:00
}
2019-02-28 17:19:05 -05:00
*/
/* ------------------ INTERFACE FUNCTIONS CALLED BY OUTPUTS.C --------------- */
2016-02-02 06:02:14 -05:00
static int
2019-02-28 17:19:05 -05:00
cast_device_start_generic ( struct output_device * device , int callback_id , cast_reply_cb reply_cb )
2016-02-02 06:02:14 -05:00
{
struct cast_session * cs ;
int ret ;
2019-02-28 17:19:05 -05:00
cs = cast_session_make ( device , AF_INET6 , callback_id ) ;
2016-02-02 06:02:14 -05:00
if ( cs )
{
ret = cast_msg_send ( cs , CONNECT , NULL ) ;
if ( ret = = 0 )
2019-02-28 17:19:05 -05:00
ret = cast_msg_send ( cs , GET_STATUS , reply_cb ) ;
2016-02-02 06:02:14 -05:00
if ( ret < 0 )
{
DPRINTF ( E_WARN , L_CAST , " Could not send CONNECT or GET_STATUS request on IPv6 (start) \n " ) ;
cast_session_cleanup ( cs ) ;
}
else
return 0 ;
}
2019-02-28 17:19:05 -05:00
cs = cast_session_make ( device , AF_INET , callback_id ) ;
2016-02-02 06:02:14 -05:00
if ( ! cs )
return - 1 ;
ret = cast_msg_send ( cs , CONNECT , NULL ) ;
if ( ret = = 0 )
2019-02-28 17:19:05 -05:00
ret = cast_msg_send ( cs , GET_STATUS , reply_cb ) ;
2016-02-02 06:02:14 -05:00
if ( ret < 0 )
2016-01-31 12:42:32 -05:00
{
2016-02-02 06:02:14 -05:00
DPRINTF ( E_LOG , L_CAST , " Could not send CONNECT or GET_STATUS request on IPv4 (start) \n " ) ;
2016-01-31 12:42:32 -05:00
cast_session_cleanup ( cs ) ;
2016-02-02 06:02:14 -05:00
return - 1 ;
2016-01-31 12:42:32 -05:00
}
2016-02-02 06:02:14 -05:00
return 0 ;
2016-01-26 15:49:32 -05:00
}
2016-01-29 18:36:05 -05:00
static int
2019-02-28 17:19:05 -05:00
cast_device_start ( struct output_device * device , int callback_id )
2016-01-29 18:36:05 -05:00
{
2019-02-28 17:19:05 -05:00
return cast_device_start_generic ( device , callback_id , cast_cb_startup_connect ) ;
}
static int
cast_device_probe ( struct output_device * device , int callback_id )
{
return cast_device_start_generic ( device , callback_id , cast_cb_probe ) ;
}
static int
cast_device_stop ( struct output_device * device , int callback_id )
{
struct cast_session * cs = device - > session ;
cs - > callback_id = callback_id ;
cast_session_shutdown ( cs , CAST_STATE_NONE ) ;
return 0 ;
}
static int
cast_device_flush ( struct output_device * device , int callback_id )
{
struct cast_session * cs = device - > session ;
cs - > callback_id = callback_id ;
2019-04-05 17:06:05 -04:00
cs - > state = CAST_STATE_MEDIA_CONNECTED ;
cast_status ( cs ) ;
2019-02-28 17:19:05 -05:00
return 0 ;
}
2016-01-29 18:36:05 -05:00
2019-02-28 17:19:05 -05:00
static void
cast_device_cb_set ( struct output_device * device , int callback_id )
{
struct cast_session * cs = device - > session ;
cs - > callback_id = callback_id ;
}
static int
cast_device_volume_set ( struct output_device * device , int callback_id )
{
struct cast_session * cs = device - > session ;
int ret ;
if ( ! cs | | ! ( cs - > state & CAST_STATE_F_MEDIA_CONNECTED ) )
2016-01-29 18:36:05 -05:00
return 0 ;
cs - > volume = 0.01 * device - > volume ;
ret = cast_msg_send ( cs , SET_VOLUME , cast_cb_volume ) ;
if ( ret < 0 )
{
2016-02-02 06:02:14 -05:00
cast_session_shutdown ( cs , CAST_STATE_FAILED ) ;
2016-01-29 18:36:05 -05:00
return 0 ;
}
2016-02-02 06:02:14 -05:00
// Setting it here means it will not be used for the above cast_session_shutdown
2019-02-28 17:19:05 -05:00
cs - > callback_id = callback_id ;
2016-01-29 18:36:05 -05:00
return 1 ;
}
2016-01-31 12:42:32 -05:00
static void
2019-02-28 17:19:05 -05:00
cast_write ( struct output_buffer * obuf )
2016-01-31 12:42:32 -05:00
{
2019-02-28 17:19:05 -05:00
struct cast_master_session * cms ;
struct cast_session * cs ;
int i ;
2016-01-31 12:42:32 -05:00
2019-02-28 17:19:05 -05:00
if ( ! cast_sessions )
return ;
2016-01-31 12:42:32 -05:00
2019-02-28 17:19:05 -05:00
cms = cast_master_session ;
2016-01-31 12:42:32 -05:00
2019-02-28 17:19:05 -05:00
for ( i = 0 ; obuf - > data [ i ] . buffer ; i + + )
2016-01-31 12:42:32 -05:00
{
2019-02-28 17:19:05 -05:00
if ( ! quality_is_equal ( & obuf - > data [ i ] . quality , & cast_quality_default ) )
2016-01-31 12:42:32 -05:00
continue ;
2019-02-28 17:19:05 -05:00
// Sends sync packets to new sessions, and if it is sync time then also to old sessions
// packets_sync_send(cms, obuf->pts);
2016-01-31 12:42:32 -05:00
2019-02-28 17:19:05 -05:00
// TODO avoid this copy
evbuffer_add ( cms - > evbuf , obuf - > data [ i ] . buffer , obuf - > data [ i ] . bufsize ) ;
cms - > evbuf_samples + = obuf - > data [ i ] . samples ;
2016-01-31 12:42:32 -05:00
2019-02-28 17:19:05 -05:00
// Send as many packets as we have data for (one packet requires rawbuf_size bytes)
while ( evbuffer_get_length ( cms - > evbuf ) > = cms - > rawbuf_size )
{
evbuffer_remove ( cms - > evbuf , cms - > rawbuf , cms - > rawbuf_size ) ;
cms - > evbuf_samples - = cms - > samples_per_packet ;
2016-01-31 12:42:32 -05:00
2019-02-28 17:19:05 -05:00
packets_send ( cms ) ;
}
}
2016-01-31 12:42:32 -05:00
2019-02-28 17:19:05 -05:00
// Check for devices that have joined since last write (we have already sent them
// initialization sync and rtp packets via packets_sync_send and packets_send)
for ( cs = cast_sessions ; cs ; cs = cs - > next )
{
if ( cs - > state ! = CAST_STATE_MEDIA_CONNECTED )
continue ;
2016-01-26 15:49:32 -05:00
2019-02-28 17:19:05 -05:00
cs - > state = CAST_STATE_MEDIA_STREAMING ;
// Make a cb?
}
2016-01-26 15:49:32 -05:00
}
2019-04-09 15:03:14 -04:00
/* Doesn't work, but left here so it can be fixed
2019-03-26 18:13:09 -04:00
static void
cast_metadata_send ( struct output_metadata * metadata )
{
struct cast_session * cs ;
struct cast_session * next ;
for ( cs = cast_sessions ; cs ; cs = next )
{
next = cs - > next ;
if ( cs - > state ! = CAST_STATE_MEDIA_CONNECTED )
continue ;
metadata_send ( cs ) ;
}
// TODO free the metadata
}
2019-04-09 15:03:14 -04:00
*/
2019-03-26 18:13:09 -04:00
2016-01-26 15:49:32 -05:00
static int
2016-01-17 14:59:16 -05:00
cast_init ( void )
{
2019-02-28 17:19:05 -05:00
struct decode_ctx * decode_ctx ;
2016-10-12 16:24:58 -04:00
int family ;
2016-01-26 15:49:32 -05:00
int i ;
2016-01-17 14:59:16 -05:00
int ret ;
2016-01-26 15:49:32 -05:00
// Sanity check
for ( i = 1 ; cast_msg [ i ] . type ; i + + )
2016-01-23 19:14:07 -05:00
{
2016-01-26 15:49:32 -05:00
if ( cast_msg [ i ] . type ! = i )
{
DPRINTF ( E_LOG , L_CAST , " BUG! Cast messages and types are misaligned (type %d!=%d). Could not initialize. \n " , cast_msg [ i ] . type , i ) ;
return - 1 ;
}
2016-01-23 19:14:07 -05:00
}
2016-02-12 16:04:07 -05:00
// Setting the cert file seems not to be required
if ( ( ( ret = gnutls_global_init ( ) ) ! = GNUTLS_E_SUCCESS )
| | ( ( ret = gnutls_certificate_allocate_credentials ( & tls_credentials ) ) ! = GNUTLS_E_SUCCESS )
// || ((ret = gnutls_certificate_set_x509_trust_file(tls_credentials, CAFILE, GNUTLS_X509_FMT_PEM)) < 0)
)
2016-01-17 14:59:16 -05:00
{
DPRINTF ( E_LOG , L_CAST , " Could not initialize GNUTLS: %s \n " , gnutls_strerror ( ret ) ) ;
return - 1 ;
}
2019-04-02 16:47:11 -04:00
decode_ctx = transcode_decode_setup_raw ( XCODE_PCM16 , & cast_quality_default ) ;
2019-02-28 17:19:05 -05:00
if ( ! decode_ctx )
{
DPRINTF ( E_LOG , L_CAST , " Could not create decoding context \n " ) ;
goto out_tls_deinit ;
}
2019-04-02 16:47:11 -04:00
cast_encode_ctx = transcode_encode_setup ( XCODE_OPUS , & cast_quality_default , decode_ctx , NULL , 0 , 0 ) ;
2019-02-28 17:19:05 -05:00
transcode_decode_cleanup ( & decode_ctx ) ;
if ( ! cast_encode_ctx )
2016-01-31 12:42:32 -05:00
{
2019-02-28 17:19:05 -05:00
DPRINTF ( E_LOG , L_CAST , " Will not be able to stream Chromecast, libav does not support Opus encoding \n " ) ;
2016-01-31 12:42:32 -05:00
goto out_tls_deinit ;
}
2016-10-12 16:24:58 -04:00
if ( cfg_getbool ( cfg_getsec ( cfg , " general " ) , " ipv6 " ) )
family = AF_UNSPEC ;
else
family = AF_INET ;
2016-01-26 15:49:32 -05:00
2018-10-14 16:10:28 -04:00
ret = mdns_browse ( " _googlecast._tcp " , family , cast_device_cb , 0 ) ;
2016-01-26 15:49:32 -05:00
if ( ret < 0 )
{
2016-02-02 16:37:08 -05:00
DPRINTF ( E_LOG , L_CAST , " Could not add mDNS browser for Chromecast devices \n " ) ;
2019-02-28 17:19:05 -05:00
goto out_encode_ctx_free ;
2016-01-26 15:49:32 -05:00
}
2019-02-28 17:19:05 -05:00
CHECK_NULL ( L_CAST , cast_encoded_data = evbuffer_new ( ) ) ;
2016-01-17 14:59:16 -05:00
return 0 ;
2016-01-26 15:49:32 -05:00
2019-02-28 17:19:05 -05:00
out_encode_ctx_free :
transcode_encode_cleanup ( & cast_encode_ctx ) ;
2016-01-26 15:49:32 -05:00
out_tls_deinit :
gnutls_certificate_free_credentials ( tls_credentials ) ;
gnutls_global_deinit ( ) ;
return - 1 ;
2016-01-17 14:59:16 -05:00
}
2016-01-26 15:49:32 -05:00
static void
2016-01-17 14:59:16 -05:00
cast_deinit ( void )
{
struct cast_session * cs ;
2019-02-28 17:19:05 -05:00
for ( cs = cast_sessions ; cast_sessions ; cs = cast_sessions )
2016-01-17 14:59:16 -05:00
{
2019-02-28 17:19:05 -05:00
cast_sessions = cs - > next ;
2016-01-17 14:59:16 -05:00
cast_session_free ( cs ) ;
}
2019-02-28 17:19:05 -05:00
evbuffer_free ( cast_encoded_data ) ;
transcode_encode_cleanup ( & cast_encode_ctx ) ;
2016-01-31 12:42:32 -05:00
2016-01-17 14:59:16 -05:00
gnutls_certificate_free_credentials ( tls_credentials ) ;
gnutls_global_deinit ( ) ;
}
2016-01-26 15:49:32 -05:00
struct output_definition output_cast =
{
. name = " Chromecast " ,
. type = OUTPUT_TYPE_CAST ,
. priority = 2 ,
. disabled = 0 ,
2019-03-26 18:13:09 -04:00
. init = cast_init ,
. deinit = cast_deinit ,
2016-01-26 15:49:32 -05:00
. device_start = cast_device_start ,
2016-02-02 06:02:14 -05:00
. device_probe = cast_device_probe ,
2019-02-28 17:19:05 -05:00
. device_stop = cast_device_stop ,
. device_flush = cast_device_flush ,
. device_cb_set = cast_device_cb_set ,
. device_volume_set = cast_device_volume_set ,
. write = cast_write ,
2019-03-26 18:13:09 -04:00
// .metadata_prepare = cast_metadata_prepare,
2019-04-09 15:03:14 -04:00
// .metadata_send = cast_metadata_send,
2019-03-26 18:13:09 -04:00
// .metadata_purge = cast_metadata_purge,
2016-01-26 15:49:32 -05:00
} ;