1 /*****************************************************************************
2 * vod.c: rtsp VoD server module
3 *****************************************************************************
4 * Copyright (C) 2003-2006, 2010 VLC authors and VideoLAN
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 it
12 * under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with this program; if not, write to the Free Software Foundation,
23 * 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>
43 #include <vlc_memstream.h>
49 /*****************************************************************************
51 *****************************************************************************/
53 typedef struct media_es_t media_es_t
;
59 rtsp_stream_id_t
*rtsp_id
;
85 block_fifo_t
*p_fifo_cmd
;
88 /* rtsp delayed command (to avoid deadlock between vlm/httpd) */
100 vod_media_t
*p_media
;
104 static vod_media_t
*MediaNew( vod_t
*, const char *, input_item_t
* );
105 static void MediaDel( vod_t
*, vod_media_t
* );
106 static void MediaAskDel ( vod_t
*, vod_media_t
* );
108 static void* CommandThread( void *obj
);
109 static void CommandPush( vod_t
*, rtsp_cmd_type_t
, vod_media_t
*,
110 const char *psz_arg
);
112 /*****************************************************************************
113 * Open: Starts the RTSP server module
114 *****************************************************************************/
115 int OpenVoD( vlc_object_t
*p_this
)
117 vod_t
*p_vod
= (vod_t
*)p_this
;
118 vod_sys_t
*p_sys
= NULL
;
121 p_vod
->p_sys
= p_sys
= malloc( sizeof( vod_sys_t
) );
122 if( !p_sys
) goto error
;
124 psz_url
= var_InheritString( p_vod
, "rtsp-host" );
126 if( psz_url
== NULL
)
127 p_sys
->psz_rtsp_path
= strdup( "/" );
131 vlc_UrlParse( &url
, psz_url
);
134 if( url
.psz_path
== NULL
)
135 p_sys
->psz_rtsp_path
= strdup( "/" );
137 if( !( strlen( url
.psz_path
) > 0
138 && url
.psz_path
[strlen( url
.psz_path
) - 1] == '/' ) )
140 if( asprintf( &p_sys
->psz_rtsp_path
, "%s/", url
.psz_path
) == -1 )
142 p_sys
->psz_rtsp_path
= NULL
;
143 vlc_UrlClean( &url
);
148 p_sys
->psz_rtsp_path
= strdup( url
.psz_path
);
150 vlc_UrlClean( &url
);
153 p_vod
->pf_media_new
= MediaNew
;
154 p_vod
->pf_media_del
= MediaAskDel
;
156 p_sys
->p_fifo_cmd
= block_FifoNew();
157 if( vlc_clone( &p_sys
->thread
, CommandThread
, p_vod
, 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_path
);
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_cancel( p_sys
->thread
);
186 vlc_join( p_sys
->thread
, NULL
);
188 while( block_FifoCount( p_sys
->p_fifo_cmd
) > 0 )
191 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
192 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
193 block_Release( p_block_cmd
);
194 if ( cmd
.i_type
== RTSP_CMD_TYPE_DEL
)
195 MediaDel(p_vod
, cmd
.p_media
);
198 block_FifoRelease( p_sys
->p_fifo_cmd
);
200 free( p_sys
->psz_rtsp_path
);
204 /*****************************************************************************
206 *****************************************************************************/
207 static vod_media_t
*MediaNew( vod_t
*p_vod
, const char *psz_name
,
208 input_item_t
*p_item
)
210 vod_media_t
*p_media
= calloc( 1, sizeof(vod_media_t
) );
214 p_media
->p_vod
= p_vod
;
215 p_media
->rtsp
= NULL
;
216 TAB_INIT( p_media
->i_es
, p_media
->es
);
217 p_media
->psz_mux
= NULL
;
218 p_media
->i_length
= input_item_GetDuration( p_item
);
220 vlc_mutex_lock( &p_item
->lock
);
221 msg_Dbg( p_vod
, "media '%s' has %i declared ES", psz_name
, p_item
->i_es
);
222 for( int i
= 0; i
< p_item
->i_es
; i
++ )
224 es_format_t
*p_fmt
= p_item
->es
[i
];
226 switch( p_fmt
->i_codec
)
228 case VLC_FOURCC( 'm', 'p', '2', 't' ):
229 p_media
->psz_mux
= "ts";
231 case VLC_FOURCC( 'm', 'p', '2', 'p' ):
232 p_media
->psz_mux
= "ps";
235 assert(p_media
->psz_mux
== NULL
|| p_item
->i_es
== 1);
237 media_es_t
*p_es
= calloc( 1, sizeof(media_es_t
) );
241 p_es
->es_id
= p_fmt
->i_id
;
242 p_es
->rtsp_id
= NULL
;
244 if (rtp_get_fmt(VLC_OBJECT(p_vod
), p_fmt
, p_media
->psz_mux
,
245 &p_es
->rtp_fmt
) != VLC_SUCCESS
)
251 TAB_APPEND( p_media
->i_es
, p_media
->es
, p_es
);
252 msg_Dbg(p_vod
, " - added ES %u %s (%4.4s)",
253 p_es
->rtp_fmt
.payload_type
, p_es
->rtp_fmt
.ptname
,
254 (char *)&p_fmt
->i_codec
);
256 vlc_mutex_unlock( &p_item
->lock
);
258 if (p_media
->i_es
== 0)
260 msg_Err(p_vod
, "no ES was added to the media, aborting");
264 msg_Dbg(p_vod
, "adding media '%s'", psz_name
);
266 CommandPush( p_vod
, RTSP_CMD_TYPE_ADD
, p_media
, psz_name
);
270 MediaDel(p_vod
, p_media
);
274 static void MediaSetup( vod_t
*p_vod
, vod_media_t
*p_media
,
275 const char *psz_name
)
277 vod_sys_t
*p_sys
= p_vod
->p_sys
;
280 if( asprintf( &psz_path
, "%s%s", p_sys
->psz_rtsp_path
, psz_name
) < 0 )
283 p_media
->rtsp
= RtspSetup(VLC_OBJECT(p_vod
), p_media
, psz_path
);
286 if (p_media
->rtsp
== NULL
)
289 for (int i
= 0; i
< p_media
->i_es
; i
++)
291 media_es_t
*p_es
= p_media
->es
[i
];
292 p_es
->rtsp_id
= RtspAddId(p_media
->rtsp
, NULL
, 0,
293 p_es
->rtp_fmt
.clock_rate
, -1);
297 static void MediaAskDel ( vod_t
*p_vod
, vod_media_t
*p_media
)
299 msg_Dbg( p_vod
, "deleting media" );
300 CommandPush( p_vod
, RTSP_CMD_TYPE_DEL
, p_media
, NULL
);
303 static void MediaDel( vod_t
*p_vod
, vod_media_t
*p_media
)
307 if (p_media
->rtsp
!= NULL
)
309 for (int i
= 0; i
< p_media
->i_es
; i
++)
311 media_es_t
*p_es
= p_media
->es
[i
];
312 if (p_es
->rtsp_id
!= NULL
)
313 RtspDelId(p_media
->rtsp
, p_es
->rtsp_id
);
315 RtspUnsetup(p_media
->rtsp
);
318 for( int i
= 0; i
< p_media
->i_es
; i
++ )
320 free( p_media
->es
[i
]->rtp_fmt
.fmtp
);
321 free( p_media
->es
[i
] );
328 static void CommandPush( vod_t
*p_vod
, rtsp_cmd_type_t i_type
,
329 vod_media_t
*p_media
, const char *psz_arg
)
335 cmd
.p_media
= p_media
;
337 cmd
.psz_arg
= strdup(psz_arg
);
341 p_cmd
= block_Alloc( sizeof(rtsp_cmd_t
) );
342 memcpy( p_cmd
->p_buffer
, &cmd
, sizeof(cmd
) );
344 vod_sys_t
*p_sys
= p_vod
->p_sys
;
345 block_FifoPut( p_sys
->p_fifo_cmd
, p_cmd
);
348 static void* CommandThread( void *obj
)
350 vod_t
*p_vod
= (vod_t
*)obj
;
351 vod_sys_t
*p_sys
= p_vod
->p_sys
;
355 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
361 int canc
= vlc_savecancel ();
362 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
363 block_Release( p_block_cmd
);
368 case RTSP_CMD_TYPE_ADD
:
369 MediaSetup(p_vod
, cmd
.p_media
, cmd
.psz_arg
);
371 case RTSP_CMD_TYPE_DEL
:
372 MediaDel(p_vod
, cmd
.p_media
);
374 case RTSP_CMD_TYPE_STOP
:
375 vod_MediaControl( p_vod
, cmd
.p_media
, cmd
.psz_arg
, VOD_MEDIA_STOP
);
383 vlc_restorecancel (canc
);
389 /*****************************************************************************
391 * FIXME: needs to be merged more?
392 *****************************************************************************/
393 char *SDPGenerateVoD( const vod_media_t
*p_media
, const char *rtsp_url
)
395 assert(rtsp_url
!= NULL
);
396 /* Check against URL format rtsp://[<ipv6>]:<port>/<path> */
397 bool ipv6
= strlen( rtsp_url
) > 7 && rtsp_url
[7] == '[';
399 /* Dummy destination address for RTSP */
400 struct sockaddr_storage dst
;
401 socklen_t dstlen
= ipv6
? sizeof( struct sockaddr_in6
)
402 : sizeof( struct sockaddr_in
);
403 memset (&dst
, 0, dstlen
);
404 dst
.ss_family
= ipv6
? AF_INET6
: AF_INET
;
409 struct vlc_memstream sdp
;
411 if( vlc_sdp_Start( &sdp
, VLC_OBJECT( p_media
->p_vod
), "sout-rtp-",
412 NULL
, 0, (struct sockaddr
*)&dst
, dstlen
) )
415 if( p_media
->i_length
> 0 )
417 lldiv_t d
= lldiv( MS_FROM_VLC_TICK(p_media
->i_length
), 1000 );
418 sdp_AddAttribute( &sdp
, "range"," npt=0-%lld.%03u", d
.quot
,
422 sdp_AddAttribute( &sdp
, "control", "%s", rtsp_url
);
424 /* No locking needed, the ES table can't be modified now */
425 for( int i
= 0; i
< p_media
->i_es
; i
++ )
427 media_es_t
*p_es
= p_media
->es
[i
];
428 rtp_format_t
*rtp_fmt
= &p_es
->rtp_fmt
;
429 const char *mime_major
; /* major MIME type */
431 switch( rtp_fmt
->cat
)
434 mime_major
= "video";
437 mime_major
= "audio";
446 sdp_AddMedia( &sdp
, mime_major
, "RTP/AVP", 0,
447 rtp_fmt
->payload_type
, false, 0,
448 rtp_fmt
->ptname
, rtp_fmt
->clock_rate
, rtp_fmt
->channels
,
451 char *track_url
= RtspAppendTrackPath( p_es
->rtsp_id
, rtsp_url
);
452 if( track_url
!= NULL
)
454 sdp_AddAttribute( &sdp
, "control", "%s", track_url
);
459 return vlc_memstream_close( &sdp
) ? NULL
: sdp
.ptr
;
462 int vod_check_range(vod_media_t
*p_media
, const char *psz_session
,
463 int64_t start
, int64_t end
)
467 if (p_media
->i_length
> 0 && (start
> p_media
->i_length
468 || end
> p_media
->i_length
))
474 /* TODO: add support in the VLM for queueing proper PLAY requests with
475 * start and end times, fetch whether the input is seekable... and then
477 void vod_play(vod_media_t
*p_media
, const char *psz_session
,
478 int64_t *start
, int64_t end
)
480 if (vod_check_range(p_media
, psz_session
, *start
, end
) != VLC_SUCCESS
)
483 /* We're passing the #vod{} sout chain here */
484 vod_MediaControl(p_media
->p_vod
, p_media
, psz_session
,
485 VOD_MEDIA_PLAY
, "vod", start
);
488 void vod_pause(vod_media_t
*p_media
, const char *psz_session
, int64_t *npt
)
490 vod_MediaControl(p_media
->p_vod
, p_media
, psz_session
,
491 VOD_MEDIA_PAUSE
, npt
);
494 void vod_stop(vod_media_t
*p_media
, const char *psz_session
)
496 CommandPush(p_media
->p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
);
500 const char *vod_get_mux(const vod_media_t
*p_media
)
502 return p_media
->psz_mux
;
506 /* Match an RTP id to a VoD media ES and RTSP track to initialize it
507 * with the data that was already set up */
508 int vod_init_id(vod_media_t
*p_media
, const char *psz_session
, int es_id
,
509 sout_stream_id_sys_t
*sout_id
, rtp_format_t
*rtp_fmt
,
510 uint32_t *ssrc
, uint16_t *seq_init
)
514 if (p_media
->psz_mux
!= NULL
)
516 assert(p_media
->i_es
== 1);
517 p_es
= p_media
->es
[0];
522 /* No locking needed, the ES table can't be modified now */
523 for (int i
= 0; i
< p_media
->i_es
; i
++)
525 if (p_media
->es
[i
]->es_id
== es_id
)
527 p_es
= p_media
->es
[i
];
535 memcpy(rtp_fmt
, &p_es
->rtp_fmt
, sizeof(*rtp_fmt
));
536 if (p_es
->rtp_fmt
.fmtp
!= NULL
)
537 rtp_fmt
->fmtp
= strdup(p_es
->rtp_fmt
.fmtp
);
539 return RtspTrackAttach(p_media
->rtsp
, psz_session
, p_es
->rtsp_id
,
540 sout_id
, ssrc
, seq_init
);
543 /* Remove references to the RTP id from its RTSP track */
544 void vod_detach_id(vod_media_t
*p_media
, const char *psz_session
,
545 sout_stream_id_sys_t
*sout_id
)
547 RtspTrackDetach(p_media
->rtsp
, psz_session
, sout_id
);