rtsp.c 53.9 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 28
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

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

33
#include <vlc_common.h>
34
#include <vlc_plugin.h>
Clément Stenac's avatar
Clément Stenac committed
35 36
#include <vlc_input.h>
#include <vlc_sout.h>
37
#include <vlc_block.h>
38 39 40

#include "vlc_httpd.h"
#include "vlc_vod.h"
41
#include "vlc_url.h"
Clément Stenac's avatar
Clément Stenac committed
42 43
#include <vlc_network.h>
#include <vlc_charset.h>
Christophe Mutricy's avatar
Christophe Mutricy committed
44
#include <vlc_strings.h>
45

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
46
#include <errno.h>
47

48 49 50
#ifndef WIN32
# include <locale.h>
#endif
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
51

52 53 54 55
#ifdef HAVE_XLOCALE_H
# include <xlocale.h>
#endif

56 57 58 59 60 61
/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );

62
#define HOST_TEXT N_( "RTSP host address" )
63
#define HOST_LONGTEXT N_( \
64 65
    "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
66
    "interfaces (address 0.0.0.0), on port 554, with no path.\nTo listen " \
67
    "only on the local interface, use \"localhost\" as address." )
68

69
#define THROTLE_TEXT N_( "Maximum number of connections" )
70 71
#define THROTLE_LONGTEXT N_( "This limits the maximum number of clients " \
    "that can connect to the RTSP VOD. 0 means no limit."  )
72

73 74
#define RAWMUX_TEXT N_( "MUX for RAW RTSP transport" )

75 76 77 78 79 80 81
#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." )

82
vlc_module_begin();
83 84
    set_shortname( N_("RTSP VoD" ) );
    set_description( N_("RTSP VoD server") );
85 86
    set_category( CAT_SOUT );
    set_subcategory( SUBCAT_SOUT_VOD );
87 88 89
    set_capability( "vod server", 1 );
    set_callbacks( Open, Close );
    add_shortcut( "rtsp" );
90
    add_string ( "rtsp-host", NULL, NULL, HOST_TEXT, HOST_LONGTEXT, true );
91
    add_string( "rtsp-raw-mux", "ts", NULL, RAWMUX_TEXT,
92
                RAWMUX_TEXT, true );
93
    add_integer( "rtsp-throttle-users", 0, NULL, THROTLE_TEXT,
94
                                           THROTLE_LONGTEXT, true );
95
    add_integer( "rtsp-session-timeout", 5, NULL, SESSION_TIMEOUT_TEXT,
96
                 SESSION_TIMEOUT_LONGTEXT, true );
97 98 99 100 101 102
vlc_module_end();

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

103 104 105 106 107 108 109 110 111 112
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;

113 114 115 116 117
typedef struct
{
    char *psz_session;
    int64_t i_last; /* for timeout */

118 119
    bool b_playing; /* is it in "play" state */
    bool b_paused; /* is it in "pause" state */
120 121 122

    int i_es;
    rtsp_client_es_t **es;
123 124 125

} rtsp_client_t;

126
struct media_es_t
127 128 129 130 131
{
    /* VoD server */
    vod_t *p_vod;

    /* RTSP server */
132
    httpd_url_t *p_rtsp_url;
133 134 135

    vod_media_t *p_media;

136 137 138 139 140 141
    es_format_t fmt;
    int         i_port;
    uint8_t     i_payload_type;
    char        *psz_rtpmap;
    char        *psz_fmtp;

142
};
143 144 145

struct vod_media_t
{
146 147
    int id;

148 149 150 151 152
    /* VoD server */
    vod_t *p_vod;

    /* RTSP server */
    httpd_url_t  *p_rtsp_url;
153 154
    char         *psz_rtsp_control_v4;
    char         *psz_rtsp_control_v6;
155 156 157 158 159 160
    char         *psz_rtsp_path;

    int  i_port;
    int  i_port_audio;
    int  i_port_video;
    int  i_ttl;
161 162 163 164 165
    int  i_payload_type;

    int64_t i_sdp_id;
    int     i_sdp_version;

166
    bool b_multicast;
167

168
    vlc_mutex_t lock;
169 170 171 172

