rtsp.c 53.7 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>
Christophe Mutricy's avatar
Christophe Mutricy committed
43
#include <vlc_strings.h>
44 45 46 47 48 49 50

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

51
#define HOST_TEXT N_( "RTSP host address" )
52
#define HOST_LONGTEXT N_( \
53 54
    "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
55
    "interfaces (address 0.0.0.0), on port 554, with no path.\nTo listen " \
56
    "only on the local interface, use \"localhost\" as address." )
57

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

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

64 65 66 67 68 69 70
#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." )

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

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

91 92 93 94 95 96 97 98 99 100
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;

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

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

    int i_es;
    rtsp_client_es_t **es;
111 112 113

} rtsp_client_t;

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

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

    vod_media_t *p_media;

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

130
};
131 132 133

struct vod_media_t
{
134 135
    int id;

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

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

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

    int64_t i_sdp_id;
    int     i_sdp_version;

154 155
    vlc_bool_t b_multicast;

156
    vlc_mutex_t lock;
157 158 159 160

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

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

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

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

185 186
    char *psz_raw_mux;

187 188
    int i_session_timeout;

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

    /* */
    block_fifo_t *p_fifo_cmd;
197 198
};

199 200 201 202 203 204 205 206 207 208 209 210 211
/* 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;

212
static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * );
213 214 215 216
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 * );

217 218 219 220
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 );

221
static rtsp_client_t *RtspClientNew( vod_media_t *, char * );
222
static rtsp_client_t *RtspClientGet( vod_media_t *, const char * );
223 224 225 226
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 * );
227
static int RtspCallbackES( httpd_callback_sys_t *, httpd_client_t *,
228 229
                           httpd_message_t *, httpd_message_t * );

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

232 233 234 235 236 237 238 239 240 241 242 243 244
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';
}

245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
/*****************************************************************************
 * 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;

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

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

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

273 274 275 276
    p_sys->p_rtsp_host =
        httpd_HostNew( VLC_OBJECT(p_vod), url.psz_host, url.i_port );
    if( !p_sys->p_rtsp_host )
    {
277
        msg_Err( p_vod, "cannot create RTSP server (%s:%i)",
278 279 280 281 282 283 284 285
                 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 );
286 287 288 289 290

    vlc_mutex_init( p_vod, &p_sys->lock_media );

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

    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;

297 298 299 300 301 302 303 304 305 306
    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;
    }

307 308
    return VLC_SUCCESS;

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

315 316 317 318 319 320 321 322 323 324 325
    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;

326 327 328 329 330 331 332
    /* 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 );

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

338 339 340 341 342 343 344
    /* 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
345
    free( p_sys->psz_path );
346
    free( p_sys->psz_raw_mux );
347 348 349 350 351 352
    free( p_sys );
}

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

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

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

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

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

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

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

395 396
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_SETUP,
                    RtspCallback, (void*)p_media );
397 398 399 400 401 402
    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 );
403 404
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_GETPARAMETER,
                    RtspCallback, (void*)p_media );
405 406 407 408 409
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_TEARDOWN,
                    RtspCallback, (void*)p_media );

    p_media->p_vod = p_vod;

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

414 415 416 417 418 419 420 421 422 423 424 425 426
    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;
427
    p_media->i_length = p_item->i_duration;
428

429 430 431
    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++ )
432 433 434
    {
        MediaAddES( p_vod, p_media, p_item->es[i] );
    }
435 436
    vlc_mutex_unlock( &p_item->lock );

437 438 439 440 441 442 443
    return p_media;
}

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

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

446 447 448 449 450 451 452
    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 );
453

454 455
    httpd_UrlDelete( p_media->p_rtsp_url );
    if( p_media->psz_rtsp_path ) free( p_media->psz_rtsp_path );
456 457
    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 );
458

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

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

    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 );
470 471 472 473 474 475
    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) );
476
    char *psz_urlc;
477

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

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

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

489
    switch( p_fmt->i_codec )
490
    {
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
        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', ' ', ' ' ):
509
            p_es->i_payload_type = p_media->i_payload_type++;
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
            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;
530 531 532
        case VLC_FOURCC( 'h', '2', '6', '4' ):
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = strdup( "H264/90000" );
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 582
            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" );
583
            break;
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 613
        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' ):
614
            p_media->psz_mux = strdup("ts");
615 616 617 618
            p_es->i_payload_type = 33;
            p_es->psz_rtpmap = strdup( "MP2T/90000" );
            break;
        case VLC_FOURCC( 'm', 'p', '2', 'p' ):
619
            p_media->psz_mux = strdup("ps");
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
            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;
635

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

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

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

    httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_SETUP,
                    RtspCallbackES, (void*)p_es );
658 659 660 661 662 663
    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 );
664

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

669
#if 0
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
    /* 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;
    }
692 693 694 695
#else

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

    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++;

703 704 705 706 707
    return VLC_SUCCESS;
}

static void MediaDelES( vod_t *p_vod, vod_media_t *p_media, es_format_t *p_fmt)
{
708
    media_es_t *p_es = NULL;
709 710 711 712 713 714 715 716 717 718 719 720 721
    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;
722

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

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

729 730 731
    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++;
732 733

    if( p_es->p_rtsp_url ) httpd_UrlDelete( p_es->p_rtsp_url );
734
    es_format_Clean( &p_es->fmt );
735
    free( p_es );
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 845
/* */
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 );
    }
}

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

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

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

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

