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>
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_path
= strdup( "/" );
130 vlc_UrlParse( &url
, psz_url
, 0 );
133 if( url
.psz_path
== NULL
)
134 p_sys
->psz_rtsp_path
= strdup( "/" );
136 if( !( strlen( url
.psz_path
) > 0
137 && url
.psz_path
[strlen( url
.psz_path
) - 1] == '/' ) )
139 if( asprintf( &p_sys
->psz_rtsp_path
, "%s/", url
.psz_path
) == -1 )
141 p_sys
->psz_rtsp_path
= NULL
;
142 vlc_UrlClean( &url
);
147 p_sys
->psz_rtsp_path
= strdup( url
.psz_path
);
149 vlc_UrlClean( &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_clone( &p_sys
->thread
, CommandThread
, p_vod
, VLC_THREAD_PRIORITY_LOW
) )
158 msg_Err( p_vod
, "cannot spawn rtsp vod thread" );
159 block_FifoRelease( p_sys
->p_fifo_cmd
);
168 free( p_sys
->psz_rtsp_path
);
175 /*****************************************************************************
177 *****************************************************************************/
178 void CloseVoD( vlc_object_t
* p_this
)
180 vod_t
*p_vod
= (vod_t
*)p_this
;
181 vod_sys_t
*p_sys
= p_vod
->p_sys
;
183 /* Stop command thread */
184 vlc_cancel( p_sys
->thread
);
185 vlc_join( p_sys
->thread
, NULL
);
187 while( block_FifoCount( p_sys
->p_fifo_cmd
) > 0 )
190 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
191 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
192 block_Release( p_block_cmd
);
193 if ( cmd
.i_type
== RTSP_CMD_TYPE_DEL
)
194 MediaDel(p_vod
, cmd
.p_media
);
197 block_FifoRelease( p_sys
->p_fifo_cmd
);
199 free( p_sys
->psz_rtsp_path
);
203 /*****************************************************************************
205 *****************************************************************************/
206 static vod_media_t
*MediaNew( vod_t
*p_vod
, const char *psz_name
,
207 input_item_t
*p_item
)
209 vod_media_t
*p_media
= calloc( 1, sizeof(vod_media_t
) );
213 p_media
->p_vod
= p_vod
;
214 p_media
->rtsp
= NULL
;
215 TAB_INIT( p_media
->i_es
, p_media
->es
);
216 p_media
->psz_mux
= NULL
;
217 p_media
->i_length
= input_item_GetDuration( p_item
);
219 vlc_mutex_lock( &p_item
->lock
);
220 msg_Dbg( p_vod
, "media '%s' has %i declared ES", psz_name
, p_item
->i_es
);
221 for( int i
= 0; i
< p_item
->i_es
; i
++ )
223 es_format_t
*p_fmt
= p_item
->es
[i
];
225 switch( p_fmt
->i_codec
)
227 case VLC_FOURCC( 'm', 'p', '2', 't' ):
228 p_media
->psz_mux
= "ts";
230 case VLC_FOURCC( 'm', 'p', '2', 'p' ):
231 p_media
->psz_mux
= "ps";
234 assert(p_media
->psz_mux
== NULL
|| p_item
->i_es
== 1);
236 media_es_t
*p_es
= calloc( 1, sizeof(media_es_t
) );
240 p_es
->es_id
= p_fmt
->i_id
;
241 p_es
->rtsp_id
= NULL
;
243 if (rtp_get_fmt(VLC_OBJECT(p_vod
), p_fmt
, p_media
->psz_mux
,
244 &p_es
->rtp_fmt
) != VLC_SUCCESS
)
250 TAB_APPEND( p_media
->i_es
, p_media
->es
, p_es
);
251 msg_Dbg(p_vod
, " - added ES %u %s (%4.4s)",
252 p_es
->rtp_fmt
.payload_type
, p_es
->rtp_fmt
.ptname
,
253 (char *)&p_fmt
->i_codec
);
255 vlc_mutex_unlock( &p_item
->lock
);
257 if (p_media
->i_es
== 0)
259 msg_Err(p_vod
, "no ES was added to the media, aborting");
263 msg_Dbg(p_vod
, "adding media '%s'", psz_name
);
265 CommandPush( p_vod
, RTSP_CMD_TYPE_ADD
, p_media
, psz_name
);
269 MediaDel(p_vod
, p_media
);
273 static void MediaSetup( vod_t
*p_vod
, vod_media_t
*p_media
,
274 const char *psz_name
)
276 vod_sys_t
*p_sys
= p_vod
->p_sys
;
279 if( asprintf( &psz_path
, "%s%s", p_sys
->psz_rtsp_path
, psz_name
) < 0 )
282 p_media
->rtsp
= RtspSetup(VLC_OBJECT(p_vod
), p_media
, psz_path
);
285 if (p_media
->rtsp
== NULL
)
288 for (int i
= 0; i
< p_media
->i_es
; i
++)
290 media_es_t
*p_es
= p_media
->es
[i
];
291 p_es
->rtsp_id
= RtspAddId(p_media
->rtsp
, NULL
, 0,
292 p_es
->rtp_fmt
.clock_rate
, -1);
296 static void MediaAskDel ( vod_t
*p_vod
, vod_media_t
*p_media
)
298 msg_Dbg( p_vod
, "deleting media" );
299 CommandPush( p_vod
, RTSP_CMD_TYPE_DEL
, p_media
, NULL
);
302 static void MediaDel( vod_t
*p_vod
, vod_media_t
*p_media
)
306 if (p_media
->rtsp
!= NULL
)
308 for (int i
= 0; i
< p_media
->i_es
; i
++)
310 media_es_t
*p_es
= p_media
->es
[i
];
311 if (p_es
->rtsp_id
!= NULL
)
312 RtspDelId(p_media
->rtsp
, p_es
->rtsp_id
);
314 RtspUnsetup(p_media
->rtsp
);
317 for( int i
= 0; i
< p_media
->i_es
; i
++ )
319 free( p_media
->es
[i
]->rtp_fmt
.fmtp
);
320 free( p_media
->es
[i
] );
327 static void CommandPush( vod_t
*p_vod
, rtsp_cmd_type_t i_type
,
328 vod_media_t
*p_media
, const char *psz_arg
)
334 cmd
.p_media
= p_media
;
336 cmd
.psz_arg
= strdup(psz_arg
);
340 p_cmd
= block_Alloc( sizeof(rtsp_cmd_t
) );
341 memcpy( p_cmd
->p_buffer
, &cmd
, sizeof(cmd
) );
343 block_FifoPut( p_vod
->p_sys
->p_fifo_cmd
, p_cmd
);
346 static void* CommandThread( void *obj
)
348 vod_t
*p_vod
= (vod_t
*)obj
;
349 vod_sys_t
*p_sys
= p_vod
->p_sys
;
353 block_t
*p_block_cmd
= block_FifoGet( p_sys
->p_fifo_cmd
);
359 int canc
= vlc_savecancel ();
360 memcpy( &cmd
, p_block_cmd
->p_buffer
, sizeof(cmd
) );
361 block_Release( p_block_cmd
);
366 case RTSP_CMD_TYPE_ADD
:
367 MediaSetup(p_vod
, cmd
.p_media
, cmd
.psz_arg
);
369 case RTSP_CMD_TYPE_DEL
:
370 MediaDel(p_vod
, cmd
.p_media
);
372 case RTSP_CMD_TYPE_STOP
:
373 vod_MediaControl( p_vod
, cmd
.p_media
, cmd
.psz_arg
, VOD_MEDIA_STOP
);
381 vlc_restorecancel (canc
);
387 /*****************************************************************************
389 * FIXME: needs to be merged more?
390 *****************************************************************************/
391 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 psz_sdp
= vlc_sdp_Start( VLC_OBJECT( p_media
->p_vod
), "sout-rtp-",
410 NULL
, 0, (struct sockaddr
*)&dst
, dstlen
);
411 if( psz_sdp
== NULL
)
414 if( p_media
->i_length
> 0 )
416 lldiv_t d
= lldiv( p_media
->i_length
/ 1000, 1000 );
417 sdp_AddAttribute( &psz_sdp
, "range"," npt=0-%lld.%03u", d
.quot
,
421 sdp_AddAttribute ( &psz_sdp
, "control", "%s", rtsp_url
);
423 /* No locking needed, the ES table can't be modified now */
424 for( int i
= 0; i
< p_media
->i_es
; i
++ )
426 media_es_t
*p_es
= p_media
->es
[i
];
427 rtp_format_t
*rtp_fmt
= &p_es
->rtp_fmt
;
428 const char *mime_major
; /* major MIME type */
430 switch( rtp_fmt
->cat
)
433 mime_major
= "video";
436 mime_major
= "audio";
445 sdp_AddMedia( &psz_sdp
, mime_major
, "RTP/AVP", 0,
446 rtp_fmt
->payload_type
, false, 0,
447 rtp_fmt
->ptname
, rtp_fmt
->clock_rate
, rtp_fmt
->channels
,
450 char *track_url
= RtspAppendTrackPath( p_es
->rtsp_id
, rtsp_url
);
451 if( track_url
!= NULL
)
453 sdp_AddAttribute ( &psz_sdp
, "control", "%s", track_url
);
461 int vod_check_range(vod_media_t
*p_media
, const char *psz_session
,
462 int64_t start
, int64_t end
)
466 if (p_media
->i_length
> 0 && (start
> p_media
->i_length
467 || end
> p_media
->i_length
))
473 /* TODO: add support in the VLM for queueing proper PLAY requests with
474 * start and end times, fetch whether the input is seekable... and then
476 void vod_play(vod_media_t
*p_media
, const char *psz_session
,
477 int64_t *start
, int64_t end
)
479 if (vod_check_range(p_media
, psz_session
, *start
, end
) != VLC_SUCCESS
)
482 /* We're passing the #vod{} sout chain here */
483 vod_MediaControl(p_media
->p_vod
, p_media
, psz_session
,
484 VOD_MEDIA_PLAY
, "vod", start
);
487 void vod_pause(vod_media_t
*p_media
, const char *psz_session
, int64_t *npt
)
489 vod_MediaControl(p_media
->p_vod
, p_media
, psz_session
,
490 VOD_MEDIA_PAUSE
, npt
);
493 void vod_stop(vod_media_t
*p_media
, const char *psz_session
)
495 CommandPush(p_media
->p_vod
, RTSP_CMD_TYPE_STOP
, p_media
, psz_session
);
499 const char *vod_get_mux(const vod_media_t
*p_media
)
501 return p_media
->psz_mux
;
505 /* Match an RTP id to a VoD media ES and RTSP track to initialize it
506 * with the data that was already set up */
507 int vod_init_id(vod_media_t
*p_media
, const char *psz_session
, int es_id
,
508 sout_stream_id_sys_t
*sout_id
, rtp_format_t
*rtp_fmt
,
509 uint32_t *ssrc
, uint16_t *seq_init
)
513 if (p_media
->psz_mux
!= NULL
)
515 assert(p_media
->i_es
== 1);
516 p_es
= p_media
->es
[0];
521 /* No locking needed, the ES table can't be modified now */
522 for (int i
= 0; i
< p_media
->i_es
; i
++)
524 if (p_media
->es
[i
]->es_id
== es_id
)
526 p_es
= p_media
->es
[i
];
534 memcpy(rtp_fmt
, &p_es
->rtp_fmt
, sizeof(*rtp_fmt
));
535 if (p_es
->rtp_fmt
.fmtp
!= NULL
)
536 rtp_fmt
->fmtp
= strdup(p_es
->rtp_fmt
.fmtp
);
538 return RtspTrackAttach(p_media
->rtsp
, psz_session
, p_es
->rtsp_id
,
539 sout_id
, ssrc
, seq_init
);
542 /* Remove references to the RTP id from its RTSP track */
543 void vod_detach_id(vod_media_t
*p_media
, const char *psz_session
,
544 sout_stream_id_sys_t
*sout_id
)
546 RtspTrackDetach(p_media
->rtsp
, psz_session
, sout_id
);