rtsp.c 49.5 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
dionoea's avatar
dionoea 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>
zorglub's avatar
zorglub committed
35 36
#include <vlc_input.h>
#include <vlc_sout.h>
37
#include <vlc_block.h>
38

39 40 41
#include <vlc_httpd.h>
#include <vlc_vod.h>
#include <vlc_url.h>
zorglub's avatar
zorglub committed
42 43
#include <vlc_network.h>
#include <vlc_charset.h>
Christophe Mutricy's avatar
Christophe Mutricy committed
44
#include <vlc_strings.h>
45
#include <vlc_rand.h>
46

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

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

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

Pierre Ynard's avatar
Pierre Ynard committed
61 62
#define THROTTLE_TEXT N_( "Maximum number of connections" )
#define THROTTLE_LONGTEXT N_( "This limits the maximum number of clients " \
63
    "that can connect to the RTSP VOD. 0 means no limit."  )
64

65 66
#define RAWMUX_TEXT N_( "MUX for RAW RTSP transport" )

67 68 69 70 71 72 73
#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." )

74 75 76 77 78 79 80 81
vlc_module_begin ()
    set_shortname( N_("RTSP VoD" ) )
    set_description( N_("RTSP VoD server") )
    set_category( CAT_SOUT )
    set_subcategory( SUBCAT_SOUT_VOD )
    set_capability( "vod server", 1 )
    set_callbacks( Open, Close )
    add_shortcut( "rtsp" )
82
    add_string( "rtsp-raw-mux", "ts", RAWMUX_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
83
                RAWMUX_TEXT, true )
84
    add_integer( "rtsp-throttle-users", 0, THROTTLE_TEXT,
Pierre Ynard's avatar
Pierre Ynard committed
85
                 THROTTLE_LONGTEXT, true )
86
    add_integer( "rtsp-session-timeout", 5, SESSION_TIMEOUT_TEXT,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
87
                 SESSION_TIMEOUT_LONGTEXT, true )
88
vlc_module_end ()
89 90 91 92 93

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

94 95 96 97 98 99 100 101 102
typedef struct media_es_t media_es_t;

typedef struct
{
    media_es_t *p_media_es;
    int i_port;

} rtsp_client_es_t;

103 104 105 106
typedef struct
{
    char *psz_session;

107
    bool b_playing; /* is it in "play" state */
108
    int i_port_raw;
109 110 111

    int i_es;
    rtsp_client_es_t **es;
112 113 114

} rtsp_client_t;

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

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

    vod_media_t *p_media;

125 126
    es_format_t fmt;
    uint8_t     i_payload_type;
127 128 129
    const char  *psz_ptname;
    unsigned    i_clock_rate;
    unsigned    i_channels;
130 131
    char        *psz_fmtp;

132
};
133 134 135

struct vod_media_t
{
136 137
    int id;

138 139 140 141 142
    /* VoD server */
    vod_t *p_vod;

    /* RTSP server */
    httpd_url_t  *p_rtsp_url;
143 144
    char         *psz_rtsp_control_v4;
    char         *psz_rtsp_control_v6;
145 146
    char         *psz_rtsp_path;

147
    vlc_mutex_t lock;
148 149 150 151

    /* ES list */
    int        i_es;
    media_es_t **es;
Pierre Ynard's avatar
Pierre Ynard committed
152
    const char *psz_mux;
153
    bool  b_raw;
154 155 156 157

    /* RTSP client */
    int           i_rtsp;
    rtsp_client_t **rtsp;
158 159

    /* Infos */
160
    mtime_t i_length;
161 162 163 164 165 166 167
};

struct vod_sys_t
{
    /* RTSP server */
    httpd_host_t *p_rtsp_host;
    int i_port;
168
    int i_throttle_users;
169
    int i_connections;
170

171 172
    char *psz_raw_mux;

173 174
    int i_session_timeout;

175
    /* List of media */
176
    int i_media_id;
177 178
    int i_media;
    vod_media_t **media;
179 180

