rtsp.c 23 KB
Newer Older
1 2 3
/*****************************************************************************
 * rtsp.c: RTSP support for RTP stream output module
 *****************************************************************************
4 5 6
 * Copyright (C) 2003-2004 the VideoLAN team
 * Copyright © 2007 Rémi Denis-Courmont
 *
Pierre's avatar
Pierre committed
7
 * $Id$
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
29 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33 34 35 36 37 38
#include <vlc/vlc.h>
#include <vlc_sout.h>

#include <vlc_httpd.h>
#include <vlc_url.h>
#include <vlc_network.h>
39
#include <vlc_rand.h>
40
#include <assert.h>
41 42
#include <errno.h>
#include <stdlib.h>
43 44 45

#include "rtp.h"

46
typedef struct rtsp_session_t rtsp_session_t;
47

48 49 50 51 52 53 54
struct rtsp_stream_t
{
    vlc_mutex_t     lock;
    sout_stream_t  *owner;
    httpd_host_t   *host;
    httpd_url_t    *url;
    char           *psz_path;
55
    const char     *track_fmt;
56
    unsigned        port;
57 58 59

    int             sessionc;
    rtsp_session_t **sessionv;
60 61
};

62

63
static int  RtspCallback( httpd_callback_sys_t *p_args,
64 65
                          httpd_client_t *cl, httpd_message_t *answer,
                          const httpd_message_t *query );
66
static int  RtspCallbackId( httpd_callback_sys_t *p_args,
67 68
                            httpd_client_t *cl, httpd_message_t *answer,
                            const httpd_message_t *query );
69
static void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session );
70

71
rtsp_stream_t *RtspSetup( sout_stream_t *p_stream, const vlc_url_t *url )
72
{
73
    rtsp_stream_t *rtsp = malloc( sizeof( *rtsp ) );
74

75
    if( rtsp == NULL || ( url->i_port > 99999 ) )
76 77
    {
        free( rtsp );
78
        return NULL;
79
    }
80

81 82 83
    rtsp->owner = p_stream;
    rtsp->sessionc = 0;
    rtsp->sessionv = NULL;
84 85 86
    rtsp->host = NULL;
    rtsp->url = NULL;
    rtsp->psz_path = NULL;
87
    vlc_mutex_init( &rtsp->lock );
88

89
    rtsp->port = (url->i_port > 0) ? url->i_port : 554;
90 91 92 93 94 95 96
    rtsp->psz_path = strdup( ( url->psz_path != NULL ) ? url->psz_path : "/" );
    if( rtsp->psz_path == NULL )
        goto error;

    assert( strlen( rtsp->psz_path ) > 0 );
    if( rtsp->psz_path[strlen( rtsp->psz_path ) - 1] == '/' )
        rtsp->track_fmt = "%strackID=%u";
97
    else
98 99 100 101
        rtsp->track_fmt = "%s/trackID=%u";

    msg_Dbg( p_stream, "RTSP stream: host %s port %d at %s",
             url->psz_host, rtsp->port, rtsp->psz_path );
102 103

    rtsp->host = httpd_HostNew( VLC_OBJECT(p_stream), url->psz_host,
104
                                rtsp->port );
105 106 107
    if( rtsp->host == NULL )
        goto error;

108 109
    rtsp->url = httpd_UrlNewUnique( rtsp->host, rtsp->psz_path,
                                    NULL, NULL, NULL );
110 111 112 113 114 115 116
    if( rtsp->url == NULL )
        goto error;

    httpd_UrlCatch( rtsp->url, HTTPD_MSG_DESCRIBE, RtspCallback, (void*)rtsp );
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_SETUP,    RtspCallback, (void*)rtsp );
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_PLAY,     RtspCallback, (void*)rtsp );
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_PAUSE,    RtspCallback, (void*)rtsp );
117 118
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_GETPARAMETER, RtspCallback,
                    (void*)rtsp );
119 120 121 122 123 124
    httpd_UrlCatch( rtsp->url, HTTPD_MSG_TEARDOWN, RtspCallback, (void*)rtsp );
    return rtsp;

error:
    RtspUnsetup( rtsp );
    return NULL;
125 126 127
}


128
void RtspUnsetup( rtsp_stream_t *rtsp )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
129
{
130 131 132 133 134
    while( rtsp->sessionc > 0 )
        RtspClientDel( rtsp, rtsp->sessionv[0] );

    if( rtsp->url )
        httpd_UrlDelete( rtsp->url );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
135

136 137
    if( rtsp->host )
        httpd_HostDelete( rtsp->host );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
138

139
    free( rtsp->psz_path );
140
    vlc_mutex_destroy( &rtsp->lock );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
141 142 143
}


144 145
struct rtsp_stream_id_t
{
146
    rtsp_stream_t    *stream;
147 148
    sout_stream_id_t *sout_id;
    httpd_url_t      *url;
149 150
    const char       *dst;
    int               ttl;
151 152
    uint32_t          ssrc;
    uint16_t          loport, hiport;
153 154
};

155

156 157
typedef struct rtsp_strack_t rtsp_strack_t;

158 159 160 161
/* For unicast streaming */
struct rtsp_session_t
{
    rtsp_stream_t *stream;
162
    uint64_t       id;
163 164