    /* ES list */
    int        i_es;
    media_es_t **es;
173
    char       *psz_mux;
174
    bool  b_raw;
175 176 177 178

    /* RTSP client */
    int           i_rtsp;
    rtsp_client_t **rtsp;
179 180 181 182 183 184

    /* Infos */
    char *psz_session_name;
    char *psz_session_description;
    char *psz_session_url;
    char *psz_session_email;
185
    mtime_t i_length;
186 187 188 189 190 191 192 193
};

struct vod_sys_t
{
    /* RTSP server */
    httpd_host_t *p_rtsp_host;
    char *psz_path;
    int i_port;
194
    int i_throttle_users;
195
    int i_connections;
196

197 198
    char *psz_raw_mux;

199 200
    int i_session_timeout;

201
    /* List of media */
202 203
    vlc_mutex_t lock_media;
    int i_media_id;
204 205
    int i_media;
    vod_media_t **media;
206 207 208

    /* */
    block_fifo_t *p_fifo_cmd;
209 210
};

211 212 213 214 215 216 217 218 219 220 221 222 223
/* 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;

224
static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * );
225 226 227 228
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 * );

229 230 231
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 );
232

233
static rtsp_client_t *RtspClientNew( vod_media_t *, char * );
234
static rtsp_client_t *RtspClientGet( vod_media_t *, const char * );
235 236 237
static void           RtspClientDel( vod_media_t *, rtsp_client_t * );

static int RtspCallback( httpd_callback_sys_t *, httpd_client_t *,
238
                         httpd_message_t *, const httpd_message_t * );
239
static int RtspCallbackES( httpd_callback_sys_t *, httpd_client_t *,
240
                           httpd_message_t *, const httpd_message_t * );
241

242
static char *SDPGenerate( const vod_media_t *, httpd_client_t *cl );
243

244 245 246 247 248 249 250 251 252 253 254 255 256
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';
}

257 258 259 260 261 262
/*****************************************************************************
 * Open: Starts the RTSP server module
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    vod_t *p_vod = (vod_t *)p_this;
263 264
    vod_sys_t *p_sys = NULL;
    char *psz_url = NULL;
265 266 267 268
    vlc_url_t url;

    psz_url = config_GetPsz( p_vod, "rtsp-host" );
    vlc_UrlParse( &url, psz_url, 0 );
269
    free( psz_url );
270 271 272 273 274 275 276

    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;

277
    p_sys->i_session_timeout = var_CreateGetInteger( p_this, "rtsp-session-timeout" );
278

279
    p_sys->i_throttle_users = var_CreateGetInteger( p_this, "rtsp-throttle-users" );
280
    msg_Dbg( p_this, "allowing up to %d connections", p_sys->i_throttle_users );
281 282
    p_sys->i_connections = 0;

283
    p_sys->psz_raw_mux = var_CreateGetString( p_this, "rtsp-raw-mux" );
284

285 286 287 288
    p_sys->p_rtsp_host =
        httpd_HostNew( VLC_OBJECT(p_vod), url.psz_host, url.i_port );
    if( !p_sys->p_rtsp_host )
    {
289
        msg_Err( p_vod, "cannot create RTSP server (%s:%i)",
290 291 292 293 294 295 296 297
                 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 );
298

299
    vlc_mutex_init( &p_sys->lock_media );
300 301 302

    TAB_INIT( p_sys->i_media, p_sys->media );
    p_sys->i_media_id = 0;
303 304 305 306 307 308

    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;

309
    p_sys->p_fifo_cmd = block_FifoNew();
310
    if( vlc_thread_create( p_vod, "rtsp vod thread", CommandThread,
311
                           VLC_THREAD_PRIORITY_LOW, false ) )
312 313 314 315 316 317 318
    {
        msg_Err( p_vod, "cannot spawn rtsp vod thread" );
        block_FifoRelease( p_sys->p_fifo_cmd );
        free( p_sys->psz_path );
        goto error;
    }

319 320
    return VLC_SUCCESS;

321
error:
322 323 324 325 326 327
    if( p_sys )
    {
        if( p_sys->p_rtsp_host ) httpd_HostDelete( p_sys->p_rtsp_host );
        free( p_sys->psz_raw_mux );
        free( p_sys );
    }
328
    vlc_UrlClean( &url );
329

330 331 332 333 334 335 336 337 338 339 340
    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;

341
    /* Stop command thread */
