live555.cpp 66.3 KB
Newer Older
1
/*****************************************************************************
2
 * live555.cpp : LIVE555 Streaming Media support.
3
 *****************************************************************************
4
 * Copyright (C) 2003-2007 the VideoLAN team
Carlo Calabrò's avatar
Carlo Calabrò committed
5
 * $Id$
6
7
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
8
9
 *          Derk-Jan Hartman <hartman at videolan. org>
 *          Derk-Jan Hartman <djhartman at m2x .dot. nl> for M2X
10
11
12
13
14
15
16
17
18
19
20
21
22
 *
 * 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
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
24
25
26
27
28
29
 *****************************************************************************/

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

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

34
35
36
#define __STDC_CONSTANT_MACROS 1
#include <inttypes.h>

37
#include <vlc/vlc.h>
38
#include <vlc_plugin.h>
39

zorglub's avatar
zorglub committed
40
41
42
#include <vlc_demux.h>
#include <vlc_interface.h>
#include <vlc_network.h>
43
#include <vlc_url.h>
44
45

#include <iostream>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
46
#include <limits.h>
47

48

gbazin's avatar
   
gbazin committed
49
50
51
52
#if defined( WIN32 )
#   include <winsock2.h>
#endif

53
#include "UsageEnvironment.hh"
54
55
56
57
#include "BasicUsageEnvironment.hh"
#include "GroupsockHelper.hh"
#include "liveMedia.hh"

58
59
60
61
extern "C" {
#include "../access/mms/asf.h"  /* Who said ugly ? */
}

62
63
64
65
66
using namespace std;

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
67
68
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );
69

hartman's avatar
hartman committed
70
#define CACHING_TEXT N_("Caching value (ms)")
Laurent Aimar's avatar
Laurent Aimar committed
71
#define CACHING_LONGTEXT N_( \
Carlo Calabrò's avatar
Carlo Calabrò committed
72
    "Allows you to modify the default caching value for RTSP streams. This " \
73
    "value should be set in millisecond units." )
Laurent Aimar's avatar
Laurent Aimar committed
74

75
#define KASENNA_TEXT N_( "Kasenna RTSP dialect")
76
#define KASENNA_LONGTEXT N_( "Kasenna servers use an old and unstandard " \
77
    "dialect of RTSP. When you set this parameter, VLC will try this dialect "\
78
    "for communication. In this mode you cannot connect to normal RTSP servers." )
79

80
81
82
83
84
85
86
#define USER_TEXT N_("RTSP user name")
#define USER_LONGTEXT N_("Allows you to modify the user name that will " \
    "be used for authenticating the connection.")
#define PASS_TEXT N_("RTSP password")
#define PASS_LONGTEXT N_("Allows you to modify the password that will be " \
    "used for the connection.")

87
vlc_module_begin();
88
    set_description( N_("RTP/RTSP/SDP demuxer (using Live555)" ) );
89
    set_capability( "demux", 50 );
zorglub's avatar
zorglub committed
90
    set_shortname( "RTP/RTSP");
Laurent Aimar's avatar
Laurent Aimar committed
91
    set_callbacks( Open, Close );
92
    add_shortcut( "live" );
93
    add_shortcut( "livedotcom" );
zorglub's avatar
zorglub committed
94
95
    set_category( CAT_INPUT );
    set_subcategory( SUBCAT_INPUT_DEMUX );
96
97

    add_submodule();
98
        set_description( N_("RTSP/RTP access and demux") );
99
        add_shortcut( "rtsp" );
sigmunau's avatar
sigmunau committed
100
        add_shortcut( "sdp" );
Laurent Aimar's avatar
Laurent Aimar committed
101
102
        set_capability( "access_demux", 0 );
        set_callbacks( Open, Close );
hartman's avatar
hartman committed
103
        add_bool( "rtsp-tcp", 0, NULL,
Carlo Calabrò's avatar
Carlo Calabrò committed
104
                  N_("Use RTP over RTSP (TCP)"),
105
                  N_("Use RTP over RTSP (TCP)"), true );
106
107
        add_integer( "rtp-client-port", -1, NULL,
                  N_("Client port"),
108
                  N_("Port to use for the RTP source of the session"), true );
109
110
        add_bool( "rtsp-http", 0, NULL,
                  N_("Tunnel RTSP and RTP over HTTP"),
111
                  N_("Tunnel RTSP and RTP over HTTP"), true );
112
113
        add_integer( "rtsp-http-port", 80, NULL,
                  N_("HTTP tunnel port"),
114
                  N_("Port to use for tunneling the RTSP/RTP over HTTP."),
115
                  true );
hartman's avatar
hartman committed
116
        add_integer("rtsp-caching", 4 * DEFAULT_PTS_DELAY / 1000, NULL,
117
118
119
                    CACHING_TEXT, CACHING_LONGTEXT, true );
        add_bool(   "rtsp-kasenna", false, NULL, KASENNA_TEXT,
                    KASENNA_LONGTEXT, true );
120
        add_string( "rtsp-user", NULL, NULL, USER_TEXT,
121
                    USER_LONGTEXT, true );
122
        add_string( "rtsp-pwd", NULL, NULL, PASS_TEXT,
123
                    PASS_LONGTEXT, true );
124
125
126
127
vlc_module_end();


/*****************************************************************************
Laurent Aimar's avatar
Laurent Aimar committed
128
 * Local prototypes
129
 *****************************************************************************/
130

131
132
typedef struct
{
133
134
    demux_t         *p_demux;
    MediaSubsession *sub;
135

136
137
    es_format_t     fmt;
    es_out_id_t     *p_es;
138

139
140
141
    bool      b_muxed;
    bool      b_quicktime;
    bool      b_asf;
142
    stream_t        *p_out_muxed;    /* for muxed stream */
143

144
145
    uint8_t         *p_buffer;
    unsigned int    i_buffer;
146

147
    bool      b_rtcp_sync;
148
149
150
    char            waiting;
    int64_t         i_pts;
    u_int32_t       i_start_seq;
151

152
153
} live_track_t;