    /* output (id-access) */
165 166
    int            trackc;
    rtsp_strack_t *trackv;
167 168 169
};


170 171 172 173
/* Unicast session track */
struct rtsp_strack_t
{
    sout_stream_id_t  *id;
174
    int                fd;
175
    bool         playing;
176 177 178
};


179
rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_t *sid,
180
                             unsigned num, uint32_t ssrc,
181 182
                             /* Multicast stuff - TODO: cleanup */
                             const char *dst, int ttl,
183
                             unsigned loport, unsigned hiport )
184
{
185
    char urlbuf[sizeof( "/trackID=123" ) + strlen( rtsp->psz_path )];
186 187 188 189 190 191
    rtsp_stream_id_t *id = malloc( sizeof( *id ) );
    httpd_url_t *url;

    if( id == NULL )
        return NULL;

192
    id->stream = rtsp;
193
    id->sout_id = sid;
194
    id->ssrc = ssrc;
195 196 197 198 199 200 201 202
    /* TODO: can we assume that this need not be strdup'd? */
    id->dst = dst;
    if( id->dst != NULL )
    {
        id->ttl = ttl;
        id->loport = loport;
        id->hiport = hiport;
    }
203

204
    /* FIXME: num screws up if any ES has been removed and re-added */
205
    snprintf( urlbuf, sizeof( urlbuf ), rtsp->track_fmt, rtsp->psz_path,
206
              num );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
207
    msg_Dbg( rtsp->owner, "RTSP: adding %s", urlbuf );
208
    url = id->url = httpd_UrlNewUnique( rtsp->host, urlbuf, NULL, NULL, NULL );
209

210
    if( url == NULL )
211
    {
212 213
        free( id );
        return NULL;
214 215
    }

216 217 218 219
    httpd_UrlCatch( url, HTTPD_MSG_DESCRIBE, RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_SETUP,    RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_PLAY,     RtspCallbackId, (void *)id );
    httpd_UrlCatch( url, HTTPD_MSG_PAUSE,    RtspCallbackId, (void *)id );
220
    httpd_UrlCatch( url, HTTPD_MSG_GETPARAMETER, RtspCallbackId, (void *)id );
221 222 223
    httpd_UrlCatch( url, HTTPD_MSG_TEARDOWN, RtspCallbackId, (void *)id );

    return id;
224 225 226
}


227
void RtspDelId( rtsp_stream_t *rtsp, rtsp_stream_id_t *id )
228
{
229 230 231 232 233
    vlc_mutex_lock( &rtsp->lock );
    for( int i = 0; i < rtsp->sessionc; i++ )
    {
        rtsp_session_t *ses = rtsp->sessionv[i];

234
        for( int j = 0; j < ses->trackc; j++ )
235
        {
236
            if( ses->trackv[j].id == id->sout_id )
237
            {
238
                rtsp_strack_t *tr = ses->trackv + j;
239
                net_Close( tr->fd );
240
                REMOVE_ELEM( ses->trackv, ses->trackc, j );
241 242 243 244 245 246 247
            }
        }
    }

    vlc_mutex_unlock( &rtsp->lock );
    httpd_UrlDelete( id->url );
    free( id );
248 249 250
}


