clock.c 14 KB
Newer Older
1
/*****************************************************************************
2
 * input_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 35
#include <vlc_input.h>
#include "input_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
 */

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. */
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 205

    assert( !cl->b_paused );

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

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

        /* Feed synchro with a new reference point. */
Laurent Aimar's avatar
Laurent Aimar committed
232 233 234
        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 ) );
235
    }
236

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

241
        AvgUpdate( &cl->drift, i_converted - i_ck_stream );
242

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

    vlc_mutex_unlock( &cl->lock );
248 249
}

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

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

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

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

271
    /* Move the reference point */
272
    if( cl->b_has_reference )
Laurent Aimar's avatar
Laurent Aimar committed
273
        cl->ref = cl->last;
274

275
    cl->i_rate = i_rate;
276 277

    vlc_mutex_unlock( &cl->lock );
278 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
/*****************************************************************************
 * 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 );
}

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

311 312 313 314 315 316 317 318 319
    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;
320 321 322 323 324
}

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

330 331
    vlc_mutex_lock( &cl->lock );

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

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

    /* */
342 343 344 345
    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;

346 347
    vlc_mutex_unlock( &cl->lock );

348
    return i_converted_ts + i_pts_delay;
349
}
350 351 352 353 354 355 356 357 358 359 360 361 362
/*****************************************************************************
 * 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;
}
363

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

Laurent Aimar's avatar
Laurent Aimar committed
408
    return ( i_stream - cl->ref.i_stream ) * cl->i_rate / INPUT_RATE_DEFAULT +
Laurent Aimar's avatar
Laurent Aimar committed
409 410 411 412 413 414 415 416 417 418 419 420
           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
421
            cl->ref.i_stream;
Laurent Aimar's avatar
Laurent Aimar committed
422 423
}

424 425 426
/*****************************************************************************
 * Long term average helpers
 *****************************************************************************/
427
static void AvgInit( average_t *p_avg, int i_divider )
428 429
{
    p_avg->i_divider = i_divider;
430
    AvgReset( p_avg );
431
}
432
static void AvgClean( average_t *p_avg )
433 434 435
{
    VLC_UNUSED(p_avg);
}
436
static void AvgReset( average_t *p_avg )
437 438 439 440 441
{
    p_avg->i_value = 0;
    p_avg->i_residue = 0;
    p_avg->i_count = 0;
}
442
static void AvgUpdate( average_t *p_avg, mtime_t i_value )
443 444 445 446 447 448 449 450 451 452 453
{
    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++;
}
454
static mtime_t AvgGet( average_t *p_avg )
455 456 457
{
    return p_avg->i_value;
}
Laurent Aimar's avatar
Laurent Aimar committed
458