X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fstream_out%2Frtp.c;h=fb93f2346a27c3d069d9f5e18291d4fd1ed6eb95;hb=824481bce797de9122c37711468cb36178edf979;hp=99daaf2bc6f172d89e9644ec812d297a514a2603;hpb=a2bfd50577abf06b4d9d4895d3665e08418efc6e;p=vlc diff --git a/modules/stream_out/rtp.c b/modules/stream_out/rtp.c index 99daaf2bc6..fb93f2346a 100644 --- a/modules/stream_out/rtp.c +++ b/modules/stream_out/rtp.c @@ -39,7 +39,19 @@ #include "rtp.h" #ifdef HAVE_UNISTD_H +# include # include +# include +# include +#endif +#ifdef HAVE_LINUX_DCCP_H +# include +#endif +#ifndef IPPROTO_DCCP +# define IPPROTO_DCCP 33 +#endif +#ifndef IPPROTO_UDPLITE +# define IPPROTO_UDPLITE 136 #endif #include @@ -66,10 +78,10 @@ #define NAME_LONGTEXT N_( \ "This is the name of the session that will be announced in the SDP " \ "(Session Descriptor)." ) -#define DESC_TEXT N_("Session description") +#define DESC_TEXT N_("Session descriptipn") #define DESC_LONGTEXT N_( \ - "This allows you to give a broader description of the stream, that will " \ - "be announced in the SDP (Session Descriptor)." ) + "This allows you to give a short description with details about the stream, " \ + "that will be announced in the SDP (Session Descriptor)." ) #define URL_TEXT N_("Session URL") #define URL_LONGTEXT N_( \ "This allows you to give an URL with more details about the stream " \ @@ -77,8 +89,13 @@ "be announced in the SDP (Session Descriptor)." ) #define EMAIL_TEXT N_("Session email") #define EMAIL_LONGTEXT N_( \ - "This allows you to give a contact mail address for the stream, that will " \ - "be announced in the SDP (Session Descriptor)." ) + "This allows you to give a contact mail address for the stream, that will " \ + "be announced in the SDP (Session Descriptor)." ) +#define PHONE_TEXT N_("Session phone number") +#define PHONE_LONGTEXT N_( \ + "This allows you to give a contact telephone number for the stream, that will " \ + "be announced in the SDP (Session Descriptor)." ) + #define PORT_TEXT N_("Port") #define PORT_LONGTEXT N_( \ "This allows you to specify the base port for the RTP streaming." ) @@ -95,6 +112,21 @@ "the multicast packets sent by the stream output (0 = use operating " \ "system built-in default).") +#define RTCP_MUX_TEXT N_("RTP/RTCP multiplexing") +#define RTCP_MUX_LONGTEXT N_( \ + "This sends and receives RTCP packet multiplexed over the same port " \ + "as RTP packets." ) + +#define DCCP_TEXT N_("DCCP transport") +#define DCCP_LONGTEXT N_( \ + "This enables DCCP instead of UDP as a transport for RTP." ) +#define TCP_TEXT N_("TCP transport") +#define TCP_LONGTEXT N_( \ + "This enables TCP instead of UDP as a transport for RTP." ) +#define UDP_LITE_TEXT N_("UDP-Lite transport") +#define UDP_LITE_LONGTEXT N_( \ + "This enables UDP-Lite instead of UDP as a transport for RTP." ) + #define RFC3016_TEXT N_("MP4A LATM") #define RFC3016_LONGTEXT N_( \ "This allows you to stream MPEG4 LATM audio streams (see RFC3016)." ) @@ -120,7 +152,7 @@ vlc_module_begin(); add_string( SOUT_CFG_PREFIX "mux", "", NULL, MUX_TEXT, MUX_LONGTEXT, VLC_TRUE ); - add_string( SOUT_CFG_PREFIX "name", "NONE", NULL, NAME_TEXT, + add_string( SOUT_CFG_PREFIX "name", "", NULL, NAME_TEXT, NAME_LONGTEXT, VLC_TRUE ); add_string( SOUT_CFG_PREFIX "description", "", NULL, DESC_TEXT, DESC_LONGTEXT, VLC_TRUE ); @@ -128,6 +160,8 @@ vlc_module_begin(); URL_LONGTEXT, VLC_TRUE ); add_string( SOUT_CFG_PREFIX "email", "", NULL, EMAIL_TEXT, EMAIL_LONGTEXT, VLC_TRUE ); + add_string( SOUT_CFG_PREFIX "phone", "", NULL, PHONE_TEXT, + PHONE_LONGTEXT, VLC_TRUE ); add_integer( SOUT_CFG_PREFIX "port", 1234, NULL, PORT_TEXT, PORT_LONGTEXT, VLC_TRUE ); @@ -139,6 +173,16 @@ vlc_module_begin(); add_integer( SOUT_CFG_PREFIX "ttl", 0, NULL, TTL_TEXT, TTL_LONGTEXT, VLC_TRUE ); + add_bool( SOUT_CFG_PREFIX "rtcp-mux", VLC_FALSE, NULL, + RTCP_MUX_TEXT, RTCP_MUX_LONGTEXT, VLC_FALSE ); + + add_bool( SOUT_CFG_PREFIX "dccp", VLC_FALSE, NULL, + DCCP_TEXT, DCCP_LONGTEXT, VLC_FALSE ); + add_bool( SOUT_CFG_PREFIX "tcp", VLC_FALSE, NULL, + TCP_TEXT, TCP_LONGTEXT, VLC_FALSE ); + add_bool( SOUT_CFG_PREFIX "udplite", VLC_FALSE, NULL, + UDP_LITE_TEXT, UDP_LITE_LONGTEXT, VLC_FALSE ); + add_bool( SOUT_CFG_PREFIX "mp4a-latm", 0, NULL, RFC3016_TEXT, RFC3016_LONGTEXT, VLC_FALSE ); @@ -150,7 +194,9 @@ vlc_module_end(); *****************************************************************************/ static const char *ppsz_sout_options[] = { "dst", "name", "port", "port-audio", "port-video", "*sdp", "ttl", "mux", - "description", "url","email", "mp4a-latm", NULL + "description", "url", "email", "phone", + "rtcp-mux", "dccp", "tcp", "udplite", + "mp4a-latm", NULL }; static sout_stream_id_t *Add ( sout_stream_t *, es_format_t * ); @@ -173,36 +219,34 @@ static int HttpSetup( sout_stream_t *p_stream, vlc_url_t * ); struct sout_stream_sys_t { - /* sdp */ - int64_t i_sdp_id; - int i_sdp_version; + /* SDP */ char *psz_sdp; vlc_mutex_t lock_sdp; - char *psz_session_name; - char *psz_session_description; - char *psz_session_url; - char *psz_session_email; - - /* */ + /* SDP to disk */ vlc_bool_t b_export_sdp_file; char *psz_sdp_file; - /* sap */ + + /* SDP via SAP */ vlc_bool_t b_export_sap; session_descriptor_t *p_session; + /* SDP via HTTP */ httpd_host_t *p_httpd_host; httpd_file_t *p_httpd_file; + /* RTSP */ rtsp_stream_t *rtsp; /* */ - char *psz_destination; - int i_port; - int i_port_audio; - int i_port_video; - int i_ttl; + char *psz_destination; + uint8_t proto; + uint8_t i_ttl; + uint16_t i_port; + uint16_t i_port_audio; + uint16_t i_port_video; vlc_bool_t b_latm; + vlc_bool_t rtcp_mux; /* when need to use a private one or when using muxer */ int i_payload_type; @@ -221,13 +265,18 @@ struct sout_stream_sys_t typedef int (*pf_rtp_packetizer_t)( sout_stream_t *, sout_stream_id_t *, block_t * ); +typedef struct rtp_sink_t +{ + int rtp_fd; + rtcp_sender_t *rtcp; +} rtp_sink_t; + struct sout_stream_id_t { VLC_COMMON_MEMBERS sout_stream_t *p_stream; /* rtp field */ - uint32_t i_timestamp_start; uint16_t i_sequence; uint8_t i_payload_type; uint8_t ssrc[4]; @@ -246,9 +295,10 @@ struct sout_stream_id_t /* Packets sinks */ vlc_mutex_t lock_sink; - int fdc; - int *fdv; + int sinkc; + rtp_sink_t *sinkv; rtsp_stream_id_t *rtsp_id; + int *listen_fd; block_fifo_t *p_fifo; int64_t i_caching; @@ -275,14 +325,11 @@ static int Open( vlc_object_t *p_this ) return VLC_ENOMEM; p_sys->psz_destination = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "dst" ); - p_sys->psz_session_name = var_GetString( p_stream, SOUT_CFG_PREFIX "name" ); - p_sys->psz_session_description = var_GetString( p_stream, SOUT_CFG_PREFIX "description" ); - p_sys->psz_session_url = var_GetString( p_stream, SOUT_CFG_PREFIX "url" ); - p_sys->psz_session_email = var_GetString( p_stream, SOUT_CFG_PREFIX "email" ); p_sys->i_port = var_GetInteger( p_stream, SOUT_CFG_PREFIX "port" ); p_sys->i_port_audio = var_GetInteger( p_stream, SOUT_CFG_PREFIX "port-audio" ); p_sys->i_port_video = var_GetInteger( p_stream, SOUT_CFG_PREFIX "port-video" ); + p_sys->rtcp_mux = var_GetBool( p_stream, SOUT_CFG_PREFIX "rtcp-mux" ); p_sys->psz_sdp_file = NULL; @@ -293,23 +340,14 @@ static int Open( vlc_object_t *p_this ) p_sys->i_port_video = 0; } - if( !p_sys->psz_session_name ) - { - if( p_sys->psz_destination ) - p_sys->psz_session_name = strdup( p_sys->psz_destination ); - else - p_sys->psz_session_name = strdup( "NONE" ); - } - for( p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next ) { - if( !strcmp( p_cfg->psz_name, "sdp" ) ) + if( !strcmp( p_cfg->psz_name, "sdp" ) + && ( p_cfg->psz_value != NULL ) + && !strncasecmp( p_cfg->psz_value, "rtsp:", 5 ) ) { - if( p_cfg->psz_value && !strncasecmp( p_cfg->psz_value, "rtsp", 4 ) ) - { - b_rtsp = VLC_TRUE; - break; - } + b_rtsp = VLC_TRUE; + break; } } if( !b_rtsp ) @@ -317,24 +355,35 @@ static int Open( vlc_object_t *p_this ) psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "sdp" ); if( psz != NULL ) { - if( !strncasecmp( psz, "rtsp", 4 ) ) + if( !strncasecmp( psz, "rtsp:", 5 ) ) b_rtsp = VLC_TRUE; free( psz ); } } - if( p_sys->psz_destination == NULL ) + /* Transport protocol */ + p_sys->proto = IPPROTO_UDP; + + if( var_GetBool( p_stream, SOUT_CFG_PREFIX "dccp" ) ) { - if( !b_rtsp ) - { - msg_Err( p_stream, "missing destination and not in RTSP mode" ); - free( p_sys ); - return VLC_EGENERIC; - } + p_sys->proto = IPPROTO_DCCP; + p_sys->rtcp_mux = VLC_TRUE; /* Force RTP/RTCP mux */ } - else if( p_sys->i_port <= 0 ) +#if 0 + else + if( var_GetBool( p_stream, SOUT_CFG_PREFIX "tcp" ) ) { - msg_Err( p_stream, "invalid port" ); + p_sys->proto = IPPROTO_TCP; + p_sys->rtcp_mux = VLC_TRUE; /* Force RTP/RTCP mux */ + } + else +#endif + if( var_GetBool( p_stream, SOUT_CFG_PREFIX "udplite" ) ) + p_sys->proto = IPPROTO_UDPLITE; + + if( ( p_sys->psz_destination == NULL ) && !b_rtsp ) + { + msg_Err( p_stream, "missing destination and not in RTSP mode" ); free( p_sys ); return VLC_EGENERIC; } @@ -348,9 +397,6 @@ static int Open( vlc_object_t *p_this ) * ttl are set. */ p_sys->i_ttl = config_GetInt( p_stream, "ttl" ); } - if( p_sys->i_ttl > 255 ) - p_sys->i_ttl = 255; - /* must not exceed 999 once formatted */ p_sys->b_latm = var_GetBool( p_stream, SOUT_CFG_PREFIX "mp4a-latm" ); @@ -360,10 +406,6 @@ static int Open( vlc_object_t *p_this ) p_sys->rtsp = NULL; p_sys->psz_sdp = NULL; - p_sys->i_sdp_id = mdate(); - p_sys->i_sdp_version = 1; - p_sys->psz_sdp = NULL; - p_sys->b_export_sap = VLC_FALSE; p_sys->b_export_sdp_file = VLC_FALSE; p_sys->p_session = NULL; @@ -380,21 +422,11 @@ static int Open( vlc_object_t *p_this ) if( psz != NULL ) { sout_stream_id_t *id; - const char *psz_rtpmap; - int i_payload_type; /* Check muxer type */ - if( !strncasecmp( psz, "ps", 2 ) - || !strncasecmp( psz, "mpeg1", 5 ) ) - { - psz_rtpmap = "MP2P/90000"; - } - else if( !strncasecmp( psz, "ts", 2 ) ) - { - psz_rtpmap = "MP2T/90000"; - i_payload_type = 33; - } - else + if( strncasecmp( psz, "ps", 2 ) + && strncasecmp( psz, "mpeg1", 5 ) + && strncasecmp( psz, "ts", 2 ) ) { msg_Err( p_stream, "unsupported muxer type for RTP (only TS/PS)" ); free( psz ); @@ -429,9 +461,6 @@ static int Open( vlc_object_t *p_this ) return VLC_EGENERIC; } - id->psz_rtpmap = strdup( psz_rtpmap ); - id->i_payload_type = i_payload_type; - p_sys->packet = NULL; p_stream->pf_add = MuxAdd; @@ -463,7 +492,7 @@ static int Open( vlc_object_t *p_this ) continue; /* needed both :sout-rtp-sdp= and rtp{sdp=} can be used */ - if( !strcmp( p_cfg->psz_value, psz ) ) + if( !strcmp( p_cfg->psz_value, psz ) ) continue; SDPHandleUrl( p_stream, p_cfg->psz_value ); @@ -519,10 +548,6 @@ static void Close( vlc_object_t * p_this ) if( p_sys->p_httpd_host ) httpd_HostDelete( p_sys->p_httpd_host ); - free( p_sys->psz_session_name ); - free( p_sys->psz_session_description ); - free( p_sys->psz_session_url ); - free( p_sys->psz_session_email ); free( p_sys->psz_sdp ); if( p_sys->b_export_sdp_file ) @@ -581,7 +606,7 @@ static void SDPHandleUrl( sout_stream_t *p_stream, char *psz_url ) id->i_port, id->i_port + 1 ); } } - else if( ( url.psz_protocol && !strcasecmp( url.psz_protocol, "sap" ) ) || + else if( ( url.psz_protocol && !strcasecmp( url.psz_protocol, "sap" ) ) || ( url.psz_host && !strcasecmp( url.psz_host, "sap" ) ) ) { p_sys->b_export_sap = VLC_TRUE; @@ -613,26 +638,13 @@ out: /***************************************************************************** * SDPGenerate *****************************************************************************/ - /* FIXME http://www.faqs.org/rfcs/rfc2327.html - All text fields should be UTF-8 encoded. Use global a:charset to announce this. - o= - should be local username (no spaces allowed) - o= time should be hashed with some other value to garantue uniqueness - o= we need IP6 support? - o= don't use the localhost address. use fully qualified domain name or IP4 address - p= international phone number (pass via vars?) - c= IP6 support - a= recvonly (missing) - a= type:broadcast (missing) - a= charset: (normally charset should be UTF-8, this can be used to override s= and i=) - a= x-plgroup: (missing) - RTP packets need to get the correct src IP address */ /*static*/ char *SDPGenerate( const sout_stream_t *p_stream, const char *rtsp_url ) { const sout_stream_sys_t *p_sys = p_stream->p_sys; - size_t i_size; - const char *psz_destination = p_sys->psz_destination; - char *psz_sdp, *p, ipv; + char *psz_sdp; + struct sockaddr_storage dst; + socklen_t dstlen; int i; /* * When we have a fixed destination (typically when we do multicast), @@ -649,114 +661,106 @@ char *SDPGenerate( const sout_stream_t *p_stream, const char *rtsp_url ) * output chain with two different RTSP URLs if you need to handle this * scenario. */ - int inclport = (psz_destination != NULL); - - /* FIXME: breaks IP version check on unknown destination */ - if( psz_destination == NULL ) - psz_destination = "0.0.0.0"; - - i_size = sizeof( "v=0\r\n" ) + - sizeof( "o=- * * IN IP4 127.0.0.1\r\n" ) + 10 + 10 + - sizeof( "s=*\r\n" ) + strlen( p_sys->psz_session_name ) + - sizeof( "i=*\r\n" ) + strlen( p_sys->psz_session_description ) + - sizeof( "u=*\r\n" ) + strlen( p_sys->psz_session_url ) + - sizeof( "e=*\r\n" ) + strlen( p_sys->psz_session_email ) + - sizeof( "t=0 0\r\n" ) + - sizeof( "b=RR:0\r\n" ) + - sizeof( "a=tool:"PACKAGE_STRING"\r\n" ) + - sizeof( "a=recvonly\r\n" ) + - sizeof( "a=type:broadcast\r\n" ) + - sizeof( "c=IN IP4 */*\r\n" ) + 20 + 10 + - strlen( psz_destination ) ; - for( i = 0; i < p_sys->i_es; i++ ) + int inclport; + + if( p_sys->psz_destination != NULL ) { - sout_stream_id_t *id = p_sys->es[i]; + inclport = 1; - i_size += strlen( "m=**d*o * RTP/AVP *\r\n" ) + 10 + 10; - if ( id->i_bitrate ) - { - i_size += strlen( "b=AS: *\r\n") + 10; - } - if( id->psz_rtpmap ) - { - i_size += strlen( "a=rtpmap:* *\r\n" ) + strlen( id->psz_rtpmap )+10; - } - if( id->psz_fmtp ) - { - i_size += strlen( "a=fmtp:* *\r\n" ) + strlen( id->psz_fmtp ) + 10; - } - if( rtsp_url != NULL ) - { - i_size += strlen( "a=control:*/trackID=*\r\n" ) + strlen( rtsp_url ) + 10; - } + /* Oh boy, this is really ugly! (+ race condition on lock_es) */ + dstlen = sizeof( dst ); + if( p_sys->es[0]->listen_fd != NULL ) + getsockname( p_sys->es[0]->listen_fd[0], + (struct sockaddr *)&dst, &dstlen ); + else + getpeername( p_sys->es[0]->sinkv[0].rtp_fd, + (struct sockaddr *)&dst, &dstlen ); } + else + { + inclport = 0; - ipv = ( strchr( psz_destination, ':' ) != NULL ) ? '6' : '4'; - - p = psz_sdp = malloc( i_size ); - p += sprintf( p, "v=0\r\n" ); - p += sprintf( p, "o=- "I64Fu" %d IN IP%c %s\r\n", - p_sys->i_sdp_id, p_sys->i_sdp_version, - ipv, ipv == '6' ? "::1" : "127.0.0.1" ); - if( *p_sys->psz_session_name ) - p += sprintf( p, "s=%s\r\n", p_sys->psz_session_name ); - if( *p_sys->psz_session_description ) - p += sprintf( p, "i=%s\r\n", p_sys->psz_session_description ); - if( *p_sys->psz_session_url ) - p += sprintf( p, "u=%s\r\n", p_sys->psz_session_url ); - if( *p_sys->psz_session_email ) - p += sprintf( p, "e=%s\r\n", p_sys->psz_session_email ); + /* Dummy destination address for RTSP */ + memset (&dst, 0, sizeof( struct sockaddr_in ) ); + dst.ss_family = AF_INET; +#ifdef HAVE_SA_LEN + dst.ss_len = +#endif + dstlen = sizeof( struct sockaddr_in ); + } - p += sprintf( p, "t=0 0\r\n" ); /* permanent stream */ - /* when scheduled from vlm, we should set this info correctly */ - p += sprintf( p, "a=tool:"PACKAGE_STRING"\r\n" ); - p += sprintf( p, "a=recvonly\r\n" ); - p += sprintf( p, "a=type:broadcast\r\n" ); + psz_sdp = vlc_sdp_Start( VLC_OBJECT( p_stream ), SOUT_CFG_PREFIX, + NULL, 0, (struct sockaddr *)&dst, dstlen ); + if( psz_sdp == NULL ) + return NULL; - p += sprintf( p, "c=IN IP%c %s", ipv, psz_destination ); + /* TODO: a=source-filter */ + if( p_sys->rtcp_mux ) + sdp_AddAttribute( &psz_sdp, "rtcp-mux", NULL ); - if( ( ipv == 4 ) - && net_AddressIsMulticast( (vlc_object_t *)p_stream, psz_destination ) ) - { - /* Add the deprecated TTL field if it is an IPv4 multicast address */ - p += sprintf( p, "/%d", p_sys->i_ttl ?: 1 ); - } - p += sprintf( p, "\r\n" ); - p += sprintf( p, "b=RR:0\r\n" ); + if( rtsp_url != NULL ) + sdp_AddAttribute ( &psz_sdp, "control", "%s", rtsp_url ); + /* FIXME: locking?! */ for( i = 0; i < p_sys->i_es; i++ ) { sout_stream_id_t *id = p_sys->es[i]; const char *mime_major; /* major MIME type */ + const char *proto = "RTP/AVP"; /* protocol */ - if( id->i_cat == AUDIO_ES ) - mime_major = "audio"; - else - if( id->i_cat == VIDEO_ES ) - mime_major = "video"; - else - continue; - - p += sprintf( p, "m=%s %d RTP/AVP %d\r\n", mime_major, - inclport * id->i_port, id->i_payload_type ); - - if ( id->i_bitrate ) + switch( id->i_cat ) { - p += sprintf(p,"b=AS:%d\r\n",id->i_bitrate); + case VIDEO_ES: + mime_major = "video"; + break; + case AUDIO_ES: + mime_major = "audio"; + break; + case SPU_ES: + mime_major = "text"; + break; + default: + continue; } - if( id->psz_rtpmap ) + + if( rtsp_url == NULL ) { - p += sprintf( p, "a=rtpmap:%d %s\r\n", id->i_payload_type, - id->psz_rtpmap ); + switch( p_sys->proto ) + { + case IPPROTO_UDP: + break; + case IPPROTO_TCP: + proto = "TCP/RTP/AVP"; + break; + case IPPROTO_DCCP: + proto = "DCCP/RTP/AVP"; + break; + case IPPROTO_UDPLITE: + continue; + } } - if( id->psz_fmtp ) + + sdp_AddMedia( &psz_sdp, mime_major, proto, inclport * id->i_port, + id->i_payload_type, VLC_FALSE, id->i_bitrate, + id->psz_rtpmap, id->psz_fmtp); + + if( rtsp_url != NULL ) { - p += sprintf( p, "a=fmtp:%d %s\r\n", id->i_payload_type, - id->psz_fmtp ); + assert( strlen( rtsp_url ) > 0 ); + vlc_bool_t addslash = ( rtsp_url[strlen( rtsp_url ) - 1] != '/' ); + sdp_AddAttribute ( &psz_sdp, "control", + addslash ? "%s/trackID=%u" : "%strackID=%u", + rtsp_url, i ); } - if( rtsp_url != NULL ) + else { - p += sprintf( p, "a=control:/trackID=%d\r\n", i ); + if( id->listen_fd != NULL ) + sdp_AddAttribute( &psz_sdp, "setup", "passive" ); +#if 0 + if( p_sys->proto == IPPROTO_DCCP ) + sdp_AddAttribute( &psz_sdp, "dccp-service-code", + "SC:RTP%c", toupper( mime_major[0] ) ); +#endif } } @@ -777,6 +781,8 @@ static int rtp_packetize_mp4a_latm ( sout_stream_t *, sout_stream_id_t *, block_ static int rtp_packetize_h263 ( sout_stream_t *, sout_stream_id_t *, block_t * ); static int rtp_packetize_h264 ( sout_stream_t *, sout_stream_id_t *, block_t * ); static int rtp_packetize_amr ( sout_stream_t *, sout_stream_id_t *, block_t * ); +static int rtp_packetize_spx ( sout_stream_t *, sout_stream_id_t *, block_t * ); +static int rtp_packetize_t140 ( sout_stream_t *, sout_stream_id_t *, block_t * ); static void sprintf_hexa( char *s, uint8_t *p_data, int i_data ) { @@ -799,12 +805,13 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt ) * mux (TS/PS), then p_fmt is NULL. */ sout_stream_sys_t *p_sys = p_stream->p_sys; sout_stream_id_t *id; - int i_port; + int i_port, cscov = -1; char *psz_sdp; id = vlc_object_create( p_stream, sizeof( sout_stream_id_t ) ); if( id == NULL ) return NULL; + vlc_object_attach( id, p_stream ); /* Choose the port */ i_port = 0; @@ -837,7 +844,6 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt ) id->p_stream = p_stream; - id->i_timestamp_start = rand()&0xffffffff; id->i_sequence = rand()&0xffff; id->i_payload_type = p_sys->i_payload_type; id->ssrc[0] = rand()&0xff; @@ -868,41 +874,61 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt ) msg_Dbg( p_stream, "maximum RTP packet size: %d bytes", id->i_mtu ); vlc_mutex_init( p_stream, &id->lock_sink ); - id->fdc = 0; - id->fdv = NULL; + id->sinkc = 0; + id->sinkv = NULL; id->rtsp_id = NULL; + id->p_fifo = NULL; + id->listen_fd = NULL; id->i_caching = (int64_t)1000 * var_GetInteger( p_stream, SOUT_CFG_PREFIX "caching"); - id->p_fifo = block_FifoNew( p_stream ); - - if( vlc_thread_create( id, "RTP send thread", ThreadSend, - VLC_THREAD_PRIORITY_HIGHEST, VLC_FALSE ) ) - { - vlc_mutex_destroy( &id->lock_sink ); - vlc_object_destroy( id ); - return NULL; - } if( p_sys->psz_destination != NULL ) + switch( p_sys->proto ) + { + case IPPROTO_TCP: + case IPPROTO_DCCP: + id->listen_fd = net_Listen( VLC_OBJECT(p_stream), + p_sys->psz_destination, i_port, + p_sys->proto ); + if( id->listen_fd == NULL ) + { + msg_Err( p_stream, "passive COMEDIA RTP socket failed" ); + goto error; + } + break; + + default: + { + int ttl = (p_sys->i_ttl > 0) ? p_sys->i_ttl : -1; + int fd = net_ConnectDgram( p_stream, p_sys->psz_destination, + i_port, ttl, p_sys->proto ); + if( fd == -1 ) + { + msg_Err( p_stream, "cannot create RTP socket" ); + goto error; + } + rtp_add_sink( id, fd, p_sys->rtcp_mux ); + } + } + + if( p_fmt == NULL ) { - int ttl = (p_sys->i_ttl > 0) ? p_sys->i_ttl : -1; - int fd = net_ConnectDgram( p_stream, p_sys->psz_destination, - i_port, ttl, IPPROTO_UDP ); + char *psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "mux" ); - if( fd == -1 ) + if( psz == NULL ) /* Uho! */ + ; + else + if( strncmp( psz, "ts", 2 ) == 0 ) + { + id->i_payload_type = 33; + id->psz_rtpmap = strdup( "MP2T/90000" ); + } + else { - msg_Err( p_stream, "cannot create RTP socket" ); - vlc_thread_join( id ); - vlc_mutex_destroy( &id->lock_sink ); - vlc_object_destroy( id ); - return NULL; + id->psz_rtpmap = strdup( "MP2P/90000" ); } - rtp_add_sink( id, fd ); } - - if( p_fmt == NULL ) - ; else switch( p_fmt->i_codec ) { @@ -960,7 +986,7 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt ) char *p_64_pps = NULL; char hexa[6+1]; - while( i_buffer > 4 && + while( i_buffer > 4 && p_buffer[0] == 0 && p_buffer[1] == 0 && p_buffer[2] == 0 && p_buffer[3] == 1 ) { @@ -1079,26 +1105,39 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt ) id->psz_fmtp = strdup( "octet-align=1" ); id->i_clock_rate = p_fmt->audio.i_rate; id->pf_packetize = rtp_packetize_amr; - break; + break; case VLC_FOURCC( 's', 'a', 'w', 'b' ): id->psz_rtpmap = strdup( p_fmt->audio.i_channels == 2 ? "AMR-WB/16000/2" : "AMR-WB/16000" ); id->psz_fmtp = strdup( "octet-align=1" ); id->i_clock_rate = p_fmt->audio.i_rate; id->pf_packetize = rtp_packetize_amr; - break; + break; + case VLC_FOURCC( 's', 'p', 'x', ' ' ): + id->i_payload_type = p_sys->i_payload_type++; + if( asprintf( &id->psz_rtpmap, "SPEEX/%d", + p_fmt->audio.i_rate ) == -1) + id->psz_rtpmap = NULL; + id->i_clock_rate = p_fmt->audio.i_rate; + id->pf_packetize = rtp_packetize_spx; + break; + case VLC_FOURCC( 't', '1', '4', '0' ): + id->psz_rtpmap = strdup( "t140/1000" ); + id->i_clock_rate = 1000; + id->pf_packetize = rtp_packetize_t140; + break; default: msg_Err( p_stream, "cannot add this stream (unsupported " "codec:%4.4s)", (char*)&p_fmt->i_codec ); - if( id->fdc > 0 ) - rtp_del_sink( id, id->fdv[0] ); - vlc_thread_join( id ); - vlc_mutex_destroy( &id->lock_sink ); - vlc_object_destroy( id ); - return NULL; + goto error; } + if( cscov != -1 ) + cscov += 8 /* UDP */ + 12 /* RTP */; + if( id->sinkc > 0 ) + net_SetCSCov( id->sinkv[0].rtp_fd, cscov, -1 ); + if( id->i_payload_type == p_sys->i_payload_type ) p_sys->i_payload_type++; @@ -1107,6 +1146,11 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt ) p_sys->psz_destination, p_sys->i_ttl, id->i_port, id->i_port + 1 ); + id->p_fifo = block_FifoNew( p_stream ); + if( vlc_thread_create( id, "RTP send thread", ThreadSend, + VLC_THREAD_PRIORITY_HIGHEST, VLC_FALSE ) ) + goto error; + /* Update p_sys context */ vlc_mutex_lock( &p_sys->lock_es ); TAB_APPEND( p_sys->i_es, p_sys->es, id ); @@ -1119,23 +1163,30 @@ static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt ) p_sys->psz_sdp = psz_sdp; vlc_mutex_unlock( &p_sys->lock_sdp ); - p_sys->i_sdp_version++; - msg_Dbg( p_stream, "sdp=\n%s", p_sys->psz_sdp ); /* Update SDP (sap/file) */ if( p_sys->b_export_sap ) SapSetup( p_stream ); if( p_sys->b_export_sdp_file ) FileSetup( p_stream ); - vlc_object_attach( id, p_stream ); return id; + +error: + Del( p_stream, id ); + return NULL; } static int Del( sout_stream_t *p_stream, sout_stream_id_t *id ) { sout_stream_sys_t *p_sys = p_stream->p_sys; - vlc_object_kill( id ); + if( id->p_fifo != NULL ) + { + vlc_object_kill( id ); + block_FifoWake( id->p_fifo ); + vlc_thread_join( id ); + block_FifoRelease( id->p_fifo ); + } vlc_mutex_lock( &p_sys->lock_es ); TAB_REMOVE( p_sys->i_es, p_sys->es, id ); @@ -1155,12 +1206,12 @@ static int Del( sout_stream_t *p_stream, sout_stream_id_t *id ) if( id->rtsp_id ) RtspDelId( p_sys->rtsp, id->rtsp_id ); - if( id->fdc > 0 ) - rtp_del_sink( id, id->fdv[0] ); /* sink for explicit dst= */ + if( id->sinkc > 0 ) + rtp_del_sink( id, id->sinkv[0].rtp_fd ); /* sink for explicit dst= */ + if( id->listen_fd != NULL ) + net_ListenClose( id->listen_fd ); - vlc_thread_join( id ); vlc_mutex_destroy( &id->lock_sink ); - block_FifoRelease( id->p_fifo ); /* Update SDP (sap/file) */ if( p_sys->b_export_sap && !p_sys->p_mux ) SapSetup( p_stream ); @@ -1197,25 +1248,24 @@ static int SapSetup( sout_stream_t *p_stream ) { sout_stream_sys_t *p_sys = p_stream->p_sys; sout_instance_t *p_sout = p_stream->p_sout; - announce_method_t *p_method = sout_SAPMethod(); /* Remove the previous session */ if( p_sys->p_session != NULL) { sout_AnnounceUnRegister( p_sout, p_sys->p_session); - sout_AnnounceSessionDestroy( p_sys->p_session ); p_sys->p_session = NULL; } if( ( p_sys->i_es > 0 || p_sys->p_mux ) && p_sys->psz_sdp && *p_sys->psz_sdp ) { + announce_method_t *p_method = sout_SAPMethod(); p_sys->p_session = sout_AnnounceRegisterSDP( p_sout, SOUT_CFG_PREFIX, p_sys->psz_sdp, p_sys->psz_destination, p_method ); + sout_MethodRelease( p_method ); } - sout_MethodRelease( p_method ); return VLC_SUCCESS; } @@ -1229,8 +1279,8 @@ static int FileSetup( sout_stream_t *p_stream ) if( ( f = utf8_fopen( p_sys->psz_sdp_file, "wt" ) ) == NULL ) { - msg_Err( p_stream, "cannot open file '%s' (%s)", - p_sys->psz_sdp_file, strerror(errno) ); + msg_Err( p_stream, "cannot open file '%s' (%m)", + p_sys->psz_sdp_file ); return VLC_EGENERIC; } @@ -1298,21 +1348,91 @@ static void ThreadSend( vlc_object_t *p_this ) { sout_stream_id_t *id = (sout_stream_id_t *)p_this; unsigned i_caching = id->i_caching; +#ifdef HAVE_TEE + int fd[5] = { -1, -1, -1, -1, -1 }; + + if( pipe( fd ) ) + fd[0] = fd[1] = -1; + else + if( pipe( fd ) ) + fd[2] = fd[3] = -1; + else + fd[4] = open( "/dev/null", O_WRONLY ); +#endif while( !id->b_die ) { block_t *out = block_FifoGet( id->p_fifo ); + if( out == NULL ) + continue; /* Forced wakeup */ + mtime_t i_date = out->i_dts + i_caching; + ssize_t len = out->i_buffer; +#ifdef HAVE_TEE + if( fd[4] != -1 ) + len = write( fd[1], out->p_buffer, len); + if( len == -1 ) + continue; /* Uho - should not happen */ +#endif mwait( i_date ); vlc_mutex_lock( &id->lock_sink ); - for( int i = 0; i < id->fdc; i++ ) - send( id->fdv[i], out->p_buffer, out->i_buffer, 0 ); + unsigned deadc = 0; /* How many dead sockets? */ + int deadv[id->sinkc]; /* Dead sockets list */ + + for( int i = 0; i < id->sinkc; i++ ) + { + SendRTCP( id->sinkv[i].rtcp, out ); + +#ifdef HAVE_TEE + tee( fd[0], fd[3], len, 0 ); + if( splice( fd[2], NULL, id->sinkv[i].rtp_fd, NULL, len, + SPLICE_F_NONBLOCK ) >= 0 ) + continue; + if( errno == EAGAIN ) + continue; + + /* splice failed */ + splice( fd[2], NULL, fd[4], NULL, len, 0 ); +#else + if( send( id->sinkv[i].rtp_fd, out->p_buffer, len, 0 ) >= 0 ) + continue; +#endif + /* Retry sending to root out soft-errors */ + if( send( id->sinkv[i].rtp_fd, out->p_buffer, len, 0 ) >= 0 ) + continue; + + deadv[deadc++] = id->sinkv[i].rtp_fd; + } vlc_mutex_unlock( &id->lock_sink ); block_Release( out ); +#ifdef HAVE_TEE + splice( fd[0], NULL, fd[4], NULL, len, 0 ); +#endif + + for( unsigned i = 0; i < deadc; i++ ) + { + msg_Dbg( id, "removing socket %d", deadv[i] ); + rtp_del_sink( id, deadv[i] ); + } + + /* Hopefully we won't overflow the SO_MAXCONN accept queue */ + while( id->listen_fd != NULL ) + { + int fd = net_Accept( id, id->listen_fd, 0 ); + if( fd == -1 ) + break; + msg_Dbg( id, "adding socket %d", fd ); + rtp_add_sink( id, fd, VLC_TRUE ); + } } + +#ifdef HAVE_TEE + for( unsigned i = 0; i < 5; i++ ) + close( fd[i] ); +#endif } static inline void rtp_packetize_send( sout_stream_id_t *id, block_t *out ) @@ -1320,23 +1440,67 @@ static inline void rtp_packetize_send( sout_stream_id_t *id, block_t *out ) block_FifoPut( id->p_fifo, out ); } -int rtp_add_sink( sout_stream_id_t *id, int fd ) +int rtp_add_sink( sout_stream_id_t *id, int fd, vlc_bool_t rtcp_mux ) { + rtp_sink_t sink = { fd, NULL }; + sink.rtcp = OpenRTCP( VLC_OBJECT( id->p_stream ), fd, IPPROTO_UDP, + rtcp_mux ); + if( sink.rtcp == NULL ) + msg_Err( id, "RTCP failed!" ); + vlc_mutex_lock( &id->lock_sink ); - TAB_APPEND( id->fdc, id->fdv, fd ); + INSERT_ELEM( id->sinkv, id->sinkc, id->sinkc, sink ); vlc_mutex_unlock( &id->lock_sink ); return VLC_SUCCESS; } void rtp_del_sink( sout_stream_id_t *id, int fd ) { + rtp_sink_t sink = { fd, NULL }; + /* NOTE: must be safe to use if fd is not included */ vlc_mutex_lock( &id->lock_sink ); - TAB_REMOVE( id->fdc, id->fdv, fd ); + for( int i = 0; i < id->sinkc; i++ ) + { + if (id->sinkv[i].rtp_fd == fd) + { + sink = id->sinkv[i]; + REMOVE_ELEM( id->sinkv, id->sinkc, i ); + break; + } + } vlc_mutex_unlock( &id->lock_sink ); - net_Close( fd ); + + CloseRTCP( sink.rtcp ); + net_Close( sink.rtp_fd ); +} + +uint16_t rtp_get_seq( const sout_stream_id_t *id ) +{ + /* This will return values for the next packet. + * Accounting for caching would not be totally trivial. */ + return id->i_sequence; +} + +/* FIXME: this is pretty bad - if we remove and then insert an ES + * the number will get unsynched from inside RTSP */ +unsigned rtp_get_num( const sout_stream_id_t *id ) +{ + sout_stream_sys_t *p_sys = id->p_stream->p_sys; + int i; + + vlc_mutex_lock( &p_sys->lock_es ); + for( i = 0; i < p_sys->i_es; i++ ) + { + if( id == p_sys->es[i] ) + break; + } + vlc_mutex_unlock( &p_sys->lock_es ); + + return i; } + /**************************************************************************** * rtp_packetize_*: ****************************************************************************/ @@ -1899,7 +2063,7 @@ static int rtp_packetize_h264( sout_stream_t *p_stream, sout_stream_id_t *id, i_size = i_offset - ( p_buffer[i_offset-1] == 0 ? 1 : 0); i_skip = i_offset; break; - } + } } /* TODO add STAP-A to remove a lot of overhead with small slice/sei/... */ rtp_packetize_h264_nal( p_stream, id, p_buffer, i_size, @@ -1951,6 +2115,52 @@ static int rtp_packetize_amr( sout_stream_t *p_stream, sout_stream_id_t *id, return VLC_SUCCESS; } +static int rtp_packetize_t140( sout_stream_t *p_stream, sout_stream_id_t *id, + block_t *in ) +{ + const size_t i_max = id->i_mtu - 12; + const uint8_t *p_data = in->p_buffer; + size_t i_data = in->i_buffer; + + for( unsigned i_packet = 0; i_data > 0; i_packet++ ) + { + size_t i_payload = i_data; + + /* Make sure we stop on an UTF-8 character boundary + * (assuming the input is valid UTF-8) */ + if( i_data > i_max ) + { + i_payload = i_max; + + while( ( p_data[i_payload] & 0xC0 ) == 0x80 ) + { + if( i_payload == 0 ) + return VLC_SUCCESS; /* fishy input! */ + + i_payload--; + } + } + + block_t *out = block_New( p_stream, 12 + i_payload ); + if( out == NULL ) + return VLC_SUCCESS; + + rtp_packetize_common( id, out, 0, in->i_pts + i_packet ); + memcpy( out->p_buffer + 12, p_data, i_payload ); + + out->i_buffer = 12 + i_payload; + out->i_dts = out->i_pts; + out->i_length = 0; + + rtp_packetize_send( id, out ); + + p_data += i_payload; + i_data -= i_payload; + } + + return VLC_SUCCESS; +} + /***************************************************************************** * Non-RTP mux *****************************************************************************/ @@ -2084,3 +2294,80 @@ static sout_access_out_t *GrabberCreate( sout_stream_t *p_stream ) p_grab->pf_write = AccessOutGrabberWrite; return p_grab; } + +static int rtp_packetize_spx( sout_stream_t *p_stream, sout_stream_id_t *id, + block_t *in ) +{ + uint8_t *p_buffer = in->p_buffer; + int i_data_size, i_payload_size, i_payload_padding; + i_data_size = i_payload_size = in->i_buffer; + i_payload_padding = 0; + block_t *p_out; + + if ( in->i_buffer + 12 > id->i_mtu ) + { + msg_Warn( p_stream, "Cannot send packet larger than output MTU" ); + return VLC_SUCCESS; + } + + /* + RFC for Speex in RTP says that each packet must end on an octet + boundary. So, we check to see if the number of bytes % 4 is zero. + If not, we have to add some padding. + + This MAY be overkill since packetization is handled elsewhere and + appears to ensure the octet boundary. However, better safe than + sorry. + */ + if ( i_payload_size % 4 ) + { + i_payload_padding = 4 - ( i_payload_size % 4 ); + i_payload_size += i_payload_padding; + } + + /* + Allocate a new RTP p_output block of the appropriate size. + Allow for 12 extra bytes of RTP header. + */ + p_out = block_New( p_stream, 12 + i_payload_size ); + + if ( i_payload_padding ) + { + /* + The padding is required to be a zero followed by all 1s. + */ + char c_first_pad, c_remaining_pad; + c_first_pad = 0x7F; + c_remaining_pad = 0xFF; + + /* + Allow for 12 bytes before the i_data_size because + of the expected RTP header added during + rtp_packetize_common. + */ + p_out->p_buffer[12 + i_data_size] = c_first_pad; + switch (i_payload_padding) + { + case 2: + p_out->p_buffer[12 + i_data_size + 1] = c_remaining_pad; + break; + case 3: + p_out->p_buffer[12 + i_data_size + 1] = c_remaining_pad; + p_out->p_buffer[12 + i_data_size + 2] = c_remaining_pad; + break; + } + } + + /* Add the RTP header to our p_output buffer. */ + rtp_packetize_common( id, p_out, 0, (in->i_pts > 0 ? in->i_pts : in->i_dts) ); + /* Copy the Speex payload to the p_output buffer */ + memcpy( &p_out->p_buffer[12], p_buffer, i_data_size ); + + p_out->i_buffer = 12 + i_payload_size; + p_out->i_dts = in->i_dts; + p_out->i_length = in->i_length; + + /* Queue the buffer for actual transmission. */ + rtp_packetize_send( id, p_out ); + return VLC_SUCCESS; +}