clock.c 14 KB
Newer Older
1
/*****************************************************************************
2
 * clock.c: Clock/System date convertions, stream management
3
 *****************************************************************************
Laurent Aimar's avatar
Laurent Aimar committed
4
 * Copyright (C) 1999-2008 the VideoLAN team
5
 * Copyright (C) 2008 Laurent Aimar
6
 * $Id$
7 8
 *
 * Authors: Christophe Massiot <massiot@via.ecp.fr>
Laurent Aimar's avatar
Laurent Aimar committed
9
 *          Laurent Aimar < fenrir _AT_ videolan _DOT_ org >
10 11 12 13 14
 *
 * 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.
15
 *
16 17 18 19 20 21 22
 * 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
23
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
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_input.h>
35
#include "clock.h"
36

37 38 39 40 41
/* TODO:
 * - clean up locking once clock code is stable
 *
 */

42
/*
43
 * DISCUSSION : SYNCHRONIZATION METHOD
44
 *
45 46 47 48 49 50 51
 * In some cases we can impose the pace of reading (when reading from a
 * file or a pipe), and for the synchronization we simply sleep() until
 * it is time to deliver the packet to the decoders. When reading from
 * the network, we must be read at the same pace as the server writes,
 * otherwise the kernel's buffer will trash packets. The risk is now to
 * overflow the input buffers in case the server goes too fast, that is
 * why we do these calculations :
52
 *
53 54 55 56
 * We compute a mean for the pcr because we want to eliminate the
 * network jitter and keep the low frequency variations. The mean is
 * in fact a low pass filter and the jitter is a high frequency signal
 * that is why it is eliminated by the filter/average.
57
 *
58 59 60 61 62 63 64
 * The low frequency variations enable us to synchronize the client clock
 * with the server clock because they represent the time variation between
 * the 2 clocks. Those variations (ie the filtered pcr) are used to compute
 * the presentation dates for the audio and video frames. With those dates
 * we can decode (or trash) the MPEG2 stream at "exactly" the same rate
 * as it is sent by the server and so we keep the synchronization between
 * the server and the client.
65
 *
66 67
 * It is a very important matter if you want to avoid underflow or overflow
 * in all the FIFOs, but it may be not enough.
68 69
 */

Clément Stenac's avatar
Clément Stenac committed
70
/* p_input->p->i_cr_average : Maximum number of samples used to compute the
71 72 73 74 75
 * dynamic average value.
 * We use the following formula :
 * new_average = (old_average * c_average + new_sample_value) / (c_average +1)
 */

76

77
/*****************************************************************************
78
 * Constants
79
 *****************************************************************************/
80 81

/* Maximum gap allowed between two CRs. */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
82
#define CR_MAX_GAP (INT64_C(2000000)*100/9)
83

84 85
/* Latency introduced on DVDs with CR == 0 on chapter change - this is from
 * my dice --Meuuh */
Laurent Aimar's avatar
Laurent Aimar committed
86
#define CR_MEAN_PTS_GAP (300000)
87

88 89 90
/*****************************************************************************
 * Structures
 *****************************************************************************/
91 92 93 94 95 96 97 98 99 100 101 102

/**
 * This structure holds long term average
 */
typedef struct
{
    mtime_t i_value;
    int     i_residue;

    int     i_count;
    int     i_divider;
} average_t;
103 104
static void    AvgInit( average_t *, int i_divider );
static void    AvgClean( average_t * );
105

106 107 108
static void    AvgReset( average_t * );
static void    AvgUpdate( average_t *, mtime_t i_value );
static mtime_t AvgGet( average_t * );
109

Laurent Aimar's avatar
Laurent Aimar committed
110 111 112 113 114 115 116 117 118 119 120 121 122
/* */
typedef struct
{
    mtime_t i_stream;
    mtime_t i_system;
} clock_point_t;

static inline clock_point_t clock_point_Create( mtime_t i_stream, mtime_t i_system )
{
    clock_point_t p = { .i_stream = i_stream, .i_system = i_system };
    return p;
}

123
/* */
124 125
struct input_clock_t
{
126 127 128
    /* */
    vlc_mutex_t lock;

Laurent Aimar's avatar
Laurent Aimar committed
129
    /* Reference point */
Laurent Aimar's avatar
Laurent Aimar committed
130 131
    bool          b_has_reference;
    clock_point_t ref;
Laurent Aimar's avatar
Laurent Aimar committed
132 133 134