342
    vlc_object_kill( p_vod );
343 344 345 346 347
    CommandPush( p_vod, RTSP_CMD_TYPE_NONE, NULL, NULL, 0.0, NULL );
    vlc_thread_join( p_vod );

    block_FifoRelease( p_sys->p_fifo_cmd );

348
    httpd_HostDelete( p_sys->p_rtsp_host );
349
    var_Destroy( p_this, "rtsp-session-timeout" );
350
    var_Destroy( p_this, "rtsp-throttle-users" );
351
    var_Destroy( p_this, "rtsp-raw-mux" );
352

353 354 355 356 357 358 359
    /* 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
360
    free( p_sys->psz_path );
361
    free( p_sys->psz_raw_mux );
362 363 364 365 366 367
    free( p_sys );
}

/*****************************************************************************
 * Media handling
 *****************************************************************************/
368
static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name,
369
                              input_item_t *p_item )
370 371 372
{
    vod_sys_t *p_sys = p_vod->p_sys;
    vod_media_t *p_media = malloc( sizeof(vod_media_t) );
373
    int i;
374

Jean-Paul Saman's avatar
Jean-Paul Saman committed
375 376 377
    if( !p_media )
        return NULL;

378
    memset( p_media, 0, sizeof(vod_media_t) );
379 380
    p_media->id = p_sys->i_media_id++;
    TAB_INIT( p_media->i_es, p_media->es );
381
    p_media->psz_mux = NULL;
382
    TAB_INIT( p_media->i_rtsp, p_media->rtsp );
383
    p_media->b_raw = false;
384

385 386 387
    if( asprintf( &p_media->psz_rtsp_path, "%s%s",
                  p_sys->psz_path, psz_name ) <0 )
        return NULL;
388
    p_media->p_rtsp_url =
389 390
        httpd_UrlNewUnique( p_sys->p_rtsp_host, p_media->psz_rtsp_path, NULL,
                            NULL, NULL );
391 392 393

    if( !p_media->p_rtsp_url )
    {
394
        msg_Err( p_vod, "cannot create RTSP url (%s)", p_media->psz_rtsp_path);
395 396
        free( p_media->psz_rtsp_path );
        free( p_media );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
397
        return NULL;
398 399
    }

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

402
    if( asprintf( &p_media->psz_rtsp_control_v4,
403
               "a=control:rtsp://%%s:%d%s/trackID=%%d\r\n",
404
               p_sys->i_port, p_media->psz_rtsp_path ) < 0 )
Rémi Duraffort's avatar
Rémi Duraffort committed
405 406 407 408
    {
        httpd_UrlDelete( p_media->p_rtsp_url );
        free( p_media->psz_rtsp_path );
        free( p_media );
409
        return NULL;
Rémi Duraffort's avatar
Rémi Duraffort committed
410
    }
411
    if( asprintf( &p_media->psz_rtsp_control_v6,
412
               "a=control:rtsp://[%%s]:%d%s/trackID=%%d\r\n",
413
              p_sys->i_port, p_media->psz_rtsp_path ) < 0 )
Rémi Duraffort's avatar
Rémi Duraffort committed
414 415 416 417
    {
        httpd_UrlDelete( p_media->p_rtsp_url );
        free( p_media->psz_rtsp_path );
        free( p_media );
418
        return NULL;
Rémi Duraffort's avatar
Rémi Duraffort committed
419
    }
420

421 422
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_SETUP,
                    RtspCallback, (void*)p_media );
423 424 425 426 427 428
    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 );
429 430
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_GETPARAMETER,
                    RtspCallback, (void*)p_media );
431 432 433 434 435
    httpd_UrlCatch( p_media->p_rtsp_url, HTTPD_MSG_TEARDOWN,
                    RtspCallback, (void*)p_media );

    p_media->p_vod = p_vod;

