1 /*****************************************************************************
2 * vod.c: rtsp VoD server module
3 *****************************************************************************
4 * Copyright (C) 2003-2006, 2010 the VideoLAN team
7 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8 * Gildas Bazin <gbazin@videolan.org>
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24 *****************************************************************************/
26 /*****************************************************************************
28 *****************************************************************************/
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_input.h>
38 #include <vlc_block.h>
42 #include <vlc_network.h>
48 /*****************************************************************************
50 *****************************************************************************/
52 typedef struct media_es_t media_es_t;
58 rtsp_stream_id_t *rtsp_id;
83 block_fifo_t *p_fifo_cmd;
86 /* rtsp delayed command (to avoid deadlock between vlm/httpd) */
102 static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * );
103 static void MediaDel( vod_t *, vod_media_t * );
104 static void MediaAskDel ( vod_t *, vod_media_t * );
106 static void* CommandThread( vlc_object_t *p_this );
107 static void CommandPush( vod_t *, rtsp_cmd_type_t, vod_media_t *,
108 const char *psz_arg );
110 /*****************************************************************************
111 * Open: Starts the RTSP server module
112 *****************************************************************************/
113 int OpenVoD( vlc_object_t *p_this )
115 vod_t *p_vod = (vod_t *)p_this;
116 vod_sys_t *p_sys = NULL;
119 p_vod->p_sys = p_sys = malloc( sizeof( vod_sys_t ) );
120 if( !p_sys ) goto error;
122 psz_url = var_InheritString( p_vod, "rtsp-host" );
124 if( psz_url == NULL )
125 p_sys->psz_rtsp_url = strdup( "/" );
127 if( !( strlen( psz_url ) > 0 && psz_url[strlen( psz_url ) - 1] == '/' ) )
129 if( asprintf( &p_sys->psz_rtsp_url, "%s/", psz_url ) == -1 )
131 p_sys->psz_rtsp_url = NULL;
138 p_sys->psz_rtsp_url = psz_url;
140 p_vod->pf_media_new = MediaNew;
141 p_vod->pf_media_del = MediaAskDel;
143 p_sys->p_fifo_cmd = block_FifoNew();
144 if( vlc_thread_create( p_vod, CommandThread, VLC_THREAD_PRIORITY_LOW ) )
146 msg_Err( p_vod, "cannot spawn rtsp vod thread" );
147 block_FifoRelease( p_sys->p_fifo_cmd );
156 free( p_sys->psz_rtsp_url );
163 /*****************************************************************************
165 *****************************************************************************/
166 void CloseVoD( vlc_object_t * p_this )
168 vod_t *p_vod = (vod_t *)p_this;
169 vod_sys_t *p_sys = p_vod->p_sys;
171 /* Stop command thread */
172 vlc_object_kill( p_vod );
173 vlc_thread_join( p_vod );
175 while( block_FifoCount( p_sys->p_fifo_cmd ) > 0 )
178 block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
179 memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
180 block_Release( p_block_cmd );
181 if ( cmd.i_type == RTSP_CMD_TYPE_DEL )
182 MediaDel(p_vod, cmd.p_media);
185 block_FifoRelease( p_sys->p_fifo_cmd );
187 free( p_sys->psz_rtsp_url );
191 /*****************************************************************************
193 *****************************************************************************/
194 static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name,
195 input_item_t *p_item )
197 vod_media_t *p_media = calloc( 1, sizeof(vod_media_t) );
201 p_media->p_vod = p_vod;
202 p_media->rtsp = NULL;
203 TAB_INIT( p_media->i_es, p_media->es );
204 p_media->psz_mux = NULL;
205 p_media->i_length = input_item_GetDuration( p_item );
207 vlc_mutex_lock( &p_item->lock );
208 msg_Dbg( p_vod, "media '%s' has %i declared ES", psz_name, p_item->i_es );
209 for( int i = 0; i < p_item->i_es; i++ )
211 es_format_t *p_fmt = p_item->es[i];
213 switch( p_fmt->i_codec )
215 case VLC_FOURCC( 'm', 'p', '2', 't' ):
216 p_media->psz_mux = "ts";
218 case VLC_FOURCC( 'm', 'p', '2', 'p' ):
219 p_media->psz_mux = "ps";
222 assert(p_media->psz_mux == NULL || p_item->i_es == 1);
224 media_es_t *p_es = calloc( 1, sizeof(media_es_t) );
228 p_es->es_id = p_fmt->i_id;
229 p_es->rtsp_id = NULL;
231 if (rtp_get_fmt(VLC_OBJECT(p_vod), p_fmt, p_media->psz_mux,
232 &p_es->rtp_fmt) != VLC_SUCCESS)
238 TAB_APPEND( p_media->i_es, p_media->es, p_es );
239 msg_Dbg(p_vod, " - added ES %u %s (%4.4s)",
240 p_es->rtp_fmt.payload_type, p_es->rtp_fmt.ptname,
241 (char *)&p_fmt->i_codec);
243 vlc_mutex_unlock( &p_item->lock );
245 if (p_media->i_es == 0)
247 msg_Err(p_vod, "no ES was added to the media, aborting");
251 msg_Dbg(p_vod, "adding media '%s'", psz_name);
253 CommandPush( p_vod, RTSP_CMD_TYPE_ADD, p_media, psz_name );
257 MediaDel(p_vod, p_media);
261 static void MediaSetup( vod_t *p_vod, vod_media_t *p_media,
262 const char *psz_name )
264 vod_sys_t *p_sys = p_vod->p_sys;
267 if( asprintf( &psz_url, "%s%s", p_sys->psz_rtsp_url, psz_name ) < 0 )
271 vlc_UrlParse( &url, psz_url, 0 );
274 p_media->rtsp = RtspSetup(VLC_OBJECT(p_vod), p_media, &url);
276 vlc_UrlClean( &url );
278 if (p_media->rtsp == NULL)
281 for (int i = 0; i < p_media->i_es; i++)
283 media_es_t *p_es = p_media->es[i];
284 p_es->rtsp_id = RtspAddId(p_media->rtsp, NULL, 0,
285 p_es->rtp_fmt.clock_rate, -1);
289 static void MediaAskDel ( vod_t *p_vod, vod_media_t *p_media )
291 msg_Dbg( p_vod, "deleting media" );
292 CommandPush( p_vod, RTSP_CMD_TYPE_DEL, p_media, NULL );
295 static void MediaDel( vod_t *p_vod, vod_media_t *p_media )
299 if (p_media->rtsp != NULL)
301 for (int i = 0; i < p_media->i_es; i++)
303 media_es_t *p_es = p_media->es[i];
304 if (p_es->rtsp_id != NULL)
305 RtspDelId(p_media->rtsp, p_es->rtsp_id);
307 RtspUnsetup(p_media->rtsp);
310 while( p_media->i_es )
312 media_es_t *p_es = p_media->es[0];
313 TAB_REMOVE( p_media->i_es, p_media->es, p_es );
314 free( p_es->rtp_fmt.fmtp );
318 TAB_CLEAN( p_media->i_es, p_media->es );
322 static void CommandPush( vod_t *p_vod, rtsp_cmd_type_t i_type,
323 vod_media_t *p_media, const char *psz_arg )
329 cmd.p_media = p_media;
331 cmd.psz_arg = strdup(psz_arg);
335 p_cmd = block_New( p_vod, sizeof(rtsp_cmd_t) );
336 memcpy( p_cmd->p_buffer, &cmd, sizeof(cmd) );
338 block_FifoPut( p_vod->p_sys->p_fifo_cmd, p_cmd );
341 static void* CommandThread( vlc_object_t *p_this )
343 vod_t *p_vod = (vod_t*)p_this;
344 vod_sys_t *p_sys = p_vod->p_sys;
346 while( vlc_object_alive (p_vod) )
348 block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
354 int canc = vlc_savecancel ();
355 memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
356 block_Release( p_block_cmd );
361 case RTSP_CMD_TYPE_ADD:
362 MediaSetup(p_vod, cmd.p_media, cmd.psz_arg);
364 case RTSP_CMD_TYPE_DEL:
365 MediaDel(p_vod, cmd.p_media);
367 case RTSP_CMD_TYPE_STOP:
368 vod_MediaControl( p_vod, cmd.p_media, cmd.psz_arg, VOD_MEDIA_STOP );
376 vlc_restorecancel (canc);
382 /*****************************************************************************
384 * FIXME: needs to be merged more?
385 *****************************************************************************/
386 char *SDPGenerateVoD( const vod_media_t *p_media, const char *rtsp_url )
390 assert(rtsp_url != NULL);
391 /* Check against URL format rtsp://[<ipv6>]:<port>/<path> */
392 bool ipv6 = strlen( rtsp_url ) > 7 && rtsp_url[7] == '[';
394 /* Dummy destination address for RTSP */
395 struct sockaddr_storage dst;
396 socklen_t dstlen = ipv6 ? sizeof( struct sockaddr_in6 )
397 : sizeof( struct sockaddr_in );
398 memset (&dst, 0, dstlen);
399 dst.ss_family = ipv6 ? AF_INET6 : AF_INET;
404 psz_sdp = vlc_sdp_Start( VLC_OBJECT( p_media->p_vod ), "sout-rtp-",
405 NULL, 0, (struct sockaddr *)&dst, dstlen );
406 if( psz_sdp == NULL )
409 if( p_media->i_length > 0 )
411 lldiv_t d = lldiv( p_media->i_length / 1000, 1000 );
412 sdp_AddAttribute( &psz_sdp, "range"," npt=0-%lld.%03u", d.quot,
416 sdp_AddAttribute ( &psz_sdp, "control", "%s", rtsp_url );
418 /* No locking needed, the ES table can't be modified now */
419 for( int i = 0; i < p_media->i_es; i++ )
421 media_es_t *p_es = p_media->es[i];
422 rtp_format_t *rtp_fmt = &p_es->rtp_fmt;
423 const char *mime_major; /* major MIME type */
425 switch( rtp_fmt->cat )
428 mime_major = "video";
431 mime_major = "audio";
440 sdp_AddMedia( &psz_sdp, mime_major, "RTP/AVP", 0,
441 rtp_fmt->payload_type, false, 0,
442 rtp_fmt->ptname, rtp_fmt->clock_rate, rtp_fmt->channels,
445 char *track_url = RtspAppendTrackPath( p_es->rtsp_id, rtsp_url );
446 if( track_url != NULL )
448 sdp_AddAttribute ( &psz_sdp, "control", "%s", track_url );
456 int vod_check_range(vod_media_t *p_media, const char *psz_session,
457 int64_t start, int64_t end)
461 if (p_media->i_length > 0 && (start > p_media->i_length
462 || end > p_media->i_length))
468 /* TODO: add support in the VLM for queueing proper PLAY requests with
469 * start and end times, fetch whether the input is seekable... and then
471 void vod_play(vod_media_t *p_media, const char *psz_session,
472 int64_t *start, int64_t end)
474 if (vod_check_range(p_media, psz_session, *start, end) != VLC_SUCCESS)
477 /* We're passing the #vod{} sout chain here */
478 vod_MediaControl(p_media->p_vod, p_media, psz_session,
479 VOD_MEDIA_PLAY, "vod", start);
482 void vod_pause(vod_media_t *p_media, const char *psz_session, int64_t *npt)
484 vod_MediaControl(p_media->p_vod, p_media, psz_session,
485 VOD_MEDIA_PAUSE, npt);
488 void vod_stop(vod_media_t *p_media, const char *psz_session)
490 CommandPush(p_media->p_vod, RTSP_CMD_TYPE_STOP, p_media, psz_session);
494 const char *vod_get_mux(const vod_media_t *p_media)
496 return p_media->psz_mux;
500 /* Match an RTP id to a VoD media ES and RTSP track to initialize it
501 * with the data that was already set up */
502 int vod_init_id(vod_media_t *p_media, const char *psz_session, int es_id,
503 sout_stream_id_t *sout_id, rtp_format_t *rtp_fmt,
504 uint32_t *ssrc, uint16_t *seq_init)
508 if (p_media->psz_mux != NULL)
510 assert(p_media->i_es == 1);
511 p_es = p_media->es[0];
516 /* No locking needed, the ES table can't be modified now */
517 for (int i = 0; i < p_media->i_es; i++)
519 if (p_media->es[i]->es_id == es_id)
521 p_es = p_media->es[i];
529 memcpy(rtp_fmt, &p_es->rtp_fmt, sizeof(*rtp_fmt));
530 if (p_es->rtp_fmt.fmtp != NULL)
531 rtp_fmt->fmtp = strdup(p_es->rtp_fmt.fmtp);
533 return RtspTrackAttach(p_media->rtsp, psz_session, p_es->rtsp_id,
534 sout_id, ssrc, seq_init);
537 /* Remove references to the RTP id from its RTSP track */
538 void vod_detach_id(vod_media_t *p_media, const char *psz_session,
539 sout_stream_id_t *sout_id)
541 RtspTrackDetach(p_media->rtsp, psz_session, sout_id);