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;
84 block_fifo_t *p_fifo_cmd;
87 /* rtsp delayed command (to avoid deadlock between vlm/httpd) */
103 static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * );
104 static void MediaDel( vod_t *, vod_media_t * );
105 static void MediaAskDel ( vod_t *, vod_media_t * );
107 static void* CommandThread( void *obj );
108 static void CommandPush( vod_t *, rtsp_cmd_type_t, vod_media_t *,
109 const char *psz_arg );
111 /*****************************************************************************
112 * Open: Starts the RTSP server module
113 *****************************************************************************/
114 int OpenVoD( vlc_object_t *p_this )
116 vod_t *p_vod = (vod_t *)p_this;
117 vod_sys_t *p_sys = NULL;
120 p_vod->p_sys = p_sys = malloc( sizeof( vod_sys_t ) );
121 if( !p_sys ) goto error;
123 psz_url = var_InheritString( p_vod, "rtsp-host" );
125 if( psz_url == NULL )
126 p_sys->psz_rtsp_url = strdup( "/" );
128 if( !( strlen( psz_url ) > 0 && psz_url[strlen( psz_url ) - 1] == '/' ) )
130 if( asprintf( &p_sys->psz_rtsp_url, "%s/", psz_url ) == -1 )
132 p_sys->psz_rtsp_url = NULL;
139 p_sys->psz_rtsp_url = psz_url;
141 p_vod->pf_media_new = MediaNew;
142 p_vod->pf_media_del = MediaAskDel;
144 p_sys->p_fifo_cmd = block_FifoNew();
145 if( vlc_clone( &p_sys->thread, CommandThread, p_vod, VLC_THREAD_PRIORITY_LOW ) )
147 msg_Err( p_vod, "cannot spawn rtsp vod thread" );
148 block_FifoRelease( p_sys->p_fifo_cmd );
157 free( p_sys->psz_rtsp_url );
164 /*****************************************************************************
166 *****************************************************************************/
167 void CloseVoD( vlc_object_t * p_this )
169 vod_t *p_vod = (vod_t *)p_this;
170 vod_sys_t *p_sys = p_vod->p_sys;
172 /* Stop command thread */
173 vlc_cancel( p_sys->thread );
174 vlc_join( p_sys->thread, NULL );
176 while( block_FifoCount( p_sys->p_fifo_cmd ) > 0 )
179 block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
180 memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
181 block_Release( p_block_cmd );
182 if ( cmd.i_type == RTSP_CMD_TYPE_DEL )
183 MediaDel(p_vod, cmd.p_media);
186 block_FifoRelease( p_sys->p_fifo_cmd );
188 free( p_sys->psz_rtsp_url );
192 /*****************************************************************************
194 *****************************************************************************/
195 static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name,
196 input_item_t *p_item )
198 vod_media_t *p_media = calloc( 1, sizeof(vod_media_t) );
202 p_media->p_vod = p_vod;
203 p_media->rtsp = NULL;
204 TAB_INIT( p_media->i_es, p_media->es );
205 p_media->psz_mux = NULL;
206 p_media->i_length = input_item_GetDuration( p_item );
208 vlc_mutex_lock( &p_item->lock );
209 msg_Dbg( p_vod, "media '%s' has %i declared ES", psz_name, p_item->i_es );
210 for( int i = 0; i < p_item->i_es; i++ )
212 es_format_t *p_fmt = p_item->es[i];
214 switch( p_fmt->i_codec )
216 case VLC_FOURCC( 'm', 'p', '2', 't' ):
217 p_media->psz_mux = "ts";
219 case VLC_FOURCC( 'm', 'p', '2', 'p' ):
220 p_media->psz_mux = "ps";
223 assert(p_media->psz_mux == NULL || p_item->i_es == 1);
225 media_es_t *p_es = calloc( 1, sizeof(media_es_t) );
229 p_es->es_id = p_fmt->i_id;
230 p_es->rtsp_id = NULL;
232 if (rtp_get_fmt(VLC_OBJECT(p_vod), p_fmt, p_media->psz_mux,
233 &p_es->rtp_fmt) != VLC_SUCCESS)
239 TAB_APPEND( p_media->i_es, p_media->es, p_es );
240 msg_Dbg(p_vod, " - added ES %u %s (%4.4s)",
241 p_es->rtp_fmt.payload_type, p_es->rtp_fmt.ptname,
242 (char *)&p_fmt->i_codec);
244 vlc_mutex_unlock( &p_item->lock );
246 if (p_media->i_es == 0)
248 msg_Err(p_vod, "no ES was added to the media, aborting");
252 msg_Dbg(p_vod, "adding media '%s'", psz_name);
254 CommandPush( p_vod, RTSP_CMD_TYPE_ADD, p_media, psz_name );
258 MediaDel(p_vod, p_media);
262 static void MediaSetup( vod_t *p_vod, vod_media_t *p_media,
263 const char *psz_name )
265 vod_sys_t *p_sys = p_vod->p_sys;
268 if( asprintf( &psz_url, "%s%s", p_sys->psz_rtsp_url, psz_name ) < 0 )
272 vlc_UrlParse( &url, psz_url, 0 );
275 p_media->rtsp = RtspSetup(VLC_OBJECT(p_vod), p_media, &url);
277 vlc_UrlClean( &url );
279 if (p_media->rtsp == NULL)
282 for (int i = 0; i < p_media->i_es; i++)
284 media_es_t *p_es = p_media->es[i];
285 p_es->rtsp_id = RtspAddId(p_media->rtsp, NULL, 0,
286 p_es->rtp_fmt.clock_rate, -1);
290 static void MediaAskDel ( vod_t *p_vod, vod_media_t *p_media )
292 msg_Dbg( p_vod, "deleting media" );
293 CommandPush( p_vod, RTSP_CMD_TYPE_DEL, p_media, NULL );
296 static void MediaDel( vod_t *p_vod, vod_media_t *p_media )
300 if (p_media->rtsp != NULL)
302 for (int i = 0; i < p_media->i_es; i++)
304 media_es_t *p_es = p_media->es[i];
305 if (p_es->rtsp_id != NULL)
306 RtspDelId(p_media->rtsp, p_es->rtsp_id);
308 RtspUnsetup(p_media->rtsp);
311 while( p_media->i_es )
313 media_es_t *p_es = p_media->es[0];
314 TAB_REMOVE( p_media->i_es, p_media->es, p_es );
315 free( p_es->rtp_fmt.fmtp );
319 TAB_CLEAN( p_media->i_es, p_media->es );
323 static void CommandPush( vod_t *p_vod, rtsp_cmd_type_t i_type,
324 vod_media_t *p_media, const char *psz_arg )
330 cmd.p_media = p_media;
332 cmd.psz_arg = strdup(psz_arg);
336 p_cmd = block_New( p_vod, sizeof(rtsp_cmd_t) );
337 memcpy( p_cmd->p_buffer, &cmd, sizeof(cmd) );
339 block_FifoPut( p_vod->p_sys->p_fifo_cmd, p_cmd );
342 static void* CommandThread( void *obj )
344 vod_t *p_vod = (vod_t*)obj;
345 vod_sys_t *p_sys = p_vod->p_sys;
349 block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
355 int canc = vlc_savecancel ();
356 memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
357 block_Release( p_block_cmd );
362 case RTSP_CMD_TYPE_ADD:
363 MediaSetup(p_vod, cmd.p_media, cmd.psz_arg);
365 case RTSP_CMD_TYPE_DEL:
366 MediaDel(p_vod, cmd.p_media);
368 case RTSP_CMD_TYPE_STOP:
369 vod_MediaControl( p_vod, cmd.p_media, cmd.psz_arg, VOD_MEDIA_STOP );
377 vlc_restorecancel (canc);
383 /*****************************************************************************
385 * FIXME: needs to be merged more?
386 *****************************************************************************/
387 char *SDPGenerateVoD( const vod_media_t *p_media, const char *rtsp_url )
391 assert(rtsp_url != NULL);
392 /* Check against URL format rtsp://[<ipv6>]:<port>/<path> */
393 bool ipv6 = strlen( rtsp_url ) > 7 && rtsp_url[7] == '[';
395 /* Dummy destination address for RTSP */
396 struct sockaddr_storage dst;
397 socklen_t dstlen = ipv6 ? sizeof( struct sockaddr_in6 )
398 : sizeof( struct sockaddr_in );
399 memset (&dst, 0, dstlen);
400 dst.ss_family = ipv6 ? AF_INET6 : AF_INET;
405 psz_sdp = vlc_sdp_Start( VLC_OBJECT( p_media->p_vod ), "sout-rtp-",
406 NULL, 0, (struct sockaddr *)&dst, dstlen );
407 if( psz_sdp == NULL )
410 if( p_media->i_length > 0 )
412 lldiv_t d = lldiv( p_media->i_length / 1000, 1000 );
413 sdp_AddAttribute( &psz_sdp, "range"," npt=0-%lld.%03u", d.quot,
417 sdp_AddAttribute ( &psz_sdp, "control", "%s", rtsp_url );
419 /* No locking needed, the ES table can't be modified now */
420 for( int i = 0; i < p_media->i_es; i++ )
422 media_es_t *p_es = p_media->es[i];
423 rtp_format_t *rtp_fmt = &p_es->rtp_fmt;
424 const char *mime_major; /* major MIME type */
426 switch( rtp_fmt->cat )
429 mime_major = "video";
432 mime_major = "audio";
441 sdp_AddMedia( &psz_sdp, mime_major, "RTP/AVP", 0,
442 rtp_fmt->payload_type, false, 0,
443 rtp_fmt->ptname, rtp_fmt->clock_rate, rtp_fmt->channels,
446 char *track_url = RtspAppendTrackPath( p_es->rtsp_id, rtsp_url );
447 if( track_url != NULL )
449 sdp_AddAttribute ( &psz_sdp, "control", "%s", track_url );
457 int vod_check_range(vod_media_t *p_media, const char *psz_session,
458 int64_t start, int64_t end)
462 if (p_media->i_length > 0 && (start > p_media->i_length
463 || end > p_media->i_length))
469 /* TODO: add support in the VLM for queueing proper PLAY requests with
470 * start and end times, fetch whether the input is seekable... and then
472 void vod_play(vod_media_t *p_media, const char *psz_session,
473 int64_t *start, int64_t end)
475 if (vod_check_range(p_media, psz_session, *start, end) != VLC_SUCCESS)
478 /* We're passing the #vod{} sout chain here */
479 vod_MediaControl(p_media->p_vod, p_media, psz_session,
480 VOD_MEDIA_PLAY, "vod", start);
483 void vod_pause(vod_media_t *p_media, const char *psz_session, int64_t *npt)
485 vod_MediaControl(p_media->p_vod, p_media, psz_session,
486 VOD_MEDIA_PAUSE, npt);
489 void vod_stop(vod_media_t *p_media, const char *psz_session)
491 CommandPush(p_media->p_vod, RTSP_CMD_TYPE_STOP, p_media, psz_session);
495 const char *vod_get_mux(const vod_media_t *p_media)
497 return p_media->psz_mux;
501 /* Match an RTP id to a VoD media ES and RTSP track to initialize it
502 * with the data that was already set up */
503 int vod_init_id(vod_media_t *p_media, const char *psz_session, int es_id,
504 sout_stream_id_t *sout_id, rtp_format_t *rtp_fmt,
505 uint32_t *ssrc, uint16_t *seq_init)
509 if (p_media->psz_mux != NULL)
511 assert(p_media->i_es == 1);
512 p_es = p_media->es[0];
517 /* No locking needed, the ES table can't be modified now */
518 for (int i = 0; i < p_media->i_es; i++)
520 if (p_media->es[i]->es_id == es_id)
522 p_es = p_media->es[i];
530 memcpy(rtp_fmt, &p_es->rtp_fmt, sizeof(*rtp_fmt));
531 if (p_es->rtp_fmt.fmtp != NULL)
532 rtp_fmt->fmtp = strdup(p_es->rtp_fmt.fmtp);
534 return RtspTrackAttach(p_media->rtsp, psz_session, p_es->rtsp_id,
535 sout_id, ssrc, seq_init);
538 /* Remove references to the RTP id from its RTSP track */
539 void vod_detach_id(vod_media_t *p_media, const char *psz_session,
540 sout_stream_id_t *sout_id)
542 RtspTrackDetach(p_media->rtsp, psz_session, sout_id);