qt: add device preferences for mmdevice
[vlc.git] / modules / stream_out / rtsp.c
blob35ee7e7776bcc6679b41565151b2242cd2b16d71
1 /*****************************************************************************
2 * rtsp.c: RTSP support for RTP stream output module
3 *****************************************************************************
4 * Copyright (C) 2003-2004, 2010 VLC authors and VideoLAN
5 * Copyright © 2007 Rémi Denis-Courmont
7 * $Id$
9 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
10 * Pierre Ynard
12 * This program is free software; you can redistribute it and/or modify it
13 * under the terms of the GNU Lesser General Public License as published by
14 * the Free Software Foundation; either version 2.1 of the License, or
15 * (at your option) any later version.
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 Lesser General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public License
23 * along with this program; if not, write to the Free Software Foundation,
24 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25 *****************************************************************************/
27 /*****************************************************************************
28 * Preamble
29 *****************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
34 #include <vlc_common.h>
35 #include <vlc_sout.h>
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 #include <time.h>
48 #ifndef _WIN32
49 # include <locale.h>
50 #endif
51 #ifdef HAVE_XLOCALE_H
52 # include <xlocale.h>
53 #endif
55 #include "rtp.h"
57 typedef struct rtsp_session_t rtsp_session_t;
59 struct rtsp_stream_t
61 vlc_mutex_t lock;
62 vlc_object_t *owner;
63 vod_media_t *vod_media;
64 httpd_host_t *host;
65 httpd_url_t *url;
66 char *psz_path;
67 unsigned track_id;
69 int sessionc;
70 rtsp_session_t **sessionv;
72 int timeout;
73 vlc_timer_t timer;
77 static int RtspCallback( httpd_callback_sys_t *p_args,
78 httpd_client_t *cl, httpd_message_t *answer,
79 const httpd_message_t *query );
80 static int RtspCallbackId( httpd_callback_sys_t *p_args,
81 httpd_client_t *cl, httpd_message_t *answer,
82 const httpd_message_t *query );
83 static void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session );
85 static void RtspTimeOut( void *data );
87 rtsp_stream_t *RtspSetup( vlc_object_t *owner, vod_media_t *media,
88 const char *path )
90 rtsp_stream_t *rtsp = calloc( 1, sizeof( *rtsp ) );
92 if( unlikely(rtsp == NULL) )
93 return NULL;
95 rtsp->owner = owner;
96 rtsp->vod_media = media;
97 vlc_mutex_init( &rtsp->lock );
99 rtsp->timeout = var_InheritInteger(owner, "rtsp-timeout");
100 if (rtsp->timeout > 0)
102 if (vlc_timer_create(&rtsp->timer, RtspTimeOut, rtsp))
103 goto error;
106 rtsp->psz_path = strdup( (path != NULL) ? path : "/" );
107 if( rtsp->psz_path == NULL )
108 goto error;
110 msg_Dbg( owner, "RTSP stream at %s", rtsp->psz_path );
112 rtsp->host = vlc_rtsp_HostNew( VLC_OBJECT(owner) );
113 if( rtsp->host == NULL )
114 goto error;
116 char *user = var_InheritString(owner, "sout-rtsp-user");
117 char *pwd = var_InheritString(owner, "sout-rtsp-pwd");
119 rtsp->url = httpd_UrlNew( rtsp->host, rtsp->psz_path, user, pwd );
120 free(user);
121 free(pwd);
122 if( rtsp->url == NULL )
123 goto error;
125 httpd_UrlCatch( rtsp->url, HTTPD_MSG_DESCRIBE, RtspCallback, (void*)rtsp );
126 httpd_UrlCatch( rtsp->url, HTTPD_MSG_SETUP, RtspCallback, (void*)rtsp );
127 httpd_UrlCatch( rtsp->url, HTTPD_MSG_PLAY, RtspCallback, (void*)rtsp );
128 httpd_UrlCatch( rtsp->url, HTTPD_MSG_PAUSE, RtspCallback, (void*)rtsp );
129 httpd_UrlCatch( rtsp->url, HTTPD_MSG_GETPARAMETER, RtspCallback,
130 (void*)rtsp );
131 httpd_UrlCatch( rtsp->url, HTTPD_MSG_TEARDOWN, RtspCallback, (void*)rtsp );
132 return rtsp;
134 error:
135 RtspUnsetup( rtsp );
136 return NULL;
140 void RtspUnsetup( rtsp_stream_t *rtsp )
142 if( rtsp->url )
143 httpd_UrlDelete( rtsp->url );
145 if( rtsp->host )
146 httpd_HostDelete( rtsp->host );
148 while( rtsp->sessionc > 0 )
149 RtspClientDel( rtsp, rtsp->sessionv[0] );
151 if (rtsp->timeout > 0)
152 vlc_timer_destroy(rtsp->timer);
154 free( rtsp->psz_path );
155 vlc_mutex_destroy( &rtsp->lock );
157 free( rtsp );
161 struct rtsp_stream_id_t
163 rtsp_stream_t *stream;
164 sout_stream_id_sys_t *sout_id;
165 httpd_url_t *url;
166 unsigned track_id;
167 uint32_t ssrc;
168 unsigned clock_rate; /* needed to compute rtptime in RTP-Info */
169 int mcast_fd;
173 typedef struct rtsp_strack_t rtsp_strack_t;
175 /* For unicast streaming */
176 struct rtsp_session_t
178 rtsp_stream_t *stream;
179 uint64_t id;
180 mtime_t last_seen; /* for timeouts */
182 /* output (id-access) */
183 int trackc;
184 rtsp_strack_t *trackv;
188 /* Unicast session track */
189 struct rtsp_strack_t
191 rtsp_stream_id_t *id;
192 sout_stream_id_sys_t *sout_id;
193 int setup_fd; /* socket created by the SETUP request */
194 int rtp_fd; /* socket used by the RTP output, when playing */
195 uint32_t ssrc;
196 uint16_t seq_init;
199 static void RtspTrackClose( rtsp_strack_t *tr );
201 #define TRACK_PATH_SIZE (sizeof("/trackID=999") - 1)
203 char *RtspAppendTrackPath( rtsp_stream_id_t *id, const char *base )
205 const char *sep = strlen( base ) > 0 && base[strlen( base ) - 1] == '/' ?
206 "" : "/";
207 char *url;
209 if( asprintf( &url, "%s%strackID=%u", base, sep, id->track_id ) == -1 )
210 url = NULL;
211 return url;
215 rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_sys_t *sid,
216 uint32_t ssrc, unsigned clock_rate,
217 int mcast_fd)
219 if (rtsp->track_id > 999)
221 msg_Err(rtsp->owner, "RTSP: too many IDs!");
222 return NULL;
225 char *urlbuf;
226 rtsp_stream_id_t *id = malloc( sizeof( *id ) );
227 httpd_url_t *url;
229 if( id == NULL )
230 return NULL;
232 id->stream = rtsp;
233 id->sout_id = sid;
234 id->track_id = rtsp->track_id;
235 id->ssrc = ssrc;
236 id->clock_rate = clock_rate;
237 id->mcast_fd = mcast_fd;
239 urlbuf = RtspAppendTrackPath( id, rtsp->psz_path );
240 if( urlbuf == NULL )
242 free( id );
243 return NULL;
246 msg_Dbg( rtsp->owner, "RTSP: adding %s", urlbuf );
248 char *user = var_InheritString(rtsp->owner, "sout-rtsp-user");
249 char *pwd = var_InheritString(rtsp->owner, "sout-rtsp-pwd");
251 url = id->url = httpd_UrlNew( rtsp->host, urlbuf, user, pwd );
252 free( user );
253 free( pwd );
254 free( urlbuf );
256 if( url == NULL )
258 free( id );
259 return NULL;
262 httpd_UrlCatch( url, HTTPD_MSG_DESCRIBE, RtspCallbackId, (void *)id );
263 httpd_UrlCatch( url, HTTPD_MSG_SETUP, RtspCallbackId, (void *)id );
264 httpd_UrlCatch( url, HTTPD_MSG_PLAY, RtspCallbackId, (void *)id );
265 httpd_UrlCatch( url, HTTPD_MSG_PAUSE, RtspCallbackId, (void *)id );
266 httpd_UrlCatch( url, HTTPD_MSG_GETPARAMETER, RtspCallbackId, (void *)id );
267 httpd_UrlCatch( url, HTTPD_MSG_TEARDOWN, RtspCallbackId, (void *)id );
269 rtsp->track_id++;
271 return id;
275 void RtspDelId( rtsp_stream_t *rtsp, rtsp_stream_id_t *id )
277 httpd_UrlDelete( id->url );
279 vlc_mutex_lock( &rtsp->lock );
280 for( int i = 0; i < rtsp->sessionc; i++ )
282 rtsp_session_t *ses = rtsp->sessionv[i];
284 for( int j = 0; j < ses->trackc; j++ )
286 if( ses->trackv[j].id == id )
288 rtsp_strack_t *tr = ses->trackv + j;
289 RtspTrackClose( tr );
290 TAB_ERASE(ses->trackc, ses->trackv, j);
295 vlc_mutex_unlock( &rtsp->lock );
296 free( id );
300 /** rtsp must be locked */
301 static void RtspUpdateTimer( rtsp_stream_t *rtsp )
303 if (rtsp->timeout <= 0)
304 return;
306 mtime_t timeout = 0;
307 for (int i = 0; i < rtsp->sessionc; i++)
309 if (timeout == 0 || rtsp->sessionv[i]->last_seen < timeout)
310 timeout = rtsp->sessionv[i]->last_seen;
312 if (timeout != 0)
313 timeout += rtsp->timeout * CLOCK_FREQ;
314 vlc_timer_schedule(rtsp->timer, true, timeout, 0);
318 static void RtspTimeOut( void *data )
320 rtsp_stream_t *rtsp = data;
322 vlc_mutex_lock(&rtsp->lock);
323 mtime_t now = mdate();
324 for (int i = rtsp->sessionc - 1; i >= 0; i--)
326 if (rtsp->sessionv[i]->last_seen + rtsp->timeout * CLOCK_FREQ < now)
328 if (rtsp->vod_media != NULL)
330 char psz_sesbuf[17];
331 snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%"PRIx64,
332 rtsp->sessionv[i]->id );
333 vod_stop(rtsp->vod_media, psz_sesbuf);
335 RtspClientDel(rtsp, rtsp->sessionv[i]);
338 RtspUpdateTimer(rtsp);
339 vlc_mutex_unlock(&rtsp->lock);
343 /** rtsp must be locked */
344 static
345 rtsp_session_t *RtspClientNew( rtsp_stream_t *rtsp )
347 rtsp_session_t *s = malloc( sizeof( *s ) );
348 if( s == NULL )
349 return NULL;
351 s->stream = rtsp;
352 vlc_rand_bytes (&s->id, sizeof (s->id));
353 s->trackc = 0;
354 s->trackv = NULL;
356 TAB_APPEND( rtsp->sessionc, rtsp->sessionv, s );
358 return s;
362 /** rtsp must be locked */
363 static
364 rtsp_session_t *RtspClientGet( rtsp_stream_t *rtsp, const char *name )
366 char *end;
367 uint64_t id;
368 int i;
370 if( name == NULL )
371 return NULL;
373 errno = 0;
374 id = strtoull( name, &end, 0x10 );
375 if( errno || *end )
376 return NULL;
378 /* FIXME: use a hash/dictionary */
379 for( i = 0; i < rtsp->sessionc; i++ )
381 if( rtsp->sessionv[i]->id == id )
382 return rtsp->sessionv[i];
384 return NULL;
388 /** rtsp must be locked */
389 static
390 void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session )
392 int i;
393 TAB_REMOVE( rtsp->sessionc, rtsp->sessionv, session );
395 for( i = 0; i < session->trackc; i++ )
396 RtspTrackClose( &session->trackv[i] );
398 free( session->trackv );
399 free( session );
403 /** rtsp must be locked */
404 static void RtspClientAlive( rtsp_session_t *session )
406 if (session->stream->timeout <= 0)
407 return;
409 session->last_seen = mdate();
410 RtspUpdateTimer(session->stream);
413 static int dup_socket(int oldfd)
415 int newfd;
416 #ifndef _WIN32
417 newfd = vlc_dup(oldfd);
418 #else
419 WSAPROTOCOL_INFO info;
420 WSADuplicateSocket (oldfd, GetCurrentProcessId (), &info);
421 newfd = WSASocket (info.iAddressFamily, info.iSocketType,
422 info.iProtocol, &info, 0, 0);
423 #endif
424 return newfd;
427 /* Attach a starting VoD RTP id to its RTSP track, and let it
428 * initialize with the parameters of the SETUP request */
429 int RtspTrackAttach( rtsp_stream_t *rtsp, const char *name,
430 rtsp_stream_id_t *id, sout_stream_id_sys_t *sout_id,
431 uint32_t *ssrc, uint16_t *seq_init )
433 int val = VLC_EGENERIC;
434 rtsp_session_t *session;
436 vlc_mutex_lock(&rtsp->lock);
437 session = RtspClientGet(rtsp, name);
439 if (session == NULL)
440 goto out;
442 rtsp_strack_t *tr = NULL;
443 for (int i = 0; i < session->trackc; i++)
445 if (session->trackv[i].id == id)
447 tr = session->trackv + i;
448 break;
452 if (tr != NULL)
454 tr->sout_id = sout_id;
455 tr->rtp_fd = dup_socket(tr->setup_fd);
457 else
459 /* The track was not SETUP. We still create one because we'll
460 * need the sout_id if we set it up later. */
461 rtsp_strack_t track = { .id = id, .sout_id = sout_id,
462 .setup_fd = -1, .rtp_fd = -1 };
463 vlc_rand_bytes (&track.seq_init, sizeof (track.seq_init));
464 vlc_rand_bytes (&track.ssrc, sizeof (track.ssrc));
466 TAB_APPEND(session->trackc, session->trackv, track);
467 tr = session->trackv + session->trackc - 1;
470 *ssrc = ntohl(tr->ssrc);
471 *seq_init = tr->seq_init;
473 if (tr->rtp_fd != -1)
475 uint16_t seq;
476 rtp_add_sink(tr->sout_id, tr->rtp_fd, false, &seq);
477 /* To avoid race conditions, sout_id->i_seq_sent_next must
478 * be set here and now. Make sure the caller did its job
479 * properly when passing seq_init. */
480 assert(tr->seq_init == seq);
483 val = VLC_SUCCESS;
484 out:
485 vlc_mutex_unlock(&rtsp->lock);
486 return val;
490 /* Remove references to the RTP id when it is stopped */
491 void RtspTrackDetach( rtsp_stream_t *rtsp, const char *name,
492 sout_stream_id_sys_t *sout_id )
494 rtsp_session_t *session;
496 vlc_mutex_lock(&rtsp->lock);
497 session = RtspClientGet(rtsp, name);
499 if (session == NULL)
500 goto out;
502 for (int i = 0; i < session->trackc; i++)
504 rtsp_strack_t *tr = session->trackv + i;
505 if (tr->sout_id == sout_id)
507 if (tr->setup_fd == -1)
509 /* No (more) SETUP information: better get rid of the
510 * track so that we can have new random ssrc and
511 * seq_init next time. */
512 TAB_ERASE(session->trackc, session->trackv, i);
513 break;
515 /* We keep the SETUP information of the track, but stop it */
516 if (tr->rtp_fd != -1)
518 rtp_del_sink(tr->sout_id, tr->rtp_fd);
519 tr->rtp_fd = -1;
521 tr->sout_id = NULL;
522 break;
526 out:
527 vlc_mutex_unlock(&rtsp->lock);
531 /** rtsp must be locked */
532 static void RtspTrackClose( rtsp_strack_t *tr )
534 if (tr->setup_fd != -1)
536 if (tr->rtp_fd != -1)
538 rtp_del_sink(tr->sout_id, tr->rtp_fd);
539 tr->rtp_fd = -1;
541 net_Close(tr->setup_fd);
542 tr->setup_fd = -1;
547 /** Finds the next transport choice */
548 static inline const char *transport_next( const char *str )
550 /* Looks for comma */
551 str = strchr( str, ',' );
552 if( str == NULL )
553 return NULL; /* No more transport options */
555 str++; /* skips comma */
556 while( strchr( "\r\n\t ", *str ) )
557 str++;
559 return (*str) ? str : NULL;
563 /** Finds the next transport parameter */
564 static inline const char *parameter_next( const char *str )
566 while( strchr( ",;", *str ) == NULL )
567 str++;
569 return (*str == ';') ? (str + 1) : NULL;
573 static int64_t ParseNPT (const char *str)
575 locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
576 locale_t oldloc = uselocale (loc);
577 unsigned hour, min;
578 float sec;
580 if (sscanf (str, "%u:%u:%f", &hour, &min, &sec) == 3)
581 sec += ((hour * 60) + min) * 60;
582 else
583 if (sscanf (str, "%f", &sec) != 1)
584 sec = -1;
586 if (loc != (locale_t)0)
588 uselocale (oldloc);
589 freelocale (loc);
591 return sec < 0 ? -1 : sec * CLOCK_FREQ;
595 /** RTSP requests handler
596 * @param id selected track for non-aggregate URLs,
597 * NULL for aggregate URLs
599 static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
600 httpd_client_t *cl,
601 httpd_message_t *answer,
602 const httpd_message_t *query )
604 vlc_object_t *owner = rtsp->owner;
605 char psz_sesbuf[17];
606 const char *psz_session = NULL, *psz;
607 char control[sizeof("rtsp://[]:12345") + NI_MAXNUMERICHOST
608 + strlen( rtsp->psz_path )];
609 bool vod = rtsp->vod_media != NULL;
610 time_t now;
612 time (&now);
614 if( answer == NULL || query == NULL || cl == NULL )
615 return VLC_SUCCESS;
616 else
618 /* Build self-referential control URL */
619 char ip[NI_MAXNUMERICHOST], *ptr;
620 int port;
622 httpd_ServerIP( cl, ip, &port );
623 ptr = strchr( ip, '%' );
624 if( ptr != NULL )
625 *ptr = '\0';
627 if( strchr( ip, ':' ) != NULL )
628 sprintf( control, "rtsp://[%s]:%d%s", ip, port, rtsp->psz_path );
629 else
630 sprintf( control, "rtsp://%s:%d%s", ip, port, rtsp->psz_path );
633 /* */
634 answer->i_proto = HTTPD_PROTO_RTSP;
635 answer->i_version= 0;
636 answer->i_type = HTTPD_MSG_ANSWER;
637 answer->i_body = 0;
638 answer->p_body = NULL;
640 httpd_MsgAdd( answer, "Server", "VLC/%s", VERSION );
642 /* Date: is always allowed, and sometimes mandatory with RTSP/2.0. */
643 struct tm ut;
644 if (gmtime_r (&now, &ut) != NULL)
645 { /* RFC1123 format, GMT is mandatory */
646 static const char wdays[7][4] = {
647 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
648 static const char mons[12][4] = {
649 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
650 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
651 httpd_MsgAdd (answer, "Date", "%s, %02u %s %04u %02u:%02u:%02u GMT",
652 wdays[ut.tm_wday], ut.tm_mday, mons[ut.tm_mon],
653 1900 + ut.tm_year, ut.tm_hour, ut.tm_min, ut.tm_sec);
656 if( query->i_proto != HTTPD_PROTO_RTSP )
658 answer->i_status = 505;
660 else
661 if( httpd_MsgGet( query, "Require" ) != NULL )
663 answer->i_status = 551;
664 httpd_MsgAdd( answer, "Unsupported", "%s",
665 httpd_MsgGet( query, "Require" ) );
667 else
668 switch( query->i_type )
670 case HTTPD_MSG_DESCRIBE:
671 { /* Aggregate-only */
672 if( id != NULL )
674 answer->i_status = 460;
675 break;
678 answer->i_status = 200;
679 httpd_MsgAdd( answer, "Content-Type", "%s", "application/sdp" );
680 httpd_MsgAdd( answer, "Content-Base", "%s", control );
682 answer->p_body = (uint8_t *) ( vod ?
683 SDPGenerateVoD( rtsp->vod_media, control ) :
684 SDPGenerate( (sout_stream_t *)owner, control ) );
685 if( answer->p_body != NULL )
686 answer->i_body = strlen( (char *)answer->p_body );
687 else
688 answer->i_status = 500;
689 break;
692 case HTTPD_MSG_SETUP:
693 /* Non-aggregate-only */
694 if( id == NULL )
696 answer->i_status = 459;
697 break;
700 psz_session = httpd_MsgGet( query, "Session" );
701 answer->i_status = 461;
703 for( const char *tpt = httpd_MsgGet( query, "Transport" );
704 tpt != NULL;
705 tpt = transport_next( tpt ) )
707 bool b_multicast = true, b_unsupp = false;
708 unsigned loport = 5004, hiport; /* from RFC3551 */
710 /* Check transport protocol. */
711 /* Currently, we only support RTP/AVP over UDP */
712 if( strncmp( tpt, "RTP/AVP", 7 ) )
713 continue;
714 tpt += 7;
715 if( strncmp( tpt, "/UDP", 4 ) == 0 )
716 tpt += 4;
717 if( strchr( ";,", *tpt ) == NULL )
718 continue;
720 /* Parse transport options */
721 for( const char *opt = parameter_next( tpt );
722 opt != NULL;
723 opt = parameter_next( opt ) )
725 if( strncmp( opt, "multicast", 9 ) == 0)
726 b_multicast = true;
727 else
728 if( strncmp( opt, "unicast", 7 ) == 0 )
729 b_multicast = false;
730 else
731 if( sscanf( opt, "client_port=%u-%u", &loport, &hiport )
732 == 2 )
734 else
735 if( strncmp( opt, "mode=", 5 ) == 0 )
737 if( strncasecmp( opt + 5, "play", 4 )
738 && strncasecmp( opt + 5, "\"PLAY\"", 6 ) )
740 /* Not playing?! */
741 b_unsupp = true;
742 break;
745 else
746 if( strncmp( opt,"destination=", 12 ) == 0 )
748 answer->i_status = 403;
749 b_unsupp = true;
751 else
754 * Every other option is unsupported:
756 * "source" and "append" are invalid (server-only);
757 * "ssrc" also (as clarified per RFC2326bis).
759 * For multicast, "port", "layers", "ttl" are set by the
760 * stream output configuration.
762 * For unicast, we want to decide "server_port" values.
764 * "interleaved" is not implemented.
766 b_unsupp = true;
767 break;
771 if( b_unsupp )
772 continue;
774 if( b_multicast )
776 char dst[NI_MAXNUMERICHOST];
777 int dport, ttl;
778 if( id->mcast_fd == -1 )
779 continue;
781 net_GetPeerAddress(id->mcast_fd, dst, &dport);
783 ttl = var_InheritInteger(owner, "ttl");
784 if (ttl <= 0)
785 /* FIXME: the TTL is left to the OS default, we can
786 * only guess that it's 1. */
787 ttl = 1;
789 if( psz_session == NULL )
791 /* Create a dummy session ID */
792 snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%lu",
793 vlc_mrand48() );
794 psz_session = psz_sesbuf;
796 answer->i_status = 200;
798 httpd_MsgAdd( answer, "Transport",
799 "RTP/AVP/UDP;destination=%s;port=%u-%u;"
800 "ttl=%d;mode=play",
801 dst, dport, dport + 1, ttl );
802 /* FIXME: this doesn't work with RTP + RTCP mux */
804 else
806 char ip[NI_MAXNUMERICHOST], src[NI_MAXNUMERICHOST];
807 rtsp_session_t *ses = NULL;
808 int fd, sport;
809 uint32_t ssrc;
811 if( httpd_ClientIP( cl, ip, NULL ) == NULL )
813 answer->i_status = 500;
814 continue;
817 fd = net_ConnectDgram( owner, ip, loport, -1,
818 IPPROTO_UDP );
819 if( fd == -1 )
821 msg_Err( owner,
822 "cannot create RTP socket for %s port %u",
823 ip, loport );
824 answer->i_status = 500;
825 continue;
828 /* Ignore any unexpected incoming packet */
829 setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 },
830 sizeof (int));
831 net_GetSockAddress( fd, src, &sport );
833 vlc_mutex_lock( &rtsp->lock );
834 if( psz_session == NULL )
836 ses = RtspClientNew( rtsp );
837 snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%"PRIx64,
838 ses->id );
839 psz_session = psz_sesbuf;
841 else
843 ses = RtspClientGet( rtsp, psz_session );
844 if( ses == NULL )
846 answer->i_status = 454;
847 vlc_mutex_unlock( &rtsp->lock );
848 net_Close( fd );
849 continue;
852 RtspClientAlive(ses);
854 rtsp_strack_t *tr = NULL;
855 for (int i = 0; i < ses->trackc; i++)
857 if (ses->trackv[i].id == id)
859 tr = ses->trackv + i;
860 break;
864 if (tr == NULL)
866 /* Set up a new track */
867 rtsp_strack_t track = { .id = id,
868 .sout_id = id->sout_id,
869 .setup_fd = fd,
870 .rtp_fd = -1 };
872 if (vod)
874 vlc_rand_bytes (&track.seq_init,
875 sizeof (track.seq_init));
876 vlc_rand_bytes (&track.ssrc, sizeof (track.ssrc));
877 ssrc = track.ssrc;
879 else
880 ssrc = id->ssrc;
882 TAB_APPEND(ses->trackc, ses->trackv, track);
884 else if (tr->setup_fd == -1)
886 /* The track was not SETUP, but it exists
887 * because there is a sout_id running for it */
888 tr->setup_fd = fd;
889 ssrc = tr->ssrc;
891 else
893 /* The track is already set up, and we don't
894 * support changing the transport parameters on
895 * the fly */
896 vlc_mutex_unlock( &rtsp->lock );
897 answer->i_status = 455;
898 net_Close( fd );
899 break;
901 vlc_mutex_unlock( &rtsp->lock );
903 httpd_ServerIP( cl, ip, NULL );
905 /* Specify source IP only if it is different from the
906 * RTSP control connection server address */
907 if( strcmp( src, ip ) )
909 char *ptr = strchr( src, '%' );
910 if( ptr != NULL ) *ptr = '\0'; /* remove scope ID */
912 else
913 src[0] = '\0';
915 httpd_MsgAdd( answer, "Transport",
916 "RTP/AVP/UDP;unicast%s%s;"
917 "client_port=%u-%u;server_port=%u-%u;"
918 "ssrc=%08X;mode=play",
919 src[0] ? ";source=" : "", src,
920 loport, loport + 1, sport, sport + 1, ssrc );
922 answer->i_status = 200;
924 break;
926 break;
928 case HTTPD_MSG_PLAY:
930 rtsp_session_t *ses;
931 answer->i_status = 200;
933 psz_session = httpd_MsgGet( query, "Session" );
934 int64_t start = -1, end = -1, npt;
935 const char *range = httpd_MsgGet (query, "Range");
936 if (range != NULL)
938 if (strncmp (range, "npt=", 4))
940 answer->i_status = 501;
941 break;
944 start = ParseNPT (range + 4);
945 range = strchr(range, '-');
946 if (range != NULL && *(range + 1))
947 end = ParseNPT (range + 1);
949 if (end >= 0 && end < start)
951 answer->i_status = 457;
952 break;
955 if (vod)
957 if (vod_check_range(rtsp->vod_media, psz_session,
958 start, end) != VLC_SUCCESS)
960 answer->i_status = 457;
961 break;
964 /* We accept start times of 0 even for broadcast streams
965 * that already started */
966 else if (start > 0 || end >= 0)
968 answer->i_status = 456;
969 break;
972 vlc_mutex_lock( &rtsp->lock );
973 ses = RtspClientGet( rtsp, psz_session );
974 if( ses != NULL )
976 char info[ses->trackc * ( strlen( control ) + TRACK_PATH_SIZE
977 + sizeof("url=;seq=65535;rtptime=4294967295, ")
978 - 1 ) + 1];
979 size_t infolen = 0;
980 RtspClientAlive(ses);
982 sout_stream_id_sys_t *sout_id = NULL;
983 if (vod)
985 /* We don't keep a reference to the sout_stream_t,
986 * so we check if a sout_id is available instead. */
987 for (int i = 0; i < ses->trackc; i++)
989 sout_id = ses->trackv[i].sout_id;
990 if (sout_id != NULL)
991 break;
994 int64_t ts = rtp_get_ts(vod ? NULL : (sout_stream_t *)owner,
995 sout_id, rtsp->vod_media, psz_session,
996 vod ? NULL : &npt);
998 for( int i = 0; i < ses->trackc; i++ )
1000 rtsp_strack_t *tr = ses->trackv + i;
1001 if( ( id == NULL ) || ( tr->id == id ) )
1003 if (tr->setup_fd == -1)
1004 /* Track not SETUP */
1005 continue;
1007 uint16_t seq;
1008 if( tr->rtp_fd == -1 )
1010 /* Track not PLAYing yet */
1011 if (tr->sout_id == NULL)
1012 /* Instance not running yet (VoD) */
1013 seq = tr->seq_init;
1014 else
1016 /* Instance running, add a sink to it */
1017 tr->rtp_fd = dup_socket(tr->setup_fd);
1018 if (tr->rtp_fd == -1)
1019 continue;
1021 rtp_add_sink( tr->sout_id, tr->rtp_fd,
1022 false, &seq );
1025 else
1027 /* Track already playing */
1028 assert( tr->sout_id != NULL );
1029 seq = rtp_get_seq( tr->sout_id );
1031 char *url = RtspAppendTrackPath( tr->id, control );
1032 infolen += sprintf( info + infolen,
1033 "url=%s;seq=%u;rtptime=%u, ",
1034 url != NULL ? url : "", seq,
1035 rtp_compute_ts( tr->id->clock_rate, ts ) );
1036 free( url );
1039 if( infolen > 0 )
1041 info[infolen - 2] = '\0'; /* remove trailing ", " */
1042 httpd_MsgAdd( answer, "RTP-Info", "%s", info );
1045 vlc_mutex_unlock( &rtsp->lock );
1047 if (ses != NULL)
1049 if (vod)
1051 vod_play(rtsp->vod_media, psz_session, &start, end);
1052 npt = start;
1055 double f_npt = (double) npt / CLOCK_FREQ;
1056 httpd_MsgAdd( answer, "Range", "npt=%f-", f_npt );
1059 if( httpd_MsgGet( query, "Scale" ) != NULL )
1060 httpd_MsgAdd( answer, "Scale", "1." );
1061 break;
1064 case HTTPD_MSG_PAUSE:
1066 if (id == NULL && !vod)
1068 answer->i_status = 405;
1069 httpd_MsgAdd( answer, "Allow",
1070 "DESCRIBE, TEARDOWN, PLAY, GET_PARAMETER" );
1071 break;
1074 rtsp_session_t *ses;
1075 answer->i_status = 200;
1076 psz_session = httpd_MsgGet( query, "Session" );
1077 vlc_mutex_lock( &rtsp->lock );
1078 ses = RtspClientGet( rtsp, psz_session );
1079 if (ses != NULL)
1081 if (id != NULL) /* "Mute" the selected track */
1083 bool found = false;
1084 for (int i = 0; i < ses->trackc; i++)
1086 rtsp_strack_t *tr = ses->trackv + i;;
1087 if (tr->id == id)
1089 if (tr->setup_fd == -1)
1090 break;
1092 found = true;
1093 if (tr->rtp_fd != -1)
1095 rtp_del_sink(tr->sout_id, tr->rtp_fd);
1096 tr->rtp_fd = -1;
1098 break;
1101 if (!found)
1102 answer->i_status = 455;
1104 RtspClientAlive(ses);
1106 vlc_mutex_unlock( &rtsp->lock );
1108 if (ses != NULL && id == NULL)
1110 assert(vod);
1111 int64_t npt = 0;
1112 vod_pause(rtsp->vod_media, psz_session, &npt);
1113 double f_npt = (double) npt / CLOCK_FREQ;
1114 httpd_MsgAdd( answer, "Range", "npt=%f-", f_npt );
1116 break;
1119 case HTTPD_MSG_GETPARAMETER:
1120 if( query->i_body > 0 )
1122 answer->i_status = 451;
1123 break;
1126 psz_session = httpd_MsgGet( query, "Session" );
1127 answer->i_status = 200;
1128 vlc_mutex_lock( &rtsp->lock );
1129 rtsp_session_t *ses = RtspClientGet( rtsp, psz_session );
1130 if (ses != NULL)
1131 RtspClientAlive(ses);
1132 vlc_mutex_unlock( &rtsp->lock );
1133 break;
1135 case HTTPD_MSG_TEARDOWN:
1137 rtsp_session_t *ses;
1139 answer->i_status = 200;
1141 psz_session = httpd_MsgGet( query, "Session" );
1143 vlc_mutex_lock( &rtsp->lock );
1144 ses = RtspClientGet( rtsp, psz_session );
1145 if( ses != NULL )
1147 if( id == NULL ) /* Delete the entire session */
1149 RtspClientDel( rtsp, ses );
1150 if (vod)
1151 vod_stop(rtsp->vod_media, psz_session);
1152 RtspUpdateTimer(rtsp);
1154 else /* Delete one track from the session */
1156 for( int i = 0; i < ses->trackc; i++ )
1158 if( ses->trackv[i].id == id )
1160 RtspTrackClose( &ses->trackv[i] );
1161 /* Keep VoD tracks whose instance is still
1162 * running */
1163 if (!(vod && ses->trackv[i].sout_id != NULL))
1164 TAB_ERASE(ses->trackc, ses->trackv, i);
1167 RtspClientAlive(ses);
1170 vlc_mutex_unlock( &rtsp->lock );
1171 break;
1174 default:
1175 return VLC_EGENERIC;
1178 if( psz_session )
1180 if (rtsp->timeout > 0)
1181 httpd_MsgAdd( answer, "Session", "%s;timeout=%d", psz_session,
1182 rtsp->timeout );
1183 else
1184 httpd_MsgAdd( answer, "Session", "%s", psz_session );
1187 httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
1188 httpd_MsgAdd( answer, "Cache-Control", "no-cache" );
1190 psz = httpd_MsgGet( query, "Cseq" );
1191 if( psz != NULL )
1192 httpd_MsgAdd( answer, "Cseq", "%s", psz );
1193 psz = httpd_MsgGet( query, "Timestamp" );
1194 if( psz != NULL )
1195 httpd_MsgAdd( answer, "Timestamp", "%s", psz );
1197 return VLC_SUCCESS;
1201 /** Aggregate RTSP callback */
1202 static int RtspCallback( httpd_callback_sys_t *p_args,
1203 httpd_client_t *cl,
1204 httpd_message_t *answer,
1205 const httpd_message_t *query )
1207 return RtspHandler( (rtsp_stream_t *)p_args, NULL, cl, answer, query );
1211 /** Non-aggregate RTSP callback */
1212 static int RtspCallbackId( httpd_callback_sys_t *p_args,
1213 httpd_client_t *cl,
1214 httpd_message_t *answer,
1215 const httpd_message_t *query )
1217 rtsp_stream_id_t *id = (rtsp_stream_id_t *)p_args;
1218 return RtspHandler( id->stream, id, cl, answer, query );