251 252
/** rtsp must be locked */
static
253
rtsp_session_t *RtspClientNew( rtsp_stream_t *rtsp )
254
{
255 256 257
    rtsp_session_t *s = malloc( sizeof( *s ) );
    if( s == NULL )
        return NULL;
258

259
    s->stream = rtsp;
260
    vlc_rand_bytes (&s->id, sizeof (s->id));
261 262
    s->trackc = 0;
    s->trackv = NULL;
263

264
    TAB_APPEND( rtsp->sessionc, rtsp->sessionv, s );
265

266
    return s;
267 268 269
}


270 271 272
/** rtsp must be locked */
static
rtsp_session_t *RtspClientGet( rtsp_stream_t *rtsp, const char *name )
273
{
274 275
    char *end;
    uint64_t id;
276 277
    int i;

278 279
    if( name == NULL )
        return NULL;
280

281 282 283 284 285
    errno = 0;
    id = strtoull( name, &end, 0x10 );
    if( errno || *end )
        return NULL;

286 287
    /* FIXME: use a hash/dictionary */
    for( i = 0; i < rtsp->sessionc; i++ )
288
    {
289
        if( rtsp->sessionv[i]->id == id )
290
            return rtsp->sessionv[i];
291 292 293 294 295
    }
    return NULL;
}


296 297 298
/** rtsp must be locked */
static
void RtspClientDel( rtsp_stream_t *rtsp, rtsp_session_t *session )
299 300
{
    int i;
301
    TAB_REMOVE( rtsp->sessionc, rtsp->sessionv, session );
302

303
    for( i = 0; i < session->trackc; i++ )
304
        rtp_del_sink( session->trackv[i].id, session->trackv[i].fd );
305

306
    free( session->trackv );
307
    free( session );
308 309 310
}


311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
/** Finds the next transport choice */
static inline const char *transport_next( const char *str )
{
    /* Looks for comma */
    str = strchr( str, ',' );
    if( str == NULL )
        return NULL; /* No more transport options */

    str++; /* skips comma */
    while( strchr( "\r\n\t ", *str ) )
        str++;

    return (*str) ? str : NULL;
}


/** Finds the next transport parameter */
static inline const char *parameter_next( const char *str )
{
    while( strchr( ",;", *str ) == NULL )
        str++;

    return (*str == ';') ? (str + 1) : NULL;
}


/** RTSP requests handler
 * @param id selected track for non-aggregate URLs,
 *           NULL for aggregate URLs
 */
static int RtspHandler( rtsp_stream_t *rtsp, rtsp_stream_id_t *id,
                        httpd_client_t *cl,
                        httpd_message_t *answer,
                        const httpd_message_t *query )