    /* */
181
    vlc_thread_t thread;
182
    block_fifo_t *p_fifo_cmd;
183 184
};

185 186 187 188 189 190 191 192 193 194 195
/* 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,
sebastien's avatar
sebastien committed
196 197 198

    RTSP_CMD_TYPE_ADD,
    RTSP_CMD_TYPE_DEL,
199 200
} rtsp_cmd_type_t;

sebastien's avatar
sebastien committed
201 202 203 204 205
/* */
typedef struct
{
    int i_type;
    int i_media_id;
sebastien's avatar
sebastien committed
206
    vod_media_t *p_media;
sebastien's avatar
sebastien committed
207 208
    char *psz_session;
    char *psz_arg;
209
    int64_t i_arg;
sebastien's avatar
sebastien committed
210 211 212
    double f_arg;
} rtsp_cmd_t;

213
static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * );
214
static void         MediaDel( vod_t *, vod_media_t * );
sebastien's avatar
sebastien committed
215
static void         MediaAskDel ( vod_t *, vod_media_t * );
216 217 218
static int          MediaAddES( vod_t *, vod_media_t *, es_format_t * );
static void         MediaDelES( vod_t *, vod_media_t *, es_format_t * );

219
static void* CommandThread( void * );
220 221
static void  CommandPush( vod_t *, rtsp_cmd_type_t, vod_media_t *,
                          const char *psz_session, int64_t i_arg,
222
                          double f_arg, const char *psz_arg );
223

224
static rtsp_client_t *RtspClientNew( vod_media_t *, char * );
225
static rtsp_client_t *RtspClientGet( vod_media_t *, const char * );
226 227 228
static void           RtspClientDel( vod_media_t *, rtsp_client_t * );

static int RtspCallback( httpd_callback_sys_t *, httpd_client_t *,
229
                         httpd_message_t *, const httpd_message_t * );
230
static int RtspCallbackES( httpd_callback_sys_t *, httpd_client_t *,
231
                           httpd_message_t *, const httpd_message_t * );
232

233
static char *SDPGenerate( const vod_media_t *, httpd_client_t *cl );
234

235 236 237 238
static void sprintf_hexa( char *s, uint8_t *p_data, int i_data )
{
    static const char hex[16] = "0123456789abcdef";

239
    for( int i = 0; i < i_data; i++ )
240 241 242 243 244 245 246
    {
        s[2*i+0] = hex[(p_data[i]>>4)&0xf];
        s[2*i+1] = hex[(p_data[i]   )&0xf];
    }
    s[2*i_data] = '\0';
}

247 248 249 250 251 252
/*****************************************************************************
 * Open: Starts the RTSP server module
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
    vod_t *p_vod = (vod_t *)p_this;
ivoire's avatar
ivoire committed
253
    vod_sys_t *p_sys = NULL;
254 255 256 257 258

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

259
    p_sys->i_session_timeout = var_CreateGetInteger( p_this, "rtsp-session-timeout" );
260

261
    p_sys->i_throttle_users = var_CreateGetInteger( p_this, "rtsp-throttle-users" );
262
    msg_Dbg( p_this, "allowing up to %d connections", p_sys->i_throttle_users );
263 264
    p_sys->i_connections = 0;

265
    p_sys->psz_raw_mux = var_CreateGetString( p_this, "rtsp-raw-mux" );
266

267
    p_sys->p_rtsp_host = vlc_rtsp_HostNew( VLC_OBJECT(p_vod), 554 );
268 269
    if( !p_sys->p_rtsp_host )
    {
270
        msg_Err( p_vod, "cannot create RTSP server" );
271 272 273
        goto error;
    }

274
    p_sys->i_port = 554;
275 276 277

    TAB_INIT( p_sys->i_media, p_sys->media );
    p_sys->i_media_id = 0;
278 279

    p_vod->pf_media_new = MediaNew;
sebastien's avatar
sebastien committed
280
    p_vod->pf_media_del = MediaAskDel;
281

282
    p_sys->p_fifo_cmd = block_FifoNew();
283
    if( vlc_clone( &p_sys->thread, CommandThread, p_vod, VLC_THREAD_PRIORITY_LOW ) )
284 285 286 287 288 289
    {
        msg_Err( p_vod, "cannot spawn rtsp vod thread" );
        block_FifoRelease( p_sys->p_fifo_cmd );
        goto error;
    }

290 291
    return VLC_SUCCESS;

292
error:
ivoire's avatar
ivoire committed
293 294 295 296 297 298
    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 );
    }
299 300 301 302 303 304 305 306 307 308 309
    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;

310
    /* Stop command thread */
