]> git.sesse.net Git - vlc/blob - modules/stream_out/rtsp.c
Contribs: add libblurray
[vlc] / modules / stream_out / rtsp.c
1 /*****************************************************************************
2  * rtsp.c: RTSP support for RTP stream output module
3  *****************************************************************************
4  * Copyright (C) 2003-2004, 2010 the VideoLAN team
5  * Copyright © 2007 Rémi Denis-Courmont
6  *
7  * $Id$
8  *
9  * Authors: Laurent Aimar <fenrir@via.ecp.fr>
10  *          Pierre Ynard
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <vlc_common.h>
35 #include <vlc_sout.h>
36
37 #include <vlc_httpd.h>
38 #include <vlc_url.h>
39 #include <vlc_charset.h>
40 #include <vlc_fs.h>
41 #include <vlc_network.h>
42 #include <vlc_rand.h>
43 #include <assert.h>
44 #include <errno.h>
45 #include <stdlib.h>
46
47 #ifndef WIN32
48 # include <locale.h>
49 #endif
50 #ifdef HAVE_XLOCALE_H
51 # include <xlocale.h>
52 #endif
53
54 #include "rtp.h"
55
56 typedef struct rtsp_session_t rtsp_session_t;
57
58 struct rtsp_stream_t
59 {
60     vlc_mutex_t     lock;
61     vlc_object_t   *owner;
62     vod_media_t    *vod_media;
63     httpd_host_t   *host;
64     httpd_url_t    *url;
65     char           *psz_path;
66     unsigned        track_id;
67
68     int             sessionc;
69     rtsp_session_t **sessionv;
70
71     int             timeout;
72     vlc_timer_t     timer;
73 };
74
75
76 static int  RtspCallback( httpd_callback_sys_t *p_args,
77                           httpd_client_t *cl, httpd_message_t *answer,
78                           const httpd_message_t *query );
79 static int  RtspCallbackId( httpd_callback_sys_t *p_args,
80                             httpd_client_t *cl, httpd_message_t *answer,
81                             const httpd_message_t *query );
82 static void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session );
83
84 static void RtspTimeOut( void *data );
85
86 rtsp_stream_t *RtspSetup( vlc_object_t *owner, vod_media_t *media,
87                           const vlc_url_t *url )
88 {
89     rtsp_stream_t *rtsp = malloc( sizeof( *rtsp ) );
90
91     if( rtsp == NULL || ( url->i_port > 99999 ) )
92     {
93         free( rtsp );
94         return NULL;
95     }
96
97     rtsp->owner = owner;
98     rtsp->vod_media = media;
99     rtsp->sessionc = 0;
100     rtsp->sessionv = NULL;
101     rtsp->host = NULL;
102     rtsp->url = NULL;
103     rtsp->psz_path = NULL;
104     rtsp->track_id = 0;
105     vlc_mutex_init( &rtsp->lock );
106
107     rtsp->timeout = var_InheritInteger(owner, "rtsp-timeout");
108     if (rtsp->timeout > 0)
109     {
110         if (vlc_timer_create(&rtsp->timer, RtspTimeOut, rtsp))
111             goto error;
112     }
113
114     rtsp->psz_path = strdup( ( url->psz_path != NULL ) ? url->psz_path : "/" );
115     if( rtsp->psz_path == NULL )
116         goto error;
117
118     msg_Dbg( owner, "RTSP stream at %s", rtsp->psz_path );
119
120     rtsp->host = vlc_rtsp_HostNew( VLC_OBJECT(owner) );
121     if( rtsp->host == NULL )
122         goto error;
123
124     rtsp->url = httpd_UrlNewUnique( rtsp->host, rtsp->psz_path,
125                                     NULL, NULL, NULL );
126     if( rtsp->url == NULL )
127         goto error;
128
129     httpd_UrlCatch( rtsp->url, HTTPD_MSG_DESCRIBE, RtspCallback, (void*)rtsp );
130     httpd_UrlCatch( rtsp->url, HTTPD_MSG_SETUP,    RtspCallback, (void*)rtsp );
131     httpd_UrlCatch( rtsp->url, HTTPD_MSG_PLAY,     RtspCallback, (void*)rtsp );
132     httpd_UrlCatch( rtsp->url, HTTPD_MSG_PAUSE,    RtspCallback, (void*)rtsp );
133     httpd_UrlCatch( rtsp->url, HTTPD_MSG_GETPARAMETER, RtspCallback,
134                     (void*)rtsp );
135     httpd_UrlCatch( rtsp->url, HTTPD_MSG_TEARDOWN, RtspCallback, (void*)rtsp );
136     return rtsp;
137
138 error:
139     RtspUnsetup( rtsp );
140     return NULL;
141 }
142
143
144 void RtspUnsetup( rtsp_stream_t *rtsp )
145 {
146     if( rtsp->url )
147         httpd_UrlDelete( rtsp->url );
148
149     if( rtsp->host )
150         httpd_HostDelete( rtsp->host );
151
152     while( rtsp->sessionc > 0 )
153         RtspClientDel( rtsp, rtsp->sessionv[0] );
154
155     if (rtsp->timeout > 0)
156         vlc_timer_destroy(rtsp->timer);
157
158     free( rtsp->psz_path );
159     vlc_mutex_destroy( &rtsp->lock );
160
161     free( rtsp );
162 }
163
164
165 struct rtsp_stream_id_t
166 {
167     rtsp_stream_t    *stream;
168     sout_stream_id_t *sout_id;
169     httpd_url_t      *url;
170     unsigned          track_id;
171     uint32_t          ssrc;
172     unsigned          clock_rate; /* needed to compute rtptime in RTP-Info */
173     int               mcast_fd;
174 };
175
176
177 typedef struct rtsp_strack_t rtsp_strack_t;
178
179 /* For unicast streaming */
180 struct rtsp_session_t
181 {
182     rtsp_stream_t *stream;
183     uint64_t       id;
184     mtime_t        last_seen; /* for timeouts */
185
186     /* output (id-access) */
187     int            trackc;
188     rtsp_strack_t *trackv;
189 };
190
191
192 /* Unicast session track */
193 struct rtsp_strack_t
194 {
195     rtsp_stream_id_t  *id;
196     sout_stream_id_t  *sout_id;
197     int          setup_fd;  /* socket created by the SETUP request */
198     int          rtp_fd;    /* socket used by the RTP output, when playing */
199     uint32_t     ssrc;
200     uint16_t     seq_init;
201 };
202
203 static void RtspTrackClose( rtsp_strack_t *tr );
204
205 #define TRACK_PATH_SIZE (sizeof("/trackID=999") - 1)
206
207 char *RtspAppendTrackPath( rtsp_stream_id_t *id, const char *base )
208 {
209     const char *sep = strlen( base ) > 0 && base[strlen( base ) - 1] == '/' ?
210                       "" : "/";
211     char *url;
212
213     if( asprintf( &url, "%s%strackID=%u", base, sep, id->track_id ) == -1 )
214         url = NULL;
215     return url;
216 }
217
218
219 rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_t *sid,
220                              uint32_t ssrc, unsigned clock_rate,
221                              int mcast_fd)
222 {
223     if (rtsp->track_id > 999)
224     {
225         msg_Err(rtsp->owner, "RTSP: too many IDs!");
226         return NULL;
227     }
228
229     char *urlbuf;
230     rtsp_stream_id_t *id = malloc( sizeof( *id ) );
231     httpd_url_t *url;
232
233     if( id == NULL )
234         return NULL;
235
236     id->stream = rtsp;
237     id->sout_id = sid;
238     id->track_id = rtsp->track_id;
239     id->ssrc = ssrc;
240     id->clock_rate = clock_rate;
241     id->mcast_fd = mcast_fd;
242
243     urlbuf = RtspAppendTrackPath( id, rtsp->psz_path );
244     if( urlbuf == NULL )
245     {
246         free( id );
247         return NULL;
248     }
249
250     msg_Dbg( rtsp->owner, "RTSP: adding %s", urlbuf );
251     url = id->url = httpd_UrlNewUnique( rtsp->host, urlbuf, NULL, NULL, NULL );
252     free( urlbuf );
253
254     if( url == NULL )
255     {
256         free( id );
257         return NULL;
258     }
259
260     httpd_UrlCatch( url, HTTPD_MSG_DESCRIBE, RtspCallbackId, (void *)id );
261     httpd_UrlCatch( url, HTTPD_MSG_SETUP,    RtspCallbackId, (void *)id );
262     httpd_UrlCatch( url, HTTPD_MSG_PLAY,     RtspCallbackId, (void *)id );
263     httpd_UrlCatch( url, HTTPD_MSG_PAUSE,    RtspCallbackId, (void *)id );
264     httpd_UrlCatch( url, HTTPD_MSG_GETPARAMETER, RtspCallbackId, (void *)id );
265     httpd_UrlCatch( url, HTTPD_MSG_TEARDOWN, RtspCallbackId, (void *)id );
266
267     rtsp->track_id++;
268
269     return id;
270 }
271
272
273 void RtspDelId( rtsp_stream_t *rtsp, rtsp_stream_id_t *id )
274 {
275     httpd_UrlDelete( id->url );
276
277     vlc_mutex_lock( &rtsp->lock );
278     for( int i = 0; i < rtsp->sessionc; i++ )
279     {
280         rtsp_session_t *ses = rtsp->sessionv[i];
281
282         for( int j = 0; j < ses->trackc; j++ )
283         {
284             if( ses->trackv[j].id == id )
285             {
286                 rtsp_strack_t *tr = ses->trackv + j;
287                 RtspTrackClose( tr );
288                 REMOVE_ELEM( ses->trackv, ses->trackc, j );
289             }
290         }
291     }
292
293     vlc_mutex_unlock( &rtsp->lock );
294     free( id );
295 }
296
297
298 /** rtsp must be locked */
299 static void RtspUpdateTimer( rtsp_stream_t *rtsp )
300 {
301     if (rtsp->timeout <= 0)
302         return;
303
304     mtime_t timeout = 0;
305     for (int i = 0; i < rtsp->sessionc; i++)
306     {
307         if (timeout == 0 || rtsp->sessionv[i]->last_seen < timeout)
308             timeout = rtsp->sessionv[i]->last_seen;
309     }
310     if (timeout != 0)
311         timeout += rtsp->timeout * CLOCK_FREQ;
312     vlc_timer_schedule(rtsp->timer, true, timeout, 0);
313 }
314
315
316 static void RtspTimeOut( void *data )
317 {
318     rtsp_stream_t *rtsp = data;
319
320     vlc_mutex_lock(&rtsp->lock);
321     mtime_t now = mdate();
322     for (int i = rtsp->sessionc - 1; i >= 0; i--)
323     {
324         if (rtsp->sessionv[i]->last_seen + rtsp->timeout * CLOCK_FREQ < now)
325         {
326             if (rtsp->vod_media != NULL)
327             {
328                 char psz_sesbuf[17];
329                 snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%"PRIx64,
330                           rtsp->sessionv[i]->id );
331                 vod_stop(rtsp->vod_media, psz_sesbuf);
332             }
333             RtspClientDel(rtsp, rtsp->sessionv[i]);
334         }
335     }
336     RtspUpdateTimer(rtsp);
337     vlc_mutex_unlock(&rtsp->lock);
338 }
339
340
341 /** rtsp must be locked */
342 static
343 rtsp_session_t *RtspClientNew( rtsp_stream_t *rtsp )
344 {
345     rtsp_session_t *s = malloc( sizeof( *s ) );
346     if( s == NULL )
347         return NULL;
348
349     s->stream = rtsp;
350     vlc_rand_bytes (&s->id, sizeof (s->id));
351     s->trackc = 0;
352     s->trackv = NULL;
353
354     TAB_APPEND( rtsp->sessionc, rtsp->sessionv, s );
355
356     return s;
357 }
358
359
360 /** rtsp must be locked */
361 static
362 rtsp_session_t *RtspClientGet( rtsp_stream_t *rtsp, const char *name )
363 {
364     char *end;
365     uint64_t id;
366     int i;
367
368     if( name == NULL )
369         return NULL;
370
371     errno = 0;
372     id = strtoull( name, &end, 0x10 );
373     if( errno || *end )
374         return NULL;
375
376     /* FIXME: use a hash/dictionary */
377     for( i = 0; i < rtsp->sessionc; i++ )
378     {
379         if( rtsp->sessionv[i]->id == id )
380             return rtsp->sessionv[i];
381     }
382     return NULL;
383 }
384
385
386 /** rtsp must be locked */
387 static
388 void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session )
389 {
390     int i;
391     TAB_REMOVE( rtsp->sessionc, rtsp->sessionv, session );
392
393     for( i = 0; i < session->trackc; i++ )
394         RtspTrackClose( &session->trackv[i] );
395
396     free( session->trackv );
397     free( session );
398 }
399
400
401 /** rtsp must be locked */
402 static void RtspClientAlive( rtsp_session_t *session )
403 {
404     if (session->stream->timeout <= 0)
405         return;
406
407     session->last_seen = mdate();
408     RtspUpdateTimer(session->stream);
409 }
410
411 static int dup_socket(int oldfd)
412 {
413     int newfd;
414 #if !defined(WIN32) || defined(UNDER_CE)
415     newfd = vlc_dup(oldfd);
416 #else
417     WSAPROTOCOL_INFO info;
418     WSADuplicateSocket (oldfd, GetCurrentProcessId (), &info);
419     newfd = WSASocket (info.iAddressFamily, info.iSocketType,
420                        info.iProtocol, &info, 0, 0);
421 #endif
422     return newfd;
423 }
424
425 /* Attach a starting VoD RTP id to its RTSP track, and let it
426  * initialize with the parameters of the SETUP request */
427 int RtspTrackAttach( rtsp_stream_t *rtsp, const char *name,
428                      rtsp_stream_id_t *id, sout_stream_id_t *sout_id,
429                      uint32_t *ssrc, uint16_t *seq_init )
430 {
431     int val = VLC_EGENERIC;
432     rtsp_session_t *session;
433
434     vlc_mutex_lock(&rtsp->lock);
435     session = RtspClientGet(rtsp, name);
436
437     if (session == NULL)
438         goto out;
439
440     rtsp_strack_t *tr = NULL;
441     for (int i = 0; i < session->trackc; i++)
442     {
443         if (session->trackv[i].id == id)
444         {
445             tr = session->trackv + i;
446             break;
447         }
448     }
449
450     if (tr != NULL)
451     {
452         tr->sout_id = sout_id;
453         tr->rtp_fd = dup_socket(tr->setup_fd);
454     }
455     else
456     {
457         /* The track was not SETUP. We still create one because we'll
458          * need the sout_id if we set it up later. */
459         rtsp_strack_t track = { .id = id, .sout_id = sout_id,
460                                 .setup_fd = -1, .rtp_fd = -1 };
461         vlc_rand_bytes (&track.seq_init, sizeof (track.seq_init));
462         vlc_rand_bytes (&track.ssrc, sizeof (track.ssrc));
463
464         INSERT_ELEM(session->trackv, session->trackc, session->trackc, track);
465         tr = session->trackv + session->trackc - 1;
466     }
467
468     *ssrc = ntohl(tr->ssrc);
469     *seq_init = tr->seq_init;
470
471     if (tr->rtp_fd != -1)
472     {
473         uint16_t seq;
474         rtp_add_sink(tr->sout_id, tr->rtp_fd, false, &seq);
475         /* To avoid race conditions, sout_id->i_seq_sent_next must
476          * be set here and now. Make sure the caller did its job
477          * properly when passing seq_init. */
478         assert(tr->seq_init == seq);
479     }
480
481     val = VLC_SUCCESS;
482 out:
483     vlc_mutex_unlock(&rtsp->lock);
484     return val;
485 }
486
487
488 /* Remove references to the RTP id when it is stopped */
489 void RtspTrackDetach( rtsp_stream_t *rtsp, const char *name,
490                       sout_stream_id_t *sout_id )
491 {
492     rtsp_session_t *session;
493
494     vlc_mutex_lock(&rtsp->lock);
495     session = RtspClientGet(rtsp, name);
496
497     if (session == NULL)
498         goto out;
499
500     for (int i = 0; i < session->trackc; i++)
501     {
502         rtsp_strack_t *tr = session->trackv + i;
503         if (tr->sout_id == sout_id)
504         {
505             if (tr->setup_fd == -1)
506             {
507                 /* No (more) SETUP information: better get rid of the
508                  * track so that we can have new random ssrc and
509                  * seq_init next time. */
510                 REMOVE_ELEM( session->trackv, session->trackc, i );
511                 break;
512             }
513             /* We keep the SETUP information of the track, but stop it */
514             if (tr->rtp_fd != -1)
515             {
516                 rtp_del_sink(tr->sout_id, tr->rtp_fd);
517                 tr->rtp_fd = -1;
518             }
519             tr->sout_id = NULL;
520             break;
521         }
522     }
523
524 out:
525     vlc_mutex_unlock(&rtsp->lock);
526 }
527
528
529 /** rtsp must be locked */
530 static void RtspTrackClose( rtsp_strack_t *tr )
531 {
532     if (tr->setup_fd != -1)
533     {
534         if (tr->rtp_fd != -1)
535         {
536             rtp_del_sink(tr->sout_id, tr->rtp_fd);
537             tr->rtp_fd = -1;
538         }
539         net_Close(tr->setup_fd);
540         tr->setup_fd = -1;
541     }
542 }
543
544
545 /** Finds the next transport choice */
546 static inline const char *transport_next( const char *str )
547 {
548     /* Looks for comma */
549     str = strchr( str, ',' );
550     if( str == NULL )
551         return NULL; /* No more transport options */
552
553     str++; /* skips comma */
554     while( strchr( "\r\n\t ", *str ) )
555         str++;
556
557     return (*str) ? str : NULL;
558 }
559
560
561 /** Finds the next transport parameter */
562 static inline const char *parameter_next( const char *str )
563 {
564     while( strchr( ",;", *str ) == NULL )
565         str++;
566
567     return (*str == ';') ? (str + 1) : NULL;
568 }
569
570
571 static int64_t ParseNPT (const char *str)
572 {
573     locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
574     locale_t oldloc = uselocale (loc);
575     unsigned hour, min;
576     float sec;
577
578     if (sscanf (str, "%u:%u:%f", &hour, &min, &sec) == 3)
579         sec += ((hour * 60) + min) * 60;
580     else
581     if (sscanf (str, "%f", &sec) != 1)
582         sec = -1;
583
584     if (loc != (locale_t)0)
585     {
586         uselocale (oldloc);
587         freelocale (loc);
588     }
589     return sec < 0 ? -1 : sec * CLOCK_FREQ;
590 }
591
592
593 /** RTSP requests handler
594  * @param id selected track for non-aggregate URLs,
595  *           NULL for aggregate URLs
596  */
597 static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
598                         httpd_client_t *cl,
599                         httpd_message_t *answer,
600                         const httpd_message_t *query )
601 {
602     vlc_object_t *owner = rtsp->owner;
603     char psz_sesbuf[17];
604     const char *psz_session = NULL, *psz;
605     char control[sizeof("rtsp://[]:12345") + NI_MAXNUMERICHOST
606                   + strlen( rtsp->psz_path )];
607     bool vod = rtsp->vod_media != NULL;
608     time_t now;
609
610     time (&now);
611
612     if( answer == NULL || query == NULL || cl == NULL )
613         return VLC_SUCCESS;
614     else
615     {
616         /* Build self-referential control URL */
617         char ip[NI_MAXNUMERICHOST], *ptr;
618         int port;
619
620         httpd_ServerIP( cl, ip, &port );
621         ptr = strchr( ip, '%' );
622         if( ptr != NULL )
623             *ptr = '\0';
624
625         if( strchr( ip, ':' ) != NULL )
626             sprintf( control, "rtsp://[%s]:%d%s", ip, port, rtsp->psz_path );
627         else
628             sprintf( control, "rtsp://%s:%d%s", ip, port, rtsp->psz_path );
629     }
630
631     /* */
632     answer->i_proto = HTTPD_PROTO_RTSP;
633     answer->i_version= 0;
634     answer->i_type   = HTTPD_MSG_ANSWER;
635     answer->i_body = 0;
636     answer->p_body = NULL;
637
638     httpd_MsgAdd( answer, "Server", "VLC/%s", VERSION );
639
640     /* Date: is always allowed, and sometimes mandatory with RTSP/2.0. */
641     struct tm ut;
642     if (gmtime_r (&now, &ut) != NULL)
643     {   /* RFC1123 format, GMT is mandatory */
644         static const char wdays[7][4] = {
645             "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
646         static const char mons[12][4] = {
647             "Jan", "Feb", "Mar", "Apr", "May", "Jun",
648             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
649         httpd_MsgAdd (answer, "Date", "%s, %02u %s %04u %02u:%02u:%02u GMT",
650                       wdays[ut.tm_wday], ut.tm_mday, mons[ut.tm_mon],
651                       1900 + ut.tm_year, ut.tm_hour, ut.tm_min, ut.tm_sec);
652     }
653
654     if( query->i_proto != HTTPD_PROTO_RTSP )
655     {
656         answer->i_status = 505;
657     }
658     else
659     if( httpd_MsgGet( query, "Require" ) != NULL )
660     {
661         answer->i_status = 551;
662         httpd_MsgAdd( answer, "Unsupported", "%s",
663                       httpd_MsgGet( query, "Require" ) );
664     }
665     else
666     switch( query->i_type )
667     {
668         case HTTPD_MSG_DESCRIBE:
669         {   /* Aggregate-only */
670             if( id != NULL )
671             {
672                 answer->i_status = 460;
673                 break;
674             }
675
676             answer->i_status = 200;
677             httpd_MsgAdd( answer, "Content-Type",  "%s", "application/sdp" );
678             httpd_MsgAdd( answer, "Content-Base",  "%s", control );
679
680             answer->p_body = (uint8_t *) ( vod ?
681                 SDPGenerateVoD( rtsp->vod_media, control ) :
682                 SDPGenerate( (sout_stream_t *)owner, control ) );
683             if( answer->p_body != NULL )
684                 answer->i_body = strlen( (char *)answer->p_body );
685             else
686                 answer->i_status = 500;
687             break;
688         }
689
690         case HTTPD_MSG_SETUP:
691             /* Non-aggregate-only */
692             if( id == NULL )
693             {
694                 answer->i_status = 459;
695                 break;
696             }
697
698             psz_session = httpd_MsgGet( query, "Session" );
699             answer->i_status = 461;
700
701             for( const char *tpt = httpd_MsgGet( query, "Transport" );
702                  tpt != NULL;
703                  tpt = transport_next( tpt ) )
704             {
705                 bool b_multicast = true, b_unsupp = false;
706                 unsigned loport = 5004, hiport; /* from RFC3551 */
707
708                 /* Check transport protocol. */
709                 /* Currently, we only support RTP/AVP over UDP */
710                 if( strncmp( tpt, "RTP/AVP", 7 ) )
711                     continue;
712                 tpt += 7;
713                 if( strncmp( tpt, "/UDP", 4 ) == 0 )
714                     tpt += 4;
715                 if( strchr( ";,", *tpt ) == NULL )
716                     continue;
717
718                 /* Parse transport options */
719                 for( const char *opt = parameter_next( tpt );
720                      opt != NULL;
721                      opt = parameter_next( opt ) )
722                 {
723                     if( strncmp( opt, "multicast", 9 ) == 0)
724                         b_multicast = true;
725                     else
726                     if( strncmp( opt, "unicast", 7 ) == 0 )
727                         b_multicast = false;
728                     else
729                     if( sscanf( opt, "client_port=%u-%u", &loport, &hiport )
730                                 == 2 )
731                         ;
732                     else
733                     if( strncmp( opt, "mode=", 5 ) == 0 )
734                     {
735                         if( strncasecmp( opt + 5, "play", 4 )
736                          && strncasecmp( opt + 5, "\"PLAY\"", 6 ) )
737                         {
738                             /* Not playing?! */
739                             b_unsupp = true;
740                             break;
741                         }
742                     }
743                     else
744                     if( strncmp( opt,"destination=", 12 ) == 0 )
745                     {
746                         answer->i_status = 403;
747                         b_unsupp = true;
748                     }
749                     else
750                     {
751                     /*
752                      * Every other option is unsupported:
753                      *
754                      * "source" and "append" are invalid (server-only);
755                      * "ssrc" also (as clarified per RFC2326bis).
756                      *
757                      * For multicast, "port", "layers", "ttl" are set by the
758                      * stream output configuration.
759                      *
760                      * For unicast, we want to decide "server_port" values.
761                      *
762                      * "interleaved" is not implemented.
763                      */
764                         b_unsupp = true;
765                         break;
766                     }
767                 }
768
769                 if( b_unsupp )
770                     continue;
771
772                 if( b_multicast )
773                 {
774                     char dst[NI_MAXNUMERICHOST];
775                     int dport, ttl;
776                     if( id->mcast_fd == -1 )
777                         continue;
778
779                     net_GetPeerAddress(id->mcast_fd, dst, &dport);
780
781                     ttl = var_InheritInteger(owner, "ttl");
782                     if (ttl <= 0)
783                     /* FIXME: the TTL is left to the OS default, we can
784                      * only guess that it's 1. */
785                         ttl = 1;
786
787                     if( psz_session == NULL )
788                     {
789                         /* Create a dummy session ID */
790                         snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%lu",
791                                   vlc_mrand48() );
792                         psz_session = psz_sesbuf;
793                     }
794                     answer->i_status = 200;
795
796                     httpd_MsgAdd( answer, "Transport",
797                                   "RTP/AVP/UDP;destination=%s;port=%u-%u;"
798                                   "ttl=%d;mode=play",
799                                   dst, dport, dport + 1, ttl );
800                      /* FIXME: this doesn't work with RTP + RTCP mux */
801                 }
802                 else
803                 {
804                     char ip[NI_MAXNUMERICHOST], src[NI_MAXNUMERICHOST];
805                     rtsp_session_t *ses = NULL;
806                     int fd, sport;
807                     uint32_t ssrc;
808
809                     if( httpd_ClientIP( cl, ip, NULL ) == NULL )
810                     {
811                         answer->i_status = 500;
812                         continue;
813                     }
814
815                     fd = net_ConnectDgram( owner, ip, loport, -1,
816                                            IPPROTO_UDP );
817                     if( fd == -1 )
818                     {
819                         msg_Err( owner,
820                                  "cannot create RTP socket for %s port %u",
821                                  ip, loport );
822                         answer->i_status = 500;
823                         continue;
824                     }
825
826                     /* Ignore any unexpected incoming packet */
827                     setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 },
828                                 sizeof (int));
829                     net_GetSockAddress( fd, src, &sport );
830
831                     vlc_mutex_lock( &rtsp->lock );
832                     if( psz_session == NULL )
833                     {
834                         ses = RtspClientNew( rtsp );
835                         snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%"PRIx64,
836                                   ses->id );
837                         psz_session = psz_sesbuf;
838                     }
839                     else
840                     {
841                         ses = RtspClientGet( rtsp, psz_session );
842                         if( ses == NULL )
843                         {
844                             answer->i_status = 454;
845                             vlc_mutex_unlock( &rtsp->lock );
846                             net_Close( fd );
847                             continue;
848                         }
849                     }
850                     RtspClientAlive(ses);
851
852                     rtsp_strack_t *tr = NULL;
853                     for (int i = 0; i < ses->trackc; i++)
854                     {
855                         if (ses->trackv[i].id == id)
856                         {
857                             tr = ses->trackv + i;
858                             break;
859                         }
860                     }
861
862                     if (tr == NULL)
863                     {
864                         /* Set up a new track */
865                         rtsp_strack_t track = { .id = id,
866                                                 .sout_id = id->sout_id,
867                                                 .setup_fd = fd,
868                                                 .rtp_fd = -1 };
869
870                         if (vod)
871                         {
872                             vlc_rand_bytes (&track.seq_init,
873                                             sizeof (track.seq_init));
874                             vlc_rand_bytes (&track.ssrc, sizeof (track.ssrc));
875                             ssrc = track.ssrc;
876                         }
877                         else
878                             ssrc = id->ssrc;
879
880                         INSERT_ELEM( ses->trackv, ses->trackc, ses->trackc,
881                                      track );
882                     }
883                     else if (tr->setup_fd == -1)
884                     {
885                         /* The track was not SETUP, but it exists
886                          * because there is a sout_id running for it */
887                         tr->setup_fd = fd;
888                         ssrc = tr->ssrc;
889                     }
890                     else
891                     {
892                         /* The track is already set up, and we don't
893                          * support changing the transport parameters on
894                          * the fly */
895                         vlc_mutex_unlock( &rtsp->lock );
896                         answer->i_status = 455;
897                         net_Close( fd );
898                         break;
899                     }
900                     vlc_mutex_unlock( &rtsp->lock );
901
902                     httpd_ServerIP( cl, ip, NULL );
903
904                     /* Specify source IP only if it is different from the
905                      * RTSP control connection server address */
906                     if( strcmp( src, ip ) )
907                     {
908                         char *ptr = strchr( src, '%' );
909                         if( ptr != NULL ) *ptr = '\0'; /* remove scope ID */
910                     }
911                     else
912                         src[0] = '\0';
913
914                     httpd_MsgAdd( answer, "Transport",
915                                   "RTP/AVP/UDP;unicast%s%s;"
916                                   "client_port=%u-%u;server_port=%u-%u;"
917                                   "ssrc=%08X;mode=play",
918                                   src[0] ? ";source=" : "", src,
919                                   loport, loport + 1, sport, sport + 1, ssrc );
920
921                     answer->i_status = 200;
922                 }
923                 break;
924             }
925             break;
926
927         case HTTPD_MSG_PLAY:
928         {
929             rtsp_session_t *ses;
930             answer->i_status = 200;
931
932             psz_session = httpd_MsgGet( query, "Session" );
933             int64_t start = -1, end = -1, npt;
934             const char *range = httpd_MsgGet (query, "Range");
935             if (range != NULL)
936             {
937                 if (strncmp (range, "npt=", 4))
938                 {
939                     answer->i_status = 501;
940                     break;
941                 }
942
943                 start = ParseNPT (range + 4);
944                 range = strchr(range, '-');
945                 if (range != NULL && *(range + 1))
946                     end = ParseNPT (range + 1);
947
948                 if (end >= 0 && end < start)
949                 {
950                     answer->i_status = 457;
951                     break;
952                 }
953
954                 if (vod)
955                 {
956                     if (vod_check_range(rtsp->vod_media, psz_session,
957                                         start, end) != VLC_SUCCESS)
958                     {
959                         answer->i_status = 457;
960                         break;
961                     }
962                 }
963                 /* We accept start times of 0 even for broadcast streams
964                  * that already started */
965                 else if (start > 0 || end >= 0)
966                 {
967                     answer->i_status = 456;
968                     break;
969                 }
970             }
971             vlc_mutex_lock( &rtsp->lock );
972             ses = RtspClientGet( rtsp, psz_session );
973             if( ses != NULL )
974             {
975                 char info[ses->trackc * ( strlen( control ) + TRACK_PATH_SIZE
976                           + sizeof("url=;seq=65535;rtptime=4294967295, ")
977                                           - 1 ) + 1];
978                 size_t infolen = 0;
979                 RtspClientAlive(ses);
980
981                 sout_stream_id_t *sout_id = NULL;
982                 if (vod)
983                 {
984                     /* We don't keep a reference to the sout_stream_t,
985                      * so we check if a sout_id is available instead. */
986                     for (int i = 0; i < ses->trackc; i++)
987                     {
988                         sout_id = ses->trackv[i].sout_id;
989                         if (sout_id != NULL)
990                             break;
991                     }
992                 }
993                 int64_t ts = rtp_get_ts(vod ? NULL : (sout_stream_t *)owner,
994                                         sout_id, rtsp->vod_media, psz_session,
995                                         vod ? NULL : &npt);
996
997                 for( int i = 0; i < ses->trackc; i++ )
998                 {
999                     rtsp_strack_t *tr = ses->trackv + i;
1000                     if( ( id == NULL ) || ( tr->id == id ) )
1001                     {
1002                         if (tr->setup_fd == -1)
1003                             /* Track not SETUP */
1004                             continue;
1005
1006                         uint16_t seq;
1007                         if( tr->rtp_fd == -1 )
1008                         {
1009                             /* Track not PLAYing yet */
1010                             if (tr->sout_id == NULL)
1011                                 /* Instance not running yet (VoD) */
1012                                 seq = tr->seq_init;
1013                             else
1014                             {
1015                                 /* Instance running, add a sink to it */
1016                                 tr->rtp_fd = dup_socket(tr->setup_fd);
1017                                 if (tr->rtp_fd == -1)
1018                                     continue;
1019
1020                                 rtp_add_sink( tr->sout_id, tr->rtp_fd,
1021                                               false, &seq );
1022                             }
1023                         }
1024                         else
1025                         {
1026                             /* Track already playing */
1027                             assert( tr->sout_id != NULL );
1028                             seq = rtp_get_seq( tr->sout_id );
1029                         }
1030                         char *url = RtspAppendTrackPath( tr->id, control );
1031                         infolen += sprintf( info + infolen,
1032                                     "url=%s;seq=%u;rtptime=%u, ",
1033                                     url != NULL ? url : "", seq,
1034                                     rtp_compute_ts( tr->id->clock_rate, ts ) );
1035                         free( url );
1036                     }
1037                 }
1038                 if( infolen > 0 )
1039                 {
1040                     info[infolen - 2] = '\0'; /* remove trailing ", " */
1041                     httpd_MsgAdd( answer, "RTP-Info", "%s", info );
1042                 }
1043                 if (vod)
1044                 {
1045                     vod_play(rtsp->vod_media, psz_session, &start, end);
1046                     npt = start;
1047                 }
1048             }
1049             vlc_mutex_unlock( &rtsp->lock );
1050
1051             if (ses != NULL)
1052             {
1053                 double f_npt = (double) npt / CLOCK_FREQ;
1054                 httpd_MsgAdd( answer, "Range", "npt=%f-", f_npt );
1055             }
1056
1057             if( httpd_MsgGet( query, "Scale" ) != NULL )
1058                 httpd_MsgAdd( answer, "Scale", "1." );
1059             break;
1060         }
1061
1062         case HTTPD_MSG_PAUSE:
1063         {
1064             if (id == NULL && !vod)
1065             {
1066                 answer->i_status = 405;
1067                 httpd_MsgAdd( answer, "Allow",
1068                               "%s, TEARDOWN, PLAY, GET_PARAMETER",
1069                               ( id != NULL ) ? "SETUP" : "DESCRIBE" );
1070                 break;
1071             }
1072
1073             rtsp_session_t *ses;
1074             answer->i_status = 200;
1075             psz_session = httpd_MsgGet( query, "Session" );
1076             vlc_mutex_lock( &rtsp->lock );
1077             ses = RtspClientGet( rtsp, psz_session );
1078             if (ses != NULL)
1079             {
1080                 if (id == NULL)
1081                 {
1082                     assert(vod);
1083                     int64_t npt;
1084                     vod_pause(rtsp->vod_media, psz_session, &npt);
1085                     double f_npt = (double) npt / CLOCK_FREQ;
1086                     httpd_MsgAdd( answer, "Range", "npt=%f-", f_npt );
1087                 }
1088                 else /* "Mute" the selected track */
1089                 {
1090                     bool found = false;
1091                     for (int i = 0; i < ses->trackc; i++)
1092                     {
1093                         rtsp_strack_t *tr = ses->trackv + i;;
1094                         if (tr->id == id)
1095                         {
1096                             if (tr->setup_fd == -1)
1097                                 break;
1098
1099                             found = true;
1100                             if (tr->rtp_fd != -1)
1101                             {
1102                                 rtp_del_sink(tr->sout_id, tr->rtp_fd);
1103                                 tr->rtp_fd = -1;
1104                             }
1105                             break;
1106                         }
1107                     }
1108                     if (!found)
1109                         answer->i_status = 455;
1110                 }
1111                 RtspClientAlive(ses);
1112             }
1113             vlc_mutex_unlock( &rtsp->lock );
1114             break;
1115         }
1116
1117         case HTTPD_MSG_GETPARAMETER:
1118             if( query->i_body > 0 )
1119             {
1120                 answer->i_status = 451;
1121                 break;
1122             }
1123
1124             psz_session = httpd_MsgGet( query, "Session" );
1125             answer->i_status = 200;
1126             vlc_mutex_lock( &rtsp->lock );
1127             rtsp_session_t *ses = RtspClientGet( rtsp, psz_session );
1128             if (ses != NULL)
1129                 RtspClientAlive(ses);
1130             vlc_mutex_unlock( &rtsp->lock );
1131             break;
1132
1133         case HTTPD_MSG_TEARDOWN:
1134         {
1135             rtsp_session_t *ses;
1136
1137             answer->i_status = 200;
1138
1139             psz_session = httpd_MsgGet( query, "Session" );
1140
1141             vlc_mutex_lock( &rtsp->lock );
1142             ses = RtspClientGet( rtsp, psz_session );
1143             if( ses != NULL )
1144             {
1145                 if( id == NULL ) /* Delete the entire session */
1146                 {
1147                     RtspClientDel( rtsp, ses );
1148                     if (vod)
1149                         vod_stop(rtsp->vod_media, psz_session);
1150                     RtspUpdateTimer(rtsp);
1151                 }
1152                 else /* Delete one track from the session */
1153                 {
1154                     for( int i = 0; i < ses->trackc; i++ )
1155                     {
1156                         if( ses->trackv[i].id == id )
1157                         {
1158                             RtspTrackClose( &ses->trackv[i] );
1159                             /* Keep VoD tracks whose instance is still
1160                              * running */
1161                             if (!(vod && ses->trackv[i].sout_id != NULL))
1162                                 REMOVE_ELEM( ses->trackv, ses->trackc, i );
1163                         }
1164                     }
1165                     RtspClientAlive(ses);
1166                 }
1167             }
1168             vlc_mutex_unlock( &rtsp->lock );
1169             break;
1170         }
1171
1172         default:
1173             return VLC_EGENERIC;
1174     }
1175
1176     if( psz_session )
1177     {
1178         if (rtsp->timeout > 0)
1179             httpd_MsgAdd( answer, "Session", "%s;timeout=%d", psz_session,
1180                                                               rtsp->timeout );
1181         else
1182             httpd_MsgAdd( answer, "Session", "%s", psz_session );
1183     }
1184
1185     httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
1186     httpd_MsgAdd( answer, "Cache-Control", "no-cache" );
1187
1188     psz = httpd_MsgGet( query, "Cseq" );
1189     if( psz != NULL )
1190         httpd_MsgAdd( answer, "Cseq", "%s", psz );
1191     psz = httpd_MsgGet( query, "Timestamp" );
1192     if( psz != NULL )
1193         httpd_MsgAdd( answer, "Timestamp", "%s", psz );
1194
1195     return VLC_SUCCESS;
1196 }
1197
1198
1199 /** Aggregate RTSP callback */
1200 static int RtspCallback( httpd_callback_sys_t *p_args,
1201                          httpd_client_t *cl,
1202                          httpd_message_t *answer,
1203                          const httpd_message_t *query )
1204 {
1205     return RtspHandler( (rtsp_stream_t *)p_args, NULL, cl, answer, query );
1206 }
1207
1208
1209 /** Non-aggregate RTSP callback */
1210 static int RtspCallbackId( httpd_callback_sys_t *p_args,
1211                            httpd_client_t *cl,
1212                            httpd_message_t *answer,
1213                            const httpd_message_t *query )
1214 {
1215     rtsp_stream_id_t *id = (rtsp_stream_id_t *)p_args;
1216     return RtspHandler( id->stream, id, cl, answer, query );
1217 }