/** RTSP requests handler * @param id selected track for non-aggregate URLs, * NULL for aggregate URLs */ static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id, httpd_client_t *cl, httpd_message_t *answer, const httpd_message_t *query ) { sout_stream_t *p_stream = rtsp->owner; char psz_sesbuf[17]; const char *psz_session = NULL, *psz; char control[sizeof("rtsp://[]:12345") + NI_MAXNUMERICHOST + strlen( rtsp->psz_path )]; time_t now; time (&now); if( answer == NULL || query == NULL || cl == NULL ) return VLC_SUCCESS; else { /* Build self-referential control URL */ char ip[NI_MAXNUMERICHOST], *ptr; httpd_ServerIP( cl, ip ); ptr = strchr( ip, '%' ); if( ptr != NULL ) *ptr = '\0'; if( strchr( ip, ':' ) != NULL ) sprintf( control, "rtsp://[%s]:%u%s", ip, rtsp->port, rtsp->psz_path ); else sprintf( control, "rtsp://%s:%u%s", ip, rtsp->port, rtsp->psz_path ); } /* */ answer->i_proto = HTTPD_PROTO_RTSP; answer->i_version= 0; answer->i_type = HTTPD_MSG_ANSWER; answer->i_body = 0; answer->p_body = NULL; httpd_MsgAdd( answer, "Server", "%s", PACKAGE_STRING ); /* Date: is always allowed, and sometimes mandatory with RTSP/2.0. */ struct tm ut; if (gmtime_r (&now, &ut) != NULL) { /* RFC1123 format, GMT is mandatory */ static const char wdays[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const char mons[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; httpd_MsgAdd (answer, "Date", "%s, %02u %s %04u %02u:%02u:%02u GMT", wdays[ut.tm_wday], ut.tm_mday, mons[ut.tm_mon], 1900 + ut.tm_year, ut.tm_hour, ut.tm_min, ut.tm_sec); } if( query->i_proto != HTTPD_PROTO_RTSP ) { answer->i_status = 505; } else if( httpd_MsgGet( query, "Require" ) != NULL ) { answer->i_status = 551; httpd_MsgAdd( answer, "Unsupported", "%s", httpd_MsgGet( query, "Require" ) ); } else switch( query->i_type ) { case HTTPD_MSG_DESCRIBE: { /* Aggregate-only */ if( id != NULL ) { answer->i_status = 460; break; } answer->i_status = 200; httpd_MsgAdd( answer, "Content-Type", "%s", "application/sdp" ); httpd_MsgAdd( answer, "Content-Base", "%s", control ); answer->p_body = (uint8_t *)SDPGenerate( rtsp->owner, control ); if( answer->p_body != NULL ) answer->i_body = strlen( (char *)answer->p_body ); else answer->i_status = 500; break; } case HTTPD_MSG_SETUP: /* Non-aggregate-only */ if( id == NULL ) { answer->i_status = 459; break; } psz_session = httpd_MsgGet( query, "Session" ); answer->i_status = 461; for( const char *tpt = httpd_MsgGet( query, "Transport" ); tpt != NULL; tpt = transport_next( tpt ) ) { bool b_multicast = true, b_unsupp = false; unsigned loport = 5004, hiport = 5005; /* from RFC3551 */ /* Check transport protocol. */ /* Currently, we only support RTP/AVP over UDP */ if( strncmp( tpt, "RTP/AVP", 7 ) ) continue; tpt += 7; if( strncmp( tpt, "/UDP", 4 ) == 0 ) tpt += 4; if( strchr( ";,", *tpt ) == NULL ) continue; /* Parse transport options */ for( const char *opt = parameter_next( tpt ); opt != NULL; opt = parameter_next( opt ) ) { if( strncmp( opt, "multicast", 9 ) == 0) b_multicast = true; else if( strncmp( opt, "unicast", 7 ) == 0 ) b_multicast = false; else if( sscanf( opt, "client_port=%u-%u", &loport, &hiport ) == 2 ) ; else if( strncmp( opt, "mode=", 5 ) == 0 ) { if( strncasecmp( opt + 5, "play", 4 ) && strncasecmp( opt + 5, "\"PLAY\"", 6 ) ) { /* Not playing?! */ b_unsupp = true; break; } } else if( strncmp( opt,"destination=", 12 ) == 0 ) { answer->i_status = 403; b_unsupp = true; } else { /* * Every other option is unsupported: * * "source" and "append" are invalid (server-only); * "ssrc" also (as clarified per RFC2326bis). * * For multicast, "port", "layers", "ttl" are set by the * stream output configuration. * * For unicast, we want to decide "server_port" values. * * "interleaved" is not implemented. */ b_unsupp = true; break; } } if( b_unsupp ) continue; if( b_multicast ) { const char *dst = id->dst; if( dst == NULL ) continue; if( psz_session == NULL ) { /* Create a dummy session ID */ snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%d", rand() ); psz_session = psz_sesbuf; } answer->i_status = 200; httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;destination=%s;port=%u-%u;" "ttl=%d;mode=play", dst, id->loport, id->hiport, ( id->ttl > 0 ) ? id->ttl : 1 ); } else { char ip[NI_MAXNUMERICHOST], src[NI_MAXNUMERICHOST]; rtsp_session_t *ses = NULL; rtsp_strack_t track = { id->sout_id, -1, false }; int sport; if( httpd_ClientIP( cl, ip ) == NULL ) { answer->i_status = 500; continue; } track.fd = net_ConnectDgram( p_stream, ip, loport, -1, IPPROTO_UDP ); if( track.fd == -1 ) { msg_Err( p_stream, "cannot create RTP socket for %s port %u", ip, loport ); answer->i_status = 500; continue; } net_GetSockAddress( track.fd, src, &sport ); vlc_mutex_lock( &rtsp->lock ); if( psz_session == NULL ) { ses = RtspClientNew( rtsp ); snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%"PRIx64, ses->id ); psz_session = psz_sesbuf; } else { /* FIXME: we probably need to remove an access out, * if there is already one for the same ID */ ses = RtspClientGet( rtsp, psz_session ); if( ses == NULL ) { answer->i_status = 454; vlc_mutex_unlock( &rtsp->lock ); continue; } } INSERT_ELEM( ses->trackv, ses->trackc, ses->trackc, track ); vlc_mutex_unlock( &rtsp->lock ); httpd_ServerIP( cl, ip ); if( strcmp( src, ip ) ) { /* Specify source IP if it is different from the RTSP * control connection server address */ char *ptr = strchr( src, '%' ); if( ptr != NULL ) *ptr = '\0'; /* remove scope ID */ httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;unicast;source=%s;" "client_port=%u-%u;server_port=%u-%u;" "ssrc=%08X;mode=play", src, loport, loport + 1, sport, sport + 1, id->ssrc ); } else { httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;unicast;" "client_port=%u-%u;server_port=%u-%u;" "ssrc=%08X;mode=play", loport, loport + 1, sport, sport + 1, id->ssrc ); } answer->i_status = 200; } break; } break; case HTTPD_MSG_PLAY: { rtsp_session_t *ses; answer->i_status = 200; psz_session = httpd_MsgGet( query, "Session" ); const char *range = httpd_MsgGet (query, "Range"); if (range && strncmp (range, "npt=", 4)) { answer->i_status = 501; break; } vlc_mutex_lock( &rtsp->lock ); ses = RtspClientGet( rtsp, psz_session ); if( ses != NULL ) { /* FIXME: we really need to limit the number of tracks... */ char info[ses->trackc * ( strlen( control ) + sizeof("url=/trackID=123;seq=65535, ") ) + 1]; size_t infolen = 0; for( int i = 0; i < ses->trackc; i++ ) { rtsp_strack_t *tr = ses->trackv + i; if( ( id == NULL ) || ( tr->id == id->sout_id ) ) { if( !tr->playing ) { tr->playing = true; rtp_add_sink( tr->id, tr->fd, false ); } infolen += sprintf( info + infolen, "url=%s/trackID=%u;seq=%u, ", control, rtp_get_num( tr->id ), rtp_get_seq( tr->id ) ); } } if( infolen > 0 ) { info[infolen - 2] = '\0'; /* remove trailing ", " */ httpd_MsgAdd( answer, "RTP-Info", "%s", info ); } } vlc_mutex_unlock( &rtsp->lock ); if( httpd_MsgGet( query, "Scale" ) != NULL ) httpd_MsgAdd( answer, "Scale", "1." ); break; } case HTTPD_MSG_PAUSE: answer->i_status = 405; httpd_MsgAdd( answer, "Allow", "%s, TEARDOWN, PLAY, GET_PARAMETER", ( id != NULL ) ? "SETUP" : "DESCRIBE" ); break; case HTTPD_MSG_GETPARAMETER: if( query->i_body > 0 ) { answer->i_status = 451; break; } psz_session = httpd_MsgGet( query, "Session" ); answer->i_status = 200; break; case HTTPD_MSG_TEARDOWN: { rtsp_session_t *ses; answer->i_status = 200; psz_session = httpd_MsgGet( query, "Session" ); vlc_mutex_lock( &rtsp->lock ); ses = RtspClientGet( rtsp, psz_session ); if( ses != NULL ) { if( id == NULL ) /* Delete the entire session */ RtspClientDel( rtsp, ses ); else /* Delete one track from the session */ for( int i = 0; i < ses->trackc; i++ ) { if( ses->trackv[i].id == id->sout_id ) { rtp_del_sink( id->sout_id, ses->trackv[i].fd ); REMOVE_ELEM( ses->trackv, ses->trackc, i ); } } } vlc_mutex_unlock( &rtsp->lock ); break; } default: return VLC_EGENERIC; } if( psz_session ) httpd_MsgAdd( answer, "Session", "%s"/*;timeout=5*/, psz_session ); httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body ); httpd_MsgAdd( answer, "Cache-Control", "no-cache" ); psz = httpd_MsgGet( query, "Cseq" ); if( psz != NULL ) httpd_MsgAdd( answer, "Cseq", "%s", psz ); psz = httpd_MsgGet( query, "Timestamp" ); if( psz != NULL ) httpd_MsgAdd( answer, "Timestamp", "%s", psz ); return VLC_SUCCESS; }
/***************************************************************************** * Open: open the file *****************************************************************************/ static int Open( vlc_object_t *p_this ) { sout_access_out_t *p_access = (sout_access_out_t*)p_this; sout_access_out_sys_t *p_sys; char *psz_dst_addr = NULL; int i_dst_port; int i_handle; config_ChainParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg ); config_ChainParse( p_access, "", ppsz_core_options, p_access->p_cfg ); if (var_Create (p_access, "dst-port", VLC_VAR_INTEGER) || var_Create (p_access, "src-port", VLC_VAR_INTEGER) || var_Create (p_access, "dst-addr", VLC_VAR_STRING) || var_Create (p_access, "src-addr", VLC_VAR_STRING)) { return VLC_ENOMEM; } if( !( p_sys = malloc ( sizeof( *p_sys ) ) ) ) return VLC_ENOMEM; p_access->p_sys = p_sys; i_dst_port = DEFAULT_PORT; char *psz_parser = psz_dst_addr = strdup( p_access->psz_path ); if( !psz_dst_addr ) { free( p_sys ); return VLC_ENOMEM; } if (psz_parser[0] == '[') psz_parser = strchr (psz_parser, ']'); psz_parser = strchr (psz_parser ? psz_parser : psz_dst_addr, ':'); if (psz_parser != NULL) { *psz_parser++ = '\0'; i_dst_port = atoi (psz_parser); } i_handle = net_ConnectDgram( p_this, psz_dst_addr, i_dst_port, -1, IPPROTO_UDP ); free (psz_dst_addr); if( i_handle == -1 ) { msg_Err( p_access, "failed to create raw UDP socket" ); free (p_sys); return VLC_EGENERIC; } else { char addr[NI_MAXNUMERICHOST]; int port; if (net_GetSockAddress (i_handle, addr, &port) == 0) { msg_Dbg (p_access, "source: %s port %d", addr, port); var_SetString (p_access, "src-addr", addr); var_SetInteger (p_access, "src-port", port); } if (net_GetPeerAddress (i_handle, addr, &port) == 0) { msg_Dbg (p_access, "destination: %s port %d", addr, port); var_SetString (p_access, "dst-addr", addr); var_SetInteger (p_access, "dst-port", port); } } shutdown( i_handle, SHUT_RD ); p_sys->i_caching = UINT64_C(1000) * var_GetInteger( p_access, SOUT_CFG_PREFIX "caching"); p_sys->i_handle = i_handle; p_sys->i_mtu = var_CreateGetInteger( p_this, "mtu" ); p_sys->b_mtu_warning = false; p_sys->p_fifo = block_FifoNew(); p_sys->p_empty_blocks = block_FifoNew(); p_sys->p_buffer = NULL; if( vlc_clone( &p_sys->thread, ThreadWrite, p_access, VLC_THREAD_PRIORITY_HIGHEST ) ) { msg_Err( p_access, "cannot spawn sout access thread" ); block_FifoRelease( p_sys->p_fifo ); block_FifoRelease( p_sys->p_empty_blocks ); net_Close (i_handle); free (p_sys); return VLC_EGENERIC; } p_access->pf_write = Write; p_access->pf_seek = Seek; p_access->pf_control = Control; return VLC_SUCCESS; }
/***************************************************************************** * 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(std::nothrow) sout_stream_sys_t; if (p_sys == NULL) return VLC_ENOMEM; p_stream->p_sys = p_sys; config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg); char *psz_ipChromecast = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "ip"); if (psz_ipChromecast == NULL) { msg_Err(p_stream, "No Chromecast receiver IP provided"); Clean(p_stream); 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_stream); return VLC_EGENERIC; } p_sys->i_status = CHROMECAST_TLS_CONNECTED; char psz_localIP[NI_MAXNUMERICHOST]; if (net_GetSockAddress(p_sys->i_sock_fd, psz_localIP, NULL)) { msg_Err(p_this, "Cannot get local IP address"); Clean(p_stream); return VLC_EGENERIC; } p_sys->serverIP = psz_localIP; char *psz_mux = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mux"); if (psz_mux == NULL) { Clean(p_stream); return VLC_EGENERIC; } char *psz_chain = NULL; int i_bytes = asprintf(&psz_chain, "http{dst=:%u/stream,mux=%s}", (unsigned)var_InheritInteger(p_stream, SOUT_CFG_PREFIX"http-port"), psz_mux); free(psz_mux); if (i_bytes < 0) { Clean(p_stream); 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_stream); return VLC_EGENERIC; } vlc_mutex_init(&p_sys->lock); vlc_cond_init(&p_sys->loadCommandCond); // Start the Chromecast event thread. if (vlc_clone(&p_sys->chromecastThread, chromecastThread, p_stream, VLC_THREAD_PRIORITY_LOW)) { msg_Err(p_stream, "Could not start the Chromecast talking thread"); Clean(p_stream); return VLC_EGENERIC; } /* Ugly part: * We want to be sure that the Chromecast receives the first data packet sent by * the HTTP server. */ // Lock the sout thread until we have sent the media loading command to the Chromecast. int i_ret = 0; const mtime_t deadline = mdate() + 6 * CLOCK_FREQ; vlc_mutex_lock(&p_sys->lock); while (p_sys->i_status != CHROMECAST_MEDIA_LOAD_SENT) { i_ret = vlc_cond_timedwait(&p_sys->loadCommandCond, &p_sys->lock, deadline); if (i_ret == ETIMEDOUT) { msg_Err(p_stream, "Timeout reached before sending the media loading command"); vlc_mutex_unlock(&p_sys->lock); vlc_cancel(p_sys->chromecastThread); Clean(p_stream); return VLC_EGENERIC; } } vlc_mutex_unlock(&p_sys->lock); /* Even uglier: sleep more to let to the Chromecast initiate the connection * to the http server. */ msleep(2 * CLOCK_FREQ); // Set the sout callbacks. p_stream->pf_add = Add; p_stream->pf_del = Del; p_stream->pf_send = Send; return VLC_SUCCESS; }