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 263 264 265 266 267 268
/*****************************************************************************
 * 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 );
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 = 0;
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 596 597 598 599 600 601 602 603 604 605 606 607
            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 )
608
                {
609
                    if( asprintf( &p_es->psz_fmtp,
610 611 612
                                  "packetization-mode=1;profile-level-id=%s;"
                                  "sprop-parameter-sets=%s,%s;", hexa, p_64_sps,
                                  p_64_pps ) < 0 )
613 614 615 616 617
                    {
                        free( p_64_sps );
                        free( p_64_pps );
                        free( psz_urlc );
                        free( p_es );
618
                        return VLC_ENOMEM;
619 620
                    }
                }
621 622
                free( p_64_sps );
                free( p_64_pps );
623 624 625
            }
            if( !p_es->psz_fmtp )
                p_es->psz_fmtp = strdup( "packetization-mode=1" );
626
            break;
627 628 629 630 631 632 633
        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
634 635 636
                if( asprintf( &p_es->psz_fmtp,
                              "profile-level-id=3; config=%s;", p_hexa ) == -1 )
                    p_es->psz_fmtp = NULL;
637 638 639 640 641
                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
642 643
            if( asprintf( &p_es->psz_rtpmap, "mpeg4-generic/%d", p_fmt->audio.i_rate ) == -1 )
                p_es->psz_rtpmap = NULL;
644 645 646 647
            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
648 649 650 651 652
                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;
653 654 655 656
                free( p_hexa );
            }
            break;
        case VLC_FOURCC( 'm', 'p', '2', 't' ):
657
            p_media->psz_mux = strdup("ts");
658 659 660 661
            p_es->i_payload_type = 33;
            p_es->psz_rtpmap = strdup( "MP2T/90000" );
            break;
        case VLC_FOURCC( 'm', 'p', '2', 'p' ):
662
            p_media->psz_mux = strdup("ps");
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
            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;
678

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

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

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

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

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

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

    p_es->i_port = 0;
#endif
740 741 742 743 744 745 746

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

747 748 749 750 751
    return VLC_SUCCESS;
}

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

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

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

773 774
    free( p_es->psz_rtpmap );
    free( p_es->psz_fmtp );
775
    p_media->i_sdp_version++;
776 777

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

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

821
    while( vlc_object_alive (p_vod) )
822 823 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
    {
        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 );
884 885
        free( cmd.psz_session );
        free( cmd.psz_arg );
886
    }
887 888

    vlc_restorecancel (canc);
889
    return NULL;
890 891
}

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

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

903 904
    p_rtsp->psz_session = psz_session;
    TAB_APPEND( p_media->i_rtsp, p_media->rtsp, p_rtsp );
905

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

910
    return p_rtsp;
911 912
}

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

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

    return NULL;
}

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

932 933
    while( p_rtsp->i_es-- )
    {
934
        free( p_rtsp->es[p_rtsp->i_es]->psz_ip );
935 936
        free( p_rtsp->es[p_rtsp->i_es] );
        if( !p_rtsp->i_es ) free( p_rtsp->es );
937 938
    }

939
    TAB_REMOVE( p_media->i_rtsp, p_media->rtsp, p_rtsp );
940

941 942
    free( p_rtsp->psz_session );
    free( p_rtsp );
943 944
}

945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967

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


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

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

983
    msg_Dbg( p_vod, "RtspCallback query: type=%d", query->i_type );
984 985 986 987

    answer->i_proto   = HTTPD_PROTO_RTSP;
    answer->i_version = query->i_version;
    answer->i_type    = HTTPD_MSG_ANSWER;
988
    answer->i_body    = 0;
989
    answer->p_body    = NULL;
990 991 992

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

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

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

1021 1022 1023 1024 1025 1026 1027 1028 1029
                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",
1030
                         ip, i_port );
1031 1032 1033 1034

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

                    p_rtsp = RtspClientNew( p_media, psz_new );
1049 1050 1051 1052 1053 1054 1055
                    if( !p_rtsp )
                    {
                        answer->i_status = 454;
                        answer->i_body = 0;
                        answer->p_body = NULL;
                        break;
                    }
1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
                }
                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;

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

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

        case HTTPD_MSG_PLAY:
        {
1107
            char *psz_output, ip[NI_MAXNUMERICHOST];
1108
            int i, i_port_audio = 0, i_port_video = 0;
1109 1110

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

1118 1119
            if( !psz_session )
                psz_session = httpd_MsgGet( query, "Session" );
1120 1121
            msg_Dbg( p_vod, "HTTPD_MSG_PLAY for session: %s", psz_session );

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

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

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

1180 1181
            if( p_rtsp->b_playing && p_rtsp->b_paused )
            {
1182 1183
                CommandPush( p_vod, RTSP_CMD_TYPE_PAUSE, p_media,
                             psz_session, 0, NULL );
1184
                p_rtsp->b_paused = false;
1185 1186 1187 1188
                break;
            }
            else if( p_rtsp->b_playing ) break;

1189
            if( httpd_ClientIP( cl, ip ) == NULL ) break;
1190