rtsp.c 53.6 KB
Newer Older
1 2 3
/*****************************************************************************
 * rtsp.c: rtsp VoD server module
 *****************************************************************************
4
 * Copyright (C) 2003-2006 the VideoLAN team
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * $Id$
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
 *          Gildas Bazin <gbazin@videolan.org>
 *
 * 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
Antoine Cellerier's avatar
Antoine Cellerier committed
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 24 25 26 27
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
28
#define _GNU_SOURCE
29 30 31 32 33
#include <stdlib.h>

#include <errno.h>

#include <vlc/vlc.h>
Clément Stenac's avatar
Clément Stenac committed
34 35
#include <vlc_input.h>
#include <vlc_sout.h>
36
#include <vlc_block.h>
37 38 39

#include "vlc_httpd.h"
#include "vlc_vod.h"
40
#include "vlc_url.h"
Clément Stenac's avatar
Clément Stenac committed
41 42
#include <vlc_network.h>
#include <vlc_charset.h>
43 44 45 46 47 48 49

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );

50
#define HOST_TEXT N_( "RTSP host address" )
51
#define HOST_LONGTEXT N_( \
52 53
    "This defines the address, port and path the RTSP VOD server will listen " \
    "on.\nSyntax is address:port/path. The default is to listen on all "\
Christophe Mutricy's avatar
Christophe Mutricy committed
54
    "interfaces (address 0.0.0.0), on port 554, with no path.\nTo listen " \
55
    "only on the local interface, use \"localhost\" as address." )
56

57
#define THROTLE_TEXT N_( "Maximum number of connections" )
58 59
#define THROTLE_LONGTEXT N_( "This limits the maximum number of clients " \
    "that can connect to the RTSP VOD. 0 means no limit."  )
60

61 62
#define RAWMUX_TEXT N_( "MUX for RAW RTSP transport" )

63 64 65 66 67 68 69
#define SESSION_TIMEOUT_TEXT N_( "Sets the timeout option in the RTSP " \
    "session string" )
#define SESSION_TIMEOUT_LONGTEXT N_( "Defines what timeout option to add " \
    "to the RTSP session ID string. Setting it to a negative number removes " \
    "the timeout option entirely. This is needed by some IPTV STBs (such as " \
    "those made by HansunTech) which get confused by it. The default is 5." )

70
vlc_module_begin();
Clément Stenac's avatar
 
Clément Stenac committed
71
    set_shortname( _("RTSP VoD" ) );
72
    set_description( _("RTSP VoD server") );
73 74
    set_category( CAT_SOUT );
    set_subcategory( SUBCAT_SOUT_VOD );
75 76 77 78
    set_capability( "vod server", 1 );
    set_callbacks( Open, Close );
    add_shortcut( "rtsp" );
    add_string ( "rtsp-host", NULL, NULL, HOST_TEXT, HOST_LONGTEXT, VLC_TRUE );
79
    add_string( "rtsp-raw-mux", "ts", NULL, RAWMUX_TEXT, RAWMUX_TEXT, VLC_TRUE );
80 81
    add_integer( "rtsp-throttle-users", 0, NULL, THROTLE_TEXT,
                                           THROTLE_LONGTEXT, VLC_TRUE );
82 83
    add_integer( "rtsp-session-timeout", 5, NULL, SESSION_TIMEOUT_TEXT,
                 SESSION_TIMEOUT_LONGTEXT, VLC_TRUE );
84 85 86 87 88 89
vlc_module_end();

/*****************************************************************************
 * Exported prototypes
 *****************************************************************************/

90 91 92 93 94 95 96 97 98 99
typedef struct media_es_t media_es_t;

typedef struct
{
    media_es_t *p_media_es;
    char *psz_ip;
    int i_port;

} rtsp_client_es_t;

100 101 102 103 104 105
typedef struct
{
    char *psz_session;
    int64_t i_last; /* for timeout */

    vlc_bool_t b_playing; /* is it in "play" state */
106 107 108 109
    vlc_bool_t b_paused; /* is it in "pause" state */

    int i_es;
    rtsp_client_es_t **es;
110 111 112

} rtsp_client_t;

113
struct media_es_t
114 115 116 117 118
{
    /* VoD server */
    vod_t *p_vod;

    /* RTSP server */
119
    httpd_url_t *p_rtsp_url;
120 121 122

    vod_media_t *p_media;

123 124 125 126 127 128
    es_format_t fmt;
    int         i_port;
    uint8_t     i_payload_type;
    char        *psz_rtpmap;
    char        *psz_fmtp;

129
};
130 131 132

struct vod_media_t
{
133 134
    int id;

135 136 137 138 139
    /* VoD server */
    vod_t *p_vod;

    /* RTSP server */
    httpd_url_t  *p_rtsp_url;
140 141
    char         *psz_rtsp_control_v4;
    char         *psz_rtsp_control_v6;
142 143 144 145 146 147
    char         *psz_rtsp_path;

    int  i_port;
    int  i_port_audio;
    int  i_port_video;
    int  i_ttl;
148 149 150 151 152
    int  i_payload_type;

    int64_t i_sdp_id;
    int     i_sdp_version;

153 154
    vlc_bool_t b_multicast;

155
    vlc_mutex_t lock;
156 157 158 159

    /* ES list */
    int        i_es;
    media_es_t **es;
160
    char       *psz_mux;
161
    vlc_bool_t  b_raw;
162 163 164 165

    /* RTSP client */
    int           i_rtsp;
    rtsp_client_t **rtsp;
166 167 168 169 170 171

    /* Infos */
    char *psz_session_name;
    char *psz_session_description;
    char *psz_session_url;
    char *psz_session_email;
172
    mtime_t i_length;
173 174 175 176 177 178 179 180
};

struct vod_sys_t
{
    /* RTSP server */
    httpd_host_t *p_rtsp_host;
    char *psz_path;
    int i_port;
181
    int i_throttle_users;
182
    int i_connections;
183

184 185
    char *psz_raw_mux;

186 187
    int i_session_timeout;

188
    /* List of media */
189 190
    vlc_mutex_t lock_media;
    int i_media_id;
191 192
    int i_media;
    vod_media_t **media;
193 194 195

    /* */
    block_fifo_t *p_fifo_cmd;
196 197
};

198 199 200 201 202 203 204 205 206 207 208 209 210
/* rtsp delayed command (to avoid deadlock between vlm/httpd) */
typedef enum
{
    RTSP_CMD_TYPE_NONE,  /* Exit requested */

    RTSP_CMD_TYPE_PLAY,
    RTSP_CMD_TYPE_PAUSE,
    RTSP_CMD_TYPE_STOP,
    RTSP_CMD_TYPE_SEEK,
    RTSP_CMD_TYPE_REWIND,
    RTSP_CMD_TYPE_FORWARD,
} rtsp_cmd_type_t;