436
    vlc_mutex_lock( &p_sys->lock_media );
437
    TAB_APPEND( p_sys->i_media, p_sys->media, p_media );
438
    vlc_mutex_unlock( &p_sys->lock_media );
439

440
    vlc_mutex_init( &p_media->lock );
441 442 443 444 445 446 447 448 449 450 451 452
    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;
453
    p_media->i_length = input_item_GetDuration( p_item );
454

455 456 457
    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++ )
458 459 460
    {
        MediaAddES( p_vod, p_media, p_item->es[i] );
    }
461 462
    vlc_mutex_unlock( &p_item->lock );

463 464 465 466 467 468 469
    return p_media;
}

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

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

472 473 474 475 476 477 478
    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 );
479

480
    httpd_UrlDelete( p_media->p_rtsp_url );
481 482 483
    free( p_media->psz_rtsp_path );
    free( p_media->psz_rtsp_control_v6 );
    free( p_media->psz_rtsp_control_v4 );
484

485 486 487
    while( p_media->i_es )
        MediaDelES( p_vod, p_media, &p_media->es[0]->fmt );
    TAB_CLEAN( p_media->i_es, p_media->es );
488 489

    vlc_mutex_destroy( &p_media->lock );
490

491 492 493 494 495
    free( p_media->psz_session_name );
    free( p_media->psz_session_description );
    free( p_media->psz_session_url );
    free( p_media->psz_session_email );
    free( p_media->psz_mux );
496 497 498 499 500 501
    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) );
502
    char *psz_urlc;
503

504
    if( !p_es ) return VLC_ENOMEM;
505
    memset( p_es, 0, sizeof(media_es_t) );
506

507
    free( p_media->psz_mux );
508
    p_media->psz_mux = NULL;
509 510

    /* TODO: update SDP, etc... */
511 512
    if( asprintf( &psz_urlc, "%s/trackID=%d",
              p_media->psz_rtsp_path, p_media->i_es ) < 0 )
513 514
    {
        free( p_es );
515
        return VLC_ENOMEM;
516
    }
517
    msg_Dbg( p_vod, "  - ES %4.4s (%s)", (char *)&p_fmt->i_codec, psz_urlc );
518

519
    switch( p_fmt->i_codec )
520
    {
521 522 523 524 525
        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;
            }
526
            else if( p_fmt->audio.i_channels == 2 &&
527
                     p_fmt->audio.i_rate == 44100 )
528 529 530 531 532 533 534
            {
                p_es->i_payload_type = 10;
            }
            else
            {
                p_es->i_payload_type = p_media->i_payload_type++;
            }
Rémi Duraffort's avatar
Rémi Duraffort committed
535 536 537
            if( asprintf( &p_es->psz_rtpmap, "L16/%d/%d", p_fmt->audio.i_rate,
                          p_fmt->audio.i_channels ) == -1 )
                p_es->psz_rtpmap = NULL;
538 539
            break;
        case VLC_FOURCC( 'u', '8', ' ', ' ' ):
540
            p_es->i_payload_type = p_media->i_payload_type++;
Rémi Duraffort's avatar
Rémi Duraffort committed
541 542 543
            if( asprintf( &p_es->psz_rtpmap, "L8/%d/%d", p_fmt->audio.i_rate,
                          p_fmt->audio.i_channels ) == -1 )
                p_es->psz_rtpmap = NULL;
544 545
            break;
        case VLC_FOURCC( 'm', 'p', 'g', 'a' ):
546
        case VLC_FOURCC( 'm', 'p', '3', ' ' ):
547
            p_es->i_payload_type = 14;
548
            p_es->psz_rtpmap = strdup( "MPA/90000" );
549 550 551 552 553 554 555
            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++;
Rémi Duraffort's avatar
Rémi Duraffort committed
556 557
            if( asprintf( &p_es->psz_rtpmap, "ac3/%d", p_fmt->audio.i_rate ) == -1 )
                p_es->psz_rtpmap = NULL;
558 559 560 561 562
            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;
