# include <fcntl.h>
# include <sys/stat.h>
#endif
+#ifdef HAVE_LINUX_DCCP_H
+# include <linux/dccp.h>
+#endif
+#ifndef IPPROTO_DCCP
+# define IPPROTO_DCCP 33
+#endif
+#ifndef IPPROTO_UDPLITE
+# define IPPROTO_UDPLITE 136
+#endif
#include <errno.h>
"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." )
add_integer( SOUT_CFG_PREFIX "ttl", 0, NULL, TTL_TEXT,
TTL_LONGTEXT, VLC_TRUE );
- add_bool( SOUT_CFG_PREFIX "dccp", 0, NULL,
+ 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", 0, NULL,
+ add_bool( SOUT_CFG_PREFIX "tcp", VLC_FALSE, NULL,
TCP_TEXT, TCP_LONGTEXT, VLC_FALSE );
- add_bool( SOUT_CFG_PREFIX "udplite", 0, NULL,
+ 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,
static const char *ppsz_sout_options[] = {
"dst", "name", "port", "port-audio", "port-video", "*sdp", "ttl", "mux",
"description", "url", "email", "phone",
- "dccp", "tcp", "udplite",
+ "rtcp-mux", "dccp", "tcp", "udplite",
"mp4a-latm", NULL
};
struct sout_stream_sys_t
{
/* SDP */
- int64_t i_sdp_id;
- int i_sdp_version;
char *psz_sdp;
vlc_mutex_t lock_sdp;
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;
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];
int sinkc;
rtp_sink_t *sinkv;
rtsp_stream_id_t *rtsp_id;
+ int *listen_fd;
block_fifo_t *p_fifo;
int64_t i_caching;
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;
/* Transport protocol */
p_sys->proto = IPPROTO_UDP;
-#if 0
if( var_GetBool( p_stream, SOUT_CFG_PREFIX "dccp" ) )
{
- p_sys->sotype = SOCK_DCCP;
- p_sys->proto = 33 /*IPPROTO_DCCP*/;
+ p_sys->proto = IPPROTO_DCCP;
+ p_sys->rtcp_mux = VLC_TRUE; /* Force RTP/RTCP mux */
}
+#if 0
else
if( var_GetBool( p_stream, SOUT_CFG_PREFIX "tcp" ) )
{
- p_sys->sotype = SOCK_STREAM;
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 = 136 /*IPPROTO_UDPLITE*/;
+ p_sys->proto = IPPROTO_UDPLITE;
if( ( p_sys->psz_destination == NULL ) && !b_rtsp )
{
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;
/* Oh boy, this is really ugly! (+ race condition on lock_es) */
dstlen = sizeof( dst );
- getpeername( p_sys->es[0]->sinkv[0].rtp_fd, (struct sockaddr *)&dst,
- &dstlen );
+ 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
{
return NULL;
/* TODO: a=source-filter */
+ if( p_sys->rtcp_mux )
+ sdp_AddAttribute( &psz_sdp, "rtcp-mux", NULL );
if( rtsp_url != NULL )
sdp_AddAttribute ( &psz_sdp, "control", "%s", rtsp_url );
{
sout_stream_id_t *id = p_sys->es[i];
const char *mime_major; /* major MIME type */
+ const char *proto = "RTP/AVP"; /* protocol */
switch( id->i_cat )
{
continue;
}
- sdp_AddMedia( &psz_sdp, mime_major, "RTP/AVP", inclport * id->i_port,
+ if( rtsp_url == NULL )
+ {
+ 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;
+ }
+ }
+
+ 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);
addslash ? "%s/trackID=%u" : "%strackID=%u",
rtsp_url, i );
}
+ else
+ {
+ 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
+ }
}
return psz_sdp;
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 )
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;
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;
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 )
- {
- 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 )
+ switch( p_sys->proto )
{
- msg_Err( p_stream, "cannot create RTP socket" );
- vlc_thread_join( id );
- vlc_mutex_destroy( &id->lock_sink );
- vlc_object_destroy( id );
- return NULL;
+ 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 );
+ }
}
- rtp_add_sink( id, fd );
- }
if( p_fmt == NULL )
{
id->i_clock_rate = p_fmt->audio.i_rate;
id->pf_packetize = rtp_packetize_amr;
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;
default:
msg_Err( p_stream, "cannot add this stream (unsupported "
"codec:%4.4s)", (char*)&p_fmt->i_codec );
- if( id->sinkc > 0 )
- rtp_del_sink( id, id->sinkv[0].rtp_fd );
- vlc_thread_join( id );
- vlc_mutex_destroy( &id->lock_sink );
- vlc_object_destroy( id );
- return NULL;
+ goto error;
}
if( cscov != -1 )
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 );
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 );
- block_FifoWake( id->p_fifo );
+ 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 );
RtspDelId( p_sys->rtsp, id->rtsp_id );
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 );
mwait( i_date );
vlc_mutex_lock( &id->lock_sink );
+ 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 );
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
- send( id->sinkv[i].rtp_fd, out->p_buffer, len, 0 );
+ /* 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 );
#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
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 );
+ sink.rtcp = OpenRTCP( VLC_OBJECT( id->p_stream ), fd, IPPROTO_UDP,
+ rtcp_mux );
if( sink.rtcp == NULL )
msg_Err( id, "RTCP failed!" );
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_*:
****************************************************************************/
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;
+}