154
155
156
157
158
struct timeout_thread_t
{
    VLC_COMMON_MEMBERS

    int64_t      i_remain;
159
    bool   b_handle_keep_alive;
160
161
162
    demux_sys_t  *p_sys;
};

163
164
struct demux_sys_t
{
hartman's avatar
hartman committed
165
166
    char            *p_sdp;    /* XXX mallocated */
    char            *psz_path; /* URL-encoded path */
167
    vlc_url_t       url;
168
169
170
171
172
173

    MediaSession     *ms;
    TaskScheduler    *scheduler;
    UsageEnvironment *env ;
    RTSPClient       *rtsp;

174
    /* */
175
176
177
    int              i_track;
    live_track_t     **track;   /* XXX mallocated */

178
    /* Weird formats */
179
180
    asf_header_t     asfh;
    stream_t         *p_out_asf;
181
    bool       b_real;
182
183

    /* */
184
185
186
187
    int64_t          i_pcr; /* The clock */
    int64_t          i_npt; /* The current time in the stream */
    int64_t          i_npt_length;
    int64_t          i_npt_start;
188

189
190
    /* timeout thread information */
    int              i_timeout;     /* session timeout value in seconds */
191
    bool       b_timeout_call;/* mark to send an RTSP call to prevent server timeout */
192
193
    timeout_thread_t *p_timeout;    /* the actual thread that makes sure we don't timeout */

194
    /* */
195
196
    bool       b_multicast;   /* true if one of the tracks is multicasted */
    bool       b_no_data;     /* true if we never receive any data */
197
198
    int              i_no_data_ti;  /* consecutive number of TaskInterrupt */

199
200
201
202
203
204
    char             event;
};

static int Demux  ( demux_t * );
static int Control( demux_t *, int, va_list );

hartman's avatar
hartman committed
205
206
207
208
209
static int Connect      ( demux_t * );
static int SessionsSetup( demux_t * );
static int Play         ( demux_t *);
static int ParseASF     ( demux_t * );
static int RollOverTcp  ( demux_t * );
210

hartman's avatar
hartman committed
211
212
213
static void StreamRead  ( void *, unsigned int, unsigned int,
                          struct timeval, unsigned int );
static void StreamClose ( void * );
214
static void TaskInterrupt( void * );
215

216
217
static void TimeoutPrevention( timeout_thread_t * );

218
static unsigned char* parseH264ConfigStr( char const* configStr,
219
                                          unsigned int& configSize );
220

221
222
223
/*****************************************************************************
 * DemuxOpen:
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
224
static int  Open ( vlc_object_t *p_this )
225
{
226
    demux_t     *p_demux = (demux_t*)p_this;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
227
    demux_sys_t *p_sys = NULL;
228

hartman's avatar
hartman committed
229
230
    MediaSubsessionIterator *iter   = NULL;
    MediaSubsession         *sub    = NULL;
231
    int i, i_return;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
232
    int i_error = VLC_EGENERIC;
233

Laurent Aimar's avatar
Laurent Aimar committed
234
    if( p_demux->s )
235
    {
Laurent Aimar's avatar
Laurent Aimar committed
236
237
        /* See if it looks like a SDP
           v, o, s fields are mandatory and in this order */
238
        const uint8_t *p_peek;
239
240
        if( stream_Peek( p_demux->s, &p_peek, 7 ) < 7 ) return VLC_EGENERIC;

241
242
        if( memcmp( p_peek, "v=0\r\n", 5 ) &&
            memcmp( p_peek, "v=0\n", 4 ) &&
Laurent Aimar's avatar
Laurent Aimar committed
243
244
245
246
            ( p_peek[0] < 'a' || p_peek[0] > 'z' || p_peek[1] != '=' ) )
        {
            return VLC_EGENERIC;
        }
247
    }
Laurent Aimar's avatar
Laurent Aimar committed
248
    else
249
    {
Laurent Aimar's avatar
Laurent Aimar committed
250
        var_Create( p_demux, "rtsp-caching", VLC_VAR_INTEGER|VLC_VAR_DOINHERIT );
251
252
    }

253
254
255
    p_demux->pf_demux  = Demux;
    p_demux->pf_control= Control;
    p_demux->p_sys     = p_sys = (demux_sys_t*)malloc( sizeof( demux_sys_t ) );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
256
257
    if( !p_sys ) return VLC_ENOMEM;

258
259
260
261
262
263
264
    p_sys->p_sdp = NULL;
    p_sys->scheduler = NULL;
    p_sys->env = NULL;
    p_sys->ms = NULL;
    p_sys->rtsp = NULL;
    p_sys->i_track = 0;
    p_sys->track   = NULL;
265
266
267
268
    p_sys->i_pcr = 0;
    p_sys->i_npt = 0;
    p_sys->i_npt_start = 0;
    p_sys->i_npt_length = 0;
269
    p_sys->p_out_asf = NULL;
270
    p_sys->b_no_data = true;
271
    p_sys->i_no_data_ti = 0;
272
273
    p_sys->p_timeout = NULL;
    p_sys->i_timeout = 0;
274
275
276
    p_sys->b_timeout_call = false;
    p_sys->b_multicast = false;
    p_sys->b_real = false;
hartman's avatar
hartman committed
277
    p_sys->psz_path = strdup( p_demux->psz_path );
278

279
280
281
    /* parse URL for rtsp://[user:[passwd]@]serverip:port/options */
    vlc_UrlParse( &p_sys->url, p_sys->psz_path, 0 );

282
283
    if( ( p_sys->scheduler = BasicTaskScheduler::createNew() ) == NULL )
    {
284
        msg_Err( p_demux, "BasicTaskScheduler::createNew failed" );
285
286
        goto error;
    }
287
    if( !( p_sys->env = BasicUsageEnvironment::createNew(*p_sys->scheduler) ) )
