#define HOST_TEXT N_( "Host address" )
#define HOST_LONGTEXT N_( \
- "You can set the address, port and path the rtsp interface will bind to." )
-
+ "You can set the address, port and path the rtsp interface will bind to." \
+ ".\n Syntax is address:port/path. Default is to bind to localhost address"\
+ "on port 554, with no path. Use 0.0.0.0 to bind to all addresses." )
vlc_module_begin();
+ set_shortname( _("RTSP VoD" ) );
set_description( _("RTSP VoD server") );
+ set_category( CAT_SOUT );
+ set_subcategory( SUBCAT_SOUT_VOD );
set_capability( "vod server", 1 );
set_callbacks( Open, Close );
add_shortcut( "rtsp" );
* Exported prototypes
*****************************************************************************/
+typedef struct media_es_t media_es_t;
+
+typedef struct
+{
+ media_es_t *p_media_es;
+ char *psz_ip;
+ int i_port;
+
+} rtsp_client_es_t;
+
typedef struct
{
char *psz_session;
int64_t i_last; /* for timeout */
vlc_bool_t b_playing; /* is it in "play" state */
+ vlc_bool_t b_paused; /* is it in "pause" state */
+
+ int i_es;
+ rtsp_client_es_t **es;
} rtsp_client_t;
-typedef struct
+struct media_es_t
{
/* VoD server */
vod_t *p_vod;
/* RTSP server */
- httpd_url_t *p_rtsp_url;
+ httpd_url_t *p_rtsp_url;
vod_media_t *p_media;
char *psz_rtpmap;
char *psz_fmtp;
-} media_es_t;
+};
struct vod_media_t
{
int64_t i_sdp_id;
int i_sdp_version;
+ vlc_bool_t b_multicast;
+
vlc_mutex_t lock;
/* ES list */
int i_es;
media_es_t **es;
+ char *psz_mux;
/* RTSP client */
int i_rtsp;
char *psz_session_description;
char *psz_session_url;
char *psz_session_email;
+ mtime_t i_length;
};
struct vod_sys_t
memset( p_media, 0, sizeof(vod_media_t) );
p_media->es = 0;
+ p_media->psz_mux = 0;
p_media->rtsp = 0;
asprintf( &p_media->psz_rtsp_path, "%s%s", p_sys->psz_path, psz_name );
p_media->i_sdp_id = mdate();
p_media->i_sdp_version = 1;
+ p_media->i_length = p_item->i_duration;
vlc_mutex_lock( &p_item->lock );
msg_Dbg( p_vod, "media has %i declared ES", p_item->i_es );
char *psz_urlc;
memset( p_es, 0, sizeof(media_es_t) );
+ p_media->psz_mux = NULL;
/* TODO: update SDP, etc... */
asprintf( &psz_urlc, "%s/trackid=%d",
free( p_hexa );
}
break;
+ case VLC_FOURCC( 'm', 'p', '2', 't' ):
+ p_media->psz_mux = "ts";
+ p_es->i_payload_type = 33;
+ p_es->psz_rtpmap = strdup( "MP2T/90000" );
+ break;
+ case VLC_FOURCC( 'm', 'p', '2', 'p' ):
+ p_media->psz_mux = "ps";
+ p_es->i_payload_type = p_media->i_payload_type++;
+ p_es->psz_rtpmap = strdup( "MP2P/90000" );
+ break;
default:
msg_Err( p_vod, "cannot add this stream (unsupported "
httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_SETUP,
RtspCallbackES, (void*)p_es );
+ httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_TEARDOWN,
+ RtspCallbackES, (void*)p_es );
+ httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_PLAY,
+ RtspCallbackES, (void*)p_es );
+ httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_PAUSE,
+ RtspCallbackES, (void*)p_es );
es_format_Copy( &p_es->fmt, p_fmt );
p_es->p_vod = p_vod;
p_es->p_media = p_media;
+#if 0
/* Choose the port */
if( p_fmt->i_cat == AUDIO_ES && p_media->i_port_audio > 0 )
{
}
p_media->i_port += 2;
}
+#else
+
+ p_es->i_port = 0;
+#endif
vlc_mutex_lock( &p_media->lock );
TAB_APPEND( p_media->i_es, p_media->es, p_es );
static rtsp_client_t *RtspClientNew( vod_media_t *p_media, char *psz_session )
{
rtsp_client_t *p_rtsp = malloc( sizeof(rtsp_client_t) );
+ memset( p_rtsp, 0, sizeof(rtsp_client_t) );
+ p_rtsp->es = 0;
p_rtsp->psz_session = psz_session;
- p_rtsp->i_last = 0;
- p_rtsp->b_playing = VLC_FALSE;
-
TAB_APPEND( p_media->i_rtsp, p_media->rtsp, p_rtsp );
msg_Dbg( p_media->p_vod, "new session: %s", psz_session );
{
msg_Dbg( p_media->p_vod, "closing session: %s", p_rtsp->psz_session );
+ while( p_rtsp->i_es-- )
+ {
+ if( p_rtsp->es[p_rtsp->i_es]->psz_ip )
+ free( p_rtsp->es[p_rtsp->i_es]->psz_ip );
+ free( p_rtsp->es[p_rtsp->i_es] );
+ if( !p_rtsp->i_es ) free( p_rtsp->es );
+ }
+
TAB_REMOVE( p_media->i_rtsp, p_media->rtsp, p_rtsp );
free( p_rtsp->psz_session );
vod_t *p_vod = p_media->p_vod;
char *psz_destination = p_media->psz_destination;
char *psz_session = NULL;
+ rtsp_client_t *p_rtsp;
if( answer == NULL || query == NULL ) return VLC_SUCCESS;
case HTTPD_MSG_PLAY:
{
- rtsp_client_t *rtsp;
char *psz_output, *ip;
int i, i_port_audio = 0, i_port_video = 0;
psz_session = httpd_MsgGet( query, "Session" );
msg_Dbg( p_vod, "HTTPD_MSG_PLAY for session: %s", psz_session );
- rtsp = RtspClientGet( p_media, psz_session );
- if( !rtsp || rtsp->b_playing ) break;
+ p_rtsp = RtspClientGet( p_media, psz_session );
+ if( !p_rtsp ) break;
+
+ if( p_rtsp->b_playing && p_rtsp->b_paused )
+ {
+ vod_MediaControl( p_vod, p_media, psz_session,
+ VOD_MEDIA_PAUSE );
+ p_rtsp->b_paused = VLC_FALSE;
+ break;
+ }
+ else if( p_rtsp->b_playing ) break;
+
if( !(ip = httpd_ClientIP( cl )) ) break;
- rtsp->b_playing = VLC_TRUE;
+ p_rtsp->b_playing = VLC_TRUE;
/* FIXME for != 1 video and 1 audio */
- for( i = 0; i < p_media->i_es; i++ )
+ for( i = 0; i < p_rtsp->i_es; i++ )
{
- if( p_media->es[i]->fmt.i_cat == AUDIO_ES )
- i_port_audio = p_media->es[i]->i_port;
- if( p_media->es[i]->fmt.i_cat == VIDEO_ES )
- i_port_video = p_media->es[i]->i_port;
+ if( p_rtsp->es[i]->p_media_es->fmt.i_cat == AUDIO_ES )
+ i_port_audio = p_rtsp->es[i]->i_port;
+ if( p_rtsp->es[i]->p_media_es->fmt.i_cat == VIDEO_ES )
+ i_port_video = p_rtsp->es[i]->i_port;
+ }
+
+ if( p_media->psz_mux )
+ {
+ asprintf( &psz_output, "rtp{dst=%s,port=%i,mux=%s}",
+ ip, i_port_video, p_media->psz_mux );
+ }
+ else
+ {
+ asprintf( &psz_output, "rtp{dst=%s,port-video=%i,"
+ "port-audio=%i}", ip, i_port_video, i_port_audio );
}
- asprintf( &psz_output, "rtp{dst=%s,port-video=%i,port-audio=%i}",
- ip, i_port_video, i_port_audio );
vod_MediaControl( p_vod, p_media, psz_session, VOD_MEDIA_PLAY,
psz_output );
free( psz_output );
psz_session = httpd_MsgGet( query, "Session" );
msg_Dbg( p_vod, "HTTPD_MSG_PAUSE for session: %s", psz_session );
+ p_rtsp = RtspClientGet( p_media, psz_session );
+ if( !p_rtsp ) break;
+
vod_MediaControl( p_vod, p_media, psz_session, VOD_MEDIA_PAUSE );
- /* TODO: do something useful */
- return VLC_EGENERIC;
+ p_rtsp->b_paused = VLC_TRUE;
- case HTTPD_MSG_TEARDOWN:
- {
- rtsp_client_t *rtsp;
+ answer->i_status = 200;
+ answer->psz_status = strdup( "OK" );
+ answer->i_body = 0;
+ answer->p_body = NULL;
+ break;
+ case HTTPD_MSG_TEARDOWN:
/* for now only multicast so easy again */
answer->i_status = 200;
answer->psz_status = strdup( "OK" );
psz_session = httpd_MsgGet( query, "Session" );
msg_Dbg( p_vod, "HTTPD_MSG_TEARDOWN for session: %s", psz_session);
- rtsp = RtspClientGet( p_media, psz_session );
- if( !rtsp ) break;
+ p_rtsp = RtspClientGet( p_media, psz_session );
+ if( !p_rtsp ) break;
vod_MediaControl( p_vod, p_media, psz_session, VOD_MEDIA_STOP );
- RtspClientDel( p_media, rtsp );
+ RtspClientDel( p_media, p_rtsp );
break;
- }
default:
return VLC_EGENERIC;
media_es_t *p_es = (media_es_t*)p_args;
vod_media_t *p_media = p_es->p_media;
vod_t *p_vod = p_media->p_vod;
+ rtsp_client_t *p_rtsp = NULL;
char *psz_session = NULL;
char *psz_transport = NULL;
+ char *psz_position = NULL;
+ int i;
if( answer == NULL || query == NULL ) return VLC_SUCCESS;
else if( strstr( psz_transport, "unicast" ) &&
strstr( psz_transport, "client_port=" ) )
{
- rtsp_client_t *rtsp = NULL;
+ rtsp_client_t *p_rtsp;
+ rtsp_client_es_t *p_rtsp_es;
char *ip = httpd_ClientIP( cl );
int i_port = atoi( strstr( psz_transport, "client_port=" ) +
strlen("client_port=") );
if( !psz_session || !*psz_session )
{
asprintf( &psz_session, "%d", rand() );
- rtsp = RtspClientNew( p_media, psz_session );
+ p_rtsp = RtspClientNew( p_media, psz_session );
}
else
{
- rtsp = RtspClientGet( p_media, psz_session );
- if( !rtsp )
+ p_rtsp = RtspClientGet( p_media, psz_session );
+ if( !p_rtsp )
{
/* FIXME right error code */
answer->i_status = 400;
}
}
- /* TODO: do something useful */
+ p_rtsp_es = malloc( sizeof(rtsp_client_es_t) );
+ p_rtsp_es->i_port = i_port;
+ p_rtsp_es->psz_ip = strdup( ip );
+ p_rtsp_es->p_media_es = p_es;
+ TAB_APPEND( p_rtsp->i_es, p_rtsp->es, p_rtsp_es );
answer->i_status = 200;
answer->psz_status = strdup( "OK" );
}
break;
+ case HTTPD_MSG_TEARDOWN:
+ answer->i_status = 200;
+ answer->psz_status = strdup( "OK" );
+ answer->i_body = 0;
+ answer->p_body = NULL;
+
+ psz_session = httpd_MsgGet( query, "Session" );
+ msg_Dbg( p_vod, "HTTPD_MSG_TEARDOWN for session: %s", psz_session);
+
+ p_rtsp = RtspClientGet( p_media, psz_session );
+ if( !p_rtsp ) break;
+
+ for( i = 0; i < p_rtsp->i_es; i++ )
+ {
+ if( p_rtsp->es[i]->p_media_es == p_es )
+ {
+ if( p_rtsp->es[i]->psz_ip ) free( p_rtsp->es[i]->psz_ip );
+ TAB_REMOVE( p_rtsp->i_es, p_rtsp->es, p_rtsp->es[i] );
+ break;
+ }
+ }
+
+ if( !p_rtsp->i_es )
+ {
+ vod_MediaControl( p_vod, p_media, psz_session,
+ VOD_MEDIA_STOP );
+ RtspClientDel( p_media, p_rtsp );
+ }
+ break;
+
+ case HTTPD_MSG_PLAY:
+ /* This is kind of a kludge. Should we only support Aggregate
+ * Operations ? */
+ psz_session = httpd_MsgGet( query, "Session" );
+ msg_Dbg( p_vod, "HTTPD_MSG_PLAY for session: %s", psz_session );
+
+ p_rtsp = RtspClientGet( p_media, psz_session );
+
+ psz_position = httpd_MsgGet( query, "Range" );
+ if( psz_position ) psz_position = strstr( psz_position, "npt=" );
+ if( psz_position )
+ {
+ float f_pos;
+
+ msg_Dbg( p_vod, "seeking request: %s", psz_position );
+
+ psz_position += 4;
+ if( sscanf( psz_position, "%f", &f_pos ) == 1 )
+ {
+ f_pos /= ((float)(p_media->i_length/1000))/1000 / 100;
+ vod_MediaControl( p_vod, p_media, psz_session,
+ VOD_MEDIA_SEEK, (double)f_pos );
+ }
+ }
+
+ answer->i_status = 200;
+ answer->psz_status = strdup( "OK" );
+ answer->i_body = 0;
+ answer->p_body = NULL;
+ break;
+
+ case HTTPD_MSG_PAUSE:
+ /* This is kind of a kludge. Should we only support Aggregate
+ * Operations ? */
+ psz_session = httpd_MsgGet( query, "Session" );
+ msg_Dbg( p_vod, "HTTPD_MSG_PAUSE for session: %s", psz_session );
+
+ p_rtsp = RtspClientGet( p_media, psz_session );
+ if( !p_rtsp ) break;
+
+ vod_MediaControl( p_vod, p_media, psz_session, VOD_MEDIA_PAUSE );
+ p_rtsp->b_paused = VLC_TRUE;
+
+ answer->i_status = 200;
+ answer->psz_status = strdup( "OK" );
+ answer->i_body = 0;
+ answer->p_body = NULL;
+ break;
+
default:
return VLC_EGENERIC;
break;
*****************************************************************************/
static char *SDPGenerate( vod_media_t *p_media, char *psz_destination )
{
-#if 0
- return strdup( "" );
-
-#else
int i, i_size;
char *p, *psz_sdp;
strlen( "t=0 0\r\n" ) + /* FIXME */
strlen( "a=tool:"PACKAGE_STRING"\r\n" ) +
strlen( "c=IN IP4 */*\r\n" ) + 20 + 10 +
- strlen( psz_destination ? psz_destination : "0.0.0.0" ) ;
+ strlen( psz_destination ? psz_destination : "0.0.0.0" ) +
+ strlen( "a=range:npt=0-1000000000.000\r\n" );
for( i = 0; i < p_media->i_es; i++ )
{
p += sprintf( p, "c=IN IP4 %s/%d\r\n", psz_destination ?
psz_destination : "0.0.0.0", p_media->i_ttl );
+ if( p_media->i_length > 0 )
+ p += sprintf( p, "a=range:npt=0-%.3f\r\n",
+ ((float)(p_media->i_length/1000))/1000 );
+
for( i = 0; i < p_media->i_es; i++ )
{
media_es_t *p_es = p_media->es[i];
fprintf( stderr, psz_sdp );
return psz_sdp;
-#endif
}