211
static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * );
212 213 214 215
static void         MediaDel( vod_t *, vod_media_t * );
static int          MediaAddES( vod_t *, vod_media_t *, es_format_t * );
static void         MediaDelES( vod_t *, vod_media_t *, es_format_t * );

216 217 218 219
static void CommandThread( vlc_object_t *p_this );
static void CommandPush( vod_t *, rtsp_cmd_type_t, vod_media_t *, const char *psz_session,
                         double f_arg, const char *psz_arg );

220
static rtsp_client_t *RtspClientNew( vod_media_t *, char * );
221
static rtsp_client_t *RtspClientGet( vod_media_t *, const char * );
222 223 224 225
static void           RtspClientDel( vod_media_t *, rtsp_client_t * );

static int RtspCallback( httpd_callback_sys_t *, httpd_client_t *,
                         httpd_message_t *, httpd_message_t * );
226
static int RtspCallbackES( httpd_callback_sys_t *, httpd_client_t *,
227 228
                           httpd_message_t *, httpd_message_t * );

229
static char *SDPGenerate( const vod_media_t *, httpd_client_t *cl );
230

231 232 233 234 235 236 237 238 239 240 241 242 243
static void sprintf_hexa( char *s, uint8_t *p_data, int i_data )
{
    static const char hex[16] = "0123456789abcdef";
    int i;

    for( i = 0; i < i_data; i++ )
    {
        s[2*i+0] = hex[(p_data[i]>>4)&0xf];
        s[2*i+1] = hex[(p_data[i]   )&0xf];
    }
    s[2*i_data] = '\0';
}

244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
/*****************************************************************************
 * Open: Starts the RTSP server module
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    vod_t *p_vod = (vod_t *)p_this;
    vod_sys_t *p_sys = 0;
    char *psz_url = 0;
    vlc_url_t url;

    psz_url = config_GetPsz( p_vod, "rtsp-host" );
    vlc_UrlParse( &url, psz_url, 0 );
    if( psz_url ) free( psz_url );

    if( url.i_port <= 0 ) url.i_port = 554;

    p_vod->p_sys = p_sys = malloc( sizeof( vod_sys_t ) );
    if( !p_sys ) goto error;
    p_sys->p_rtsp_host = 0;

264
    p_sys->i_session_timeout = var_CreateGetInteger( p_this, "rtsp-session-timeout" );
265

266
    p_sys->i_throttle_users = var_CreateGetInteger( p_this, "rtsp-throttle-users" );
267
    msg_Dbg( p_this, "allowing up to %d connections", p_sys->i_throttle_users );
268 269
    p_sys->i_connections = 0;

270
    p_sys->psz_raw_mux = var_CreateGetString( p_this, "rtsp-raw-mux" );
271

272 273 274 275
    p_sys->p_rtsp_host =
        httpd_HostNew( VLC_OBJECT(p_vod), url.psz_host, url.i_port );
    if( !p_sys->p_rtsp_host )
    {
276
        msg_Err( p_vod, "cannot create RTSP server (%s:%i)",
277 278 279 280 281 282 283 284
                 url.psz_host, url.i_port );
        goto error;
    }

    p_sys->psz_path = strdup( url.psz_path ? url.psz_path : "/" );
    p_sys->i_port = url.i_port;

    vlc_UrlClean( &url );
285 286 287 288 289

    vlc_mutex_init( p_vod, &p_sys->lock_media );

    TAB_INIT( p_sys->i_media, p_sys->media );
    p_sys->i_media_id = 0;
290 291 292 293 294 295

    p_vod->pf_media_new = MediaNew;
    p_vod->pf_media_del = MediaDel;
    p_vod->pf_media_add_es = MediaAddES;
    p_vod->pf_media_del_es = MediaDelES;

296 297 298 299 300 301 302 303 304 305
    p_sys->p_fifo_cmd = block_FifoNew( p_vod );
    if( vlc_thread_create( p_vod, "rtsp vod thread", CommandThread,
                           VLC_THREAD_PRIORITY_LOW, VLC_FALSE ) )
    {
        msg_Err( p_vod, "cannot spawn rtsp vod thread" );
        block_FifoRelease( p_sys->p_fifo_cmd );
        free( p_sys->psz_path );
        goto error;
    }

306 307
    return VLC_SUCCESS;

308
error:
309
    if( p_sys && p_sys->p_rtsp_host ) httpd_HostDelete( p_sys->p_rtsp_host );
310
    if( p_sys && p_sys->psz_raw_mux ) free( p_sys->psz_raw_mux );
311 312
    if( p_sys ) free( p_sys );
    vlc_UrlClean( &url );
313

314 315 316 317 318 319 320 321 322 323 324
    return VLC_EGENERIC;
}

/*****************************************************************************
 * Close:
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
    vod_t *p_vod = (vod_t *)p_this;
    vod_sys_t *p_sys = p_vod->p_sys;

325 326 327 328 329 330 331
    /* Stop command thread */
    p_vod->b_die = VLC_TRUE;
    CommandPush( p_vod, RTSP_CMD_TYPE_NONE, NULL, NULL, 0.0, NULL );
    vlc_thread_join( p_vod );

    block_FifoRelease( p_sys->p_fifo_cmd );

332
    httpd_HostDelete( p_sys->p_rtsp_host );
333
    var_Destroy( p_this, "rtsp-session-timeout" );
334
    var_Destroy( p_this, "rtsp-throttle-users" );
335
    var_Destroy( p_this, "rtsp-raw-mux" );
336

337 338 339 340 341 342 343
    /* Check VLM is not buggy */
    if( p_sys->i_media > 0 )
        msg_Err( p_vod, "rtsp vod leaking %d medias", p_sys->i_media );
    TAB_CLEAN( p_sys->i_media, p_sys->media );

    vlc_mutex_destroy( &p_sys->lock_media );

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
344
    free( p_sys->psz_path );
345
    free( p_sys->psz_raw_mux );
346 347 348 349 350 351
    free( p_sys );
}

/*****************************************************************************
 * Media handling
 *****************************************************************************/
352
static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name,
353
                              input_item_t *p_item )