864
    return p_rtsp;
865 866
}

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

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

    return NULL;
}

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

886 887
    while( p_rtsp->i_es-- )
    {
888 889 890 891
        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 );
892 893
    }

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

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

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;
905 906 907 908
    const char *psz_transport = NULL;
    const char *psz_playnow = NULL; /* support option: x-playNow */
    const char *psz_session = NULL;
    const char *psz_cseq = NULL;
909
    rtsp_client_t *p_rtsp;
910
    int i_port = 0;
911
    int i_cseq = 0;
912 913 914

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

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

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

    switch( query->i_type )
    {
925 926
        case HTTPD_MSG_SETUP:
        {
927
            psz_playnow = httpd_MsgGet( query, "x-playNow" );
928 929 930 931 932 933
            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=" ) )
            {
934
                rtsp_client_t *p_rtsp = NULL;
935
                char ip[NI_MAXNUMERICHOST];
936
                i_port = atoi( strstr( psz_transport, "client_port=" ) +
937 938
                                strlen("client_port=") );

939 940 941
                if( strstr( psz_transport, "MP2T/H2221/UDP" ) ||
                    strstr( psz_transport, "RAW/RAW/UDP" ) )
                {
942 943 944
                    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 );
945
                    p_media->b_raw = VLC_TRUE;
946
                }
947

948 949 950 951 952 953 954 955 956 957
                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",
958
                         ip, i_port );
959 960 961 962

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

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

1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
                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 );
1020 1021 1022 1023 1024 1025 1026 1027
            }
            else /* TODO  strstr( psz_transport, "interleaved" ) ) */
            {
                answer->i_status = 461;
                answer->psz_status = strdup( "Unsupported Transport" );
                answer->i_body = 0;
                answer->p_body = NULL;
            }
1028

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

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

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

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

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

1062 1063
            if( p_rtsp->b_playing )
            {
1064 1065
                const char *psz_position = httpd_MsgGet( query, "Range" );
                const char *psz_scale = httpd_MsgGet( query, "Scale" );
1066
                if( psz_position ) psz_position = strstr( psz_position, "npt=" );
1067
                if( psz_position && !psz_scale )
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078
                {
                    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;
1079
                        CommandPush( p_vod, RTSP_CMD_TYPE_SEEK, p_media, psz_session, f_pos, NULL );
1080 1081 1082
                    }
                    break;
                }
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
                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 );
1095
                            CommandPush( p_vod, RTSP_CMD_TYPE_REWIND, p_media, psz_session, f_scale, NULL );
1096 1097 1098 1099
                        }
                        else if(psz_scale[0] != '1' ) /* fast-forward */
                        {
                            msg_Dbg( p_vod, "fastforward request: %s", psz_scale );
1100
                            CommandPush( p_vod, RTSP_CMD_TYPE_FORWARD, p_media, psz_session, f_scale, NULL );
1101 1102 1103 1104 1105
                        }

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

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

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

1123
            p_rtsp->b_playing = VLC_TRUE;
1124 1125

            /* FIXME for != 1 video and 1 audio */
1126
            for( i = 0; i < p_rtsp->i_es; i++ )
1127
            {
1128 1129 1130 1131
                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;
1132
            }
1133

1134 1135
            if( p_media->psz_mux )
            {
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145
                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 );
                }
1146 1147 1148 1149 1150 1151 1152
            }
            else
            {
                asprintf( &psz_output, "rtp{dst=%s,port-video=%i,"
                          "port-audio=%i}", ip, i_port_video, i_port_audio );
            }

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

1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181
        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;
        }

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

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

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

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

        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);

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

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

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

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

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

    if( psz_session )
    {
1235 1236 1237 1238 1239 1240
         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 );
    } 
1241 1242 1243 1244

    return VLC_SUCCESS;
}

1245
static int RtspCallbackES( httpd_callback_sys_t *p_args, httpd_client_t *cl,
1246 1247
                           httpd_message_t *answer, httpd_message_t *query )
{
1248 1249
    media_es_t *p_es = (media_es_t*)p_args;
    vod_media_t *p_media = p_es->p_media;
1250
    vod_t *p_vod = p_media->p_vod;
1251
    rtsp_client_t *p_rtsp = NULL;
1252 1253 1254 1255 1256
    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;
1257
    int i_cseq = 0;
1258
    int i;
1259 1260 1261

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

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

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

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

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

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

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

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

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