]> git.sesse.net Git - vlc/blob - modules/stream_out/vod.c
Qt: Simple Preferences: conditionally hide the whole groupbox, not
[vlc] / modules / stream_out / vod.c
1 /*****************************************************************************
2  * vod.c: rtsp VoD server module
3  *****************************************************************************
4  * Copyright (C) 2003-2006, 2010 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8  *          Gildas Bazin <gbazin@videolan.org>
9  *          Pierre Ynard
10  *
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.
15  *
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.
20  *
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  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <vlc_common.h>
35 #include <vlc_plugin.h>
36 #include <vlc_input.h>
37 #include <vlc_sout.h>
38 #include <vlc_block.h>
39
40 #include <vlc_vod.h>
41 #include <vlc_url.h>
42 #include <vlc_network.h>
43
44 #include <assert.h>
45
46 #include "rtp.h"
47
48 /*****************************************************************************
49  * Exported prototypes
50  *****************************************************************************/
51
52 typedef struct media_es_t media_es_t;
53
54 struct media_es_t
55 {
56     int es_id;
57     rtp_format_t rtp_fmt;
58     rtsp_stream_id_t *rtsp_id;
59 };
60
61 struct vod_media_t
62 {
63     /* VoD server */
64     vod_t *p_vod;
65
66     /* RTSP server */
67     rtsp_stream_t *rtsp;
68
69     /* ES list */
70     int        i_es;
71     media_es_t **es;
72     const char *psz_mux;
73
74     /* Infos */
75     mtime_t i_length;
76 };
77
78 struct vod_sys_t
79 {
80     char *psz_rtsp_path;
81
82     /* */
83     vlc_thread_t thread;
84     block_fifo_t *p_fifo_cmd;
85 };
86
87 /* rtsp delayed command (to avoid deadlock between vlm/httpd) */
88 typedef enum
89 {
90     RTSP_CMD_TYPE_STOP,
91     RTSP_CMD_TYPE_ADD,
92     RTSP_CMD_TYPE_DEL,
93 } rtsp_cmd_type_t;
94
95 /* */
96 typedef struct
97 {
98     int i_type;
99     vod_media_t *p_media;
100     char *psz_arg;
101 } rtsp_cmd_t;
102
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 * );
106
107 static void* CommandThread( void *obj );
108 static void  CommandPush( vod_t *, rtsp_cmd_type_t, vod_media_t *,
109                           const char *psz_arg );
110
111 /*****************************************************************************
112  * Open: Starts the RTSP server module
113  *****************************************************************************/
114 int OpenVoD( vlc_object_t *p_this )
115 {
116     vod_t *p_vod = (vod_t *)p_this;
117     vod_sys_t *p_sys = NULL;
118     char *psz_url;
119
120     p_vod->p_sys = p_sys = malloc( sizeof( vod_sys_t ) );
121     if( !p_sys ) goto error;
122
123     psz_url = var_InheritString( p_vod, "rtsp-host" );
124
125     if( psz_url == NULL )
126         p_sys->psz_rtsp_path = strdup( "/" );
127     else
128     {
129         vlc_url_t url;
130         vlc_UrlParse( &url, psz_url, 0 );
131         free( psz_url );
132
133         if( url.psz_path == NULL )
134             p_sys->psz_rtsp_path = strdup( "/" );
135         else
136         if( !( strlen( url.psz_path ) > 0
137                && url.psz_path[strlen( url.psz_path ) - 1] == '/' ) )
138         {
139             if( asprintf( &p_sys->psz_rtsp_path, "%s/", url.psz_path ) == -1 )
140             {
141                 p_sys->psz_rtsp_path = NULL;
142                 vlc_UrlClean( &url );
143                 goto error;
144             }
145         }
146         else
147             p_sys->psz_rtsp_path = strdup( url.psz_path );
148
149         vlc_UrlClean( &url );
150     }
151
152     p_vod->pf_media_new = MediaNew;
153     p_vod->pf_media_del = MediaAskDel;
154
155     p_sys->p_fifo_cmd = block_FifoNew();
156     if( vlc_clone( &p_sys->thread, CommandThread, p_vod, VLC_THREAD_PRIORITY_LOW ) )
157     {
158         msg_Err( p_vod, "cannot spawn rtsp vod thread" );
159         block_FifoRelease( p_sys->p_fifo_cmd );
160         goto error;
161     }
162
163     return VLC_SUCCESS;
164
165 error:
166     if( p_sys )
167     {
168         free( p_sys->psz_rtsp_path );
169         free( p_sys );
170     }
171
172     return VLC_EGENERIC;
173 }
174
175 /*****************************************************************************
176  * Close:
177  *****************************************************************************/
178 void CloseVoD( vlc_object_t * p_this )
179 {
180     vod_t *p_vod = (vod_t *)p_this;
181     vod_sys_t *p_sys = p_vod->p_sys;
182
183     /* Stop command thread */
184     vlc_cancel( p_sys->thread );
185     vlc_join( p_sys->thread, NULL );
186
187     while( block_FifoCount( p_sys->p_fifo_cmd ) > 0 )
188     {
189         rtsp_cmd_t cmd;
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);
195         free( cmd.psz_arg );
196     }
197     block_FifoRelease( p_sys->p_fifo_cmd );
198
199     free( p_sys->psz_rtsp_path );
200     free( p_sys );
201 }
202
203 /*****************************************************************************
204  * Media handling
205  *****************************************************************************/
206 static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name,
207                               input_item_t *p_item )
208 {
209     vod_media_t *p_media = calloc( 1, sizeof(vod_media_t) );
210     if( !p_media )
211         return NULL;
212
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 );
218
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++ )
222     {
223         es_format_t *p_fmt = p_item->es[i];
224
225         switch( p_fmt->i_codec )
226         {
227             case VLC_FOURCC( 'm', 'p', '2', 't' ):
228                 p_media->psz_mux = "ts";
229                 break;
230             case VLC_FOURCC( 'm', 'p', '2', 'p' ):
231                 p_media->psz_mux = "ps";
232                 break;
233         }
234         assert(p_media->psz_mux == NULL || p_item->i_es == 1);
235
236         media_es_t *p_es = calloc( 1, sizeof(media_es_t) );
237         if( !p_es )
238             continue;
239
240         p_es->es_id = p_fmt->i_id;
241         p_es->rtsp_id = NULL;
242
243         if (rtp_get_fmt(VLC_OBJECT(p_vod), p_fmt, p_media->psz_mux,
244                         &p_es->rtp_fmt) != VLC_SUCCESS)
245         {
246             free(p_es);
247             continue;
248         }
249
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);
254     }
255     vlc_mutex_unlock( &p_item->lock );
256
257     if (p_media->i_es == 0)
258     {
259         msg_Err(p_vod, "no ES was added to the media, aborting");
260         goto error;
261     }
262
263     msg_Dbg(p_vod, "adding media '%s'", psz_name);
264
265     CommandPush( p_vod, RTSP_CMD_TYPE_ADD, p_media, psz_name );
266     return p_media;
267
268 error:
269     MediaDel(p_vod, p_media);
270     return NULL;
271 }
272
273 static void MediaSetup( vod_t *p_vod, vod_media_t *p_media,
274                         const char *psz_name )
275 {
276     vod_sys_t *p_sys = p_vod->p_sys;
277     char *psz_path;
278
279     if( asprintf( &psz_path, "%s%s", p_sys->psz_rtsp_path, psz_name ) < 0 )
280         return;
281
282     p_media->rtsp = RtspSetup(VLC_OBJECT(p_vod), p_media, psz_path);
283     free( psz_path );
284
285     if (p_media->rtsp == NULL)
286         return;
287
288     for (int i = 0; i < p_media->i_es; i++)
289     {
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);
293     }
294 }
295
296 static void MediaAskDel ( vod_t *p_vod, vod_media_t *p_media )
297 {
298     msg_Dbg( p_vod, "deleting media" );
299     CommandPush( p_vod, RTSP_CMD_TYPE_DEL, p_media, NULL );
300 }
301
302 static void MediaDel( vod_t *p_vod, vod_media_t *p_media )
303 {
304     (void) p_vod;
305
306     if (p_media->rtsp != NULL)
307     {
308         for (int i = 0; i < p_media->i_es; i++)
309         {
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);
313         }
314         RtspUnsetup(p_media->rtsp);
315     }
316
317     while( p_media->i_es )
318     {
319         media_es_t *p_es = p_media->es[0];
320         TAB_REMOVE( p_media->i_es, p_media->es, p_es );
321         free( p_es->rtp_fmt.fmtp );
322         free( p_es );
323     }
324
325     TAB_CLEAN( p_media->i_es, p_media->es );
326     free( p_media );
327 }
328
329 static void CommandPush( vod_t *p_vod, rtsp_cmd_type_t i_type,
330                          vod_media_t *p_media, const char *psz_arg )
331 {
332     rtsp_cmd_t cmd;
333     block_t *p_cmd;
334
335     cmd.i_type = i_type;
336     cmd.p_media = p_media;
337     if( psz_arg )
338         cmd.psz_arg = strdup(psz_arg);
339     else
340         cmd.psz_arg = NULL;
341
342     p_cmd = block_Alloc( sizeof(rtsp_cmd_t) );
343     memcpy( p_cmd->p_buffer, &cmd, sizeof(cmd) );
344
345     block_FifoPut( p_vod->p_sys->p_fifo_cmd, p_cmd );
346 }
347
348 static void* CommandThread( void *obj )
349 {
350     vod_t *p_vod = (vod_t*)obj;
351     vod_sys_t *p_sys = p_vod->p_sys;
352
353     for( ;; )
354     {
355         block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
356         rtsp_cmd_t cmd;
357
358         if( !p_block_cmd )
359             break;
360
361         int canc = vlc_savecancel ();
362         memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
363         block_Release( p_block_cmd );
364
365         /* */
366         switch( cmd.i_type )
367         {
368         case RTSP_CMD_TYPE_ADD:
369             MediaSetup(p_vod, cmd.p_media, cmd.psz_arg);
370             break;
371         case RTSP_CMD_TYPE_DEL:
372             MediaDel(p_vod, cmd.p_media);
373             break;
374         case RTSP_CMD_TYPE_STOP:
375             vod_MediaControl( p_vod, cmd.p_media, cmd.psz_arg, VOD_MEDIA_STOP );
376             break;
377
378         default:
379             break;
380         }
381
382         free( cmd.psz_arg );
383         vlc_restorecancel (canc);
384     }
385
386     return NULL;
387 }
388
389 /*****************************************************************************
390  * SDPGenerateVoD
391  * FIXME: needs to be merged more?
392  *****************************************************************************/
393 char *SDPGenerateVoD( const vod_media_t *p_media, const char *rtsp_url )
394 {
395     char *psz_sdp;
396
397     assert(rtsp_url != NULL);
398     /* Check against URL format rtsp://[<ipv6>]:<port>/<path> */
399     bool ipv6 = strlen( rtsp_url ) > 7 && rtsp_url[7] == '[';
400
401     /* Dummy destination address for RTSP */
402     struct sockaddr_storage dst;
403     socklen_t dstlen = ipv6 ? sizeof( struct sockaddr_in6 )
404                             : sizeof( struct sockaddr_in );
405     memset (&dst, 0, dstlen);
406     dst.ss_family = ipv6 ? AF_INET6 : AF_INET;
407 #ifdef HAVE_SA_LEN
408     dst.ss_len = dstlen;
409 #endif
410
411     psz_sdp = vlc_sdp_Start( VLC_OBJECT( p_media->p_vod ), "sout-rtp-",
412                              NULL, 0, (struct sockaddr *)&dst, dstlen );
413     if( psz_sdp == NULL )
414         return NULL;
415
416     if( p_media->i_length > 0 )
417     {
418         lldiv_t d = lldiv( p_media->i_length / 1000, 1000 );
419         sdp_AddAttribute( &psz_sdp, "range"," npt=0-%lld.%03u", d.quot,
420                           (unsigned)d.rem );
421     }
422
423     sdp_AddAttribute ( &psz_sdp, "control", "%s", rtsp_url );
424
425     /* No locking needed, the ES table can't be modified now */
426     for( int i = 0; i < p_media->i_es; i++ )
427     {
428         media_es_t *p_es = p_media->es[i];
429         rtp_format_t *rtp_fmt = &p_es->rtp_fmt;
430         const char *mime_major; /* major MIME type */
431
432         switch( rtp_fmt->cat )
433         {
434             case VIDEO_ES:
435                 mime_major = "video";
436                 break;
437             case AUDIO_ES:
438                 mime_major = "audio";
439                 break;
440             case SPU_ES:
441                 mime_major = "text";
442                 break;
443             default:
444                 continue;
445         }
446
447         sdp_AddMedia( &psz_sdp, mime_major, "RTP/AVP", 0,
448                       rtp_fmt->payload_type, false, 0,
449                       rtp_fmt->ptname, rtp_fmt->clock_rate, rtp_fmt->channels,
450                       rtp_fmt->fmtp );
451
452         char *track_url = RtspAppendTrackPath( p_es->rtsp_id, rtsp_url );
453         if( track_url != NULL )
454         {
455             sdp_AddAttribute ( &psz_sdp, "control", "%s", track_url );
456             free( track_url );
457         }
458     }
459
460     return psz_sdp;
461 }
462
463 int vod_check_range(vod_media_t *p_media, const char *psz_session,
464                     int64_t start, int64_t end)
465 {
466     (void) psz_session;
467
468     if (p_media->i_length > 0 && (start > p_media->i_length
469                                   || end > p_media->i_length))
470         return VLC_EGENERIC;
471
472     return VLC_SUCCESS;
473 }
474
475 /* TODO: add support in the VLM for queueing proper PLAY requests with
476  * start and end times, fetch whether the input is seekable... and then
477  * clean this up */
478 void vod_play(vod_media_t *p_media, const char *psz_session,
479               int64_t *start, int64_t end)
480 {
481     if (vod_check_range(p_media, psz_session, *start, end) != VLC_SUCCESS)
482         return;
483
484     /* We're passing the #vod{} sout chain here */
485     vod_MediaControl(p_media->p_vod, p_media, psz_session,
486                      VOD_MEDIA_PLAY, "vod", start);
487 }
488
489 void vod_pause(vod_media_t *p_media, const char *psz_session, int64_t *npt)
490 {
491     vod_MediaControl(p_media->p_vod, p_media, psz_session,
492                      VOD_MEDIA_PAUSE, npt);
493 }
494
495 void vod_stop(vod_media_t *p_media, const char *psz_session)
496 {
497     CommandPush(p_media->p_vod, RTSP_CMD_TYPE_STOP, p_media, psz_session);
498 }
499
500
501 const char *vod_get_mux(const vod_media_t *p_media)
502 {
503     return p_media->psz_mux;
504 }
505
506
507 /* Match an RTP id to a VoD media ES and RTSP track to initialize it
508  * with the data that was already set up */
509 int vod_init_id(vod_media_t *p_media, const char *psz_session, int es_id,
510                 sout_stream_id_t *sout_id, rtp_format_t *rtp_fmt,
511                 uint32_t *ssrc, uint16_t *seq_init)
512 {
513     media_es_t *p_es;
514
515     if (p_media->psz_mux != NULL)
516     {
517         assert(p_media->i_es == 1);
518         p_es = p_media->es[0];
519     }
520     else
521     {
522         p_es = NULL;
523         /* No locking needed, the ES table can't be modified now */
524         for (int i = 0; i < p_media->i_es; i++)
525         {
526             if (p_media->es[i]->es_id == es_id)
527             {
528                 p_es = p_media->es[i];
529                 break;
530             }
531         }
532         if (p_es == NULL)
533             return VLC_EGENERIC;
534     }
535
536     memcpy(rtp_fmt, &p_es->rtp_fmt, sizeof(*rtp_fmt));
537     if (p_es->rtp_fmt.fmtp != NULL)
538         rtp_fmt->fmtp = strdup(p_es->rtp_fmt.fmtp);
539
540     return RtspTrackAttach(p_media->rtsp, psz_session, p_es->rtsp_id,
541                            sout_id, ssrc, seq_init);
542 }
543
544 /* Remove references to the RTP id from its RTSP track */
545 void vod_detach_id(vod_media_t *p_media, const char *psz_session,
546                    sout_stream_id_t *sout_id)
547 {
548     RtspTrackDetach(p_media->rtsp, psz_session, sout_id);
549 }
550