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