    /* Last point
     * It is used to detect unexpected stream discontinuities */
Laurent Aimar's avatar
Laurent Aimar committed
135
    clock_point_t last;
136

137
    /* Maximal timestamp returned by input_clock_GetTS (in system unit) */
138
    mtime_t i_ts_max;
139

Laurent Aimar's avatar
Laurent Aimar committed
140
    /* Clock drift */
141 142
    mtime_t i_next_drift_update;
    average_t drift;
143

Laurent Aimar's avatar
Laurent Aimar committed
144 145
    /* Current modifiers */
    int     i_rate;
146 147
    bool    b_paused;
    mtime_t i_pause_date;
148 149
};

Laurent Aimar's avatar
Laurent Aimar committed
150
static mtime_t ClockStreamToSystem( input_clock_t *, mtime_t i_stream );
Laurent Aimar's avatar
Laurent Aimar committed
151
static mtime_t ClockSystemToStream( input_clock_t *, mtime_t i_system );
152

153
/*****************************************************************************
Laurent Aimar's avatar
Laurent Aimar committed
154
 * input_clock_New: create a new clock
155
 *****************************************************************************/
156
input_clock_t *input_clock_New( int i_cr_average, int i_rate )
157
{
158 159 160 161
    input_clock_t *cl = malloc( sizeof(*cl) );
    if( !cl )
        return NULL;

162
    vlc_mutex_init( &cl->lock );
163
    cl->b_has_reference = false;
Laurent Aimar's avatar
Laurent Aimar committed
164
    cl->ref = clock_point_Create( 0, 0 );
165

Laurent Aimar's avatar
Laurent Aimar committed
166
    cl->last = clock_point_Create( 0, 0 );
167 168

    cl->i_ts_max = 0;
169

170
    cl->i_next_drift_update = 0;
171
    AvgInit( &cl->drift, i_cr_average );
172

Laurent Aimar's avatar
Laurent Aimar committed
173
    cl->i_rate = i_rate;
174 175
    cl->b_paused = false;
    cl->i_pause_date = 0;
Laurent Aimar's avatar
Laurent Aimar committed
176

177 178 179 180
    return cl;
}

/*****************************************************************************
Laurent Aimar's avatar
Laurent Aimar committed
181
 * input_clock_Delete: destroy a new clock
182
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
183
void input_clock_Delete( input_clock_t *cl )
184
{
185
    AvgClean( &cl->drift );
186
    vlc_mutex_destroy( &cl->lock );
187
    free( cl );
188 189 190
}

/*****************************************************************************
191
 * input_clock_Update: manages a clock reference
192 193 194
 *
 *  i_ck_stream: date in stream clock
 *  i_ck_system: date in system clock
195
 *****************************************************************************/
196
void input_clock_Update( input_clock_t *cl,
197
                         vlc_object_t *p_log, bool b_can_pace_control,
Laurent Aimar's avatar
Laurent Aimar committed
198
                         mtime_t i_ck_stream, mtime_t i_ck_system )
199
{
200
    bool b_reset_reference = false;
201

202
    vlc_mutex_lock( &cl->lock );
203

204
    if( ( !cl->b_has_reference ) ||
Laurent Aimar's avatar
Laurent Aimar committed
205
        ( i_ck_stream == 0 && cl->last.i_stream != 0 ) )
206
    {
207 208
        /* */
        b_reset_reference= true;
209
    }
Laurent Aimar's avatar
Laurent Aimar committed
210 211 212
    else if( cl->last.i_stream != 0 &&
             ( (cl->last.i_stream - i_ck_stream) > CR_MAX_GAP ||
               (cl->last.i_stream - i_ck_stream) < -CR_MAX_GAP ) )
213
    {
214 215 216
        /* Stream discontinuity, for which we haven't received a
         * warning from the stream control facilities (dd-edited
         * stream ?). */
217
        msg_Warn( p_log, "clock gap, unexpected stream discontinuity" );
218
        cl->i_ts_max = 0;
219 220

        /* */
221
        msg_Warn( p_log, "feeding synchro with a new reference point trying to recover from clock gap" );
222 223 224 225
        b_reset_reference= true;
    }
    if( b_reset_reference )
    {
226
        cl->i_next_drift_update = 0;
227
        AvgReset( &cl->drift );
228 229

        /* Feed synchro with a new reference point. */
Laurent Aimar's avatar
Laurent Aimar committed
230 231 232
        cl->b_has_reference = true;
        cl->ref = clock_point_Create( i_ck_stream,
                                      __MAX( cl->i_ts_max + CR_MEAN_PTS_GAP, i_ck_system ) );
233
    }
234

235
    if( !b_can_pace_control && cl->i_next_drift_update < i_ck_system )
236
    {
237
        const mtime_t i_converted = ClockSystemToStream( cl, i_ck_system );
238

239
        AvgUpdate( &cl->drift, i_converted - i_ck_stream );
240

241
        cl->i_next_drift_update = i_ck_system + CLOCK_FREQ/5; /* FIXME why that */
242
    }
Laurent Aimar's avatar
Laurent Aimar committed
243
    cl->last = clock_point_Create( i_ck_stream, i_ck_system );
244 245

    vlc_mutex_unlock( &cl->lock );
246 247
}

