You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

510 lines
18 KiB

/*****************************************************************************
* input_clock.c: Input clock/System date conversions, stream management
*****************************************************************************
* Copyright (C) 1999-2018 VLC authors and VideoLAN
* Copyright (C) 2008 Laurent Aimar
*
* Authors: Christophe Massiot <massiot@via.ecp.fr>
* Laurent Aimar < fenrir _AT_ videolan _DOT_ org >
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include "input_clock.h"
#include "clock_internal.h"
#include <assert.h>
/* TODO:
* - clean up locking once clock code is stable
*
*/
/*
* DISCUSSION : SYNCHRONIZATION METHOD
*
* 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 :
*
* 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.
*
* 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.
*
* It is a very important matter if you want to avoid underflow or overflow
* in all the FIFOs, but it may be not enough.
*/
/* i_cr_average : Maximum number of samples used to compute the
* dynamic average value.
* We use the following formula :
* new_average = (old_average * c_average + new_sample_value) / (c_average +1)
*/
/*****************************************************************************
* Constants
*****************************************************************************/
/* Latency introduced on DVDs with CR == 0 on chapter change - this is from
* my dice --Meuuh */
#define CR_MEAN_PTS_GAP VLC_TICK_FROM_MS(300)
#define CR_MAX_GAP CR_MEAN_PTS_GAP
/* Rate (in 1/256) at which we will read faster to try to increase our
* internal buffer (if we control the pace of the source).
*/
#define CR_BUFFERING_RATE (48)
/* Extra internal buffer value (in CLOCK_FREQ)
* It is 60s max, remember as it is limited by the size it takes by es_out.c
* it can be really large.
*/
//#define CR_BUFFERING_TARGET VLC_TICK_FROM_SEC(60)
/* Due to some problems in es_out, we cannot use a large value yet */
#define CR_BUFFERING_TARGET VLC_TICK_FROM_MS(100)
/* */
#define INPUT_CLOCK_LATE_COUNT (3)
/* */
struct input_clock_t
{
struct {
const struct vlc_input_clock_cbs *cbs;
void *opaque;
} listener;
/* Last point
* It is used to detect unexpected stream discontinuities */
clock_point_t last;
/* Amount of extra buffering expressed in stream clock */
vlc_tick_t i_buffering_duration;
/* Clock drift */
vlc_tick_t i_next_drift_update;
average_t drift;
/* Late statistics */
struct
{
vlc_tick_t pi_value[INPUT_CLOCK_LATE_COUNT];
unsigned i_index;
} late;
/* Reference point */
clock_point_t ref;
bool b_has_reference;
bool b_origin_changed;
/* Current modifiers */
bool b_paused;
float rate;
vlc_tick_t i_pts_delay;
vlc_tick_t i_pause_date;
};
static vlc_tick_t ClockStreamToSystem( input_clock_t *, vlc_tick_t i_stream );
static vlc_tick_t ClockSystemToStream( input_clock_t *, vlc_tick_t i_system );
static vlc_tick_t ClockGetTsOffset( input_clock_t * );
static void UpdateListener( input_clock_t *cl, bool discontinuity )
{
if (cl->listener.cbs == NULL)
return;
const vlc_tick_t system_expected =
ClockStreamToSystem( cl, cl->last.stream + AvgGet( &cl->drift ) ) +
cl->i_pts_delay + ClockGetTsOffset( cl );
/* The returned drift value is ignored for now since a different
* value is computed by the input_clock. */
cl->listener.cbs->update(cl->listener.opaque, system_expected,
cl->last.stream, cl->rate, discontinuity);
}
/*****************************************************************************
* input_clock_New: create a new clock
*****************************************************************************/
input_clock_t *input_clock_New( float rate )
{
input_clock_t *cl = malloc( sizeof(*cl) );
if( !cl )
return NULL;
cl->listener.cbs = NULL;
cl->listener.opaque = NULL;
cl->b_has_reference = false;
cl->b_origin_changed = false;
cl->ref = clock_point_Create( VLC_TICK_INVALID, VLC_TICK_INVALID );
cl->last = clock_point_Create( VLC_TICK_INVALID, VLC_TICK_INVALID );
cl->i_buffering_duration = 0;
cl->i_next_drift_update = VLC_TICK_INVALID;
AvgInit( &cl->drift, 10 );
cl->late.i_index = 0;
for( int i = 0; i < INPUT_CLOCK_LATE_COUNT; i++ )
cl->late.pi_value[i] = 0;
cl->rate = rate;
cl->i_pts_delay = 0;
cl->b_paused = false;
cl->i_pause_date = VLC_TICK_INVALID;
return cl;
}
/*****************************************************************************
* input_clock_Delete: destroy a new clock
*****************************************************************************/
void input_clock_Delete( input_clock_t *cl )
{
AvgClean( &cl->drift );
free( cl );
}
void input_clock_AttachListener(input_clock_t *cl,
const struct vlc_input_clock_cbs *cbs,
void *opaque)
{
assert(cbs && cl->listener.cbs == NULL);
assert( !cl->b_has_reference );
cl->listener.cbs = cbs;
cl->listener.opaque = opaque;
}
/*****************************************************************************
* input_clock_Update: manages a clock reference
*
* i_ck_stream: date in stream clock
* i_ck_system: date in system clock
*****************************************************************************/
vlc_tick_t input_clock_Update( input_clock_t *cl, vlc_object_t *p_log,
bool b_can_pace_control, bool b_buffering_allowed,
vlc_tick_t i_ck_stream, vlc_tick_t i_ck_system )
{
bool b_reset_reference = false;
bool discontinuity = false;
assert( i_ck_stream != VLC_TICK_INVALID && i_ck_system != VLC_TICK_INVALID );
if( !cl->b_has_reference )
{
/* */
b_reset_reference= true;
}
/* Don't check discontinuities if the origin has just been changed */
else if (cl->last.stream != VLC_TICK_INVALID && !cl->b_origin_changed)
{
assert(cl->last.system != VLC_TICK_INVALID);
/* We need compare both stream and system times for discontinuity.
* Indeed, a big stream diff is OK if we have the same system diff. */
vlc_tick_t stream_diff = i_ck_stream - cl->last.stream;
vlc_tick_t system_diff = (i_ck_system - cl->last.system) * cl->rate;
vlc_tick_t diff = stream_diff - system_diff;
if (diff > CR_MAX_GAP || diff < -CR_MAX_GAP)
{
/* Stream discontinuity, for which we haven't received a
* warning from the stream control facilities (dd-edited
* stream ?). */
msg_Warn(p_log, "clock gap, unexpected stream discontinuity: "
"system_diff: %"PRId64" stream_diff: %"PRId64,
system_diff, stream_diff);
/* */
msg_Warn(p_log, "feeding synchro with a new reference point trying"
" to recover from clock gap");
b_reset_reference= true;
discontinuity = true;
}
}
cl->b_origin_changed = false;
/* */
if( b_reset_reference )
{
cl->i_next_drift_update = VLC_TICK_INVALID;
AvgReset( &cl->drift );
/* Feed synchro with a new reference point. */
cl->b_has_reference = true;
cl->ref = clock_point_Create( __MAX( CR_MEAN_PTS_GAP, i_ck_system ),
i_ck_stream );
}
/* Compute the drift between the stream clock and the system clock
* when we don't control the source pace */
if( !b_can_pace_control && cl->i_next_drift_update < i_ck_system )
{
const vlc_tick_t i_converted = ClockSystemToStream( cl, i_ck_system );
AvgUpdate( &cl->drift, i_converted - i_ck_stream );
cl->i_next_drift_update = i_ck_system + VLC_TICK_FROM_MS(200); /* FIXME why that */
}
/* Update the extra buffering value */
if( !b_can_pace_control || b_reset_reference )
{
cl->i_buffering_duration = 0;
}
else if( b_buffering_allowed )
{
/* Try to bufferize more than necessary by reading
* CR_BUFFERING_RATE/256 faster until we have CR_BUFFERING_TARGET.
*/
const vlc_tick_t i_duration = __MAX( i_ck_stream - cl->last.stream, 0 );
cl->i_buffering_duration += ( i_duration * CR_BUFFERING_RATE + 255 ) / 256;
if( cl->i_buffering_duration > CR_BUFFERING_TARGET )
cl->i_buffering_duration = CR_BUFFERING_TARGET;
}
//fprintf( stderr, "input_clock_Update: %d :: %lld\n", b_buffering_allowed, cl->i_buffering_duration/1000 );
/* */
cl->last = clock_point_Create( i_ck_system, i_ck_stream );
/* It does not take the decoder latency into account but it is not really
* the goal of the clock here */
const vlc_tick_t i_system_expected = ClockStreamToSystem( cl, i_ck_stream + AvgGet( &cl->drift ) );
const vlc_tick_t i_late = __MAX(0, ( i_ck_system - cl->i_pts_delay ) - i_system_expected);
if( i_late > 0 )
{
cl->late.pi_value[cl->late.i_index] = i_late;
cl->late.i_index = ( cl->late.i_index + 1 ) % INPUT_CLOCK_LATE_COUNT;
}
UpdateListener( cl, discontinuity );
return i_late;
}
/*****************************************************************************
* input_clock_Reset:
*****************************************************************************/
void input_clock_Reset( input_clock_t *cl )
{
cl->b_has_reference = false;
cl->b_origin_changed = false;
cl->ref = cl->last
= clock_point_Create( VLC_TICK_INVALID, VLC_TICK_INVALID );
if (cl->listener.cbs != NULL && cl->listener.cbs->reset != NULL)
cl->listener.cbs->reset(cl->listener.opaque);
}
/*****************************************************************************
* input_clock_ChangeRate:
*****************************************************************************/
void input_clock_ChangeRate( input_clock_t *cl, float rate )
{
if( cl->b_has_reference )
{
/* Move the reference point (as if we were playing at the new rate
* from the start */
cl->ref.system = cl->last.system - (vlc_tick_t) ((cl->last.system - cl->ref.system) / rate * cl->rate);
}
cl->rate = rate;
UpdateListener( cl, false );
}
/*****************************************************************************
* input_clock_ChangePause:
*****************************************************************************/
void input_clock_ChangePause( input_clock_t *cl, bool b_paused, vlc_tick_t i_date )
{
assert( (!cl->b_paused) != (!b_paused) );
if( cl->b_paused )
{
const vlc_tick_t i_duration = i_date - cl->i_pause_date;
if( cl->b_has_reference && i_duration > 0 )
{
cl->ref.system += i_duration;
cl->last.system += i_duration;
UpdateListener( cl, false );
}
}
cl->i_pause_date = i_date;
cl->b_paused = b_paused;
}
/*****************************************************************************
* input_clock_GetWakeup
*****************************************************************************/
vlc_tick_t input_clock_GetWakeup( input_clock_t *cl )
{
vlc_tick_t i_wakeup = 0;
/* Synchronized, we can wait */
if( cl->b_has_reference )
i_wakeup = ClockStreamToSystem( cl, cl->last.stream + AvgGet( &cl->drift ) - cl->i_buffering_duration );
return i_wakeup;
}
/*****************************************************************************
* input_clock_GetRate: Return current rate
*****************************************************************************/
float input_clock_GetRate( input_clock_t *cl )
{
return cl->rate;
}
int input_clock_GetState( input_clock_t *cl,
vlc_tick_t *pi_stream_start, vlc_tick_t *pi_system_start,
vlc_tick_t *pi_stream_duration, vlc_tick_t *pi_system_duration )
{
if( !cl->b_has_reference )
return VLC_EGENERIC;
*pi_stream_start = cl->ref.stream;
*pi_system_start = cl->ref.system;
*pi_stream_duration = cl->last.stream - cl->ref.stream;
*pi_system_duration = cl->last.system - cl->ref.system;
return VLC_SUCCESS;
}
void input_clock_ChangeSystemOrigin( input_clock_t *cl, vlc_tick_t i_system )
{
assert( cl->b_has_reference );
cl->b_origin_changed = true;
vlc_tick_t i_offset = i_system - cl->ref.system - ClockGetTsOffset( cl );
cl->ref.system += i_offset;
cl->last.system += i_offset;
UpdateListener( cl, false );
}
#warning "input_clock_SetJitter needs more work"
void input_clock_SetJitter( input_clock_t *cl,
vlc_tick_t i_pts_delay, int i_cr_average )
{
/* Update late observations */
const vlc_tick_t i_delay_delta = i_pts_delay - cl->i_pts_delay;
vlc_tick_t pi_late[INPUT_CLOCK_LATE_COUNT];
for( int i = 0; i < INPUT_CLOCK_LATE_COUNT; i++ )
pi_late[i] = __MAX( cl->late.pi_value[(cl->late.i_index + 1 + i)%INPUT_CLOCK_LATE_COUNT] - i_delay_delta, 0 );
for( int i = 0; i < INPUT_CLOCK_LATE_COUNT; i++ )
cl->late.pi_value[i] = 0;
cl->late.i_index = 0;
for( int i = 0; i < INPUT_CLOCK_LATE_COUNT; i++ )
{
if( pi_late[i] <= 0 )
continue;
cl->late.pi_value[cl->late.i_index] = pi_late[i];
cl->late.i_index = ( cl->late.i_index + 1 ) % INPUT_CLOCK_LATE_COUNT;
}
/* TODO always save the value, and when rebuffering use the new one if smaller
* TODO when increasing -> force rebuffering
*/
if( cl->i_pts_delay < i_pts_delay )
cl->i_pts_delay = i_pts_delay;
/* */
if( i_cr_average < 10 )
i_cr_average = 10;
if( cl->drift.range != i_cr_average )
AvgRescale( &cl->drift, i_cr_average );
}
vlc_tick_t input_clock_GetJitter( input_clock_t *cl )
{
static_assert (INPUT_CLOCK_LATE_COUNT == 3,
"unsupported INPUT_CLOCK_LATE_COUNT");
/* Find the median of the last late values
* It works pretty well at rejecting bad values
*
* XXX we only increase pts_delay over time, decreasing it is
* not that easy if we want to be robust.
*/
const vlc_tick_t *p = cl->late.pi_value;
vlc_tick_t i_late_median = p[0] + p[1] + p[2] - __MIN(__MIN(p[0],p[1]),p[2]) - __MAX(__MAX(p[0],p[1]),p[2]);
vlc_tick_t i_pts_delay = cl->i_pts_delay ;
return i_pts_delay + i_late_median;
}
/*****************************************************************************
* ClockStreamToSystem: converts a movie clock to system date
*****************************************************************************/
static vlc_tick_t ClockStreamToSystem( input_clock_t *cl, vlc_tick_t i_stream )
{
if( !cl->b_has_reference )
return VLC_TICK_INVALID;
return (vlc_tick_t) (( i_stream - cl->ref.stream ) / cl->rate) + cl->ref.system;
}
/*****************************************************************************
* ClockSystemToStream: converts a system date to movie clock
*****************************************************************************
* Caution : a valid reference point is needed for this to operate.
*****************************************************************************/
static vlc_tick_t ClockSystemToStream( input_clock_t *cl, vlc_tick_t i_system )
{
assert( cl->b_has_reference );
return (vlc_tick_t) (( i_system - cl->ref.system ) * cl->rate) + cl->ref.stream;
}
/**
* It returns timestamp display offset due to ref/last modified on rate changes
* It ensures that currently converted dates are not changed.
*/
static vlc_tick_t ClockGetTsOffset( input_clock_t *cl )
{
return cl->i_pts_delay * ( 1.0f / cl->rate - 1.0f );
}