563 564 565
        case VLC_FOURCC( 'h', '2', '6', '4' ):
            p_es->i_payload_type = p_media->i_payload_type++;
            p_es->psz_rtpmap = strdup( "H264/90000" );
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
            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 )
                    {
Rémi Duraffort's avatar
Rémi Duraffort committed
596
                        free( p_64_sps );
597 598 599 600 601
                        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 )
                    {
Rémi Duraffort's avatar
Rémi Duraffort committed
602
                        free( p_64_pps );
603 604 605 606 607 608 609
                        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 )
610
                {
611
                    if( asprintf( &p_es->psz_fmtp,
612 613 614
                                  "packetization-mode=1;profile-level-id=%s;"
                                  "sprop-parameter-sets=%s,%s;", hexa, p_64_sps,
                                  p_64_pps ) < 0 )
615 616 617 618 619
                    {
                        free( p_64_sps );
                        free( p_64_pps );
                        free( psz_urlc );
                        free( p_es );
620
                        return VLC_ENOMEM;
621 622
                    }
                }
623 624
                free( p_64_sps );
                free( p_64_pps );
625 626 627
            }
            if( !p_es->psz_fmtp )
                p_es->psz_fmtp = strdup( "packetization-mode=1" );
628
            break;
629 630 631 632 633 634 635
        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 );
                sprintf_hexa( p_hexa, p_fmt->p_extra, p_fmt->i_extra );
Rémi Duraffort's avatar
Rémi Duraffort committed
636 637 638
                if( asprintf( &p_es->psz_fmtp,
                              "profile-level-id=3; config=%s;", p_hexa ) == -1 )
                    p_es->psz_fmtp = NULL;
639 640 641 642 643
                free( p_hexa );
            }
            break;
        case VLC_FOURCC( 'm', 'p', '4', 'a' ):
            p_es->i_payload_type = p_media->i_payload_type++;
Rémi Duraffort's avatar
Rémi Duraffort committed
644 645
            if( asprintf( &p_es->psz_rtpmap, "mpeg4-generic/%d", p_fmt->audio.i_rate ) == -1 )
                p_es->psz_rtpmap = NULL;
646 647 648 649
            if( p_fmt->i_extra > 0 )
            {
                char *p_hexa = malloc( 2 * p_fmt->i_extra + 1 );
                sprintf_hexa( p_hexa, p_fmt->p_extra, p_fmt->i_extra );
Rémi Duraffort's avatar
Rémi Duraffort committed
650 651 652 653 654
                if( asprintf( &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 ) == -1 )
                    p_es->psz_fmtp = NULL;
655 656 657 658
                free( p_hexa );
            }
            break;
        case VLC_FOURCC( 'm', 'p', '2', 't' ):
659
            p_media->psz_mux = strdup("ts");
660 661 662 663
            p_es->i_payload_type = 33;
            p_es->psz_rtpmap = strdup( "MP2T/90000" );
            break;
        case VLC_FOURCC( 'm', 'p', '2', 'p' ):
664
            p_media->psz_mux = strdup("ps");
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679
            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;
680

681 682 683
        default:
            msg_Err( p_vod, "cannot add this stream (unsupported "
                    "codec: %4.4s)", (char*)&p_fmt->i_codec );
684
            free( psz_urlc );
685 686
            free( p_es );
            return VLC_EGENERIC;
687 688 689
    }

    p_es->p_rtsp_url =
690 691
        httpd_UrlNewUnique( p_vod->p_sys->p_rtsp_host, psz_urlc, NULL, NULL,
                            NULL );
692 693 694

    if( !p_es->p_rtsp_url )
    {
695
        msg_Err( p_vod, "cannot create RTSP url (%s)", psz_urlc );
696 697 698
        free( psz_urlc );
        free( p_es );
        return VLC_EGENERIC;
699
    }
700 701 702 703
    free( psz_urlc );

    httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_SETUP,
                    RtspCallbackES, (void*)p_es );
704 705 706 707 708 709
    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 );
710

711
    es_format_Copy( &p_es->fmt, p_fmt );
712 713 714
    p_es->p_vod = p_vod;
    p_es->p_media = p_media;

