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