345
{
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
346
    sout_stream_t *p_stream = rtsp->owner;
347
    char psz_sesbuf[17];
348
    const char *psz_session = NULL, *psz;
349 350
    char control[sizeof("rtsp://[]:12345") + NI_MAXNUMERICHOST
                  + strlen( rtsp->psz_path )];
351

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
352
    if( answer == NULL || query == NULL || cl == NULL )
353
        return VLC_SUCCESS;
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
    else
    {
        /* Build self-referential control URL */
        char ip[NI_MAXNUMERICHOST], *ptr;

        httpd_ServerIP( cl, ip );
        ptr = strchr( ip, '%' );
        if( ptr != NULL )
            *ptr = '\0';

        if( strchr( ip, ':' ) != NULL )
            sprintf( control, "rtsp://[%s]:%u%s", ip, rtsp->port,
                     rtsp->psz_path );
        else
            sprintf( control, "rtsp://%s:%u%s", ip, rtsp->port,
                     rtsp->psz_path );
    }
371

372
    /* */
373
    answer->i_proto = HTTPD_PROTO_RTSP;
374
    answer->i_version= 0;
375 376 377 378
    answer->i_type   = HTTPD_MSG_ANSWER;
    answer->i_body = 0;
    answer->p_body = NULL;

379 380 381 382 383
    if( query->i_proto != HTTPD_PROTO_RTSP )
    {
        answer->i_status = 505;
    }
    else
384 385 386
    if( httpd_MsgGet( query, "Require" ) != NULL )
    {
        answer->i_status = 551;
387
        httpd_MsgAdd( answer, "Unsupported", "%s",
388 389 390 391 392 393
                      httpd_MsgGet( query, "Require" ) );
    }
    else
    switch( query->i_type )
    {
        case HTTPD_MSG_DESCRIBE:
394 395 396 397 398 399 400
        {   /* Aggregate-only */
            if( id != NULL )
            {
                answer->i_status = 460;
                break;
            }

401 402
            answer->i_status = 200;
            httpd_MsgAdd( answer, "Content-Type",  "%s", "application/sdp" );
403
            httpd_MsgAdd( answer, "Content-Base",  "%s", control );
404 405 406 407 408
            answer->p_body = (uint8_t *)SDPGenerate( rtsp->owner, control );
            if( answer->p_body != NULL )
                answer->i_body = strlen( (char *)answer->p_body );
            else
                answer->i_status = 500;
409 410 411 412
            break;
        }

        case HTTPD_MSG_SETUP:
413 414
            /* Non-aggregate-only */
            if( id == NULL )
415
            {
416
                answer->i_status = 459;
417 418
                break;
            }
419

420 421 422 423 424 425 426
            psz_session = httpd_MsgGet( query, "Session" );
            answer->i_status = 461;

            for( const char *tpt = httpd_MsgGet( query, "Transport" );
                 tpt != NULL;
                 tpt = transport_next( tpt ) )
            {
427
                bool b_multicast = true, b_unsupp = false;
428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
                unsigned loport = 5004, hiport = 5005; /* from RFC3551 */

                /* Check transport protocol. */
                /* Currently, we only support RTP/AVP over UDP */
                if( strncmp( tpt, "RTP/AVP", 7 ) )
                    continue;
                tpt += 7;
                if( strncmp( tpt, "/UDP", 4 ) == 0 )
                    tpt += 4;
                if( strchr( ";,", *tpt ) == NULL )
                    continue;

                /* Parse transport options */
                for( const char *opt = parameter_next( tpt );
                     opt != NULL;
                     opt = parameter_next( opt ) )
                {
                    if( strncmp( opt, "multicast", 9 ) == 0)
446
                        b_multicast = true;
447 448
                    else
                    if( strncmp( opt, "unicast", 7 ) == 0 )
449
                        b_multicast = false;
450
                    else
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
451 452
                    if( sscanf( opt, "client_port=%u-%u", &loport, &hiport )
                                == 2 )
453 454 455 456 457 458 459 460
                        ;
                    else
                    if( strncmp( opt, "mode=", 5 ) == 0 )
                    {
                        if( strncasecmp( opt + 5, "play", 4 )
                         && strncasecmp( opt + 5, "\"PLAY\"", 6 ) )
                        {
                            /* Not playing?! */
461
                            b_unsupp = true;
462 463 464 465
                            break;
                        }
                    }
                    else
466 467 468
                    if( strncmp( opt,"destination=", 12 ) == 0 )
                    {
                        answer->i_status = 403;
469
                        b_unsupp = true;
470 471
                    }
                    else
472 473 474 475
                    {
                    /*
                     * Every other option is unsupported:
                     *
476 477
                     * "source" and "append" are invalid (server-only);
                     * "ssrc" also (as clarified per RFC2326bis).
478 479 480 481
                     *
                     * For multicast, "port", "layers", "ttl" are set by the
                     * stream output configuration.
                     *
482
                     * For unicast, we want to decide "server_port" values.
483
                     *
484
                     * "interleaved" is not implemented.
485
                     */
486
                        b_unsupp = true;
487 488 489 490 491 492 493 494 495
                        break;
                    }
                }

                if( b_unsupp )
                    continue;

                if( b_multicast )
                {
496 497
                    const char *dst = id->dst;
                    if( dst == NULL )
498 499
                        continue;

500 501 502 503 504 505 506
                    if( psz_session == NULL )
                    {
                        /* Create a dummy session ID */
                        snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%d",
                                  rand() );
                        psz_session = psz_sesbuf;
                    }
507 508 509
                    answer->i_status = 200;

                    httpd_MsgAdd( answer, "Transport",
510
                                  "RTP/AVP/UDP;destination=%s;port=%u-%u;"
511
                                  "ttl=%d;mode=play",
512 513
                                  dst, id->loport, id->hiport,
                                  ( id->ttl > 0 ) ? id->ttl : 1 );
514 515 516
                }
                else
                {
517
                    char ip[NI_MAXNUMERICHOST], src[NI_MAXNUMERICHOST];
518
                    rtsp_session_t *ses = NULL;
519
                    rtsp_strack_t track = { id->sout_id, -1, false };
520
                    int sport;
521 522 523 524 525 526 527

                    if( httpd_ClientIP( cl, ip ) == NULL )
                    {
                        answer->i_status = 500;
                        continue;
                    }

528 529 530
                    track.fd = net_ConnectDgram( p_stream, ip, loport, -1,
                                                 IPPROTO_UDP );
                    if( track.fd == -1 )
531 532
                    {
                        msg_Err( p_stream,
533 534
                                 "cannot create RTP socket for %s port %u",
                                 ip, loport );
535
                        answer->i_status = 500;
536 537 538
                        continue;
                    }

539
                    net_GetSockAddress( track.fd, src, &sport );
540

541
                    vlc_mutex_lock( &rtsp->lock );
542 543
                    if( psz_session == NULL )
                    {
544
                        ses = RtspClientNew( rtsp );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
545
                        snprintf( psz_sesbuf, sizeof( psz_sesbuf ), "%"PRIx64,
546 547
                                  ses->id );
                        psz_session = psz_sesbuf;
548 549 550 551 552
                    }
                    else
                    {
                        /* FIXME: we probably need to remove an access out,
                         * if there is already one for the same ID */
553
                        ses = RtspClientGet( rtsp, psz_session );
554 555 556
                        if( ses == NULL )
                        {
                            answer->i_status = 454;
557
                            vlc_mutex_unlock( &rtsp->lock );
558 559
                            continue;
                        }
560 561
                    }

562 563
                    INSERT_ELEM( ses->trackv, ses->trackc, ses->trackc,
                                 track );
564
                    vlc_mutex_unlock( &rtsp->lock );
565 566 567

                    httpd_ServerIP( cl, ip );

568
                    if( strcmp( src, ip ) )
569 570 571 572 573 574 575 576 577
                    {
                        /* Specify source IP if it is different from the RTSP
                         * control connection server address */
                        char *ptr = strchr( src, '%' );
                        if( ptr != NULL ) *ptr = '\0'; /* remove scope ID */

                        httpd_MsgAdd( answer, "Transport",
                                      "RTP/AVP/UDP;unicast;source=%s;"
                                      "client_port=%u-%u;server_port=%u-%u;"
578
                                      "ssrc=%08X;mode=play",
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
579
                                      src, loport, loport + 1, sport,
580
                                      sport + 1, id->ssrc );
581 582 583 584 585 586
                    }
                    else
                    {
                        httpd_MsgAdd( answer, "Transport",
                                      "RTP/AVP/UDP;unicast;"
                                      "client_port=%u-%u;server_port=%u-%u;"
587 588 589
                                      "ssrc=%08X;mode=play",
                                      loport, loport + 1, sport, sport + 1,
                                      id->ssrc );
590 591 592 593 594 595 596 597
                    }

                    answer->i_status = 200;
                }
                break;
            }
            break;

598 599 600 601 602 603
        case HTTPD_MSG_PLAY:
        {
            rtsp_session_t *ses;
            answer->i_status = 200;

            psz_session = httpd_MsgGet( query, "Session" );
Rafaël Carré's avatar
Rafaël Carré committed
604 605
#if 0
            /* FIXME: This breaks totem, mplayer and quicktime at least */
606 607
            if( httpd_MsgGet( query, "Range" ) != NULL )
            {
608
                answer->i_status = 456; /* cannot seek, stream not seekable */
609 610
                break;
            }
Rafaël Carré's avatar
Rafaël Carré committed
611
#endif
612 613 614 615
            vlc_mutex_lock( &rtsp->lock );
            ses = RtspClientGet( rtsp, psz_session );
            if( ses != NULL )
            {
616 617 618 619 620
                /* FIXME: we really need to limit the number of tracks... */
                char info[ses->trackc * ( strlen( control )
                                  + sizeof("/trackID=123;seq=65535, ") ) + 1];
                size_t infolen = 0;

621 622 623
                for( int i = 0; i < ses->trackc; i++ )
                {
                    rtsp_strack_t *tr = ses->trackv + i;
624
                    if( ( id == NULL ) || ( tr->id == id->sout_id ) )
625
                    {
626 627
                        if( !tr->playing )
                        {
628 629
                            tr->playing = true;
                            rtp_add_sink( tr->id, tr->fd, false );
630 631 632 633 634
                        }
                        infolen += sprintf( info + infolen,
                                            "%s/trackID=%u;seq=%u, ", control,
                                            rtp_get_num( tr->id ),
                                            rtp_get_seq( tr->id ) );
635 636
                    }
                }
637 638 639 640 641
                if( infolen > 0 )
                {
                    info[infolen - 2] = '\0'; /* remove trailing ", " */
                    httpd_MsgAdd( answer, "RTP-Info", "%s", info );
                }
642 643
            }
            vlc_mutex_unlock( &rtsp->lock );
644 645 646

            if( httpd_MsgGet( query, "Scale" ) != NULL )
                httpd_MsgAdd( answer, "Scale", "1." );
647 648 649
            break;
        }

650 651
        case HTTPD_MSG_PAUSE:
            answer->i_status = 405;
652
            httpd_MsgAdd( answer, "Allow",
653 654
                          "%s, TEARDOWN, PLAY, GET_PARAMETER",
                          ( id != NULL ) ? "SETUP" : "DESCRIBE" );
655 656 657 658 659 660 661 662 663
            break;

        case HTTPD_MSG_GETPARAMETER:
            if( query->i_body > 0 )
            {
                answer->i_status = 451;
                break;
            }

664
            psz_session = httpd_MsgGet( query, "Session" );
665
            answer->i_status = 200;
666 667 668 669 670 671 672 673 674 675 676 677 678 679
            break;

        case HTTPD_MSG_TEARDOWN:
        {
            rtsp_session_t *ses;

            answer->i_status = 200;

            psz_session = httpd_MsgGet( query, "Session" );

            vlc_mutex_lock( &rtsp->lock );
            ses = RtspClientGet( rtsp, psz_session );
            if( ses != NULL )
            {
680 681 682
                if( id == NULL ) /* Delete the entire session */
                    RtspClientDel( rtsp, ses );
                else /* Delete one track from the session */
683
                for( int i = 0; i < ses->trackc; i++ )
684
                {
685
                    if( ses->trackv[i].id == id->sout_id )
686
                    {
687
                        rtp_del_sink( id->sout_id, ses->trackv[i].fd );
688
                        REMOVE_ELEM( ses->trackv, ses->trackc, i );
689 690 691 692 693 694 695
                    }
                }
            }
            vlc_mutex_unlock( &rtsp->lock );
            break;
        }

696
        default:
697
            return VLC_EGENERIC;
698 699
    }