Laurent Aimar's avatar
Laurent Aimar committed
248
/*****************************************************************************
Laurent Aimar's avatar
Laurent Aimar committed
249
 * input_clock_Reset:
Laurent Aimar's avatar
Laurent Aimar committed
250
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
251
void input_clock_Reset( input_clock_t *cl )
Laurent Aimar's avatar
Laurent Aimar committed
252
{
253 254
    vlc_mutex_lock( &cl->lock );

255
    cl->b_has_reference = false;
Laurent Aimar's avatar
Laurent Aimar committed
256
    cl->ref = clock_point_Create( 0, 0 );
257
    cl->i_ts_max = 0;
258 259

    vlc_mutex_unlock( &cl->lock );
Laurent Aimar's avatar
Laurent Aimar committed
260 261
}

262
/*****************************************************************************
263
 * input_clock_ChangeRate:
264
 *****************************************************************************/
265
void input_clock_ChangeRate( input_clock_t *cl, int i_rate )
266
{
267 268
    vlc_mutex_lock( &cl->lock );

269
    /* Move the reference point */
270
    if( cl->b_has_reference )
271 272
    {
        cl->last.i_system = ClockStreamToSystem( cl, cl->last.i_stream );
Laurent Aimar's avatar
Laurent Aimar committed
273
        cl->ref = cl->last;
274
    }
275

276
    cl->i_rate = i_rate;
277 278

    vlc_mutex_unlock( &cl->lock );
279 280
}

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
/*****************************************************************************
 * input_clock_ChangePause:
 *****************************************************************************/
void input_clock_ChangePause( input_clock_t *cl, bool b_paused, mtime_t i_date )
{
    vlc_mutex_lock( &cl->lock );
    assert( (!cl->b_paused) != (!b_paused) );

    if( cl->b_paused )
    {
        const mtime_t i_duration = i_date - cl->i_pause_date;

        if( cl->b_has_reference && i_duration > 0 )
        {
            cl->ref.i_system += i_duration;
            cl->last.i_system += i_duration;
        }
    }
    cl->i_pause_date = i_date;
    cl->b_paused = b_paused;

    vlc_mutex_unlock( &cl->lock );
}

305
/*****************************************************************************
Laurent Aimar's avatar
Laurent Aimar committed
306
 * input_clock_GetWakeup
307
 *****************************************************************************/
308
mtime_t input_clock_GetWakeup( input_clock_t *cl )
309
{
310
    mtime_t i_wakeup = 0;
311

312 313 314 315 316 317 318 319 320
    vlc_mutex_lock( &cl->lock );

    /* Synchronized, we can wait */
    if( cl->b_has_reference )
        i_wakeup = ClockStreamToSystem( cl, cl->last.i_stream );

    vlc_mutex_unlock( &cl->lock );

    return i_wakeup;
321 322 323 324 325
}

/*****************************************************************************
 * input_clock_GetTS: manages a PTS or DTS
 *****************************************************************************/
326
mtime_t input_clock_GetTS( input_clock_t *cl, int *pi_rate,
327 328 329 330
                           mtime_t i_pts_delay, mtime_t i_ts )
{
    mtime_t i_converted_ts;

331 332
    vlc_mutex_lock( &cl->lock );

333 334 335
    if( pi_rate )
        *pi_rate = cl->i_rate;

336
    if( !cl->b_has_reference )
337 338
    {
        vlc_mutex_unlock( &cl->lock );
339
        return 0;
340
    }
341 342

    /* */
343 344 345 346
    i_converted_ts = ClockStreamToSystem( cl, i_ts + AvgGet( &cl->drift ) );
    if( i_converted_ts > cl->i_ts_max )
        cl->i_ts_max = i_converted_ts;

347 348
    vlc_mutex_unlock( &cl->lock );

349
    return i_converted_ts + i_pts_delay;
350
}
351 352 353 354 355 356 357 358 359 360 361 362 363
/*****************************************************************************
 * input_clock_GetRate: Return current rate
 *****************************************************************************/