354 355 356
{
    vod_sys_t *p_sys = p_vod->p_sys;
    vod_media_t *p_media = malloc( sizeof(vod_media_t) );
357
    int i;
358

Jean-Paul Saman's avatar
Jean-Paul Saman committed
359 360 361 362 363 364
    if( !p_media )
    {
        msg_Err( p_vod, "not enough memory" );
        return NULL;
    }

365
    memset( p_media, 0, sizeof(vod_media_t) );
366 367
    p_media->id = p_sys->i_media_id++;
    TAB_INIT( p_media->i_es, p_media->es );
368
    p_media->psz_mux = 0;
369
    TAB_INIT( p_media->i_rtsp, p_media->rtsp );
370
    p_media->b_raw = VLC_FALSE;
371

372
    asprintf( &p_media->psz_rtsp_path, "%s%s", p_sys->psz_path, psz_name );
373
    p_media->p_rtsp_url =
374 375
        httpd_UrlNewUnique( p_sys->p_rtsp_host, p_media->psz_rtsp_path, NULL,
                            NULL, NULL );
376 377 378

    if( !p_media->p_rtsp_url )
    {
379
        msg_Err( p_vod, "cannot create RTSP url (%s)", p_media->psz_rtsp_path);
380 381
        free( p_media->psz_rtsp_path );
        free( p_media );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
382
        return NULL;
383 384
    }

385
    msg_Dbg( p_vod, "created RTSP url: %s", p_media->psz_rtsp_path );
386

387
    asprintf( &p_media->psz_rtsp_control_v4,
388
               "a=control:rtsp://%%s:%d%s/trackID=%%d\r\n",
389 390
               p_sys->i_port, p_media->psz_rtsp_path );
    asprintf( &p_media->psz_rtsp_control_v6,
391
               "a=control:rtsp://[%%s]:%d%s/trackID=%%d\r\n",
392
              p_sys->i_port, p_media->psz_rtsp_path );
393

394 395
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_SETUP,
                    RtspCallback, (void*)p_media );
396 397 398 399 400 401
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_DESCRIBE,
                    RtspCallback, (void*)p_media );
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_PLAY,
                    RtspCallback, (void*)p_media );
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_PAUSE,
                    RtspCallback, (void*)p_media );
402 403
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_GETPARAMETER,
                    RtspCallback, (void*)p_media );
404 405 406 407 408
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_TEARDOWN,
                    RtspCallback, (void*)p_media );

    p_media->p_vod = p_vod;

409
    vlc_mutex_lock( &p_sys->lock_media );
410
    TAB_APPEND( p_sys->i_media, p_sys->media, p_media );
411
    vlc_mutex_unlock( &p_sys->lock_media );
412

413 414 415 416 417 418 419 420 421 422 423 424 425
    vlc_mutex_init( p_vod, &p_media->lock );
    p_media->psz_session_name = strdup("");
    p_media->psz_session_description = strdup("");
    p_media->psz_session_url = strdup("");
    p_media->psz_session_email = strdup("");

    p_media->i_port_audio = 1234;
    p_media->i_port_video = 1236;
    p_media->i_port       = 1238;
    p_media->i_payload_type = 96;

    p_media->i_sdp_id = mdate();
    p_media->i_sdp_version = 1;
426
    p_media->i_length = p_item->i_duration;
427

428 429 430
    vlc_mutex_lock( &p_item->lock );
    msg_Dbg( p_vod, "media has %i declared ES", p_item->i_es );
    for( i = 0; i < p_item->i_es; i++ )
431 432 433
    {
        MediaAddES( p_vod, p_media, p_item->es[i] );
    }
434 435
    vlc_mutex_unlock( &p_item->lock );

436 437 438 439 440 441 442
    return p_media;
}

static void MediaDel( vod_t *p_vod, vod_media_t *p_media )
{
    vod_sys_t *p_sys = p_vod->p_sys;

443 444
    msg_Dbg( p_vod, "deleting media: %s", p_media->psz_rtsp_path );

445 446 447 448 449 450 451
    vlc_mutex_lock( &p_sys->lock_media );
    TAB_REMOVE( p_sys->i_media, p_sys->media, p_media );
    vlc_mutex_unlock( &p_sys->lock_media );

    while( p_media->i_rtsp > 0 )
        RtspClientDel( p_media, p_media->rtsp[0] );
    TAB_CLEAN( p_media->i_rtsp, p_media->rtsp );
452

453 454
    httpd_UrlDelete( p_media->p_rtsp_url );
    if( p_media->psz_rtsp_path ) free( p_media->psz_rtsp_path );
455 456
    if( p_media->psz_rtsp_control_v6 ) free( p_media->psz_rtsp_control_v6 );
    if( p_media->psz_rtsp_control_v4 ) free( p_media->psz_rtsp_control_v4 );
457

458 459 460
    while( p_media->i_es )
        MediaDelES( p_vod, p_media, &p_media->es[0]->fmt );
    TAB_CLEAN( p_media->i_es, p_media->es );
461 462

    vlc_mutex_destroy( &p_media->lock );
463 464 465 466 467 468

    if( p_media->psz_session_name ) free( p_media->psz_session_name );
    if( p_media->psz_session_description ) free( p_media->psz_session_description );
    if( p_media->psz_session_url ) free( p_media->psz_session_url );
    if( p_media->psz_session_email ) free( p_media->psz_session_email );
    if( p_media->psz_mux ) free( p_media->psz_mux );
469 470 471 472 473 474
    free( p_media );
}

