/*****************************************************************************
* rtsp.c: rtsp VoD server module
*****************************************************************************
- * Copyright (C) 2003-2004 the VideoLAN team
+ * Copyright (C) 2003-2006 the VideoLAN team
* $Id$
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
#include "vlc_httpd.h"
#include "vlc_vod.h"
+#include "vlc_url.h"
#include "network.h"
+#include "charset.h"
/*****************************************************************************
* Module descriptor
static int Open ( vlc_object_t * );
static void Close( vlc_object_t * );
-#define HOST_TEXT N_( "Host address" )
+#define HOST_TEXT N_( "RTSP host address" )
+/// \bug [String] extra space
#define HOST_LONGTEXT N_( \
- "You can set the address, port and path the rtsp interface will bind to." \
- "\nSyntax is address:port/path. Default is to bind to any address "\
- "on port 554, with no path." )
+ "This defines the address, port and path the RTSP VOD server will listen " \
+ "on.\nSyntax is address:port/path. The default is to listen on all "\
+ "interfaces (address 0.0.0.0), on port 554, with no path.\n To listen " \
+ "only on the local interface, use \"localhost\" as address." )
+
+#define THROTLE_TEXT N_( "Maximum number of connections" )
+#define THROTLE_LONGTEXT N_( "This limits the maximum number of clients " \
+ "that can connect to the RTSP VOD. 0 means no limit." )
+
+#define RAWMUX_TEXT N_( "MUX for RAW RTSP transport" )
vlc_module_begin();
set_shortname( _("RTSP VoD" ) );
set_callbacks( Open, Close );
add_shortcut( "rtsp" );
add_string ( "rtsp-host", NULL, NULL, HOST_TEXT, HOST_LONGTEXT, VLC_TRUE );
+ add_string( "rtsp-raw-mux", NULL, NULL, RAWMUX_TEXT, RAWMUX_TEXT, VLC_TRUE );
+ add_integer( "rtsp-throttle-users", 0, NULL, THROTLE_TEXT,
+ THROTLE_LONGTEXT, VLC_TRUE );
vlc_module_end();
/*****************************************************************************
/* RTSP server */
httpd_url_t *p_rtsp_url;
- char *psz_rtsp_control_v4, *psz_rtsp_control_v6;
+ char *psz_rtsp_control_v4;
+ char *psz_rtsp_control_v6;
char *psz_rtsp_path;
int i_port;
int i_es;
media_es_t **es;
char *psz_mux;
- int b_raw;
+ vlc_bool_t b_raw;
/* RTSP client */
int i_rtsp;
httpd_host_t *p_rtsp_host;
char *psz_path;
int i_port;
+ int i_throttle_users;
+ int i_connections;
+
+ char *psz_raw_mux;
/* List of media */
int i_media;
if( !p_sys ) goto error;
p_sys->p_rtsp_host = 0;
+ var_Create( p_this, "rtsp-throttle-users", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
+ p_sys->i_throttle_users = var_GetInteger( p_this, "rtsp-throtle-users" );
+ msg_Dbg( p_this, "allowing up to %d connections", p_sys->i_throttle_users );
+ p_sys->i_connections = 0;
+
+ p_sys->psz_raw_mux = config_GetPsz( p_vod, "rtsp-raw-mux" );
+
p_sys->p_rtsp_host =
httpd_HostNew( VLC_OBJECT(p_vod), url.psz_host, url.i_port );
if( !p_sys->p_rtsp_host )
{
- msg_Err( p_vod, "cannot create http server (%s:%i)",
+ msg_Err( p_vod, "cannot create RTSP server (%s:%i)",
url.psz_host, url.i_port );
goto error;
}
p_sys->i_port = url.i_port;
vlc_UrlClean( &url );
- p_sys->media = 0;
+ p_sys->media = NULL;
p_sys->i_media = 0;
p_vod->pf_media_new = MediaNew;
return VLC_SUCCESS;
- error:
-
+error:
if( p_sys && p_sys->p_rtsp_host ) httpd_HostDelete( p_sys->p_rtsp_host );
+ if( p_sys && p_sys->psz_raw_mux ) free( p_sys->psz_raw_mux );
if( p_sys ) free( p_sys );
vlc_UrlClean( &url );
+
return VLC_EGENERIC;
}
vod_sys_t *p_sys = p_vod->p_sys;
httpd_HostDelete( p_sys->p_rtsp_host );
+ var_Destroy( p_this, "rtsp-throttle-users" );
/* TODO delete medias */
free( p_sys->psz_path );
+ free( p_sys->psz_raw_mux );
free( p_sys );
}
p_media->es = 0;
p_media->psz_mux = 0;
p_media->rtsp = 0;
+ p_media->b_raw = VLC_FALSE;
asprintf( &p_media->psz_rtsp_path, "%s%s", p_sys->psz_path, psz_name );
p_media->p_rtsp_url =
if( !p_media->p_rtsp_url )
{
- msg_Err( p_vod, "cannot create http url (%s)", p_media->psz_rtsp_path);
+ msg_Err( p_vod, "cannot create RTSP url (%s)", p_media->psz_rtsp_path);
free( p_media->psz_rtsp_path );
free( p_media );
return NULL;
}
- msg_Dbg( p_vod, "created rtsp url: %s", p_media->psz_rtsp_path );
+ msg_Dbg( p_vod, "created RTSP url: %s", p_media->psz_rtsp_path );
asprintf( &p_media->psz_rtsp_control_v4,
- "a=control:rtsp://%%s:%d%s/trackid=%%d\r\n",
+ "a=control:rtsp://%%s:%d%s/trackID=%%d\r\n",
p_sys->i_port, p_media->psz_rtsp_path );
asprintf( &p_media->psz_rtsp_control_v6,
- "a=control:rtsp://[%%s]:%d%s/trackid=%%d\r\n",
+ "a=control:rtsp://[%%s]:%d%s/trackID=%%d\r\n",
p_sys->i_port, p_media->psz_rtsp_path );
httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_SETUP,
RtspCallback, (void*)p_media );
httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_PAUSE,
RtspCallback, (void*)p_media );
+ httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_GETPARAMETER,
+ RtspCallback, (void*)p_media );
httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_TEARDOWN,
RtspCallback, (void*)p_media );
p_media->psz_mux = NULL;
/* TODO: update SDP, etc... */
- asprintf( &psz_urlc, "%s/trackid=%d",
+ asprintf( &psz_urlc, "%s/trackID=%d",
p_media->psz_rtsp_path, p_media->i_es );
msg_Dbg( p_vod, " - ES %4.4s (%s)", (char *)&p_fmt->i_codec, psz_urlc );
if( !p_es->p_rtsp_url )
{
- msg_Err( p_vod, "cannot create http url (%s)", psz_urlc );
+ msg_Err( p_vod, "cannot create RTSP url (%s)", psz_urlc );
free( psz_urlc );
free( p_es );
return VLC_EGENERIC;
static rtsp_client_t *RtspClientNew( vod_media_t *p_media, char *psz_session )
{
rtsp_client_t *p_rtsp = malloc( sizeof(rtsp_client_t) );
+
+ if( !p_rtsp ) return NULL;
memset( p_rtsp, 0, sizeof(rtsp_client_t) );
p_rtsp->es = 0;
p_rtsp->psz_session = psz_session;
TAB_APPEND( p_media->i_rtsp, p_media->rtsp, p_rtsp );
- msg_Dbg( p_media->p_vod, "new session: %s", psz_session );
+ p_media->p_vod->p_sys->i_connections++;
+ msg_Dbg( p_media->p_vod, "new session: %s, connections: %d",
+ psz_session, p_media->p_vod->p_sys->i_throttle_users );
return p_rtsp;
}
static void RtspClientDel( vod_media_t *p_media, rtsp_client_t *p_rtsp )
{
- msg_Dbg( p_media->p_vod, "closing session: %s", p_rtsp->psz_session );
+ p_media->p_vod->p_sys->i_connections--;
+ msg_Dbg( p_media->p_vod, "closing session: %s, connections: %d",
+ p_rtsp->psz_session, p_media->p_vod->p_sys->i_throttle_users );
while( p_rtsp->i_es-- )
{
char *psz_transport = NULL;
char *psz_playnow = NULL; /* support option: x-playNow */
char *psz_session = NULL;
+ char *psz_cseq = NULL;
rtsp_client_t *p_rtsp;
int i_port = 0;
+ int i_cseq = 0;
if( answer == NULL || query == NULL ) return VLC_SUCCESS;
- msg_Info( p_vod, "RtspCallback query: type=%d", query->i_type );
+ msg_Dbg( p_vod, "RtspCallback query: type=%d", query->i_type );
answer->i_proto = HTTPD_PROTO_RTSP;
answer->i_version = query->i_version;
answer->i_type = HTTPD_MSG_ANSWER;
+ answer->i_body = 0;
+ answer->p_body = NULL;
switch( query->i_type )
{
if( strstr( psz_transport, "MP2T/H2221/UDP" ) ||
strstr( psz_transport, "RAW/RAW/UDP" ) )
{
- p_media->b_raw = 1;
+ p_media->psz_mux = p_vod->p_sys->psz_raw_mux;
+ p_media->b_raw = VLC_TRUE;
}
+
if( httpd_ClientIP( cl, ip ) == NULL )
{
answer->i_status = 500;
}
msg_Dbg( p_vod, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
- ip, i_port );
+ ip, i_port );
psz_session = httpd_MsgGet( query, "Session" );
if( !psz_session || !*psz_session )
{
+ if( ( p_vod->p_sys->i_throttle_users > 0 ) &&
+ ( p_vod->p_sys->i_connections >= p_vod->p_sys->i_throttle_users ) )
+ {
+ answer->i_status = 503;
+ answer->psz_status = strdup( "Too many connections" );
+ answer->i_body = 0;
+ answer->p_body = NULL;
+ break;
+ }
asprintf( &psz_session, "%d", rand() );
p_rtsp = RtspClientNew( p_media, psz_session );
}
p_rtsp = RtspClientGet( p_media, psz_session );
if( !p_rtsp )
{
- /* FIXME right error code */
answer->i_status = 454;
answer->psz_status = strdup( "Unknown session id" );
answer->i_body = 0;
answer->i_body = 0;
answer->p_body = NULL;
- httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;client_port=%d-%d",
- i_port, i_port + 1 );
+ if( p_media->b_raw )
+ {
+ if( strstr( psz_transport, "MP2T/H2221/UDP" ) )
+ {
+ httpd_MsgAdd( answer, "Transport", "MP2T/H2221/UDP;client_port=%d-%d",
+ i_port, i_port + 1 );
+ }
+ else if( strstr( psz_transport, "RAW/RAW/UDP" ) )
+ {
+ httpd_MsgAdd( answer, "Transport", "RAW/RAW/UDP;client_port=%d-%d",
+ i_port, i_port + 1 );
+ }
+ }
+ else
+ httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;client_port=%d-%d",
+ i_port, i_port + 1 );
}
else /* TODO strstr( psz_transport, "interleaved" ) ) */
{
answer->i_body = 0;
answer->p_body = NULL;
}
+
+ /* Intentional fall-through on x-playNow option in RTSP request */
if( !psz_playnow )
break;
}
int i, i_port_audio = 0, i_port_video = 0;
/* for now only multicast so easy */
- answer->i_status = 200;
- answer->psz_status = strdup( "OK" );
- answer->i_body = 0;
- answer->p_body = NULL;
+ if( !psz_playnow )
+ {
+ answer->i_status = 200;
+ answer->psz_status = strdup( "OK" );
+ answer->i_body = 0;
+ answer->p_body = NULL;
+ }
if( !psz_session )
psz_session = httpd_MsgGet( query, "Session" );
RtspClientDel( p_media, p_rtsp );
break;
+ case HTTPD_MSG_GETPARAMETER:
+ answer->i_status = 200;
+ answer->psz_status = strdup( "OK" );
+ answer->i_body = 0;
+ answer->p_body = NULL;
+ break;
+
default:
return VLC_EGENERIC;
}
httpd_MsgAdd( answer, "Server", "VLC Server" );
httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
- httpd_MsgAdd( answer, "Cseq", "%d",
- atoi( httpd_MsgGet( query, "Cseq" ) ) );
+ psz_cseq = httpd_MsgGet( query, "Cseq" );
+ psz_cseq ? i_cseq = atoi( psz_cseq ) : 0;
+ httpd_MsgAdd( answer, "CSeq", "%d", i_cseq );
httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );
if( psz_session )
char *psz_playnow = NULL; /* support option: x-playNow */
char *psz_session = NULL;
char *psz_position = NULL;
+ char *psz_cseq = NULL;
+ int i_cseq = 0;
int i;
if( answer == NULL || query == NULL ) return VLC_SUCCESS;
- msg_Info( p_vod, "RtspCallback query: type=%d", query->i_type );
+ msg_Dbg( p_vod, "RtspCallback query: type=%d", query->i_type );
answer->i_proto = HTTPD_PROTO_RTSP;
answer->i_version = query->i_version;
answer->i_type = HTTPD_MSG_ANSWER;
+ answer->i_body = 0;
+ answer->p_body = NULL;
switch( query->i_type )
{
psz_session = httpd_MsgGet( query, "Session" );
if( !psz_session || !*psz_session )
{
+ if( ( p_vod->p_sys->i_throttle_users > 0 ) &&
+ ( p_vod->p_sys->i_connections >= p_vod->p_sys->i_throttle_users ) )
+ {
+ answer->i_status = 503;
+ answer->psz_status = strdup( "Too many connections" );
+ answer->i_body = 0;
+ answer->p_body = NULL;
+ break;
+ }
asprintf( &psz_session, "%d", rand() );
p_rtsp = RtspClientNew( p_media, psz_session );
}
p_rtsp = RtspClientGet( p_media, psz_session );
if( !p_rtsp )
{
- /* FIXME right error code */
answer->i_status = 454;
answer->psz_status = strdup( "Unknown session id" );
answer->i_body = 0;
answer->i_body = 0;
answer->p_body = NULL;
- httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;client_port=%d-%d",
- i_port, i_port + 1 );
+ if( p_media->b_raw )
+ {
+ if( strstr( psz_transport, "MP2T/H2221/UDP" ) )
+ {
+ httpd_MsgAdd( answer, "Transport", "MP2T/H2221/UDP;client_port=%d-%d",
+ i_port, i_port + 1 );
+ }
+ else if( strstr( psz_transport, "RAW/RAW/UDP" ) )
+ {
+ httpd_MsgAdd( answer, "Transport", "RAW/RAW/UDP;client_port=%d-%d",
+ i_port, i_port + 1 );
+ }
+ }
+ else
+ {
+ httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;client_port=%d-%d",
+ i_port, i_port + 1 );
+ }
}
else /* TODO strstr( psz_transport, "interleaved" ) ) */
{
answer->i_body = 0;
answer->p_body = NULL;
}
+
+ /* Intentional fall-through on x-playNow option in RTSP request */
if( !psz_playnow )
break;
if( psz_position ) psz_position = strstr( psz_position, "npt=" );
if( psz_position )
{
- float f_pos;
+ double f_pos;
+ char *end;
msg_Dbg( p_vod, "seeking request: %s", psz_position );
psz_position += 4;
- if( sscanf( psz_position, "%f", &f_pos ) == 1 )
+ /* FIXME: npt= is not necessarily formatted as a float */
+ f_pos = us_strtod( psz_position, &end );
+ if( end > psz_position )
{
- f_pos /= ((float)(p_media->i_length/1000))/1000 / 100;
+ f_pos /= ((double)(p_media->i_length))/1000 /1000 / 100;
vod_MediaControl( p_vod, p_media, psz_session,
- VOD_MEDIA_SEEK, (double)f_pos );
+ VOD_MEDIA_SEEK, f_pos );
}
}
- answer->i_status = 200;
- answer->psz_status = strdup( "OK" );
- answer->i_body = 0;
- answer->p_body = NULL;
+ if( !psz_playnow )
+ {
+ answer->i_status = 200;
+ answer->psz_status = strdup( "OK" );
+ answer->i_body = 0;
+ answer->p_body = NULL;
+ }
break;
case HTTPD_MSG_TEARDOWN:
httpd_MsgAdd( answer, "Server", "VLC Server" );
httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
- httpd_MsgAdd( answer, "Cseq", "%d",
- atoi( httpd_MsgGet( query, "Cseq" ) ) );
+ psz_cseq = httpd_MsgGet( query, "Cseq" );
+ if (psz_cseq)
+ i_cseq = atoi( psz_cseq );
+ else
+ i_cseq = 0;
+ httpd_MsgAdd( answer, "Cseq", "%d", i_cseq );
httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );
if( psz_session )
p += sprintf( p, "c=IN IP%c %s\r\n", ipv, ipv == '6' ? "::" : "0.0.0.0" );
if( p_media->i_length > 0 )
- p += sprintf( p, "a=range:npt=0-%.3f\r\n",
- ((float)(p_media->i_length/1000))/1000 );
+ {
+ lldiv_t d = lldiv( p_media->i_length / 1000, 1000 );
+ p += sprintf( p, "a=range:npt=0-"I64Fd".%03u\r\n", d.quot,
+ (unsigned)d.rem );
+ }
for( i = 0; i < p_media->i_es; i++ )
{