311
    CommandPush( p_vod, RTSP_CMD_TYPE_NONE, NULL, NULL, 0, 0.0, NULL );
312
    vlc_join( p_sys->thread, NULL );
313

sebastien's avatar
sebastien committed
314 315
    while( block_FifoCount( p_sys->p_fifo_cmd ) > 0 )
    {
316 317
        rtsp_cmd_t cmd;
        block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd );
Pierre Ynard's avatar
Pierre Ynard committed
318 319 320 321 322 323
        memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) );
        block_Release( p_block_cmd );
        if ( cmd.i_type == RTSP_CMD_TYPE_DEL )
            MediaDel(p_vod, cmd.p_media);
        free( cmd.psz_session );
        free( cmd.psz_arg );
sebastien's avatar
sebastien committed
324
    }
325 326
    block_FifoRelease( p_sys->p_fifo_cmd );

327
    httpd_HostDelete( p_sys->p_rtsp_host );
328
    var_Destroy( p_this, "rtsp-session-timeout" );
329
    var_Destroy( p_this, "rtsp-throttle-users" );
330
    var_Destroy( p_this, "rtsp-raw-mux" );
331

332 333 334 335 336
    /* 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 );

337
    free( p_sys->psz_raw_mux );
338 339 340 341 342 343
    free( p_sys );
}

/*****************************************************************************
 * Media handling
 *****************************************************************************/
344
static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name,
345
                              input_item_t *p_item )
346
{
ivoire's avatar
ivoire committed
347
    vod_sys_t *p_sys = p_vod->p_sys;
348

ivoire's avatar
ivoire committed
349
    vod_media_t *p_media = calloc( 1, sizeof(vod_media_t) );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
350 351 352
    if( !p_media )
        return NULL;

353 354
    p_media->id = p_sys->i_media_id++;
    TAB_INIT( p_media->i_es, p_media->es );
ivoire's avatar
ivoire committed
355
    p_media->psz_mux = NULL;
356
    TAB_INIT( p_media->i_rtsp, p_media->rtsp );
357
    p_media->b_raw = false;
358

359
    p_media->psz_rtsp_path = strdup( psz_name );
360
    p_media->p_rtsp_url =
361
        httpd_UrlNewUnique( p_sys->p_rtsp_host, psz_name, NULL, NULL, NULL );
362

363
    if( !p_media->psz_rtsp_path || !p_media->p_rtsp_url )
364
    {
365 366 367
        msg_Err( p_vod, "cannot create RTSP url (%s)", psz_name );
        if( p_media->p_rtsp_url )
            httpd_UrlDelete( p_media->p_rtsp_url );
368 369
        free( p_media->psz_rtsp_path );
        free( p_media );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
370
        return NULL;
371 372
    }

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

375
    if( asprintf( &p_media->psz_rtsp_control_v4,
376
               "rtsp://%%s:%d%s/trackID=%%d",
377
               p_sys->i_port, p_media->psz_rtsp_path ) < 0 )
ivoire's avatar
ivoire committed
378 379 380 381
    {
        httpd_UrlDelete( p_media->p_rtsp_url );
        free( p_media->psz_rtsp_path );
        free( p_media );
382
        return NULL;
ivoire's avatar
ivoire committed
383
    }
384
    if( asprintf( &p_media->psz_rtsp_control_v6,
385
               "rtsp://[%%s]:%d%s/trackID=%%d",
386
              p_sys->i_port, p_media->psz_rtsp_path ) < 0 )
ivoire's avatar
ivoire committed
387 388 389 390
    {
        httpd_UrlDelete( p_media->p_rtsp_url );
        free( p_media->psz_rtsp_path );
        free( p_media );
391
        return NULL;
ivoire's avatar
ivoire committed
392
    }
393

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

    p_media->p_vod = p_vod;

409
    vlc_mutex_init( &p_media->lock );
Pierre Ynard's avatar
Pierre Ynard committed
410

411
    p_media->i_length = input_item_GetDuration( p_item );
412

413 414
    vlc_mutex_lock( &p_item->lock );
    msg_Dbg( p_vod, "media has %i declared ES", p_item->i_es );
415
    for( int i = 0; i < p_item->i_es; i++ )
416 417 418
    {
        MediaAddES( p_vod, p_media, p_item->es[i] );
    }
419 420
    vlc_mutex_unlock( &p_item->lock );

421
    CommandPush( p_vod, RTSP_CMD_TYPE_ADD, p_media, NULL, 0, 0.0, NULL );
422 423 424
    return p_media;
}