static int MediaAddES( vod_t *p_vod, vod_media_t *p_media, es_format_t *p_fmt )
{
    media_es_t *p_es = malloc( sizeof(media_es_t) );
475
    char *psz_urlc;
476

477
    if( !p_es ) return VLC_ENOMEM;
478
    memset( p_es, 0, sizeof(media_es_t) );
479 480

    if( p_media->psz_mux ) free( p_media->psz_mux );
481
    p_media->psz_mux = NULL;
482 483

    /* TODO: update SDP, etc... */
484
    asprintf( &psz_urlc, "%s/trackID=%d",
485 486
              p_media->psz_rtsp_path, p_media->i_es );
    msg_Dbg( p_vod, "  - ES %4.4s (%s)", (char *)&p_fmt->i_codec, psz_urlc );
487

488
    switch( p_fmt->i_codec )
489
    {
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
        case VLC_FOURCC( 's', '1', '6', 'b' ):
            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 44100 )
            {
                p_es->i_payload_type = 11;
            }
            else if( p_fmt->audio.i_channels == 2 && p_fmt->audio.i_rate == 44100 )
            {
                p_es->i_payload_type = 10;
            }
            else
            {
                p_es->i_payload_type = p_media->i_payload_type++;
            }
            p_es->psz_rtpmap = malloc( strlen( "L16/*/*" ) + 20+1 );
            sprintf( p_es->psz_rtpmap, "L16/%d/%d", p_fmt->audio.i_rate,
                    p_fmt->audio.i_channels );
            break;
        case VLC_FOURCC( 'u', '8', ' ', ' ' ):
508
            p_es->i_payload_type = p_media->i_payload_type++;
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
            p_es->psz_rtpmap = malloc( strlen( "L8/*/*" ) + 20+1 );
            sprintf( p_es->psz_rtpmap, "L8/%d/%d", p_fmt->audio.i_rate,
                    p_fmt->audio.i_channels );
            break;
        case VLC_FOURCC( 'm', 'p', 'g', 'a' ):
            p_es->i_payload_type = 14;
            p_es->psz_rtpmap = strdup( "MPA/90000" );
            break;
        case VLC_FOURCC( 'm', 'p', 'g', 'v' ):
            p_es->i_payload_type = 32;
            p_es->psz_rtpmap = strdup( "MPV/90000" );
            break;
        case VLC_FOURCC( 'a', '5', '2', ' ' ):
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = strdup( "ac3/90000" );
            break;
        case VLC_FOURCC( 'H', '2', '6', '3' ):
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = strdup( "H263-1998/90000" );
            break;
529 530 531
        case VLC_FOURCC( 'h', '2', '6', '4' ):
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = strdup( "H264/90000" );
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
            p_es->psz_fmtp = NULL;
            /* FIXME AAAAAAAAAAAARRRRRRRRGGGG copied from stream_out/rtp.c */
            if( p_fmt->i_extra > 0 )
            {
                uint8_t *p_buffer = p_fmt->p_extra;
                int     i_buffer = p_fmt->i_extra;
                char    *p_64_sps = NULL;
                char    *p_64_pps = NULL;
                char    hexa[6+1];

                while( i_buffer > 4 &&
                       p_buffer[0] == 0 && p_buffer[1] == 0 &&
                       p_buffer[2] == 0 && p_buffer[3] == 1 )
                {
                    const int i_nal_type = p_buffer[4]&0x1f;
                    int i_offset;
                    int i_size      = 0;

                    i_size = i_buffer;
                    for( i_offset = 4; i_offset+3 < i_buffer ; i_offset++)
                    {
                        if( p_buffer[i_offset] == 0 && p_buffer[i_offset+1] == 0 && p_buffer[i_offset+2] == 0 && p_buffer[i_offset+3] == 1 )
                        {
                            /* we found another startcode */
                            i_size = i_offset;
                            break;
                        }
                    }
                    if( i_nal_type == 7 )
                    {
                        p_64_sps = vlc_b64_encode_binary( &p_buffer[4], i_size - 4 );
                        sprintf_hexa( hexa, &p_buffer[5], 3 );
                    }
                    else if( i_nal_type == 8 )
                    {
                        p_64_pps = vlc_b64_encode_binary( &p_buffer[4], i_size - 4 );
                    }
                    i_buffer -= i_size;
                    p_buffer += i_size;
                }
                /* */
                if( p_64_sps && p_64_pps )
                    asprintf( &p_es->psz_fmtp, "packetization-mode=1;profile-level-id=%s;sprop-parameter-sets=%s,%s;", hexa, p_64_sps, p_64_pps );
                if( p_64_sps )
                    free( p_64_sps );
                if( p_64_pps )
                    free( p_64_pps );
            }
            if( !p_es->psz_fmtp )
                p_es->psz_fmtp = strdup( "packetization-mode=1" );
582
            break;
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
        case VLC_FOURCC( 'm', 'p', '4', 'v' ):
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = strdup( "MP4V-ES/90000" );
            if( p_fmt->i_extra > 0 )
            {
                char *p_hexa = malloc( 2 * p_fmt->i_extra + 1 );
                p_es->psz_fmtp = malloc( 100 + 2 * p_fmt->i_extra );
                sprintf_hexa( p_hexa, p_fmt->p_extra, p_fmt->i_extra );
                sprintf( p_es->psz_fmtp,
                        "profile-level-id=3; config=%s;", p_hexa );
                free( p_hexa );
            }
            break;
        case VLC_FOURCC( 'm', 'p', '4', 'a' ):
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = malloc( strlen( "mpeg4-generic/" ) + 12 );
            sprintf( p_es->psz_rtpmap, "mpeg4-generic/%d", p_fmt->audio.i_rate );
            if( p_fmt->i_extra > 0 )
            {
                char *p_hexa = malloc( 2 * p_fmt->i_extra + 1 );
                p_es->psz_fmtp = malloc( 200 + 2 * p_fmt->i_extra );
                sprintf_hexa( p_hexa, p_fmt->p_extra, p_fmt->i_extra );
                sprintf( p_es->psz_fmtp,
                        "streamtype=5; profile-level-id=15; mode=AAC-hbr; "
                        "config=%s; SizeLength=13;IndexLength=3; "
                        "IndexDeltaLength=3; Profile=1;", p_hexa );
                free( p_hexa );
            }
            break;
        case VLC_FOURCC( 'm', 'p', '2', 't' ):
613
            p_media->psz_mux = strdup("ts");
614 615 616 617
            p_es->i_payload_type = 33;
            p_es->psz_rtpmap = strdup( "MP2T/90000" );
            break;
        case VLC_FOURCC( 'm', 'p', '2', 'p' ):
618
            p_media->psz_mux = strdup("ps");
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = strdup( "MP2P/90000" );
            break;
        case VLC_FOURCC( 's', 'a', 'm', 'r' ):
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = strdup( p_fmt->audio.i_channels == 2 ?
                                    "AMR/8000/2" : "AMR/8000" );
            p_es->psz_fmtp = strdup( "octet-align=1" );
            break;
        case VLC_FOURCC( 's', 'a', 'w', 'b' ):
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = strdup( p_fmt->audio.i_channels == 2 ?
                                    "AMR-WB/16000/2" : "AMR-WB/16000" );
            p_es->psz_fmtp = strdup( "octet-align=1" );
            break;
634

635 636 637 638 639
        default:
            msg_Err( p_vod, "cannot add this stream (unsupported "
                    "codec: %4.4s)", (char*)&p_fmt->i_codec );
            free( p_es );
            return VLC_EGENERIC;
640 641 642
    }

    p_es->p_rtsp_url =
643 644
        httpd_UrlNewUnique( p_vod->p_sys->p_rtsp_host, psz_urlc, NULL, NULL,
                            NULL );