288
    {
289
        msg_Err( p_demux, "BasicUsageEnvironment::createNew failed" );
290
291
        goto error;
    }
Laurent Aimar's avatar
Laurent Aimar committed
292

293
    if( strcasecmp( p_demux->psz_access, "sdp" ) )
294
    {
295
        char *p = p_sys->psz_path;
296
        while( (p = strchr( p, ' ' )) != NULL ) *p = '+';
297
298
    }

hartman's avatar
hartman committed
299
    if( p_demux->s != NULL )
Laurent Aimar's avatar
Laurent Aimar committed
300
301
    {
        /* Gather the complete sdp file */
hartman's avatar
hartman committed
302
303
304
        int     i_sdp       = 0;
        int     i_sdp_max   = 1000;
        uint8_t *p_sdp      = (uint8_t*) malloc( i_sdp_max );
305

Jean-Paul Saman's avatar
Jean-Paul Saman committed
306
307
308
309
310
311
        if( !p_sdp )
        {
            i_error = VLC_ENOMEM;
            goto error;
        }

Laurent Aimar's avatar
Laurent Aimar committed
312
313
        for( ;; )
        {
314
315
            int i_read = stream_Read( p_demux->s, &p_sdp[i_sdp],
                                      i_sdp_max - i_sdp - 1 );
Laurent Aimar's avatar
Laurent Aimar committed
316

Jean-Paul Saman's avatar
Jean-Paul Saman committed
317
            if( p_demux->b_die || p_demux->b_error )
318
319
            {
                free( p_sdp );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
320
                goto error;
321
            }
Jean-Paul Saman's avatar
Jean-Paul Saman committed
322

Laurent Aimar's avatar
Laurent Aimar committed
323
324
325
            if( i_read < 0 )
            {
                msg_Err( p_demux, "failed to read SDP" );
326
                free( p_sdp );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
327
                goto error;
Laurent Aimar's avatar
Laurent Aimar committed
328
329
330
331
332
333
334
335
336
337
338
339
340
341
            }

            i_sdp += i_read;

            if( i_read < i_sdp_max - i_sdp - 1 )
            {
                p_sdp[i_sdp] = '\0';
                break;
            }

            i_sdp_max += 1000;
            p_sdp = (uint8_t*)realloc( p_sdp, i_sdp_max );
        }
        p_sys->p_sdp = (char*)p_sdp;
342
    }
343
344
    else if( ( p_demux->s == NULL ) &&
             !strcasecmp( p_demux->psz_access, "sdp" ) )
hartman's avatar
hartman committed
345
346
347
348
349
    {
        /* sdp:// link from SAP */
        p_sys->p_sdp = strdup( p_sys->psz_path );
    }
    else if( ( i_return = Connect( p_demux ) ) != VLC_SUCCESS )
350
    {
hartman's avatar
hartman committed
351
        msg_Err( p_demux, "Failed to connect with rtsp://%s", p_sys->psz_path );
352
353
        goto error;
    }
354

hartman's avatar
hartman committed
355
    if( p_sys->p_sdp == NULL )
356
    {
hartman's avatar
hartman committed
357
        msg_Err( p_demux, "Failed to retrieve the RTSP Session Description" );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
358
        i_error = VLC_ENOMEM;
hartman's avatar
hartman committed
359
        goto error;
360
361
    }

hartman's avatar
hartman committed
362
363
364
365
366
    if( ( i_return = SessionsSetup( p_demux ) ) != VLC_SUCCESS )
    {
        msg_Err( p_demux, "Nothing to play for rtsp://%s", p_sys->psz_path );
        goto error;
    }
367

368
    if( p_sys->b_real ) goto error;
369

hartman's avatar
hartman committed
370
371
    if( ( i_return = Play( p_demux ) ) != VLC_SUCCESS )
        goto error;
372

373
374
375
376
377
378
379
    if( p_sys->p_out_asf && ParseASF( p_demux ) )
    {
        msg_Err( p_demux, "cannot find a usable asf header" );
        /* TODO Clean tracks */
        goto error;
    }

Laurent Aimar's avatar
Laurent Aimar committed
380
381
382
    if( p_sys->i_track <= 0 )
        goto error;

383
384
385
    return VLC_SUCCESS;

error:
386
387
388
389
390
    for( i = 0; i < p_sys->i_track; i++ )
    {
        live_track_t *tk = p_sys->track[i];

        if( tk->b_muxed ) stream_DemuxDelete( tk->p_out_muxed );
391
        es_format_Clean( &tk->fmt );
392
393
394
395
396
        free( tk->p_buffer );
        free( tk );
    }

    if( p_sys->i_track ) free( p_sys->track );
397
    if( p_sys->p_out_asf ) stream_DemuxDelete( p_sys->p_out_asf );
398
    if( p_sys->rtsp && p_sys->ms ) p_sys->rtsp->teardownMediaSession( *p_sys->ms );
399
    if( p_sys->ms ) Medium::close( p_sys->ms );
hartman's avatar
hartman committed
400
    if( p_sys->rtsp ) RTSPClient::close( p_sys->rtsp );
401
    if( p_sys->env ) p_sys->env->reclaim();
402
403
    if( p_sys->p_timeout )
    {
404
        vlc_object_kill( p_sys->p_timeout );
405
406
        vlc_thread_join( p_sys->p_timeout );
        vlc_object_detach( p_sys->p_timeout );
407
        vlc_object_release( p_sys->p_timeout );
408
    }
409
410
411
    delete p_sys->scheduler;
    free( p_sys->p_sdp );
    free( p_sys->psz_path );
412

413
414
    vlc_UrlClean( &p_sys->url );

415
    free( p_sys );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
416
    return i_error;
417
418
419
420
421
}