sebastien's avatar
sebastien committed
425 426
static void MediaAskDel ( vod_t *p_vod, vod_media_t *p_media )
{
427
    CommandPush( p_vod, RTSP_CMD_TYPE_DEL, p_media, NULL, 0, 0.0, NULL );
sebastien's avatar
sebastien committed
428 429
}

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

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

436 437
    TAB_REMOVE( p_sys->i_media, p_sys->media, p_media );

438 439
    httpd_UrlDelete( p_media->p_rtsp_url );

440 441 442
    while( p_media->i_rtsp > 0 )
        RtspClientDel( p_media, p_media->rtsp[0] );
    TAB_CLEAN( p_media->i_rtsp, p_media->rtsp );
Laurent Aimar's avatar
Laurent Aimar committed
443

ivoire's avatar
ivoire committed
444 445 446
    free( p_media->psz_rtsp_path );
    free( p_media->psz_rtsp_control_v6 );
    free( p_media->psz_rtsp_control_v4 );
447

448 449 450
    while( p_media->i_es )
        MediaDelES( p_vod, p_media, &p_media->es[0]->fmt );
    TAB_CLEAN( p_media->i_es, p_media->es );
451 452

    vlc_mutex_destroy( &p_media->lock );
453

454 455 456 457 458
    free( p_media );
}

static int MediaAddES( vod_t *p_vod, vod_media_t *p_media, es_format_t *p_fmt )
{
459
    char *psz_urlc;
460

ivoire's avatar
ivoire committed
461 462 463
    media_es_t *p_es = calloc( 1, sizeof(media_es_t) );
    if( !p_es )
        return VLC_ENOMEM;
464

465
    p_media->psz_mux = NULL;
466 467

    /* TODO: update SDP, etc... */
468 469
    if( asprintf( &psz_urlc, "%s/trackID=%d",
              p_media->psz_rtsp_path, p_media->i_es ) < 0 )
ivoire's avatar
ivoire committed
470 471
    {
        free( p_es );
472
        return VLC_ENOMEM;
ivoire's avatar
ivoire committed
473
    }
474
    msg_Dbg( p_vod, "  - ES %4.4s (%s)", (char *)&p_fmt->i_codec, psz_urlc );
475

476 477 478
    /* Dynamic payload. No conflict since we put each ES in its own
     * RTP session */
    p_es->i_payload_type = 96;
479 480 481
    p_es->i_clock_rate = 90000;
    p_es->i_channels = 1;

482
    switch( p_fmt->i_codec )
483
    {
484
        case VLC_CODEC_S16B:
485 486 487 488
            if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 44100 )
            {
                p_es->i_payload_type = 11;
            }
489
            else if( p_fmt->audio.i_channels == 2 &&
490
                     p_fmt->audio.i_rate == 44100 )
491 492 493
            {
                p_es->i_payload_type = 10;
            }
494 495 496
            p_es->psz_ptname = "L16";
            p_es->i_clock_rate = p_fmt->audio.i_rate;
            p_es->i_channels = p_fmt->audio.i_channels;
497
            break;
498
        case VLC_CODEC_U8:
499 500 501
            p_es->psz_ptname = "L8";
            p_es->i_clock_rate = p_fmt->audio.i_rate;
            p_es->i_channels = p_fmt->audio.i_channels;
502
            break;
503
        case VLC_CODEC_MPGA:
504
            p_es->i_payload_type = 14;
505
            p_es->psz_ptname = "MPA";
506
            break;
507
        case VLC_CODEC_MPGV:
508
            p_es->i_payload_type = 32;
509
            p_es->psz_ptname = "MPV";
510
            break;
511
        case VLC_CODEC_A52:
512 513
            p_es->psz_ptname = "ac3";
            p_es->i_clock_rate = p_fmt->audio.i_rate;
514
            break;
515
        case VLC_CODEC_H263:
516
            p_es->psz_ptname = "H263-1998";
517
            break;
518
        case VLC_CODEC_H264:
519
            p_es->psz_ptname = "H264";
520 521 522 523 524 525 526 527 528 529
            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];