645 646 647

    if( !p_es->p_rtsp_url )
    {
648
        msg_Err( p_vod, "cannot create RTSP url (%s)", psz_urlc );
649 650 651
        free( psz_urlc );
        free( p_es );
        return VLC_EGENERIC;
652
    }
653 654 655 656
    free( psz_urlc );

    httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_SETUP,
                    RtspCallbackES, (void*)p_es );
657 658 659 660 661 662
    httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_TEARDOWN,
                    RtspCallbackES, (void*)p_es );
    httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_PLAY,
                    RtspCallbackES, (void*)p_es );
    httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_PAUSE,
                    RtspCallbackES, (void*)p_es );
663

664
    es_format_Copy( &p_es->fmt, p_fmt );
665 666 667
    p_es->p_vod = p_vod;
    p_es->p_media = p_media;

668
#if 0
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
    /* Choose the port */
    if( p_fmt->i_cat == AUDIO_ES && p_media->i_port_audio > 0 )
    {
        p_es->i_port = p_media->i_port_audio;
        p_media->i_port_audio = 0;
    }
    else if( p_fmt->i_cat == VIDEO_ES && p_media->i_port_video > 0 )
    {
        p_es->i_port = p_media->i_port_video;
        p_media->i_port_video = 0;
    }
    while( !p_es->i_port )
    {
        if( p_media->i_port != p_media->i_port_audio &&
            p_media->i_port != p_media->i_port_video )
        {
            p_es->i_port = p_media->i_port;
            p_media->i_port += 2;
            break;
        }
        p_media->i_port += 2;
    }
691 692 693 694
#else

    p_es->i_port = 0;
#endif
695 696 697 698 699 700 701

    vlc_mutex_lock( &p_media->lock );
    TAB_APPEND( p_media->i_es, p_media->es, p_es );
    vlc_mutex_unlock( &p_media->lock );

    p_media->i_sdp_version++;

702 703 704 705 706
    return VLC_SUCCESS;
}

static void MediaDelES( vod_t *p_vod, vod_media_t *p_media, es_format_t *p_fmt)
{
707
    media_es_t *p_es = NULL;
708 709 710 711 712 713 714 715 716 717 718 719 720
    int i;

    /* Find the ES */
    for( i = 0; i < p_media->i_es; i++ )
    {
        if( p_media->es[i]->fmt.i_cat == p_fmt->i_cat &&
            p_media->es[i]->fmt.i_codec == p_fmt->i_codec &&
            p_media->es[i]->fmt.i_id == p_fmt->i_id )
        {
            p_es = p_media->es[i];
        }
    }
    if( !p_es ) return;
721

722 723 724
    msg_Dbg( p_vod, "  - Removing ES %4.4s", (char *)&p_fmt->i_codec );

    vlc_mutex_lock( &p_media->lock );
725
    TAB_REMOVE( p_media->i_es, p_media->es, p_es );
726
    vlc_mutex_unlock( &p_media->lock );
727

728 729 730
    if( p_es->psz_rtpmap ) free( p_es->psz_rtpmap );
    if( p_es->psz_fmtp ) free( p_es->psz_fmtp );
    p_media->i_sdp_version++;
731 732

    if( p_es->p_rtsp_url ) httpd_UrlDelete( p_es->p_rtsp_url );
733
    es_format_Clean( &p_es->fmt );
734
    free( p_es );
735 736
}

737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844
/* */
typedef struct
{
    int i_type;
    int i_media_id;
    //vod_media_t *p_media;
    char *psz_session;
    char *psz_arg;
    double f_arg;
} rtsp_cmd_t;

static void CommandPush( vod_t *p_vod, rtsp_cmd_type_t i_type, vod_media_t *p_media, const char *psz_session,
                         double f_arg, const char *psz_arg )
{
    rtsp_cmd_t cmd;
    block_t *p_cmd;

    memset( &cmd, 0, sizeof(cmd) );
    cmd.i_type = i_type;
    if( p_media )
        cmd.i_media_id = p_media->id;
    if( psz_session )
        cmd.psz_session = strdup(psz_session);
    cmd.f_arg = f_arg;
    if( psz_arg )
        cmd.psz_arg = strdup(psz_arg);

    p_cmd = block_New( p_vod, sizeof(rtsp_cmd_t) );
    memcpy( p_cmd->p_buffer, &cmd, sizeof(cmd) );

    block_FifoPut( p_vod->p_sys->p_fifo_cmd, p_cmd );
}

static void CommandThread( vlc_object_t *p_this )
{
    vod_t *p_vod = (vod_t*)p_this;
    vod_sys_t *p_sys = p_vod->p_sys;

    while( !p_vod->b_die )
    {
        block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
        rtsp_cmd_t cmd;
        vod_media_t *p_media = NULL;
        int i;

        if( !p_block_cmd )
            break;

        memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
        block_Release( p_block_cmd );

        if( cmd.i_type == RTSP_CMD_TYPE_NONE )
            break;

        /* */
        vlc_mutex_lock( &p_sys->lock_media );
        for( i = 0; i < p_sys->i_media; i++ )
        {
            if( p_sys->media[i]->id == cmd.i_media_id )
                break;
        }
        if( i >= p_sys->i_media )
            goto next;
        p_media = p_sys->media[i];

        switch( cmd.i_type )
        {
        case RTSP_CMD_TYPE_PLAY:
            vod_MediaControl( p_vod, p_media, cmd.psz_session,
                              VOD_MEDIA_PLAY, cmd.psz_arg );
            break;
        case RTSP_CMD_TYPE_PAUSE:
            vod_MediaControl( p_vod, p_media, cmd.psz_session,
                              VOD_MEDIA_PAUSE );
            break;

        case RTSP_CMD_TYPE_STOP:
            vod_MediaControl( p_vod, p_media, cmd.psz_session, VOD_MEDIA_STOP );
            break;

        case RTSP_CMD_TYPE_SEEK:
            vod_MediaControl( p_vod, p_media, cmd.psz_session,
                              VOD_MEDIA_SEEK, cmd.f_arg );
            break;

        case RTSP_CMD_TYPE_REWIND:
            vod_MediaControl( p_vod, p_media, cmd.psz_session,
                              VOD_MEDIA_REWIND, cmd.f_arg );
            break;

        case RTSP_CMD_TYPE_FORWARD:
            vod_MediaControl( p_vod, p_media, cmd.psz_session,
                              VOD_MEDIA_FORWARD, cmd.f_arg );
            break;

        default:
            break;
        }

    next:
        vlc_mutex_unlock( &p_sys->lock_media );
        if( cmd.psz_session )
            free( cmd.psz_session );
        if( cmd.psz_arg )
            free( cmd.psz_arg );
    }
}