int input_clock_GetRate( input_clock_t *cl )
{
    int i_rate;

    vlc_mutex_lock( &cl->lock );
    i_rate = cl->i_rate;
    vlc_mutex_unlock( &cl->lock );

    return i_rate;
}
364

365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
int input_clock_GetState( input_clock_t *cl,
                          mtime_t *pi_stream_start, mtime_t *pi_system_start,
                          mtime_t *pi_stream_duration, mtime_t *pi_system_duration )
{
    vlc_mutex_lock( &cl->lock );

    if( !cl->b_has_reference )
    {
        vlc_mutex_unlock( &cl->lock );
        return VLC_EGENERIC;
    }

    *pi_stream_start = cl->ref.i_stream;
    *pi_system_start = cl->ref.i_system;

    *pi_stream_duration = cl->last.i_stream - cl->ref.i_stream;
    *pi_system_duration = cl->last.i_system - cl->ref.i_system;

    vlc_mutex_unlock( &cl->lock );

    return VLC_SUCCESS;
}

void input_clock_ChangeSystemOrigin( input_clock_t *cl, mtime_t i_system )
{
    vlc_mutex_lock( &cl->lock );

    assert( cl->b_has_reference );
    const mtime_t i_offset = i_system - cl->ref.i_system;

    cl->ref.i_system += i_offset;
    cl->last.i_system += i_offset;

    vlc_mutex_unlock( &cl->lock );
}

Laurent Aimar's avatar
Laurent Aimar committed
401 402 403
/*****************************************************************************
 * ClockStreamToSystem: converts a movie clock to system date
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
404
static mtime_t ClockStreamToSystem( input_clock_t *cl, mtime_t i_stream )
Laurent Aimar's avatar
Laurent Aimar committed
405 406 407 408
{
    if( !cl->b_has_reference )
        return 0;

Laurent Aimar's avatar
Laurent Aimar committed
409
    return ( i_stream - cl->ref.i_stream ) * cl->i_rate / INPUT_RATE_DEFAULT +
Laurent Aimar's avatar
Laurent Aimar committed
410 411 412 413 414 415 416 417 418 419 420 421
           cl->ref.i_system;
}

/*****************************************************************************
 * ClockSystemToStream: converts a system date to movie clock
 *****************************************************************************
 * Caution : a valid reference point is needed for this to operate.
 *****************************************************************************/
static mtime_t ClockSystemToStream( input_clock_t *cl, mtime_t i_system )
{
    assert( cl->b_has_reference );
    return ( i_system - cl->ref.i_system ) * INPUT_RATE_DEFAULT / cl->i_rate +
Laurent Aimar's avatar
Laurent Aimar committed
422
            cl->ref.i_stream;
Laurent Aimar's avatar
Laurent Aimar committed
423 424
}

425 426 427
/*****************************************************************************
 * Long term average helpers
 *****************************************************************************/
428
static void AvgInit( average_t *p_avg, int i_divider )
429 430
{
    p_avg->i_divider = i_divider;
431
    AvgReset( p_avg );
432
}
433
static void AvgClean( average_t *p_avg )
434 435 436
{
    VLC_UNUSED(p_avg);
}
437
static void AvgReset( average_t *p_avg )
438 439 440 441 442
{
    p_avg->i_value = 0;
    p_avg->i_residue = 0;
    p_avg->i_count = 0;
}
443
static void AvgUpdate( average_t *p_avg, mtime_t i_value )
444 445 446 447 448 449 450 451 452 453 454
{
    const int i_f0 = __MIN( p_avg->i_divider - 1, p_avg->i_count );
    const int i_f1 = p_avg->i_divider - i_f0;

    const mtime_t i_tmp = i_f0 * p_avg->i_value + i_f1 * i_value + p_avg->i_residue;

    p_avg->i_value   = i_tmp / p_avg->i_divider;
    p_avg->i_residue = i_tmp % p_avg->i_divider;

    p_avg->i_count++;
}
455
static mtime_t AvgGet( average_t *p_avg )
456 457 458
{
    return p_avg->i_value;
}
Laurent Aimar's avatar
Laurent Aimar committed
459