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) */
89 RTSP_CMD_TYPE_NONE, /* Exit requested */
97 RTSP_CMD_TYPE_FORWARD,
108 vod_media_t *p_media;
114 static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * );
115 static void MediaDel( vod_t *, vod_media_t * );
116 static void MediaAskDel ( vod_t *, vod_media_t * );
118 static void* CommandThread( vlc_object_t *p_this );
119 static void CommandPush( vod_t *, rtsp_cmd_type_t, vod_media_t *, const char *psz_session,
120 int64_t i_arg, const char *psz_arg );
122 /*****************************************************************************
123 * Open: Starts the RTSP server module
124 *****************************************************************************/
125 int OpenVoD( vlc_object_t *p_this )
127 vod_t *p_vod = (vod_t *)p_this;
128 vod_sys_t *p_sys = NULL;
131 p_vod->p_sys = p_sys = malloc( sizeof( vod_sys_t ) );
132 if( !p_sys ) goto error;
134 psz_url = var_InheritString( p_vod, "rtsp-host" );
136 if( psz_url == NULL )
137 p_sys->psz_rtsp_url = strdup( "/" );
139 if( !( strlen( psz_url ) > 0 && psz_url[strlen( psz_url ) - 1] == '/' ) )
141 if( asprintf( &p_sys->psz_rtsp_url, "%s/", psz_url ) == -1 )
143 p_sys->psz_rtsp_url = NULL;
150 p_sys->psz_rtsp_url = psz_url;
152 p_vod->pf_media_new = MediaNew;
153 p_vod->pf_media_del = MediaAskDel;
155 p_sys->p_fifo_cmd = block_FifoNew();
156 if( vlc_thread_create( p_vod, "rtsp vod thread", CommandThread,
157 VLC_THREAD_PRIORITY_LOW ) )
159 msg_Err( p_vod, "cannot spawn rtsp vod thread" );
160 block_FifoRelease( p_sys->p_fifo_cmd );
169 free( p_sys->psz_rtsp_url );
176 /*****************************************************************************
178 *****************************************************************************/
179 void CloseVoD( vlc_object_t * p_this )
181 vod_t *p_vod = (vod_t *)p_this;
182 vod_sys_t *p_sys = p_vod->p_sys;
184 /* Stop command thread */
185 vlc_object_kill( p_vod );
186 CommandPush( p_vod, RTSP_CMD_TYPE_NONE, NULL, NULL, 0, NULL );
187 vlc_thread_join( p_vod );
189 while( block_FifoCount( p_sys->p_fifo_cmd ) > 0 )
192 block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
193 memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
194 block_Release( p_block_cmd );
195 if ( cmd.i_type == RTSP_CMD_TYPE_DEL )
196 MediaDel(p_vod, cmd.p_media);
197 free( cmd.psz_session );
200 block_FifoRelease( p_sys->p_fifo_cmd );
202 free( p_sys->psz_rtsp_url );
206 /*****************************************************************************
208 *****************************************************************************/
209 static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name,
210 input_item_t *p_item )
212 vod_sys_t *p_sys = p_vod->p_sys;
214 vod_media_t *p_media = calloc( 1, sizeof(vod_media_t) );
218 p_media->p_vod = p_vod;
219 p_media->rtsp = NULL;
220 TAB_INIT( p_media->i_es, p_media->es );
221 p_media->psz_mux = NULL;
222 p_media->i_length = input_item_GetDuration( p_item );
224 vlc_mutex_lock( &p_item->lock );
225 msg_Dbg( p_vod, "media '%s' has %i declared ES", psz_name, p_item->i_es );
226 for( int i = 0; i < p_item->i_es; i++ )
228 es_format_t *p_fmt = p_item->es[i];
230 switch( p_fmt->i_codec )
232 case VLC_FOURCC( 'm', 'p', '2', 't' ):
233 p_media->psz_mux = "ts";
235 case VLC_FOURCC( 'm', 'p', '2', 'p' ):
236 p_media->psz_mux = "ps";
239 assert(p_media->psz_mux == NULL || p_item->i_es == 1);
241 media_es_t *p_es = calloc( 1, sizeof(media_es_t) );
245 p_es->es_id = p_fmt->i_id;
246 p_es->rtsp_id = NULL;
248 if (rtp_get_fmt(VLC_OBJECT(p_vod), p_fmt, p_media->psz_mux,
249 &p_es->rtp_fmt) != VLC_SUCCESS)
255 TAB_APPEND( p_media->i_es, p_media->es, p_es );
256 msg_Dbg(p_vod, " - added ES %u %s (%4.4s)",
257 p_es->rtp_fmt.payload_type, p_es->rtp_fmt.ptname,
258 (char *)&p_fmt->i_codec);
260 vlc_mutex_unlock( &p_item->lock );
262 if (p_media->i_es == 0)
264 msg_Err(p_vod, "no ES was added to the media, aborting");
270 if( asprintf( &psz_url, "%s%s", p_sys->psz_rtsp_url, psz_name ) < 0 )
274 vlc_UrlParse( &url, psz_url, 0 );
277 p_media->rtsp = RtspSetup(VLC_OBJECT(p_vod), p_media, &url);
279 vlc_UrlClean( &url );
281 if (p_media->rtsp == NULL)
284 for (int i = 0; i < p_media->i_es; i++)
286 media_es_t *p_es = p_media->es[i];
287 p_es->rtsp_id = RtspAddId(p_media->rtsp, NULL, 0,
288 p_es->rtp_fmt.clock_rate, -1);
289 if (p_es->rtsp_id == NULL)
293 msg_Dbg(p_vod, "adding media '%s'", psz_name);
295 CommandPush( p_vod, RTSP_CMD_TYPE_ADD, p_media, NULL, 0, NULL );
299 MediaDel(p_vod, p_media);
303 static void MediaAskDel ( vod_t *p_vod, vod_media_t *p_media )
305 msg_Dbg( p_vod, "deleting media" );
306 CommandPush( p_vod, RTSP_CMD_TYPE_DEL, p_media, NULL, 0, NULL );
309 static void MediaDel( vod_t *p_vod, vod_media_t *p_media )
313 if (p_media->rtsp != NULL)
315 for (int i = 0; i < p_media->i_es; i++)
317 media_es_t *p_es = p_media->es[i];
318 if (p_es->rtsp_id != NULL)
319 RtspDelId(p_media->rtsp, p_es->rtsp_id);
321 RtspUnsetup(p_media->rtsp);
324 while( p_media->i_es )
326 media_es_t *p_es = p_media->es[0];
327 TAB_REMOVE( p_media->i_es, p_media->es, p_es );
328 free( p_es->rtp_fmt.fmtp );
332 TAB_CLEAN( p_media->i_es, p_media->es );
336 static void CommandPush( vod_t *p_vod, rtsp_cmd_type_t i_type, vod_media_t *p_media, const char *psz_session,
337 int64_t i_arg, const char *psz_arg )
342 memset( &cmd, 0, sizeof(cmd) );
344 cmd.p_media = p_media;
346 cmd.psz_session = strdup(psz_session);
349 cmd.psz_arg = strdup(psz_arg);
351 p_cmd = block_New( p_vod, sizeof(rtsp_cmd_t) );
352 memcpy( p_cmd->p_buffer, &cmd, sizeof(cmd) );
354 block_FifoPut( p_vod->p_sys->p_fifo_cmd, p_cmd );
357 static void* CommandThread( vlc_object_t *p_this )
359 vod_t *p_vod = (vod_t*)p_this;
360 vod_sys_t *p_sys = p_vod->p_sys;
361 int canc = vlc_savecancel ();
363 while( vlc_object_alive (p_vod) )
365 block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
367 vod_media_t *p_media = cmd.p_media;
372 memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
373 block_Release( p_block_cmd );
375 if( cmd.i_type == RTSP_CMD_TYPE_NONE )
378 if ( cmd.i_type == RTSP_CMD_TYPE_ADD )
383 if ( cmd.i_type == RTSP_CMD_TYPE_DEL )
385 MediaDel(p_vod, cmd.p_media);
392 case RTSP_CMD_TYPE_PLAY:
393 vod_MediaControl( p_vod, p_media, cmd.psz_session,
394 VOD_MEDIA_PLAY, cmd.psz_arg );
396 case RTSP_CMD_TYPE_PAUSE:
397 vod_MediaControl( p_vod, p_media, cmd.psz_session,
401 case RTSP_CMD_TYPE_STOP:
402 vod_MediaControl( p_vod, p_media, cmd.psz_session, VOD_MEDIA_STOP );
405 case RTSP_CMD_TYPE_SEEK:
406 vod_MediaControl( p_vod, p_media, cmd.psz_session,
407 VOD_MEDIA_SEEK, cmd.i_arg );
411 case RTSP_CMD_TYPE_REWIND:
412 vod_MediaControl( p_vod, p_media, cmd.psz_session,
413 VOD_MEDIA_REWIND, cmd.f_arg );
416 case RTSP_CMD_TYPE_FORWARD:
417 vod_MediaControl( p_vod, p_media, cmd.psz_session,
418 VOD_MEDIA_FORWARD, cmd.f_arg );
427 free( cmd.psz_session );
431 vlc_restorecancel (canc);
435 /*****************************************************************************
437 * FIXME: needs to be merged more?
438 *****************************************************************************/
439 char *SDPGenerateVoD( const vod_media_t *p_media, const char *rtsp_url )
443 assert(rtsp_url != NULL);
444 /* Check against URL format rtsp://[<ipv6>]:<port>/<path> */
445 bool ipv6 = strlen( rtsp_url ) > 7 && rtsp_url[7] == '[';
447 /* Dummy destination address for RTSP */
448 struct sockaddr_storage dst;
449 socklen_t dstlen = ipv6 ? sizeof( struct sockaddr_in6 )
450 : sizeof( struct sockaddr_in );
451 memset (&dst, 0, dstlen);
452 dst.ss_family = ipv6 ? AF_INET6 : AF_INET;
457 psz_sdp = vlc_sdp_Start( VLC_OBJECT( p_media->p_vod ), "sout-rtp-",
458 NULL, 0, (struct sockaddr *)&dst, dstlen );
459 if( psz_sdp == NULL )
462 if( p_media->i_length > 0 )
464 lldiv_t d = lldiv( p_media->i_length / 1000, 1000 );
465 sdp_AddAttribute( &psz_sdp, "range"," npt=0-%lld.%03u", d.quot,
469 sdp_AddAttribute ( &psz_sdp, "control", "%s", rtsp_url );
471 /* No locking needed, the ES table can't be modified now */
472 for( int i = 0; i < p_media->i_es; i++ )
474 media_es_t *p_es = p_media->es[i];
475 rtp_format_t *rtp_fmt = &p_es->rtp_fmt;
476 const char *mime_major; /* major MIME type */
478 switch( rtp_fmt->cat )
481 mime_major = "video";
484 mime_major = "audio";
493 sdp_AddMedia( &psz_sdp, mime_major, "RTP/AVP", 0,
494 rtp_fmt->payload_type, false, 0,
495 rtp_fmt->ptname, rtp_fmt->clock_rate, rtp_fmt->channels,
498 char *track_url = RtspAppendTrackPath( p_es->rtsp_id, rtsp_url );
499 if( track_url != NULL )
501 sdp_AddAttribute ( &psz_sdp, "control", "%s", track_url );
509 int vod_check_range(vod_media_t *p_media, const char *psz_session,
510 int64_t start, int64_t end)
514 if (p_media->i_length > 0 && (start > p_media->i_length
515 || end > p_media->i_length))
521 /* TODO: add support in the VLM for queueing proper PLAY requests with
522 * start and end times, fetch whether the input is seekable... and then
523 * clean this up and remove the running argument */
524 void vod_play(vod_media_t *p_media, const char *psz_session,
525 int64_t start, int64_t end, bool running)
527 if (vod_check_range(p_media, psz_session, start, end) != VLC_SUCCESS)
530 /* We want to seek before unpausing, but it won't
531 * work if the instance is not running yet. */
534 /* We're passing the #vod{} sout chain here */
535 CommandPush(p_media->p_vod, RTSP_CMD_TYPE_PLAY, p_media,
536 psz_session, 0, "vod");
538 CommandPush(p_media->p_vod, RTSP_CMD_TYPE_SEEK, p_media,
539 psz_session, start, NULL);
541 /* This is the thing to do to unpause... */
542 CommandPush(p_media->p_vod, RTSP_CMD_TYPE_PLAY, p_media,
543 psz_session, 0, "vod");
546 void vod_pause(vod_media_t *p_media, const char *psz_session)
548 CommandPush(p_media->p_vod, RTSP_CMD_TYPE_PAUSE, p_media,
549 psz_session, 0, NULL);
552 void vod_stop(vod_media_t *p_media, const char *psz_session)
554 CommandPush(p_media->p_vod, RTSP_CMD_TYPE_STOP, p_media,
555 psz_session, 0, NULL);
559 const char *vod_get_mux(const vod_media_t *p_media)
561 return p_media->psz_mux;
565 /* Match an RTP id to a VoD media ES and RTSP track to initialize it
566 * with the data that was already set up */
567 int vod_init_id(vod_media_t *p_media, const char *psz_session, int es_id,
568 sout_stream_id_t *sout_id, rtp_format_t *rtp_fmt,
569 uint32_t *ssrc, uint16_t *seq_init)
573 if (p_media->psz_mux != NULL)
575 assert(p_media->i_es == 1);
576 p_es = p_media->es[0];
581 /* No locking needed, the ES table can't be modified now */
582 for (int i = 0; i < p_media->i_es; i++)
584 if (p_media->es[i]->es_id == es_id)
586 p_es = p_media->es[i];
594 memcpy(rtp_fmt, &p_es->rtp_fmt, sizeof(*rtp_fmt));
595 if (p_es->rtp_fmt.fmtp != NULL)
596 rtp_fmt->fmtp = strdup(p_es->rtp_fmt.fmtp);
598 return RtspTrackAttach(p_media->rtsp, psz_session, p_es->rtsp_id,
599 sout_id, ssrc, seq_init);
602 /* Remove references to the RTP id from its RTSP track */
603 void vod_detach_id(vod_media_t *p_media, const char *psz_session,
604 sout_stream_id_t *sout_id)
606 RtspTrackDetach(p_media->rtsp, psz_session, sout_id);