845 846 847 848 849
/****************************************************************************
 * RTSP server implementation
 ****************************************************************************/
static rtsp_client_t *RtspClientNew( vod_media_t *p_media, char *psz_session )
{
850
    rtsp_client_t *p_rtsp = malloc( sizeof(rtsp_client_t) );
851 852

    if( !p_rtsp ) return NULL;
853 854
    memset( p_rtsp, 0, sizeof(rtsp_client_t) );
    p_rtsp->es = 0;
855

856 857
    p_rtsp->psz_session = psz_session;
    TAB_APPEND( p_media->i_rtsp, p_media->rtsp, p_rtsp );
858

859 860
    p_media->p_vod->p_sys->i_connections++;
    msg_Dbg( p_media->p_vod, "new session: %s, connections: %d",
861
             psz_session, p_media->p_vod->p_sys->i_throttle_users );
862

863
    return p_rtsp;
864 865
}

866
static rtsp_client_t *RtspClientGet( vod_media_t *p_media, const char *psz_session )
867 868 869
{
    int i;

870
    for( i = 0; psz_session && i < p_media->i_rtsp; i++ )
871 872 873 874 875 876 877 878
    {
        if( !strcmp( p_media->rtsp[i]->psz_session, psz_session ) )
            return p_media->rtsp[i];
    }

    return NULL;
}

879
static void RtspClientDel( vod_media_t *p_media, rtsp_client_t *p_rtsp )
880
{
881 882
    p_media->p_vod->p_sys->i_connections--;
    msg_Dbg( p_media->p_vod, "closing session: %s, connections: %d",
883
             p_rtsp->psz_session, p_media->p_vod->p_sys->i_throttle_users );
884

885 886
    while( p_rtsp->i_es-- )
    {
887 888 889 890
        if( p_rtsp->es[p_rtsp->i_es]->psz_ip )
            free( p_rtsp->es[p_rtsp->i_es]->psz_ip );
        free( p_rtsp->es[p_rtsp->i_es] );
        if( !p_rtsp->i_es ) free( p_rtsp->es );
891 892
    }

893
    TAB_REMOVE( p_media->i_rtsp, p_media->rtsp, p_rtsp );
894

895 896
    free( p_rtsp->psz_session );
    free( p_rtsp );
897 898 899 900 901 902 903
}