715
#if 0
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
    /* 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;
    }
738 739 740 741
#else

    p_es->i_port = 0;
#endif
742 743 744 745 746 747 748

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

749 750 751 752 753
    return VLC_SUCCESS;
}

static void MediaDelES( vod_t *p_vod, vod_media_t *p_media, es_format_t *p_fmt)
{
754
    media_es_t *p_es = NULL;
755 756 757 758 759 760 761 762 763 764 765 766 767
    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;
768

769 770 771
    msg_Dbg( p_vod, "  - Removing ES %4.4s", (char *)&p_fmt->i_codec );

    vlc_mutex_lock( &p_media->lock );
772
    TAB_REMOVE( p_media->i_es, p_media->es, p_es );
773
    vlc_mutex_unlock( &p_media->lock );
774

775 776
    free( p_es->psz_rtpmap );
    free( p_es->psz_fmtp );
777
    p_media->i_sdp_version++;
778 779

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

817
static void* CommandThread( vlc_object_t *p_this )
818 819 820
{
    vod_t *p_vod = (vod_t*)p_this;
    vod_sys_t *p_sys = p_vod->p_sys;
821
    int canc = vlc_savecancel ();
822

823
    while( vlc_object_alive (p_vod) )
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
    {
        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 );
886 887
        free( cmd.psz_session );
        free( cmd.psz_arg );
888
    }
889 890

    vlc_restorecancel (canc);
891
    return NULL;
892 893
}

894 895 896 897 898
/****************************************************************************
 * RTSP server implementation
 ****************************************************************************/
static rtsp_client_t *RtspClientNew( vod_media_t *p_media, char *psz_session )
{
899
    rtsp_client_t *p_rtsp = malloc( sizeof(rtsp_client_t) );
900 901

    if( !p_rtsp ) return NULL;
902 903
    memset( p_rtsp, 0, sizeof(rtsp_client_t) );
    p_rtsp->es = 0;
904

905 906
    p_rtsp->psz_session = psz_session;
    TAB_APPEND( p_media->i_rtsp, p_media->rtsp, p_rtsp );
907

908 909
    p_media->p_vod->p_sys->i_connections++;
    msg_Dbg( p_media->p_vod, "new session: %s, connections: %d",
910
             psz_session, p_media->p_vod->p_sys->i_throttle_users );
911

912
    return p_rtsp;
913 914
}

915
static rtsp_client_t *RtspClientGet( vod_media_t *p_media, const char *psz_session )
916 917 918
{
    int i;

919
    for( i = 0; psz_session && i < p_media->i_rtsp; i++ )
920 921 922 923 924 925 926 927
    {
        if( !strcmp( p_media->rtsp[i]->psz_session, psz_session ) )
            return p_media->rtsp[i];
    }

    return NULL;
}

928
static void RtspClientDel( vod_media_t *p_media, rtsp_client_t *p_rtsp )
929
{
930 931
    p_media->p_vod->p_sys->i_connections--;
    msg_Dbg( p_media->p_vod, "closing session: %s, connections: %d",
932
             p_rtsp->psz_session, p_media->p_vod->p_sys->i_throttle_users );
933

934
    while( p_rtsp->i_es )
935
    {
936
        p_rtsp->i_es--;
937
        free( p_rtsp->es[p_rtsp->i_es]->psz_ip );
938
        free( p_rtsp->es[p_rtsp->i_es] );
939
    }
940
    free( p_rtsp->es );
941

942
    TAB_REMOVE( p_media->i_rtsp, p_media->rtsp, p_rtsp );
943

944 945
    free( p_rtsp->psz_session );
    free( p_rtsp );
946 947
}

948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970

static float ParseNPT (const char *str)
{
     locale_t loc = newlocale (LC_NUMERIC_MASK, "C", NULL);
     locale_t oldloc = uselocale (loc);
     unsigned hour, min;
     float sec;

     if (sscanf (str, "%u:%u:%f", &hour, &min, &sec) == 3)
         sec += ((hour * 60) + min) * 60;
     else
     if (sscanf (str, "%f", &sec) != 1)
         sec = 0.;

     if (loc != (locale_t)0)
     {
         uselocale (oldloc);
         freelocale (loc);
     }
     return sec;
}