700 701 702 703 704 705 706
    httpd_MsgAdd( answer, "Server", "%s", PACKAGE_STRING );
    if( psz_session )
        httpd_MsgAdd( answer, "Session", "%s"/*;timeout=5*/, psz_session );

    httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
    httpd_MsgAdd( answer, "Cache-Control", "no-cache" );

707 708 709 710 711 712
    psz = httpd_MsgGet( query, "Cseq" );
    if( psz != NULL )
        httpd_MsgAdd( answer, "Cseq", "%s", psz );
    psz = httpd_MsgGet( query, "Timestamp" );
    if( psz != NULL )
        httpd_MsgAdd( answer, "Timestamp", "%s", psz );
713 714 715

    return VLC_SUCCESS;
}
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736


/** Aggregate RTSP callback */
static int RtspCallback( httpd_callback_sys_t *p_args,
                         httpd_client_t *cl,
                         httpd_message_t *answer,
                         const httpd_message_t *query )
{
    return RtspHandler( (rtsp_stream_t *)p_args, NULL, cl, answer, query );
}


/** Non-aggregate RTSP callback */
static int RtspCallbackId( httpd_callback_sys_t *p_args,
                           httpd_client_t *cl,
                           httpd_message_t *answer,
                           const httpd_message_t *query )
{
    rtsp_stream_id_t *id = (rtsp_stream_id_t *)p_args;
    return RtspHandler( id->stream, id, cl, answer, query );
}