static int RtspCallback( httpd_callback_sys_t *p_args, httpd_client_t *cl,
                         httpd_message_t *answer, httpd_message_t *query )
{
    vod_media_t *p_media = (vod_media_t*)p_args;
    vod_t *p_vod = p_media->p_vod;
904 905 906 907
    const char *psz_transport = NULL;
    const char *psz_playnow = NULL; /* support option: x-playNow */
    const char *psz_session = NULL;
    const char *psz_cseq = NULL;
908
    rtsp_client_t *p_rtsp;
909
    int i_port = 0;
910
    int i_cseq = 0;
911 912 913

    if( answer == NULL || query == NULL ) return VLC_SUCCESS;

914
    msg_Dbg( p_vod, "RtspCallback query: type=%d", query->i_type );
915 916 917 918

    answer->i_proto   = HTTPD_PROTO_RTSP;
    answer->i_version = query->i_version;
    answer->i_type    = HTTPD_MSG_ANSWER;
919
    answer->i_body    = 0;
920
    answer->p_body    = NULL;
921 922 923

    switch( query->i_type )
    {
924 925
        case HTTPD_MSG_SETUP:
        {
926
            psz_playnow = httpd_MsgGet( query, "x-playNow" );
927 928 929 930 931 932
            psz_transport = httpd_MsgGet( query, "Transport" );
            msg_Dbg( p_vod, "HTTPD_MSG_SETUP: transport=%s", psz_transport );

            if( strstr( psz_transport, "unicast" ) &&
                strstr( psz_transport, "client_port=" ) )
            {
933
                rtsp_client_t *p_rtsp = NULL;
934
                char ip[NI_MAXNUMERICHOST];
935
                i_port = atoi( strstr( psz_transport, "client_port=" ) +
936 937
                                strlen("client_port=") );

938 939 940
                if( strstr( psz_transport, "MP2T/H2221/UDP" ) ||
                    strstr( psz_transport, "RAW/RAW/UDP" ) )
                {
941 942 943
                    if( p_media->psz_mux ) free( p_media->psz_mux );
                    p_media->psz_mux = NULL;
                    p_media->psz_mux = strdup( p_vod->p_sys->psz_raw_mux );
944
                    p_media->b_raw = VLC_TRUE;
945
                }
946

947 948 949 950 951 952 953 954 955 956
                if( httpd_ClientIP( cl, ip ) == NULL )
                {
                    answer->i_status = 500;
                    answer->psz_status = strdup( "Internal server error" );
                    answer->i_body = 0;
                    answer->p_body = NULL;
                    break;
                }

                msg_Dbg( p_vod, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
957
                         ip, i_port );
958 959 960 961

                psz_session = httpd_MsgGet( query, "Session" );
                if( !psz_session || !*psz_session )
                {
962
                    char *psz_new;
963 964
                    if( ( p_vod->p_sys->i_throttle_users > 0 ) &&
                        ( p_vod->p_sys->i_connections >= p_vod->p_sys->i_throttle_users ) )
965
                    {
966
                        answer->i_status = 503;
967 968 969 970 971
                        answer->psz_status = strdup( "Too many connections" );
                        answer->i_body = 0;
                        answer->p_body = NULL;
                        break;
                    }
972 973 974 975
                    asprintf( &psz_new, "%d", rand() );
                    psz_session = psz_new;

                    p_rtsp = RtspClientNew( p_media, psz_new );
976 977 978 979 980 981 982 983
                    if( !p_rtsp )
                    {
                        answer->i_status = 454;
                        answer->psz_status = strdup( "Unknown session id" );
                        answer->i_body = 0;
                        answer->p_body = NULL;
                        break;
                    }
984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
                }
                else
                {
                    p_rtsp = RtspClientGet( p_media, psz_session );
                    if( !p_rtsp )
                    {
                        answer->i_status = 454;
                        answer->psz_status = strdup( "Unknown session id" );
                        answer->i_body = 0;
                        answer->p_body = NULL;
                        break;
                    }
                }

                answer->i_status = 200;
                answer->psz_status = strdup( "OK" );
                answer->i_body = 0;
                answer->p_body = NULL;

1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
                if( p_media->b_raw )
                {
                    if( strstr( psz_transport, "MP2T/H2221/UDP" ) )
                    {
                        httpd_MsgAdd( answer, "Transport", "MP2T/H2221/UDP;client_port=%d-%d",
                                      i_port, i_port + 1 );
                    }
                    else if( strstr( psz_transport, "RAW/RAW/UDP" ) )
                    {
                        httpd_MsgAdd( answer, "Transport", "RAW/RAW/UDP;client_port=%d-%d",
                                      i_port, i_port + 1 );
                    }
                }
                else
                    httpd_MsgAdd( answer, "Transport", "RTP/AVP/UDP;client_port=%d-%d",
                                  i_port, i_port + 1 );
1019 1020 1021 1022 1023 1024 1025 1026
            }
            else /* TODO  strstr( psz_transport, "interleaved" ) ) */
            {
                answer->i_status = 461;
                answer->psz_status = strdup( "Unsupported Transport" );
                answer->i_body = 0;
                answer->p_body = NULL;
            }
1027

1028 1029
            /* Intentional fall-through on x-playNow option in RTSP request */
            if( !psz_playnow )
1030
                break;
1031 1032 1033 1034
        }

        case HTTPD_MSG_PLAY:
        {
1035
            char *psz_output, ip[NI_MAXNUMERICHOST];
1036
            int i, i_port_audio = 0, i_port_video = 0;
1037 1038

            /* for now only multicast so easy */
1039 1040 1041 1042 1043 1044 1045
            if( !psz_playnow )
            {
                answer->i_status = 200;
                answer->psz_status = strdup( "OK" );
                answer->i_body = 0;
                answer->p_body = NULL;
            }
1046

1047 1048
            if( !psz_session )
                psz_session = httpd_MsgGet( query, "Session" );
1049 1050
            msg_Dbg( p_vod, "HTTPD_MSG_PLAY for session: %s", psz_session );

1051
            p_rtsp = RtspClientGet( p_media, psz_session );
1052 1053 1054 1055 1056 1057 1058 1059
            if( !p_rtsp )
            {
                answer->i_status = 500;
                answer->psz_status = strdup( "Internal server error" );
                answer->i_body = 0;
                answer->p_body = NULL;
                break;
            }
1060

1061 1062
            if( p_rtsp->b_playing )
            {
1063 1064
                const char *psz_position = httpd_MsgGet( query, "Range" );
                const char *psz_scale = httpd_MsgGet( query, "Scale" );
1065
                if( psz_position ) psz_position = strstr( psz_position, "npt=" );
1066
                if( psz_position && !psz_scale )
1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
                {
                    double f_pos;
                    char *end;

                    msg_Dbg( p_vod, "seeking request: %s", psz_position );
                    psz_position += 4;
                    /* FIXME: npt= is not necessarily formatted as a float */
                    f_pos = us_strtod( psz_position, &end );
                    if( end > psz_position )
                    {
                        f_pos /= ((double)(p_media->i_length))/1000 /1000 / 100;
1078
                        CommandPush( p_vod, RTSP_CMD_TYPE_SEEK, p_media, psz_session, f_pos, NULL );
1079 1080 1081
                    }
                    break;
                }
1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
                if( psz_scale )
                {
                    double f_scale = 0.0;
                    char *end;

                    f_scale = us_strtod( psz_scale, &end );
                    if( end > psz_scale )
                    {
                        f_scale = (f_scale * 30.0);
                        if( psz_scale[0] == '-' ) /* rewind */
                        {
                            msg_Dbg( p_vod, "rewind request: %s", psz_scale );
1094
                            CommandPush( p_vod, RTSP_CMD_TYPE_REWIND, p_media, psz_session, f_scale, NULL );
1095 1096 1097 1098
                        }
                        else if(psz_scale[0] != '1' ) /* fast-forward */
                        {
                            msg_Dbg( p_vod, "fastforward request: %s", psz_scale );
1099
                            CommandPush( p_vod, RTSP_CMD_TYPE_FORWARD, p_media, psz_session, f_scale, NULL );
1100 1101 1102 1103 1104
                        }

                        if( p_rtsp->b_paused == VLC_TRUE )
                        {
                            p_rtsp->b_paused = VLC_FALSE;
1105
                            CommandPush( p_vod, RTSP_CMD_TYPE_PAUSE, p_media, psz_session, 0, NULL );
1106 1107 1108 1109
                        }
                    }
                    break;
                }
1110 1111
            }

1112 1113
            if( p_rtsp->b_playing && p_rtsp->b_paused )
            {
1114
                CommandPush( p_vod, RTSP_CMD_TYPE_PAUSE, p_media, psz_session, 0, NULL );
1115 1116 1117 1118 1119
                p_rtsp->b_paused = VLC_FALSE;
                break;
            }
            else if( p_rtsp->b_playing ) break;

1120
            if( httpd_ClientIP( cl, ip ) == NULL ) break;
1121

1122
            p_rtsp->b_playing = VLC_TRUE;
1123 1124

            /* FIXME for != 1 video and 1 audio */
1125
            for( i = 0; i < p_rtsp->i_es; i++ )
1126
            {
1127 1128 1129 1130
                if( p_rtsp->es[i]->p_media_es->fmt.i_cat == AUDIO_ES )
                    i_port_audio = p_rtsp->es[i]->i_port;
                if( p_rtsp->es[i]->p_media_es->fmt.i_cat == VIDEO_ES )
                    i_port_video = p_rtsp->es[i]->i_port;
1131
            }
1132

1133 1134
            if( p_media->psz_mux )
            {
1135 1136 1137 1138 1139 1140 1141 1142 1143 1144
                if( p_media->b_raw )
                {
                    asprintf( &psz_output, "std{access=udp,dst=%s:%i,mux=%s}",
                              ip, i_port, p_media->psz_mux );
                }
                else
                {
                    asprintf( &psz_output, "rtp{dst=%s,port=%i,mux=%s}",
                              ip, i_port_video, p_media->psz_mux );
                }
1145 1146 1147 1148 1149 1150 1151
            }
            else
            {
                asprintf( &psz_output, "rtp{dst=%s,port-video=%i,"
                          "port-audio=%i}", ip, i_port_video, i_port_audio );
            }

1152
            CommandPush( p_vod, RTSP_CMD_TYPE_PLAY, p_media, psz_session, 0, psz_output );
1153
            free( psz_output );
1154 1155 1156
            break;
        }

1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
        case HTTPD_MSG_DESCRIBE:
        {
            char *psz_sdp =
                SDPGenerate( p_media, cl );

            if( psz_sdp != NULL )
            {
                answer->i_status = 200;
                answer->psz_status = strdup( "OK" );
                httpd_MsgAdd( answer, "Content-type",  "%s", "application/sdp" );

                answer->p_body = (uint8_t *)psz_sdp;
                answer->i_body = strlen( psz_sdp );
            }
            else
            {
                answer->i_status = 500;
                answer->psz_status = strdup( "Internal server error" );
                answer->p_body = NULL;
                answer->i_body = 0;
            }
            break;
        }

1181 1182 1183
        case HTTPD_MSG_PAUSE:
            psz_session = httpd_MsgGet( query, "Session" );
            msg_Dbg( p_vod, "HTTPD_MSG_PAUSE for session: %s", psz_session );
1184

1185 1186 1187
            p_rtsp = RtspClientGet( p_media, psz_session );
            if( !p_rtsp ) break;

1188
            CommandPush( p_vod, RTSP_CMD_TYPE_PAUSE, p_media, psz_session, 0, NULL );
1189
            p_rtsp->b_paused = VLC_TRUE;
1190 1191 1192 1193 1194 1195

            answer->i_status = 200;
            answer->psz_status = strdup( "OK" );
            answer->i_body = 0;
            answer->p_body = NULL;
            break;
1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206

        case HTTPD_MSG_TEARDOWN:
            /* for now only multicast so easy again */
            answer->i_status = 200;
            answer->psz_status = strdup( "OK" );
            answer->i_body = 0;
            answer->p_body = NULL;

            psz_session = httpd_MsgGet( query, "Session" );
            msg_Dbg( p_vod, "HTTPD_MSG_TEARDOWN for session: %s", psz_session);

1207 1208
            p_rtsp = RtspClientGet( p_media, psz_session );
            if( !p_rtsp ) break;
1209

1210
            CommandPush( p_vod, RTSP_CMD_TYPE_STOP, p_media, psz_session, 0, NULL );
1211
            RtspClientDel( p_media, p_rtsp );
1212 1213
            break;

1214 1215 1216 1217 1218 1219 1220
        case HTTPD_MSG_GETPARAMETER:
            answer->i_status = 200;
            answer->psz_status = strdup( "OK" );
            answer->i_body = 0;
            answer->p_body = NULL;
            break;

1221 1222 1223 1224 1225 1226
        default:
            return VLC_EGENERIC;
    }

    httpd_MsgAdd( answer, "Server", "VLC Server" );
    httpd_MsgAdd( answer, "Content-Length", "%d", answer->i_body );
1227 1228
    psz_cseq = httpd_MsgGet( query, "Cseq" );
    psz_cseq ? i_cseq = atoi( psz_cseq ) : 0;
1229
    httpd_MsgAdd( answer, "CSeq", "%d", i_cseq );
1230 1231 1232 1233
    httpd_MsgAdd( answer, "Cache-Control", "%s", "no-cache" );

    if( psz_session )
    {
1234 1235 1236 1237 1238 1239
         if( p_media->p_vod->p_sys->i_session_timeout >= 0 )
             httpd_MsgAdd( answer, "Session", "%s;timeout=%i", psz_session,
               p_media->p_vod->p_sys->i_session_timeout );
         else
              httpd_MsgAdd( answer, "Session", "%s", psz_session );
    } 
1240 1241 1242 1243

    return VLC_SUCCESS;
}