971
static int RtspCallback( httpd_callback_sys_t *p_args, httpd_client_t *cl,
972
                         httpd_message_t *answer, const httpd_message_t *query )
973 974 975
{
    vod_media_t *p_media = (vod_media_t*)p_args;
    vod_t *p_vod = p_media->p_vod;
976 977 978 979
    const char *psz_transport = NULL;
    const char *psz_playnow = NULL; /* support option: x-playNow */
    const char *psz_session = NULL;
    const char *psz_cseq = NULL;
980
    rtsp_client_t *p_rtsp;
981
    int i_port = 0;
982
    int i_cseq = 0;
983 984 985

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

986
    msg_Dbg( p_vod, "RtspCallback query: type=%d", query->i_type );
987 988 989 990

    answer->i_proto   = HTTPD_PROTO_RTSP;
    answer->i_version = query->i_version;
    answer->i_type    = HTTPD_MSG_ANSWER;
991
    answer->i_body    = 0;
992
    answer->p_body    = NULL;
993 994 995

    switch( query->i_type )
    {
996 997
        case HTTPD_MSG_SETUP:
        {
998
            psz_playnow = httpd_MsgGet( query, "x-playNow" );
999
            psz_transport = httpd_MsgGet( query, "Transport" );
1000 1001 1002 1003 1004
            if( psz_transport == NULL )
            {
                answer->i_status = 400;
                break;
            }
1005 1006 1007 1008 1009
            msg_Dbg( p_vod, "HTTPD_MSG_SETUP: transport=%s", psz_transport );

            if( strstr( psz_transport, "unicast" ) &&
                strstr( psz_transport, "client_port=" ) )
            {
1010
                rtsp_client_t *p_rtsp = NULL;
1011
                char ip[NI_MAXNUMERICHOST];
1012
                i_port = atoi( strstr( psz_transport, "client_port=" ) +
1013 1014
                                strlen("client_port=") );

1015 1016 1017
                if( strstr( psz_transport, "MP2T/H2221/UDP" ) ||
                    strstr( psz_transport, "RAW/RAW/UDP" ) )
                {
1018
                    free( p_media->psz_mux );
1019 1020
                    p_media->psz_mux = NULL;
                    p_media->psz_mux = strdup( p_vod->p_sys->psz_raw_mux );
1021
                    p_media->b_raw = true;
1022
                }
1023

1024 1025 1026 1027 1028 1029 1030 1031 1032
                if( httpd_ClientIP( cl, ip ) == NULL )
                {
                    answer->i_status = 500;
                    answer->i_body = 0;
                    answer->p_body = NULL;
                    break;
                }

                msg_Dbg( p_vod, "HTTPD_MSG_SETUP: unicast ip=%s port=%d",
1033
                         ip, i_port );
1034 1035 1036 1037

                psz_session = httpd_MsgGet( query, "Session" );
                if( !psz_session || !*psz_session )
                {
1038
                    char *psz_new;
1039 1040
                    if( ( p_vod->p_sys->i_throttle_users > 0 ) &&
                        ( p_vod->p_sys->i_connections >= p_vod->p_sys->i_throttle_users ) )
1041
                    {
1042
                        answer->i_status = 503;
1043 1044 1045 1046
                        answer->i_body = 0;
                        answer->p_body = NULL;
                        break;
                    }
1047 1048
                    if( asprintf( &psz_new, "%d", rand() ) < 0 )
                        return VLC_ENOMEM;
1049 1050 1051
                    psz_session = psz_new;

                    p_rtsp = RtspClientNew( p_media, psz_new );
1052 1053 1054 1055 1056 1057 1058
                    if( !p_rtsp )
                    {
                        answer->i_status = 454;
                        answer->i_body = 0;
                        answer->p_body = NULL;
                        break;
                    }
1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
                }
                else
                {
                    p_rtsp = RtspClientGet( p_media, psz_session );
                    if( !p_rtsp )
                    {
                        answer->i_status = 454;
                        answer->i_body = 0;
                        answer->p_body = NULL;
                        break;
                    }
                }

                answer->i_status = 200;
                answer->i_body = 0;
                answer->p_body = NULL;

1076 1077 1078 1079
                if( p_media->b_raw )
                {
                    if( strstr( psz_transport, "MP2T/H2221/UDP" ) )
                    {
1080
                        httpd_MsgAdd( answer, "Transport",
1081
                                      "MP2T/H2221/UDP;unicast;client_port=%d-%d",
1082 1083 1084 1085
                                      i_port, i_port + 1 );
                    }
                    else if( strstr( psz_transport, "RAW/RAW/UDP" ) )
                    {
1086
                        httpd_MsgAdd( answer, "Transport",
1087
                                      "RAW/RAW/UDP;unicast;client_port=%d-%d",
1088 1089 1090 1091
                                      i_port, i_port + 1 );
                    }
                }
                else
1092
                    httpd_MsgAdd( answer, "Transport",
1093
                                  "RTP/AVP/UDP;unicast;client_port=%d-%d",
1094
                                  i_port, i_port + 1 );
1095 1096 1097 1098 1099 1100 1101
            }
            else /* TODO  strstr( psz_transport, "interleaved" ) ) */
            {
                answer->i_status = 461;
                answer->i_body = 0;
                answer->p_body = NULL;
            }
1102

1103 1104
            /* Intentional fall-through on x-playNow option in RTSP request */
            if( !psz_playnow )
1105
                break;
1106 1107 1108 1109
        }

        case HTTPD_MSG_PLAY:
        {
1110
            char *psz_output, ip[NI_MAXNUMERICHOST];
1111
            int i, i_port_audio = 0, i_port_video = 0;
1112 1113

            /* for now only multicast so easy */
1114 1115 1116 1117 1118 1119
            if( !psz_playnow )
            {
                answer->i_status = 200;
                answer->i_body = 0;
                answer->p_body = NULL;
            }
1120

1121 1122
            if( !psz_session )
                psz_session = httpd_MsgGet( query, "Session" );
1123 1124
            msg_Dbg( p_vod, "HTTPD_MSG_PLAY for session: %s", psz_session );

1125
            p_rtsp = RtspClientGet( p_media, psz_session );
1126 1127 1128 1129 1130 1131 1132
            if( !p_rtsp )
            {
                answer->i_status = 500;
                answer->i_body = 0;
                answer->p_body = NULL;
                break;
            }
1133

1134 1135
            if( p_rtsp->b_playing )
            {
1136 1137
                const char *psz_position = httpd_MsgGet( query, "Range" );
                const char *psz_scale = httpd_MsgGet( query, "Scale" );
1138
                if( psz_position )
1139
                    psz_position = strstr( psz_position, "npt=" );
1140
                if( psz_position && !psz_scale )
1141
                {
1142
                    double f_pos = ParseNPT (psz_position + 4);
1143
                    msg_Dbg( p_vod, "seeking request: %s", psz_position );
1144 1145 1146
                    f_pos /= ((double)(p_media->i_length))/1000 /1000 / 100;
                    CommandPush( p_vod, RTSP_CMD_TYPE_SEEK, p_media,
                                 psz_session, f_pos, NULL );
1147 1148
                    break;
                }
1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
                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 );
1161
                            CommandPush( p_vod, RTSP_CMD_TYPE_REWIND, p_media,
1162
                                         psz_session, f_scale, NULL );
1163 1164 1165
                        }
                        else if(psz_scale[0] != '1' ) /* fast-forward */
                        {
1166
                            msg_Dbg( p_vod, "fastforward request: %s",
1167 1168 1169
                                     psz_scale );
                            CommandPush( p_vod, RTSP_CMD_TYPE_FORWARD, p_media,
                                         psz_session, f_scale, NULL );
1170 1171
                        }

1172
                        if( p_rtsp->b_paused == true )
1173
                        {
1174
                            p_rtsp->b_paused = false;
1175 1176
                            CommandPush( p_vod, RTSP_CMD_TYPE_PAUSE, p_media,
                                         psz_session, 0, NULL );
1177 1178 1179 1180
                        }
                    }
                    break;
                }
1181 1182
            }