]> git.sesse.net Git - vlc/blob - modules/stream_out/rtsp.c
Use plain sockets rather than the UDP access output for RTSP.
[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     int                fd;
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                 net_Close( tr->fd );
221                 REMOVE_ELEM( ses->trackv, ses->trackc, j );
222             }
223         }
224     }
225
226     vlc_mutex_unlock( &rtsp->lock );
227     httpd_UrlDelete( id->url );
228     free( id );
229 }
230
231
232 /** rtsp must be locked */
233 static
234 rtsp_session_t *RtspClientNew( rtsp_stream_t *rtsp )
235 {
236     rtsp_session_t *s = malloc( sizeof( *s ) );
237     if( s == NULL )
238         return NULL;
239
240     s->stream = rtsp;
241     vlc_rand_bytes (&s->id, sizeof (s->id));
242     s->trackc = 0;
243     s->trackv = NULL;
244
245     TAB_APPEND( rtsp->sessionc, rtsp->sessionv, s );
246
247     return s;
248 }
249
250
251 /** rtsp must be locked */
252 static
253 rtsp_session_t *RtspClientGet( rtsp_stream_t *rtsp, const char *name )
254 {
255     char *end;
256     uint64_t id;
257     int i;
258
259     if( name == NULL )
260         return NULL;
261
262     errno = 0;
263     id = strtoull( name, &end, 0x10 );
264     if( errno || *end )
265         return NULL;
266
267     /* FIXME: use a hash/dictionary */
268     for( i = 0; i < rtsp->sessionc; i++ )
269     {
270         if( rtsp->sessionv[i]->id == id )
271             return rtsp->sessionv[i];
272     }
273     return NULL;
274 }
275
276
277 /** rtsp must be locked */
278 static
279 void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session )
280 {
281     int i;
282     TAB_REMOVE( rtsp->sessionc, rtsp->sessionv, session );
283
284     for( i = 0; i < session->trackc; i++ )
285         rtp_del_sink( session->trackv[i].id, session->trackv[i].fd );
286
287     free( session->trackv );
288     free( session );
289 }
290
291
292 /** Finds the next transport choice */
293 static inline const char *transport_next( const char *str )
294 {
295     /* Looks for comma */
296     str = strchr( str, ',' );
297     if( str == NULL )
298         return NULL; /* No more transport options */
299
300     str++; /* skips comma */
301     while( strchr( "\r\n\t ", *str ) )
302         str++;
303
304     return (*str) ? str : NULL;
305 }
306
307
308 /** Finds the next transport parameter */
309 static inline const char *parameter_next( const char *str )
310 {
311     while( strchr( ",;", *str ) == NULL )
312         str++;
313
314     return (*str == ';') ? (str + 1) : NULL;
315 }
316
317
318 /** RTSP requests handler
319  * @param id selected track for non-aggregate URLs,
320  *           NULL for aggregate URLs
321  */
322 static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
323                         httpd_client_t *cl,
324                         httpd_message_t *answer,
325                         const httpd_message_t *query )
326 {
327     sout_stream_t *p_stream = rtsp->owner;
328     char psz_sesbuf[17];
329     const char *psz_session = NULL, *psz;
330
331     if( answer == NULL || query == NULL || cl == NULL )
332         return VLC_SUCCESS;
333
334     /* */
335     answer->i_proto = HTTPD_PROTO_RTSP;
336     answer->i_version= query->i_version;
337     answer->i_type   = HTTPD_MSG_ANSWER;
338     answer->i_body = 0;
339     answer->p_body = NULL;
340
341     if( httpd_MsgGet( query, "Require" ) != NULL )
342     {
343         answer->i_status = 551;
344         httpd_MsgAdd( answer, "Unsupported", "%s",
345                       httpd_MsgGet( query, "Require" ) );
346     }
347     else
348     switch( query->i_type )
349     {
350         case HTTPD_MSG_DESCRIBE:
351         {   /* Aggregate-only */
352             if( id != NULL )
353             {
354                 answer->i_status = 460;
355                 break;
356             }
357
358             char ip[NI_MAXNUMERICHOST], *ptr;
359             char control[sizeof("rtsp://[]:12345/") + sizeof( ip )
360                             + strlen( rtsp->psz_path )];
361
362             /* Build self-referential URL */
363             httpd_ServerIP( cl, ip );
364             ptr = strchr( ip, '%' );
365             if( ptr != NULL )
366                 *ptr = '\0';
367
368             if( strchr( ip, ':' ) != NULL )
369                 sprintf( control, "rtsp://[%s]:%u/%s", ip, rtsp->port,
370                          ( rtsp->psz_path != NULL ) ? rtsp->psz_path : "" );
371             else
372                 sprintf( control, "rtsp://%s:%u/%s", ip, rtsp->port,
373                          ( rtsp->psz_path != NULL ) ? rtsp->psz_path : "" );
374
375             ptr = SDPGenerate( rtsp->owner, control );
376
377             answer->i_status = 200;
378             httpd_MsgAdd( answer, "Content-Type",  "%s", "application/sdp" );
379             httpd_MsgAdd( answer, "Content-Base",  "%s", control );
380             answer->p_body = (uint8_t *)ptr;
381             answer->i_body = strlen( ptr );
382             break;
383         }
384
385         case HTTPD_MSG_SETUP:
386             /* Non-aggregate-only */
387             if( id == NULL )
388             {
389                 answer->i_status = 459;
390                 break;
391             }
392
393             psz_session = httpd_MsgGet( query, "Session" );
394             answer->i_status = 461;
395
396             for( const char *tpt = httpd_MsgGet( query, "Transport" );
397                  tpt != NULL;
398                  tpt = transport_next( tpt ) )
399             {
400                 vlc_bool_t b_multicast = VLC_TRUE, b_unsupp = VLC_FALSE;
401                 unsigned loport = 5004, hiport = 5005; /* from RFC3551 */
402
403                 /* Check transport protocol. */
404                 /* Currently, we only support RTP/AVP over UDP */
405                 if( strncmp( tpt, "RTP/AVP", 7 ) )
406                     continue;
407                 tpt += 7;
408                 if( strncmp( tpt, "/UDP", 4 ) == 0 )
409                     tpt += 4;
410                 if( strchr( ";,", *tpt ) == NULL )
411                     continue;
412
413                 /* Parse transport options */
414                 for( const char *opt = parameter_next( tpt );
415                      opt != NULL;
416                      opt = parameter_next( opt ) )
417                 {
418                     if( strncmp( opt, "multicast", 9 ) == 0)
419                         b_multicast = VLC_TRUE;
420                     else
421                     if( strncmp( opt, "unicast", 7 ) == 0 )
422                         b_multicast = VLC_FALSE;
423                     else
424                     if( sscanf( opt, "client_port=%u-%u", &loport, &hiport )
425                                 == 2 )
426                         ;
427                     else
428                     if( strncmp( opt, "mode=", 5 ) == 0 )
429                     {
430                         if( strncasecmp( opt + 5, "play", 4 )
431                          && strncasecmp( opt + 5, "\"PLAY\"", 6 ) )
432                         {
433                             /* Not playing?! */
434                             b_unsupp = VLC_TRUE;
435                             break;
436                         }
437                     }
438                     else
439                     if( strncmp( opt,"destination=", 12 ) == 0 )
440                     {
441                         answer->i_status = 403;
442                         b_unsupp = VLC_TRUE;
443                     }
444                     else
445                     {
446                     /*
447                      * Every other option is unsupported:
448                      *
449                      * "source" and "append" are invalid (server-only);
450                      * "ssrc" also (as clarified per RFC2326bis).
451                      *
452                      * For multicast, "port", "layers", "ttl" are set by the
453                      * stream output configuration.
454                      *
455                      * For unicast, we want to decide "server_port" values.
456                      *
457                      * "interleaved" is not implemented.
458                      */
459                         b_unsupp = VLC_TRUE;
460                         break;
461                     }
462                 }
463
464                 if( b_unsupp )
465                     continue;
466
467                 if( b_multicast )
468                 {
469                     const char *dst = id->dst;
470                     if( dst == NULL )
471                         continue;
472
473                     if( psz_session == NULL )
474                     {
475                         /* Create a dummy session ID */
476                         snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%d",
477                                   rand() );
478                         psz_session = psz_sesbuf;
479                     }
480                     answer->i_status = 200;
481
482                     httpd_MsgAdd( answer, "Transport",
483                                   "RTP/AVP/UDP;destination=%s;port=%u-%u;"
484                                   "ttl=%d;mode=play",
485                                   dst, id->loport, id->hiport,
486                                   ( id->ttl > 0 ) ? id->ttl : 1 );
487                 }
488                 else
489                 {
490                     char ip[NI_MAXNUMERICHOST], src[NI_MAXNUMERICHOST];
491                     rtsp_session_t *ses = NULL;
492                     rtsp_strack_t track = { id->sout_id, -1, VLC_FALSE };
493                     int sport;
494
495                     if( httpd_ClientIP( cl, ip ) == NULL )
496                     {
497                         answer->i_status = 500;
498                         continue;
499                     }
500
501                     track.fd = net_ConnectDgram( p_stream, ip, loport, -1,
502                                                  IPPROTO_UDP );
503                     if( track.fd == -1 )
504                     {
505                         msg_Err( p_stream,
506                                  "cannot create RTP socket for %s port %u",
507                                  ip, loport );
508                         answer->i_status = 500;
509                         continue;
510                     }
511
512                     net_GetSockAddress( track.fd, src, &sport );
513
514                     vlc_mutex_lock( &rtsp->lock );
515                     if( psz_session == NULL )
516                     {
517                         ses = RtspClientNew( rtsp );
518                         snprintf( psz_sesbuf, sizeof( psz_sesbuf ), I64Fx,
519                                   ses->id );
520                         psz_session = psz_sesbuf;
521                     }
522                     else
523                     {
524                         /* FIXME: we probably need to remove an access out,
525                          * if there is already one for the same ID */
526                         ses = RtspClientGet( rtsp, psz_session );
527                         if( ses == NULL )
528                         {
529                             answer->i_status = 454;
530                             vlc_mutex_unlock( &rtsp->lock );
531                             continue;
532                         }
533                     }
534
535                     INSERT_ELEM( ses->trackv, ses->trackc, ses->trackc,
536                                  track );
537                     vlc_mutex_unlock( &rtsp->lock );
538
539                     httpd_ServerIP( cl, ip );
540
541                     if( strcmp( src, ip ) )
542                     {
543                         /* Specify source IP if it is different from the RTSP
544                          * control connection server address */
545                         char *ptr = strchr( src, '%' );
546                         if( ptr != NULL ) *ptr = '\0'; /* remove scope ID */
547
548                         httpd_MsgAdd( answer, "Transport",
549                                       "RTP/AVP/UDP;unicast;source=%s;"
550                                       "client_port=%u-%u;server_port=%u-%u;"
551                                       "mode=play",
552                                       src, loport, loport + 1, sport,
553                                       sport + 1 );
554                     }
555                     else
556                     {
557                         httpd_MsgAdd( answer, "Transport",
558                                       "RTP/AVP/UDP;unicast;"
559                                       "client_port=%u-%u;server_port=%u-%u;"
560                                       "mode=play",
561                                       loport, loport + 1, sport, sport + 1 );
562                     }
563
564                     answer->i_status = 200;
565                 }
566                 break;
567             }
568             break;
569
570         case HTTPD_MSG_PLAY:
571         {
572             rtsp_session_t *ses;
573             answer->i_status = 200;
574
575             psz_session = httpd_MsgGet( query, "Session" );
576             if( httpd_MsgGet( query, "Range" ) != NULL )
577             {
578                 answer->i_status = 456; /* cannot seek */
579                 break;
580             }
581
582             vlc_mutex_lock( &rtsp->lock );
583             ses = RtspClientGet( rtsp, psz_session );
584             if( ses != NULL )
585             {
586                 for( int i = 0; i < ses->trackc; i++ )
587                 {
588                     rtsp_strack_t *tr = ses->trackv + i;
589                     if( !tr->playing
590                      && ( ( id == NULL ) || ( tr->id == id->sout_id ) ) )
591                     {
592                         tr->playing = VLC_TRUE;
593                         rtp_add_sink( tr->id, tr->fd );
594                     }
595                 }
596             }
597             vlc_mutex_unlock( &rtsp->lock );
598
599             if( httpd_MsgGet( query, "Scale" ) != NULL )
600                 httpd_MsgAdd( answer, "Scale", "1." );
601             break;
602         }
603
604         case HTTPD_MSG_PAUSE:
605             answer->i_status = 405;
606             httpd_MsgAdd( answer, "Allow",
607                           "%s, TEARDOWN, PLAY, GET_PARAMETER",
608                           ( id != NULL ) ? "SETUP" : "DESCRIBE" );
609             break;
610
611         case HTTPD_MSG_GETPARAMETER:
612             if( query->i_body > 0 )
613             {
614                 answer->i_status = 451;
615                 break;
616             }
617
618             psz_session = httpd_MsgGet( query, "Session" );
619             answer->i_status = 200;
620             break;
621
622         case HTTPD_MSG_TEARDOWN:
623         {
624             rtsp_session_t *ses;
625
626             answer->i_status = 200;
627
628             psz_session = httpd_MsgGet( query, "Session" );
629
630             vlc_mutex_lock( &rtsp->lock );
631             ses = RtspClientGet( rtsp, psz_session );
632             if( ses != NULL )
633             {
634                 if( id == NULL ) /* Delete the entire session */
635                     RtspClientDel( rtsp, ses );
636                 else /* Delete one track from the session */
637                 for( int i = 0; i < ses->trackc; i++ )
638                 {
639                     if( ses->trackv[i].id == id->sout_id )
640                     {
641                         rtp_del_sink( id->sout_id, ses->trackv[i].fd );
642                         REMOVE_ELEM( ses->trackv, ses->trackc, i );
643                     }
644                 }
645             }
646             vlc_mutex_unlock( &rtsp->lock );
647             break;
648         }
649
650         default:
651             return VLC_EGENERIC;
652     }
653
654     httpd_MsgAdd( answer, "Server", "%s", PACKAGE_STRING );
655     if( psz_session )
656         httpd_MsgAdd( answer, "Session", "%s"/*;timeout=5*/, psz_session );
657
658     httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
659     httpd_MsgAdd( answer, "Cache-Control", "no-cache" );
660
661     psz = httpd_MsgGet( query, "Cseq" );
662     if( psz != NULL )
663         httpd_MsgAdd( answer, "Cseq", "%s", psz );
664     psz = httpd_MsgGet( query, "Timestamp" );
665     if( psz != NULL )
666         httpd_MsgAdd( answer, "Timestamp", "%s", psz );
667
668     return VLC_SUCCESS;
669 }
670
671
672 /** Aggregate RTSP callback */
673 static int RtspCallback( httpd_callback_sys_t *p_args,
674                          httpd_client_t *cl,
675                          httpd_message_t *answer,
676                          const httpd_message_t *query )
677 {
678     return RtspHandler( (rtsp_stream_t *)p_args, NULL, cl, answer, query );
679 }
680
681
682 /** Non-aggregate RTSP callback */
683 static int RtspCallbackId( httpd_callback_sys_t *p_args,
684                            httpd_client_t *cl,
685                            httpd_message_t *answer,
686                            const httpd_message_t *query )
687 {
688     rtsp_stream_id_t *id = (rtsp_stream_id_t *)p_args;
689     return RtspHandler( id->stream, id, cl, answer, query );
690 }