/*****************************************************************************
 * DemuxClose:
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
422
static void Close( vlc_object_t *p_this )
423
{
424
    demux_t *p_demux = (demux_t*)p_this;
425
426
    demux_sys_t *p_sys = p_demux->p_sys;
    int i;
427
428
429
430
431

    for( i = 0; i < p_sys->i_track; i++ )
    {
        live_track_t *tk = p_sys->track[i];

432
        if( tk->b_muxed ) stream_DemuxDelete( tk->p_out_muxed );
433
        es_format_Clean( &tk->fmt );
434
        free( tk->p_buffer );
435
436
        free( tk );
    }
437
438
439

    if( p_sys->i_track ) free( p_sys->track );
    if( p_sys->p_out_asf ) stream_DemuxDelete( p_sys->p_out_asf );
440
441
442
443
    if( p_sys->rtsp && p_sys->ms ) p_sys->rtsp->teardownMediaSession( *p_sys->ms );
    if( p_sys->ms ) Medium::close( p_sys->ms );
    if( p_sys->rtsp ) RTSPClient::close( p_sys->rtsp );
    if( p_sys->env ) p_sys->env->reclaim();
444
445
    if( p_sys->p_timeout )
    {
446
        vlc_object_kill( p_sys->p_timeout );
447
448
        vlc_thread_join( p_sys->p_timeout );
        vlc_object_detach( p_sys->p_timeout );
449
        vlc_object_release( p_sys->p_timeout );
450
    }
451
452
453
    delete p_sys->scheduler;
    free( p_sys->p_sdp );
    free( p_sys->psz_path );
454

455
456
    vlc_UrlClean( &p_sys->url );

457
458
459
    free( p_sys );
}

hartman's avatar
hartman committed
460
461
462
463
464
465
466
/*****************************************************************************
 * Connect: connects to the RTSP server to setup the session DESCRIBE
 *****************************************************************************/
static int Connect( demux_t *p_demux )
{
    demux_sys_t *p_sys = p_demux->p_sys;
    Authenticator authenticator;
467
    bool b_firstpass  = true;
hartman's avatar
hartman committed
468
469
470
471
472
473
474
    char *psz_user    = NULL;
    char *psz_pwd     = NULL;
    char *psz_url     = NULL;
    char *psz_options = NULL;
    char *p_sdp       = NULL;
    int  i_http_port  = 0;
    int  i_ret        = VLC_SUCCESS;
475
    int i_lefttries;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
476

477
    if( p_sys->url.i_port == 0 ) p_sys->url.i_port = 554;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
478
479
    if( p_sys->url.psz_username || p_sys->url.psz_password )
    {
480
481
482
483
        int err;
        err = asprintf( &psz_url, "rtsp://%s:%d%s", p_sys->url.psz_host,
                        p_sys->url.i_port, p_sys->url.psz_path );
        if( err == -1 ) return VLC_ENOMEM;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
484
485
486
487
488
489

        psz_user = strdup( p_sys->url.psz_username );
        psz_pwd  = strdup( p_sys->url.psz_password );
    }
    else
    {
490
491
492
        int err;
        err = asprintf( &psz_url, "rtsp://%s", p_sys->psz_path );
        if( err == -1 ) return VLC_ENOMEM;
Jean-Paul Saman's avatar
Jean-Paul Saman committed
493
494
495
496
497

        psz_user = var_CreateGetString( p_demux, "rtsp-user" );
        psz_pwd  = var_CreateGetString( p_demux, "rtsp-pwd" );
    }

498
    i_lefttries = 3;
hartman's avatar
hartman committed
499
createnew:
500
    i_lefttries--;
501
502
503
504
505
506
507
508
    if( p_demux->b_die || p_demux->b_error )
    {
        free( psz_user );
        free( psz_pwd );
        free( psz_url );
        return VLC_EGENERIC;
    }

hartman's avatar
hartman committed
509
510
511
    if( var_CreateGetBool( p_demux, "rtsp-http" ) )
        i_http_port = var_CreateGetInteger( p_demux, "rtsp-http-port" );

512
    if( ( p_sys->rtsp = RTSPClient::createNew( *p_sys->env,
513
          var_CreateGetInteger( p_demux, "verbose" ) > 1,
hartman's avatar
hartman committed
514
515
516
517
          "VLC media player", i_http_port ) ) == NULL )
    {
        msg_Err( p_demux, "RTSPClient::createNew failed (%s)",
                 p_sys->env->getResultMsg() );
518
519
520
        free( psz_user );
        free( psz_pwd );
        free( psz_url );
hartman's avatar
hartman committed
521
522
        return VLC_EGENERIC;
    }
523

524
525
526
527
528
529
530
531
532
533
534
535
536
537
    /* Kasenna enables KeepAlive by analysing the User-Agent string. 
     * Appending _KA to the string should be enough to enable this feature, 
     * however, there is a bug where the _KA doesn't get parsed from the 
     * default User-Agent as created by VLC/Live555 code. This is probably due 
     * to spaces in the string or the string being too long. Here we override
     * the default string with a more compact version.
     */
    if( var_CreateGetBool( p_demux, "rtsp-kasenna" ))
    {
#if LIVEMEDIA_LIBRARY_VERSION_INT > 1130457500
        p_sys->rtsp->setUserAgentString( "VLC_MEDIA_PLAYER_KA" );
#endif
    }

538
describe:
539
540
541
542
543
    authenticator.setUsernameAndPassword( (const char*)psz_user,
                                          (const char*)psz_pwd );

    psz_options = p_sys->rtsp->sendOptionsCmd( psz_url, psz_user, psz_pwd,
                                               &authenticator );
ivoire's avatar
ivoire committed
544
    delete [] psz_options;
hartman's avatar
hartman committed
545

Jean-Paul Saman's avatar
Jean-Paul Saman committed
546
    p_sdp = p_sys->rtsp->describeURL( psz_url, &authenticator,
547
                         var_GetBool( p_demux, "rtsp-kasenna" ) );
hartman's avatar
hartman committed
548
549
    if( p_sdp == NULL )
    {
550
        /* failure occurred */
hartman's avatar
hartman committed
551
552
        int i_code = 0;
        const char *psz_error = p_sys->env->getResultMsg();
553

554
        if( var_GetBool( p_demux, "rtsp-http" ) )
Jean-Paul Saman's avatar
Jean-Paul Saman committed
555
556
            sscanf( psz_error, "%*s %*s HTTP GET %*s HTTP/%*u.%*u %3u %*s",
                    &i_code );
557
558
559
        else
        {
            const char *psz_tmp = strstr( psz_error, "RTSP" );
560
561
562
563
            if( psz_tmp )
                sscanf( psz_tmp, "RTSP/%*s%3u", &i_code );
            else
                i_code = 0;
564
        }
565
        msg_Dbg( p_demux, "DESCRIBE failed with %d: %s", i_code, psz_error );
566

567
568
569
570
571
        if( b_firstpass )
        {   /* describeURL always returns an "RTSP/1.0 401 Unauthorized" the
             * first time. This is a workaround to avoid asking for a
             * user/passwd the first time the code passess here. */
            i_code = 0;
572
            b_firstpass = false;
573
574
        }

hartman's avatar
hartman committed
575
576
        if( i_code == 401 )
        {
Jean-Paul Saman's avatar
Jean-Paul Saman committed
577
            int i_result;
hartman's avatar
hartman committed
578
579
            msg_Dbg( p_demux, "authentication failed" );

580
581
            free( psz_user );
            free( psz_pwd );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
582
583
584
            psz_user = psz_pwd = NULL;

            i_result = intf_UserLoginPassword( p_demux, _("RTSP authentication"),
hartman's avatar
hartman committed
585
                           _("Please enter a valid login name and a password."),
Jean-Paul Saman's avatar
Jean-Paul Saman committed
586
587
                                                   &psz_user, &psz_pwd );
            if( i_result == DIALOG_OK_YES )
hartman's avatar
hartman committed
588
            {
589
                msg_Dbg( p_demux, "retrying with user=%s, pwd=%s",
Jean-Paul Saman's avatar
Jean-Paul Saman committed
590
                         psz_user, psz_pwd );
591
                goto describe;
hartman's avatar
hartman committed
592
593
            }
        }
594
        else if( (i_code != 0) && !var_GetBool( p_demux, "rtsp-http" ) )
hartman's avatar
hartman committed
595
596
597
        {
            /* Perhaps a firewall is being annoying. Try HTTP tunneling mode */
            vlc_value_t val;
598
            val.b_bool = true;
hartman's avatar
hartman committed
599
600
601
            msg_Dbg( p_demux, "we will now try HTTP tunneling mode" );
            var_Set( p_demux, "rtsp-http", val );
            if( p_sys->rtsp ) RTSPClient::close( p_sys->rtsp );
602
            p_sys->rtsp = NULL;
hartman's avatar
hartman committed
603
604
            goto createnew;
        }
605
606
        else
        {
Jean-Paul Saman's avatar
Jean-Paul Saman committed
607
            msg_Dbg( p_demux, "connection timeout, retrying" );
608
            if( p_sys->rtsp ) RTSPClient::close( p_sys->rtsp );
609
            p_sys->rtsp = NULL;
610
611
            if( i_lefttries > 0 )
                goto createnew;
612
        }
hartman's avatar
hartman committed
613
614
615
616
        i_ret = VLC_EGENERIC;
    }

    /* malloc-ated copy */
617
618
619
    free( psz_url );
    free( psz_user );
    free( psz_pwd );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
620

621
    free( p_sys->p_sdp );
622
    p_sys->p_sdp = NULL;
hartman's avatar
hartman committed
623
    if( p_sdp ) p_sys->p_sdp = strdup( (char*)p_sdp );
hartman's avatar
hartman committed
624
625
626
627
628
629
    delete[] p_sdp;

    return i_ret;
}