530
                while( i_buffer > 4 )
531
                {
532
                    int i_offset    = 0;
533 534
                    int i_size      = 0;

535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
                    while( p_buffer[0] != 0 || p_buffer[1] != 0 ||
                           p_buffer[2] != 1 )
                    {
                        p_buffer++;
                        i_buffer--;
                        if( i_buffer == 0 ) break;
                    }

                    if( i_buffer < 4 || memcmp(p_buffer, "\x00\x00\x01", 3 ) )
                    {
                        /* No startcode found.. */
                        break;
                    }
                    p_buffer += 3;
                    i_buffer -= 3;

                    const int i_nal_type = p_buffer[0]&0x1f;

553
                    i_size = i_buffer;
554
                    for( i_offset = 0; i_offset+2 < i_buffer ; i_offset++)
555
                    {
556
                        if( !memcmp(p_buffer + i_offset, "\x00\x00\x01", 3 ) )
557 558
                        {
                            /* we found another startcode */
559 560
                            while( i_offset > 0 && 0 == p_buffer[ i_offset - 1 ] )
                                i_offset--;
561 562 563 564
                            i_size = i_offset;
                            break;
                        }
                    }
565 566 567 568 569 570 571

                    if( i_size == 0 )
                    {
                        /* No-info found in nal */
                        continue;
                    }

572 573
                    if( i_nal_type == 7 )
                    {
ivoire's avatar
ivoire committed
574
                        free( p_64_sps );
575
                        p_64_sps = vlc_b64_encode_binary( p_buffer, i_size );
576 577
                        /* XXX: nothing ensures that i_size >= 4 ?? */
                        sprintf_hexa( hexa, &p_buffer[1], 3 );
578 579 580
                    }
                    else if( i_nal_type == 8 )
                    {
ivoire's avatar
ivoire committed
581
                        free( p_64_pps );
582
                        p_64_pps = vlc_b64_encode_binary( p_buffer, i_size );
583 584 585 586 587 588
                    }
                    i_buffer -= i_size;
                    p_buffer += i_size;
                }
                /* */
                if( p_64_sps && p_64_pps )
ivoire's avatar
ivoire committed
589
                {
590
                    if( asprintf( &p_es->psz_fmtp,
591 592 593
                                  "packetization-mode=1;profile-level-id=%s;"
                                  "sprop-parameter-sets=%s,%s;", hexa, p_64_sps,
                                  p_64_pps ) < 0 )
ivoire's avatar
ivoire committed
594 595 596 597 598
                    {
                        free( p_64_sps );
                        free( p_64_pps );
                        free( psz_urlc );
                        free( p_es );
599
                        return VLC_ENOMEM;
ivoire's avatar
ivoire committed
600 601
                    }
                }