1244
static int RtspCallbackES( httpd_callback_sys_t *p_args, httpd_client_t *cl,
1245 1246
                           httpd_message_t *answer, httpd_message_t *query )
{
1247 1248
    media_es_t *p_es = (media_es_t*)p_args;
    vod_media_t *p_media = p_es->p_media;
1249
    vod_t *p_vod = p_media->p_vod;
1250
    rtsp_client_t *p_rtsp = NULL;
1251 1252 1253 1254 1255
    const char *psz_transport = NULL;
    const char *psz_playnow = NULL; /* support option: x-playNow */
    const char *psz_session = NULL;
    const char *psz_position = NULL;
    const char *psz_cseq = NULL;
1256
    int i_cseq = 0;
1257
    int i;
1258 1259 1260

    if( answer == NULL || query == NULL ) return VLC_SUCCESS;

1261
    msg_Dbg( p_vod, "RtspCallback query: type=%d", query->i_type );
1262 1263 1264 1265

    answer->i_proto   = HTTPD_PROTO_RTSP;
    answer->i_version = query->i_version;
    answer->i_type    = HTTPD_MSG_ANSWER;
1266 1267
    answer->i_body    = 0;
    answer->p_body      = NULL;
1268 1269 1270

    switch( query->i_type )
    {
1271 1272 1273
        case HTTPD_MSG_SETUP:
            psz_playnow = httpd_MsgGet( query, "x-playNow" );
            psz_transport = httpd_MsgGet( query, "Transport" );
1274

1275
            msg_Dbg( p_vod, "HTTPD_MSG_SETUP: transport=%s", psz_transport );
1276

1277 1278
            if( strstr( psz_transport, "unicast" ) &&
                strstr( psz_transport, "client_port=" ) )
1279
            {
1280 1281
                rtsp_client_t *p_rtsp = NULL;
                rtsp_client_es_t *p_rtsp_es = NULL;
1282 1283 1284
                char ip[NI_MAXNUMERICHOST];
                int i_port = atoi( strstr( psz_transport, "client_port=" ) +
                                strlen("client_port=") );
1285

1286
                if( httpd_ClientIP( cl, ip ) == NULL )
1287
                {
1288 1289
                    answer->i_status = 500;
                    answer->psz_status = strdup( "Internal server error" );
1290 1291 1292 1293 1294
                    answer->i_body = 0;
                    answer->p_body = NULL;
                    break;
                }

1295 1296
                msg_Dbg( p_vod, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
                        ip, i_port );
1297

1298 1299 1300
                psz_session = httpd_MsgGet( query, "Session" );
                if( !psz_session || !*psz_session )
                {
1301
                    char *psz_new;
1302 1303
                    if( ( p_vod->p_sys->i_throttle_users > 0 ) &&
                        ( p_vod->p_sys->i_connections >= p_vod->p_sys->i_throttle_users ) )
1304
                    {
1305
                        answer->i_status = 503;
1306 1307 1308 1309 1310
                        answer->psz_status = strdup( "Too many connections" );
                        answer->i_body = 0;
                        answer->p_body = NULL;
                        break;
                    }
1311 1312 1313 1314