/*****************************************************************************
630
 * SessionsSetup: prepares the subsessions and does the SETUP
hartman's avatar
hartman committed
631
632
633
634
635
636
637
 *****************************************************************************/
static int SessionsSetup( demux_t *p_demux )
{
    demux_sys_t             *p_sys  = p_demux->p_sys;
    MediaSubsessionIterator *iter   = NULL;
    MediaSubsession         *sub    = NULL;

638
    bool     b_rtsp_tcp = false;
hartman's avatar
hartman committed
639
640
641
642
643
    int            i_client_port;
    int            i_return = VLC_SUCCESS;
    unsigned int   i_buffer = 0;
    unsigned const thresh = 200000; /* RTP reorder threshold .2 second (default .1) */

644
645
    b_rtsp_tcp    = var_CreateGetBool( p_demux, "rtsp-tcp" ) ||
                    var_GetBool( p_demux, "rtsp-http" );
hartman's avatar
hartman committed
646
647
    i_client_port = var_CreateGetInteger( p_demux, "rtp-client-port" );

648
649
650
651
652
653
654
655
    /* Create the session from the SDP */
    if( !( p_sys->ms = MediaSession::createNew( *p_sys->env, p_sys->p_sdp ) ) )
    {
        msg_Err( p_demux, "Could not create the RTSP Session: %s",
            p_sys->env->getResultMsg() );
        return VLC_EGENERIC;
    }

hartman's avatar
hartman committed
656
657
658
659
660
    /* Initialise each media subsession */
    iter = new MediaSubsessionIterator( *p_sys->ms );
    while( ( sub = iter->next() ) != NULL )
    {
        Boolean bInit;
661
        live_track_t *tk;
hartman's avatar
hartman committed
662

663
664
665
666
667
668
        if( p_demux->b_die || p_demux->b_error )
        {
            delete iter;
            return VLC_EGENERIC;
        }

hartman's avatar
hartman committed
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
        /* Value taken from mplayer */
        if( !strcmp( sub->mediumName(), "audio" ) )
            i_buffer = 100000;
        else if( !strcmp( sub->mediumName(), "video" ) )
            i_buffer = 2000000;
        else continue;

        if( i_client_port != -1 )
        {
            sub->setClientPortNum( i_client_port );
            i_client_port += 2;
        }

        if( strcasestr( sub->codecName(), "REAL" ) )
        {
            msg_Info( p_demux, "real codec detected, using real-RTSP instead" );
685
            p_sys->b_real = true; /* This is a problem, we'll handle it later */
686
            continue;
hartman's avatar
hartman committed
687
688
689
690
691
692
        }

        if( !strcmp( sub->codecName(), "X-ASF-PF" ) )
            bInit = sub->initiate( 4 ); /* Constant ? */
        else
            bInit = sub->initiate();
693

hartman's avatar
hartman committed
694
695
696
697
698
699
700
701
702
703
704
        if( !bInit )
        {
            msg_Warn( p_demux, "RTP subsession '%s/%s' failed (%s)",
                      sub->mediumName(), sub->codecName(),
                      p_sys->env->getResultMsg() );
        }
        else
        {
            if( sub->rtpSource() != NULL )
            {
                int fd = sub->rtpSource()->RTPgs()->socketNum();
705

hartman's avatar
hartman committed
706
707
708
                /* Increase the buffer size */
                if( i_buffer > 0 )
                    increaseReceiveBufferTo( *p_sys->env, fd, i_buffer );
709

hartman's avatar
hartman committed
710
711
712
713
714
715
716
717
718
719
720
721
                /* Increase the RTP reorder timebuffer just a bit */
                sub->rtpSource()->setPacketReorderingThresholdTime(thresh);
            }
            msg_Dbg( p_demux, "RTP subsession '%s/%s'", sub->mediumName(),
                     sub->codecName() );

            /* Issue the SETUP */
            if( p_sys->rtsp )
            {
                if( !( p_sys->rtsp->setupMediaSubsession( *sub, False,
                                                   b_rtsp_tcp ? True : False ) ) )
                {
722
723
724
725
726
727
728
729
730
                    /* if we get an unsupported transport error, toggle TCP use and try again */
                    if( !strstr(p_sys->env->getResultMsg(), "461 Unsupported Transport")
                     || !( p_sys->rtsp->setupMediaSubsession( *sub, False,
                                                   b_rtsp_tcp ? False : True ) ) )
                    {
                        msg_Err( p_demux, "SETUP of'%s/%s' failed %s", sub->mediumName(),
                                 sub->codecName(), p_sys->env->getResultMsg() );
                        continue;
                    }
hartman's avatar
hartman committed
731
                }
732
            }
hartman's avatar
hartman committed
733

734
735
            /* Check if we will receive data from this subsession for this track */
            if( sub->readSource() == NULL ) continue;
hartman's avatar
hartman committed
736
737
738
739
740
            if( !p_sys->b_multicast )
            {
                /* Check, because we need diff. rollover behaviour for multicast */
                p_sys->b_multicast = IsMulticastAddress( sub->connectionEndpointAddress() );
            }
741
742

            tk = (live_track_t*)malloc( sizeof( live_track_t ) );
743
744
745
746
747
            if( !tk )
            {
                delete iter;
                return VLC_ENOMEM;
            }
748
749
750
            tk->p_demux     = p_demux;
            tk->sub         = sub;
            tk->p_es        = NULL;
751
752
753
            tk->b_quicktime = false;
            tk->b_asf       = false;
            tk->b_muxed     = false;
754
755
            tk->p_out_muxed = NULL;
            tk->waiting     = 0;
756
            tk->b_rtcp_sync = false;
757
758
759
            tk->i_pts       = 0;
            tk->i_buffer    = 65536;
            tk->p_buffer    = (uint8_t *)malloc( 65536 );
760
761
762
763
764
            if( !tk->p_buffer )
            {
                delete iter;
                return VLC_ENOMEM;
            }
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804

            /* Value taken from mplayer */
            if( !strcmp( sub->mediumName(), "audio" ) )
            {
                es_format_Init( &tk->fmt, AUDIO_ES, VLC_FOURCC('u','n','d','f') );
                tk->fmt.audio.i_channels = sub->numChannels();
                tk->fmt.audio.i_rate = sub->rtpTimestampFrequency();

                if( !strcmp( sub->codecName(), "MPA" ) ||
                    !strcmp( sub->codecName(), "MPA-ROBUST" ) ||
                    !strcmp( sub->codecName(), "X-MP3-DRAFT-00" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 'm', 'p', 'g', 'a' );
                    tk->fmt.audio.i_rate = 0;
                }
                else if( !strcmp( sub->codecName(), "AC3" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 'a', '5', '2', ' ' );
                    tk->fmt.audio.i_rate = 0;
                }
                else if( !strcmp( sub->codecName(), "L16" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 't', 'w', 'o', 's' );
                    tk->fmt.audio.i_bitspersample = 16;
                }
                else if( !strcmp( sub->codecName(), "L8" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 'a', 'r', 'a', 'w' );
                    tk->fmt.audio.i_bitspersample = 8;
                }
                else if( !strcmp( sub->codecName(), "PCMU" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 'u', 'l', 'a', 'w' );
                }
                else if( !strcmp( sub->codecName(), "PCMA" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 'a', 'l', 'a', 'w' );
                }
                else if( !strncmp( sub->codecName(), "G726", 4 ) )
                {
805
                    tk->fmt.i_codec = VLC_FOURCC( 'g', '7', '2', '6' );
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
                    tk->fmt.audio.i_rate = 8000;
                    tk->fmt.audio.i_channels = 1;
                    if( !strcmp( sub->codecName()+5, "40" ) )
                        tk->fmt.i_bitrate = 40000;
                    else if( !strcmp( sub->codecName()+5, "32" ) )
                        tk->fmt.i_bitrate = 32000;
                    else if( !strcmp( sub->codecName()+5, "24" ) )
                        tk->fmt.i_bitrate = 24000;
                    else if( !strcmp( sub->codecName()+5, "16" ) )
                        tk->fmt.i_bitrate = 16000;
                }
                else if( !strcmp( sub->codecName(), "AMR" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 's', 'a', 'm', 'r' );
                }
                else if( !strcmp( sub->codecName(), "AMR-WB" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 's', 'a', 'w', 'b' );
                }
                else if( !strcmp( sub->codecName(), "MP4A-LATM" ) )
                {
                    unsigned int i_extra;
                    uint8_t      *p_extra;

                    tk->fmt.i_codec = VLC_FOURCC( 'm', 'p', '4', 'a' );

                    if( ( p_extra = parseStreamMuxConfigStr( sub->fmtp_config(),
                                                             i_extra ) ) )
                    {
                        tk->fmt.i_extra = i_extra;
                        tk->fmt.p_extra = malloc( i_extra );
                        memcpy( tk->fmt.p_extra, p_extra, i_extra );
                        delete[] p_extra;
                    }
                    /* Because the "faad" decoder does not handle the LATM data length field
                       at the start of each returned LATM frame, tell the RTP source to omit it. */
                    ((MPEG4LATMAudioRTPSource*)sub->rtpSource())->omitLATMDataLengthField();
                }
                else if( !strcmp( sub->codecName(), "MPEG4-GENERIC" ) )
                {
                    unsigned int i_extra;
                    uint8_t      *p_extra;

                    tk->fmt.i_codec = VLC_FOURCC( 'm', 'p', '4', 'a' );

                    if( ( p_extra = parseGeneralConfigStr( sub->fmtp_config(),
                                                           i_extra ) ) )
                    {
                        tk->fmt.i_extra = i_extra;
                        tk->fmt.p_extra = malloc( i_extra );
                        memcpy( tk->fmt.p_extra, p_extra, i_extra );
                        delete[] p_extra;
                    }
                }
                else if( !strcmp( sub->codecName(), "X-ASF-PF" ) )
                {
862
                    tk->b_asf = true;
863
864
865
866
867
868
869
                    if( p_sys->p_out_asf == NULL )
                        p_sys->p_out_asf = stream_DemuxNew( p_demux, "asf",
                                                            p_demux->out );
                }
                else if( !strcmp( sub->codecName(), "X-QT" ) ||
                         !strcmp( sub->codecName(), "X-QUICKTIME" ) )
                {
870
                    tk->b_quicktime = true;
871
                }
872
873
                else if( !strcmp( sub->codecName(), "SPEEX" ) )
                {
874
                    tk->fmt.i_codec = VLC_FOURCC( 's', 'p', 'x', 'r' );
875
876
                    if ( sub->rtpTimestampFrequency() )
                        tk->fmt.audio.i_rate = sub->rtpTimestampFrequency();
877
                    else
878
879
880
881
                    {
                        msg_Warn( p_demux,"Using 8kHz as default sample rate." );
                        tk->fmt.audio.i_rate = 8000;
                    }
882
                }
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
            }
            else if( !strcmp( sub->mediumName(), "video" ) )
            {
                es_format_Init( &tk->fmt, VIDEO_ES, VLC_FOURCC('u','n','d','f') );
                if( !strcmp( sub->codecName(), "MPV" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 'm', 'p', 'g', 'v' );
                }
                else if( !strcmp( sub->codecName(), "H263" ) ||
                         !strcmp( sub->codecName(), "H263-1998" ) ||
                         !strcmp( sub->codecName(), "H263-2000" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 'H', '2', '6', '3' );
                }
                else if( !strcmp( sub->codecName(), "H261" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 'H', '2', '6', '1' );
                }
                else if( !strcmp( sub->codecName(), "H264" ) )
                {
                    unsigned int i_extra = 0;
                    uint8_t      *p_extra = NULL;

                    tk->fmt.i_codec = VLC_FOURCC( 'h', '2', '6', '4' );
907
                    tk->fmt.b_packetized = false;
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944

                    if((p_extra=parseH264ConfigStr( sub->fmtp_spropparametersets(),
                                                    i_extra ) ) )
                    {
                        tk->fmt.i_extra = i_extra;
                        tk->fmt.p_extra = malloc( i_extra );
                        memcpy( tk->fmt.p_extra, p_extra, i_extra );

                        delete[] p_extra;
                    }
                }
                else if( !strcmp( sub->codecName(), "JPEG" ) )
                {
                    tk->fmt.i_codec = VLC_FOURCC( 'M', 'J', 'P', 'G' );
                }
                else if( !strcmp( sub->codecName(), "MP4V-ES" ) )
                {
                    unsigned int i_extra;
                    uint8_t      *p_extra;

                    tk->fmt.i_codec = VLC_FOURCC( 'm', 'p', '4', 'v' );

                    if( ( p_extra = parseGeneralConfigStr( sub->fmtp_config(),
                                                           i_extra ) ) )
                    {
                        tk->fmt.i_extra = i_extra;
                        tk->fmt.p_extra = malloc( i_extra );
                        memcpy( tk->fmt.p_extra, p_extra, i_extra );
                        delete[] p_extra;
                    }
                }
                else if( !strcmp( sub->codecName(), "X-QT" ) ||
                         !strcmp( sub->codecName(), "X-QUICKTIME" ) ||
                         !strcmp( sub->codecName(), "X-QDM" ) ||
                         !strcmp( sub->codecName(), "X-SV3V-ES" )  ||
                         !strcmp( sub->codecName(), "X-SORENSONVIDEO" ) )
                {
945
                    tk->b_quicktime = true;
946
947
948
                }
                else if( !strcmp( sub->codecName(), "MP2T" ) )
                {
949
                    tk->b_muxed = true;
950
951
952
953
954
                    tk->p_out_muxed = stream_DemuxNew( p_demux, "ts", p_demux->out );
                }
                else if( !strcmp( sub->codecName(), "MP2P" ) ||
                         !strcmp( sub->codecName(), "MP1S" ) )
                {
955
                    tk->b_muxed = true;
956
957
958
959
960
                    tk->p_out_muxed = stream_DemuxNew( p_demux, "ps",
                                                       p_demux->out );
                }
                else if( !strcmp( sub->codecName(), "X-ASF-PF" ) )
                {
961
                    tk->b_asf = true;
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
                    if( p_sys->p_out_asf == NULL )
                        p_sys->p_out_asf = stream_DemuxNew( p_demux, "asf",
                                                            p_demux->out );;
                }
            }

            if( !tk->b_quicktime && !tk->b_muxed && !tk->b_asf )
            {
                tk->p_es = es_out_Add( p_demux->out, &tk->fmt );
            }

            if( sub->rtcpInstance() != NULL )
            {
                sub->rtcpInstance()->setByeHandler( StreamClose, tk );
            }

            if( tk->p_es || tk->b_quicktime || tk->b_muxed || tk->b_asf )
            {
                /* Append */
                p_sys->track = (live_track_t**)realloc( p_sys->track, sizeof( live_track_t ) * ( p_sys->i_track + 1 ) );
                p_sys->track[p_sys->i_track++] = tk;
            }
            else
            {
                /* BUG ??? */
                msg_Err( p_demux, "unusable RTSP track. this should not happen" );
988
                es_format_Clean( &tk->fmt );
989
990
                free( tk );
            }
hartman's avatar
hartman committed
991
992
993
        }
    }
    delete iter;
994
995
    if( p_sys->i_track <= 0 ) i_return = VLC_EGENERIC;

996
#if (LIVEMEDIA_LIBRARY_VERSION_INT >= 1199404800)
997
998
    /* Retrieve the starttime if possible */
    p_sys->i_npt_start = (int64_t)( p_sys->ms->playStartTime() * (double)1000000.0 );
999
#else
Jean-Paul Saman's avatar
Jean-Paul Saman committed
1000
    p_sys->i_npt_start = (int64_t) -1;
1001
#endif
1002
1003
1004
    if( p_sys->i_npt_start < 0 )
        p_sys->i_npt_start = -1;

1005
#if (LIVEMEDIA_LIBRARY_VERSION_INT >= 1199404800)
1006
1007
    /* Retrieve the duration if possible */
    p_sys->i_npt_length = (int64_t)( p_sys->ms->playEndTime() * (double)1000000.0 );
1008
1009
1010
#else
    p_sys->i_npt_length = (int64_t) -1;
#endif
1011
1012
1013
1014
    if( p_sys->i_npt_length < 0 )
        p_sys->i_npt_length = -1;

    msg_Dbg( p_demux, "setup start: %lld stop:%lld", p_sys->i_npt_start, p_sys->i_npt_length );
hartman's avatar
hartman committed
1015
1016
1017
1018
1019
1020
1021
1022
1023
    return i_return;
}