ivoire's avatar
ivoire committed
602 603
                free( p_64_sps );
                free( p_64_pps );
604 605 606
            }
            if( !p_es->psz_fmtp )
                p_es->psz_fmtp = strdup( "packetization-mode=1" );
607
            break;
608
        case VLC_CODEC_MP4V:
609
            p_es->psz_ptname = "MP4V-ES";
610 611 612 613
            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 );
ivoire's avatar
ivoire committed
614 615 616
                if( asprintf( &p_es->psz_fmtp,
                              "profile-level-id=3; config=%s;", p_hexa ) == -1 )
                    p_es->psz_fmtp = NULL;
617 618 619
                free( p_hexa );
            }
            break;
620
        case VLC_CODEC_MP4A:
621 622
            p_es->psz_ptname = "mpeg4-generic";
            p_es->i_clock_rate = p_fmt->audio.i_rate;
623 624 625 626
            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 );
ivoire's avatar
ivoire committed
627 628 629 630 631
                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;
632 633 634 635
                free( p_hexa );
            }
            break;
        case VLC_FOURCC( 'm', 'p', '2', 't' ):
Pierre Ynard's avatar
Pierre Ynard committed
636
            p_media->psz_mux = "ts";
637
            p_es->i_payload_type = 33;
638
            p_es->psz_ptname = "MP2T";
639 640
            break;
        case VLC_FOURCC( 'm', 'p', '2', 'p' ):
Pierre Ynard's avatar
Pierre Ynard committed
641
            p_media->psz_mux = "ps";
642
            p_es->psz_ptname = "MP2P";
643
            break;
644
        case VLC_CODEC_AMR_NB:
645 646 647 648
            p_es->psz_ptname = "AMR";
            p_es->i_clock_rate = 8000;
            if(p_fmt->audio.i_channels == 2 )
                p_es->i_channels = 2;
649 650
            p_es->psz_fmtp = strdup( "octet-align=1" );
            break;
651
        case VLC_CODEC_AMR_WB:
652 653 654 655
            p_es->psz_ptname = "AMR-WB";
            p_es->i_clock_rate = 16000;
            if(p_fmt->audio.i_channels == 2 )
                p_es->i_channels = 2;
656 657
            p_es->psz_fmtp = strdup( "octet-align=1" );
            break;
658

659 660 661
        default:
            msg_Err( p_vod, "cannot add this stream (unsupported "
                    "codec: %4.4s)", (char*)&p_fmt->i_codec );
ivoire's avatar
ivoire committed
662
            free( psz_urlc );
663 664
            free( p_es );
            return VLC_EGENERIC;
665 666 667
    }

    p_es->p_rtsp_url =
668 669
        httpd_UrlNewUnique( p_vod->p_sys->p_rtsp_host, psz_urlc, NULL, NULL,
                            NULL );
670 671 672

    if( !p_es->p_rtsp_url )
    {
673
        msg_Err( p_vod, "cannot create RTSP url (%s)", psz_urlc );
674 675 676
        free( psz_urlc );
        free( p_es );
        return VLC_EGENERIC;
677
    }
678 679 680 681
    free( psz_urlc );

    httpd_UrlCatch( p_es->p_rtsp_url, HTTPD_MSG_SETUP,
                    RtspCallbackES, (void*)p_es );
682 683 684 685 686 687
    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 );
688

689
    es_format_Copy( &p_es->fmt, p_fmt );
690 691 692
    p_es->p_vod = p_vod;
    p_es->p_media = p_media;

693 694 695 696
    vlc_mutex_lock( &p_media->lock );
    TAB_APPEND( p_media->i_es, p_media->es, p_es );
    vlc_mutex_unlock( &p_media->lock );

697 698 699 700 701
    return VLC_SUCCESS;
}

