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