/*****************************************************************************
 * Play: starts the actual playback of the stream
 *****************************************************************************/
static int Play( demux_t *p_demux )
{
    demux_sys_t *p_sys = p_demux->p_sys;
1024
1025
    int i;

hartman's avatar
hartman committed
1026
1027
1028
    if( p_sys->rtsp )
    {
        /* The PLAY */
1029
        if( !p_sys->rtsp->playMediaSession( *p_sys->ms, p_sys->i_npt_start, -1, 1 ) )
hartman's avatar
hartman committed
1030
1031
1032
1033
1034
1035
1036
        {
            msg_Err( p_demux, "RTSP PLAY failed %s", p_sys->env->getResultMsg() );
            return VLC_EGENERIC;
        }

        /* Retrieve the timeout value and set up a timeout prevention thread */
        p_sys->i_timeout = p_sys->rtsp->sessionTimeoutParameter();
1037
1038
        if( p_sys->i_timeout <= 0 )
            p_sys->i_timeout = 60; /* default value from RFC2326 */
1039
1040
1041
1042
1043

        /* start timeout-thread only on x-asf streams (wms), it has rtcp support but doesn't 
         * seem to use it for liveness/keep-alive, get_parameter seems to work for it. get_parameter
         * doesn't work with dss 5.5.4 & 5.5.5, they seems to work with rtcp */
        if( !p_sys->p_timeout && p_sys->p_out_asf )
hartman's avatar
hartman committed
1044
1045
1046
1047
1048
        {
            msg_Dbg( p_demux, "We have a timeout of %d seconds",  p_sys->i_timeout );
            p_sys->p_timeout = (timeout_thread_t *)vlc_object_create( p_demux, sizeof(timeout_thread_t) );
            p_sys->p_timeout->p_sys = p_demux->p_sys; /* lol, object recursion :D */
            if( vlc_thread_create( p_sys->p_timeout, "liveMedia-timeout", TimeoutPrevention,
1049
                                   VLC_THREAD_PRIORITY_LOW, true ) )
hartman's avatar
hartman committed
1050
1051
            {
                msg_Err( p_demux, "cannot spawn liveMedia timeout thread" );
1052
                vlc_object_release( p_sys->p_timeout );
hartman's avatar
hartman committed
1053
1054
1055
1056
1057
            }
            msg_Dbg( p_demux, "spawned timeout thread" );
            vlc_object_attach( p_sys->p_timeout, p_demux );
        }
    }
1058
1059
    p_sys->i_pcr = 0;

1060
#if (LIVEMEDIA_LIBRARY_VERSION_INT >= 1195257600)
1061
1062
    for( i = 0; i < p_sys->i_track; i++ )
    {
1063
1064
        if( !p_sys->track[i]->b_rtcp_sync )
            p_sys->track[i]->i_pts = (int64_t) ( p_sys->track[i]->sub->rtpInfo.timestamp * (double)1000000.0 );
1065
        p_sys->track[i]->i_start_seq = (int)p_sys->track[i]->sub->rtpInfo.seqNum;
1066
        msg_Info( p_demux, "set startseq: %u", p_sys->track[i]->i_start_seq );
1067
1068
1069
    }
#endif

1070
#if (LIVEMEDIA_LIBRARY_VERSION_INT >= 1199404800)
1071
1072
    /* Retrieve the starttime if possible */
    p_sys->i_npt_start = (int64_t)( p_sys->ms->playStartTime() *