static void MediaDelES( vod_t *p_vod, vod_media_t *p_media, es_format_t *p_fmt)
{
702
    media_es_t *p_es = NULL;
703 704

    /* Find the ES */
705
    for( int i = 0; i < p_media->i_es; i++ )
706 707 708 709 710 711 712 713 714
    {
        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;
715

716 717 718
    msg_Dbg( p_vod, "  - Removing ES %4.4s", (char *)&p_fmt->i_codec );

    vlc_mutex_lock( &p_media->lock );
719
    TAB_REMOVE( p_media->i_es, p_media->es, p_es );
720
    vlc_mutex_unlock( &p_media->lock );
721

ivoire's avatar
ivoire committed
722
    free( p_es->psz_fmtp );
723 724

    if( p_es->p_rtsp_url ) httpd_UrlDelete( p_es->p_rtsp_url );
725
    es_format_Clean( &p_es->fmt );
Laurent Aimar's avatar
Laurent Aimar committed
726
    free( p_es );
727 728
}

729
static void CommandPush( vod_t *p_vod, rtsp_cmd_type_t i_type, vod_media_t *p_media, const char *psz_session, int64_t i_arg,
730 731 732 733 734 735 736
                         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;
sebastien's avatar
sebastien committed
737
    cmd.p_media = p_media;
738 739 740 741
    if( p_media )
        cmd.i_media_id = p_media->id;
    if( psz_session )
        cmd.psz_session = strdup(psz_session);
742
    cmd.i_arg = i_arg;
743 744 745 746 747 748 749 750 751 752
    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 );
}

753
static void* CommandThread( void *obj )
754
{
755
    vod_t *p_vod = (vod_t*)obj;
756
    vod_sys_t *p_sys = p_vod->p_sys;
757
    int canc = vlc_savecancel ();
758

759
    for( ;; )
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774
    {
        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;

sebastien's avatar
sebastien committed
775 776 777 778 779 780 781 782 783 784 785 786
        if ( cmd.i_type == RTSP_CMD_TYPE_ADD )
        {
            TAB_APPEND( p_sys->i_media, p_sys->media, cmd.p_media );
            goto next;
        }

        if ( cmd.i_type == RTSP_CMD_TYPE_DEL )
        {
            MediaDel(p_vod, cmd.p_media);
            goto next;
        }

787 788 789 790 791 792 793
        /* */
        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 )
sebastien's avatar
sebastien committed
794
        {
795
            goto next;
sebastien's avatar
sebastien committed
796
        }
797 798 799 800 801
        p_media = p_sys->media[i];

        switch( cmd.i_type )
        {
        case RTSP_CMD_TYPE_PLAY:
802
            cmd.i_arg = -1;
803
            vod_MediaControl( p_vod, p_media, cmd.psz_session,
804
                              VOD_MEDIA_PLAY, cmd.psz_arg, &cmd.i_arg );
805 806
            break;
        case RTSP_CMD_TYPE_PAUSE:
807
            cmd.i_arg = -1;
808
            vod_MediaControl( p_vod, p_media, cmd.psz_session,
809
                              VOD_MEDIA_PAUSE, &cmd.i_arg );
810 811 812 813 814 815 816 817
            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,
818
                              VOD_MEDIA_SEEK, cmd.i_arg );
819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
            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:
ivoire's avatar
ivoire committed
836 837
        free( cmd.psz_session );
        free( cmd.psz_arg );
838
    }
839 840

    vlc_restorecancel (canc);
841
    return NULL;
842 843
}

844 845 846 847 848
/****************************************************************************
 * RTSP server implementation
 ****************************************************************************/
static rtsp_client_t *RtspClientNew( vod_media_t *p_media, char *psz_session )
{
ivoire's avatar
ivoire committed
849
    rtsp_client_t *p_rtsp = calloc( 1, sizeof(rtsp_client_t) );
850

ivoire's avatar
ivoire committed
851 852
    if( !p_rtsp )
        return NULL;
853
    p_rtsp->es = 0;
854

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

858 859
    p_media->p_vod->p_sys->i_connections++;
    msg_Dbg( p_media->p_vod, "new session: %s, connections: %d",