]> git.sesse.net Git - vlc/blob - modules/stream_out/rtsp.c
Remove old unused code
[vlc] / modules / stream_out / rtsp.c
1 /*****************************************************************************
2  * rtsp.c: RTSP support for RTP stream output module
3  *****************************************************************************
4  * Copyright (C) 2003-2004 the VideoLAN team
5  * Copyright © 2007 Rémi Denis-Courmont
6  *
7  * $Id: rtp.c 21407 2007-08-22 20:10:41Z courmisch $
8  *
9  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
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 #include <vlc/vlc.h>
30 #include <vlc_sout.h>
31
32 #include <vlc_httpd.h>
33 #include <vlc_url.h>
34 #include <vlc_network.h>
35 #include <vlc_rand.h>
36 #include <assert.h>
37 #include <errno.h>
38 #include <stdlib.h>
39
40 #include "rtp.h"
41
42 typedef struct rtsp_session_t rtsp_session_t;
43
44 struct rtsp_stream_t
45 {
46     vlc_mutex_t     lock;
47     sout_stream_t  *owner;
48     httpd_host_t   *host;
49     httpd_url_t    *url;
50     char           *psz_path;
51     unsigned        port;
52
53     int             sessionc;
54     rtsp_session_t **sessionv;
55 };
56
57
58 static int  RtspCallback( httpd_callback_sys_t *p_args,
59                           httpd_client_t *cl, httpd_message_t *answer,
60                           const httpd_message_t *query );
61 static int  RtspCallbackId( httpd_callback_sys_t *p_args,
62                             httpd_client_t *cl, httpd_message_t *answer,
63                             const httpd_message_t *query );
64 static void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session );
65
66 rtsp_stream_t *RtspSetup( sout_stream_t *p_stream, const vlc_url_t *url )
67 {
68     rtsp_stream_t *rtsp = malloc( sizeof( *rtsp ) );
69
70     if( rtsp == NULL || ( url->i_port > 99999 ) )
71         return NULL;
72
73     rtsp->owner = p_stream;
74     rtsp->sessionc = 0;
75     rtsp->sessionv = NULL;
76     vlc_mutex_init( p_stream, &rtsp->lock );
77
78     msg_Dbg( p_stream, "rtsp setup: %s : %d / %s\n",
79              url->psz_host, url->i_port, url->psz_path );
80
81     rtsp->port = (url->i_port > 0) ? url->i_port : 554;
82     if( url->psz_path != NULL )
83         rtsp->psz_path = strdup( url->psz_path + 1 );
84     else
85         rtsp->psz_path = NULL;
86
87     rtsp->host = httpd_HostNew( VLC_OBJECT(p_stream), url->psz_host,
88                                 rtsp->port );
89     if( rtsp->host == NULL )
90         goto error;
91
92     rtsp->url = httpd_UrlNewUnique( rtsp->host,
93                                     url->psz_path ? url->psz_path : "/", NULL,
94                                     NULL, NULL );
95     if( rtsp->url == NULL )
96         goto error;
97
98     httpd_UrlCatch( rtsp->url, HTTPD_MSG_DESCRIBE, RtspCallback, (void*)rtsp );
99     httpd_UrlCatch( rtsp->url, HTTPD_MSG_SETUP,    RtspCallback, (void*)rtsp );
100     httpd_UrlCatch( rtsp->url, HTTPD_MSG_PLAY,     RtspCallback, (void*)rtsp );
101     httpd_UrlCatch( rtsp->url, HTTPD_MSG_PAUSE,    RtspCallback, (void*)rtsp );
102     httpd_UrlCatch( rtsp->url, HTTPD_MSG_GETPARAMETER, RtspCallback,
103                     (void*)rtsp );
104     httpd_UrlCatch( rtsp->url, HTTPD_MSG_TEARDOWN, RtspCallback, (void*)rtsp );
105     return rtsp;
106
107 error:
108     RtspUnsetup( rtsp );
109     return NULL;
110 }
111
112
113 void RtspUnsetup( rtsp_stream_t *rtsp )
114 {
115     while( rtsp->sessionc > 0 )
116         RtspClientDel( rtsp, rtsp->sessionv[0] );
117
118     if( rtsp->url )
119         httpd_UrlDelete( rtsp->url );
120
121     if( rtsp->host )
122         httpd_HostDelete( rtsp->host );
123
124     vlc_mutex_destroy( &rtsp->lock );
125 }
126
127
128 struct rtsp_stream_id_t
129 {
130     rtsp_stream_t    *stream;
131     sout_stream_id_t *sout_id;
132     httpd_url_t      *url;
133     const char       *dst;
134     int               ttl;
135     unsigned          loport, hiport;
136 };
137
138
139 typedef struct rtsp_strack_t rtsp_strack_t;
140
141 /* For unicast streaming */
142 struct rtsp_session_t
143 {
144     rtsp_stream_t *stream;
145     uint64_t       id;
146
147     /* output (id-access) */
148     int            trackc;
149     rtsp_strack_t *trackv;
150 };
151
152
153 /* Unicast session track */
154 struct rtsp_strack_t
155 {
156     sout_stream_id_t  *id;
157     sout_access_out_t *access;
158     vlc_bool_t         playing;
159 };
160
161
162 rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_t *sid,
163                              unsigned num,
164                              /* Multicast stuff - TODO: cleanup */
165                              const char *dst, int ttl,
166                              unsigned loport, unsigned hiport )
167 {
168     char urlbuf[sizeof( "//trackID=123" ) + strlen( rtsp->psz_path )];
169     rtsp_stream_id_t *id = malloc( sizeof( *id ) );
170     httpd_url_t *url;
171
172     if( id == NULL )
173         return NULL;
174
175     id->stream = rtsp;
176     id->sout_id = sid;
177     /* TODO: can we assume that this need not be strdup'd? */
178     id->dst = dst;
179     if( id->dst != NULL )
180     {
181         id->ttl = ttl;
182         id->loport = loport;
183         id->hiport = hiport;
184     }
185
186     snprintf( urlbuf, sizeof( urlbuf ), "/%s/trackID=%u", rtsp->psz_path,
187               num );
188     msg_Dbg( rtsp->owner, "RTSP: adding %s\n", urlbuf );
189     url = id->url = httpd_UrlNewUnique( rtsp->host, urlbuf, NULL, NULL, NULL );
190
191     if( url == NULL )
192     {
193         free( id );
194         return NULL;
195     }
196
197     httpd_UrlCatch( url, HTTPD_MSG_DESCRIBE, RtspCallbackId, (void *)id );
198     httpd_UrlCatch( url, HTTPD_MSG_SETUP,    RtspCallbackId, (void *)id );
199     httpd_UrlCatch( url, HTTPD_MSG_PLAY,     RtspCallbackId, (void *)id );
200     httpd_UrlCatch( url, HTTPD_MSG_PAUSE,    RtspCallbackId, (void *)id );
201     httpd_UrlCatch( url, HTTPD_MSG_GETPARAMETER, RtspCallbackId, (void *)id );
202     httpd_UrlCatch( url, HTTPD_MSG_TEARDOWN, RtspCallbackId, (void *)id );
203
204     return id;
205 }
206
207
208 void RtspDelId( rtsp_stream_t *rtsp, rtsp_stream_id_t *id )
209 {
210     vlc_mutex_lock( &rtsp->lock );
211     for( int i = 0; i < rtsp->sessionc; i++ )
212     {
213         rtsp_session_t *ses = rtsp->sessionv[i];
214
215         for( int j = 0; j < ses->trackc; j++ )
216         {
217             if( ses->trackv[j].id == id->sout_id )
218             {
219                 rtsp_strack_t *tr = ses->trackv + j;
220                 sout_AccessOutDelete( tr->access );
221                 REMOVE_ELEM( ses->trackv, ses->trackc, j );
222                 /* FIXME: are we supposed to notify the client? */
223             }
224         }
225     }
226
227     vlc_mutex_unlock( &rtsp->lock );
228     httpd_UrlDelete( id->url );
229     free( id );
230 }
231
232
233 /** rtsp must be locked */
234 static
235 rtsp_session_t *RtspClientNew( rtsp_stream_t *rtsp )
236 {
237     rtsp_session_t *s = malloc( sizeof( *s ) );
238     if( s == NULL )
239         return NULL;
240
241     s->stream = rtsp;
242     vlc_rand_bytes (&s->id, sizeof (s->id));
243     s->trackc = 0;
244     s->trackv = NULL;
245
246     TAB_APPEND( rtsp->sessionc, rtsp->sessionv, s );
247
248     return s;
249 }
250
251
252 /** rtsp must be locked */
253 static
254 rtsp_session_t *RtspClientGet( rtsp_stream_t *rtsp, const char *name )
255 {
256     char *end;
257     uint64_t id;
258     int i;
259
260     if( name == NULL )
261         return NULL;
262
263     errno = 0;
264     id = strtoull( name, &end, 0x10 );
265     if( errno || *end )
266         return NULL;
267
268     /* FIXME: use a hash/dictionary */
269     for( i = 0; i < rtsp->sessionc; i++ )
270     {
271         if( rtsp->sessionv[i]->id == id )
272             return rtsp->sessionv[i];
273     }
274     return NULL;
275 }
276
277
278 /** rtsp must be locked */
279 static
280 void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session )
281 {
282     int i;
283     TAB_REMOVE( rtsp->sessionc, rtsp->sessionv, session );
284
285     for( i = 0; i < session->trackc; i++ )
286     {
287         rtp_del_sink( session->trackv[i].id, session->trackv[i].access );
288         sout_AccessOutDelete( session->trackv[i].access );
289     }
290
291     free( session->trackv );
292     free( session );
293 }
294
295
296 /** Finds the next transport choice */
297 static inline const char *transport_next( const char *str )
298 {
299     /* Looks for comma */
300     str = strchr( str, ',' );
301     if( str == NULL )
302         return NULL; /* No more transport options */
303
304     str++; /* skips comma */
305     while( strchr( "\r\n\t ", *str ) )
306         str++;
307
308     return (*str) ? str : NULL;
309 }
310
311
312 /** Finds the next transport parameter */
313 static inline const char *parameter_next( const char *str )
314 {
315     while( strchr( ",;", *str ) == NULL )
316         str++;
317
318     return (*str == ';') ? (str + 1) : NULL;
319 }
320
321
322 /** RTSP requests handler
323  * @param id selected track for non-aggregate URLs,
324  *           NULL for aggregate URLs
325  */
326 static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
327                         httpd_client_t *cl,
328                         httpd_message_t *answer,
329                         const httpd_message_t *query )
330 {
331     sout_stream_t *p_stream = id->stream->owner;
332     char psz_sesbuf[17];
333     const char *psz_session = NULL, *psz;
334
335     if( answer == NULL || query == NULL )
336         return VLC_SUCCESS;
337
338     /* */
339     answer->i_proto = HTTPD_PROTO_RTSP;
340     answer->i_version= query->i_version;
341     answer->i_type   = HTTPD_MSG_ANSWER;
342     answer->i_body = 0;
343     answer->p_body = NULL;
344
345     if( httpd_MsgGet( query, "Require" ) != NULL )
346     {
347         answer->i_status = 551;
348         httpd_MsgAdd( answer, "Unsupported", "%s",
349                       httpd_MsgGet( query, "Require" ) );
350     }
351     else
352     switch( query->i_type )
353     {
354         case HTTPD_MSG_DESCRIBE:
355         {   /* Aggregate-only */
356             if( id != NULL )
357             {
358                 answer->i_status = 460;
359                 break;
360             }
361
362             char ip[NI_MAXNUMERICHOST], *ptr;
363             char control[sizeof("rtsp://[]:12345/") + sizeof( ip )
364                             + strlen( rtsp->psz_path )];
365
366             /* Build self-referential URL */
367             httpd_ServerIP( cl, ip );
368             ptr = strchr( ip, '%' );
369             if( ptr != NULL )
370                 *ptr = '\0';
371
372             if( strchr( ip, ':' ) != NULL )
373                 sprintf( control, "rtsp://[%s]:%u/%s", ip, rtsp->port,
374                          ( rtsp->psz_path != NULL ) ? rtsp->psz_path : "" );
375             else
376                 sprintf( control, "rtsp://%s:%u/%s", ip, rtsp->port,
377                          ( rtsp->psz_path != NULL ) ? rtsp->psz_path : "" );
378
379             ptr = SDPGenerate( rtsp->owner, control );
380
381             answer->i_status = 200;
382             httpd_MsgAdd( answer, "Content-Type",  "%s", "application/sdp" );
383             httpd_MsgAdd( answer, "Content-Base",  "%s", control );
384             answer->p_body = (uint8_t *)ptr;
385             answer->i_body = strlen( ptr );
386             break;
387         }
388
389         case HTTPD_MSG_SETUP:
390             /* Non-aggregate-only */
391             if( id == NULL )
392             {
393                 answer->i_status = 459;
394                 break;
395             }
396
397             psz_session = httpd_MsgGet( query, "Session" );
398             answer->i_status = 461;
399
400             for( const char *tpt = httpd_MsgGet( query, "Transport" );
401                  tpt != NULL;
402                  tpt = transport_next( tpt ) )
403             {
404                 vlc_bool_t b_multicast = VLC_TRUE, b_unsupp = VLC_FALSE;
405                 unsigned loport = 5004, hiport = 5005; /* from RFC3551 */
406
407                 /* Check transport protocol. */
408                 /* Currently, we only support RTP/AVP over UDP */
409                 if( strncmp( tpt, "RTP/AVP", 7 ) )
410                     continue;
411                 tpt += 7;
412                 if( strncmp( tpt, "/UDP", 4 ) == 0 )
413                     tpt += 4;
414                 if( strchr( ";,", *tpt ) == NULL )
415                     continue;
416
417                 /* Parse transport options */
418                 for( const char *opt = parameter_next( tpt );
419                      opt != NULL;
420                      opt = parameter_next( opt ) )
421                 {
422                     if( strncmp( opt, "multicast", 9 ) == 0)
423                         b_multicast = VLC_TRUE;
424                     else
425                     if( strncmp( opt, "unicast", 7 ) == 0 )
426                         b_multicast = VLC_FALSE;
427                     else
428                     if( sscanf( opt, "client_port=%u-%u", &loport, &hiport )
429                                 == 2 )
430                         ;
431                     else
432                     if( strncmp( opt, "mode=", 5 ) == 0 )
433                     {
434                         if( strncasecmp( opt + 5, "play", 4 )
435                          && strncasecmp( opt + 5, "\"PLAY\"", 6 ) )
436                         {
437                             /* Not playing?! */
438                             b_unsupp = VLC_TRUE;
439                             break;
440                         }
441                     }
442                     else
443                     if( strncmp( opt,"destination=", 12 ) == 0 )
444                     {
445                         answer->i_status = 403;
446                         b_unsupp = VLC_TRUE;
447                     }
448                     else
449                     {
450                     /*
451                      * Every other option is unsupported:
452                      *
453                      * "source" and "append" are invalid (server-only);
454                      * "ssrc" also (as clarified per RFC2326bis).
455                      *
456                      * For multicast, "port", "layers", "ttl" are set by the
457                      * stream output configuration.
458                      *
459                      * For unicast, we want to decide "server_port" values.
460                      *
461                      * "interleaved" is not implemented.
462                      */
463                         b_unsupp = VLC_TRUE;
464                         break;
465                     }
466                 }
467
468                 if( b_unsupp )
469                     continue;
470
471                 if( b_multicast )
472                 {
473                     const char *dst = id->dst;
474                     if( dst == NULL )
475                         continue;
476
477                     if( psz_session == NULL )
478                     {
479                         /* Create a dummy session ID */
480                         snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%d",
481                                   rand() );
482                         psz_session = psz_sesbuf;
483                     }
484                     answer->i_status = 200;
485
486                     httpd_MsgAdd( answer, "Transport",
487                                   "RTP/AVP/UDP;destination=%s;port=%u-%u;"
488                                   "ttl=%d;mode=play",
489                                   dst, id->loport, id->hiport,
490                                   ( id->ttl > 0 ) ? id->ttl : 1 );
491                 }
492                 else
493                 {
494                     char ip[NI_MAXNUMERICHOST], url[NI_MAXNUMERICHOST + 8];
495                     static const char access[] = "udp{raw,rtcp}";
496                     rtsp_session_t *ses = NULL;
497                     rtsp_strack_t track = { id->sout_id, NULL, VLC_FALSE };
498
499                     if( httpd_ClientIP( cl, ip ) == NULL )
500                     {
501                         answer->i_status = 500;
502                         continue;
503                     }
504
505                     snprintf( url, sizeof( url ),
506                               ( strchr( ip, ':' ) != NULL )
507                                   ? "[%s]:%d" : "%s:%d",
508                               ip, loport );
509
510                     track.access = sout_AccessOutNew( p_stream->p_sout,
511                                                       access, url );
512                     if( track.access == NULL )
513                     {
514                         msg_Err( p_stream,
515                                  "cannot create access output for %s://%s",
516                                  access, url );
517                         answer->i_status = 500;
518                         continue;
519                     }
520
521                     char *src = var_GetNonEmptyString( track.access,
522                                                        "src-addr" );
523                     int sport = var_GetInteger( track.access, "src-port" );
524
525                     vlc_mutex_lock( &rtsp->lock );
526                     if( psz_session == NULL )
527                     {
528                         ses = RtspClientNew( rtsp );
529                         snprintf( psz_sesbuf, sizeof( psz_sesbuf ), I64Fx,
530                                   ses->id );
531                         psz_session = psz_sesbuf;
532                     }
533                     else
534                     {
535                         /* FIXME: we probably need to remove an access out,
536                          * if there is already one for the same ID */
537                         ses = RtspClientGet( rtsp, psz_session );
538                         if( ses == NULL )
539                         {
540                             answer->i_status = 454;
541                             vlc_mutex_unlock( &rtsp->lock );
542                             continue;
543                         }
544                     }
545
546                     INSERT_ELEM( ses->trackv, ses->trackc, ses->trackc,
547                                  track );
548                     vlc_mutex_unlock( &rtsp->lock );
549
550                     httpd_ServerIP( cl, ip );
551
552                     if( ( src != NULL ) && strcmp( src, ip ) )
553                     {
554                         /* Specify source IP if it is different from the RTSP
555                          * control connection server address */
556                         char *ptr = strchr( src, '%' );
557                         if( ptr != NULL ) *ptr = '\0'; /* remove scope ID */
558
559                         httpd_MsgAdd( answer, "Transport",
560                                       "RTP/AVP/UDP;unicast;source=%s;"
561                                       "client_port=%u-%u;server_port=%u-%u;"
562                                       "mode=play",
563                                       src, loport, loport + 1, sport,
564                                       sport + 1 );
565                     }
566                     else
567                     {
568                         httpd_MsgAdd( answer, "Transport",
569                                       "RTP/AVP/UDP;unicast;"
570                                       "client_port=%u-%u;server_port=%u-%u;"
571                                       "mode=play",
572                                       loport, loport + 1, sport, sport + 1 );
573                     }
574
575                     answer->i_status = 200;
576                     free( src );
577                 }
578                 break;
579             }
580             break;
581
582         case HTTPD_MSG_PLAY:
583         {
584             rtsp_session_t *ses;
585             answer->i_status = 200;
586
587             psz_session = httpd_MsgGet( query, "Session" );
588             if( httpd_MsgGet( query, "Range" ) != NULL )
589             {
590                 answer->i_status = 456; /* cannot seek */
591                 break;
592             }
593
594             vlc_mutex_lock( &rtsp->lock );
595             ses = RtspClientGet( rtsp, psz_session );
596             if( ses != NULL )
597             {
598                 for( int i = 0; i < ses->trackc; i++ )
599                 {
600                     rtsp_strack_t *tr = ses->trackv + i;
601                     if( !tr->playing
602                      && ( ( id == NULL ) || ( tr->id == id->sout_id ) ) )
603                     {
604                         tr->playing = VLC_TRUE;
605                         rtp_add_sink( tr->id, tr->access );
606                     }
607                 }
608             }
609             vlc_mutex_unlock( &rtsp->lock );
610
611             if( httpd_MsgGet( query, "Scale" ) != NULL )
612                 httpd_MsgAdd( answer, "Scale", "1." );
613             break;
614         }
615
616         case HTTPD_MSG_PAUSE:
617             answer->i_status = 405;
618             httpd_MsgAdd( answer, "Allow",
619                           "%s, TEARDOWN, PLAY, GET_PARAMETER",
620                           ( id != NULL ) ? "SETUP" : "DESCRIBE" );
621             break;
622
623         case HTTPD_MSG_GETPARAMETER:
624             if( query->i_body > 0 )
625             {
626                 answer->i_status = 451;
627                 break;
628             }
629
630             psz_session = httpd_MsgGet( query, "Session" );
631             answer->i_status = 200;
632             break;
633
634         case HTTPD_MSG_TEARDOWN:
635         {
636             rtsp_session_t *ses;
637
638             answer->i_status = 200;
639
640             psz_session = httpd_MsgGet( query, "Session" );
641
642             vlc_mutex_lock( &rtsp->lock );
643             ses = RtspClientGet( rtsp, psz_session );
644             if( ses != NULL )
645             {
646                 if( id == NULL ) /* Delete the entire session */
647                     RtspClientDel( rtsp, ses );
648                 else /* Delete one track from the session */
649                 for( int i = 0; i < ses->trackc; i++ )
650                 {
651                     if( ses->trackv[i].id == id->sout_id )
652                     {
653                         rtp_del_sink( id->sout_id, ses->trackv[i].access );
654                         sout_AccessOutDelete( ses->trackv[i].access );
655                         REMOVE_ELEM( ses->trackv, ses->trackc, i );
656                     }
657                 }
658             }
659             vlc_mutex_unlock( &rtsp->lock );
660             break;
661         }
662
663         default:
664             return VLC_EGENERIC;
665     }
666
667     httpd_MsgAdd( answer, "Server", "%s", PACKAGE_STRING );
668     if( psz_session )
669         httpd_MsgAdd( answer, "Session", "%s"/*;timeout=5*/, psz_session );
670
671     httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
672     httpd_MsgAdd( answer, "Cache-Control", "no-cache" );
673
674     psz = httpd_MsgGet( query, "Cseq" );
675     if( psz != NULL )
676         httpd_MsgAdd( answer, "Cseq", "%s", psz );
677     psz = httpd_MsgGet( query, "Timestamp" );
678     if( psz != NULL )
679         httpd_MsgAdd( answer, "Timestamp", "%s", psz );
680
681     return VLC_SUCCESS;
682 }
683
684
685 /** Aggregate RTSP callback */
686 static int RtspCallback( httpd_callback_sys_t *p_args,
687                          httpd_client_t *cl,
688                          httpd_message_t *answer,
689                          const httpd_message_t *query )
690 {
691     return RtspHandler( (rtsp_stream_t *)p_args, NULL, cl, answer, query );
692 }
693
694
695 /** Non-aggregate RTSP callback */
696 static int RtspCallbackId( httpd_callback_sys_t *p_args,
697                            httpd_client_t *cl,
698                            httpd_message_t *answer,
699                            const httpd_message_t *query )
700 {
701     rtsp_stream_id_t *id = (rtsp_stream_id_t *)p_args;
702     return RtspHandler( id->stream, id, cl, answer, query );
703 }