vlc 播放网络视频流,比如rtsp流,需要进行是否超时的判断,判断是网路的延迟接受,还是因为解码器太慢导致延迟等,利用到网络流的时间戳,把网络流的时间戳和系统实际的时间建立一个对应关系,就是 /src/input/clock.c这个模块的作用。实际从vlc软件实际使用上可以观察出来,比如播放一个网络rtsp流,即使没有丢帧,仔细观察也会发现有些视频画面时快时慢,就是因为网络的不稳定导致接受到数据一会儿块一会儿慢,但是视频流数据中每一帧的时间戳信息是不会变的,原因在于vlc解码播放的时候把这个 时间漂移 考虑进去了,尽量按照接受到的实际样子播放,体现 Real-time Transport Protocol 实时性。
单独抽取出这个clock.c ,可以在ubuntu 中 gcc clock.c -lpthread 直接编译,其中有测试的main程序。测试研究其用法原理。
(其中有关于网络流时间漂移 平均值的计算逻辑。还有统计算术平均值的一个小方法)
其中有一个很重要的概念, PCR。 PCR(program clock reference)节目时钟基准,这个很重要,它是一个位置比较的参考点,一个系统时间,一个流时间,两者都在增长,但是两个并不一定是完全的线性关系,所以根据实际情况会间隔性地取某一时刻两个时间戳的瞬时值作为比较的参考。
环境:ubuntu , vlc3.0.6源码
其中clock 模块函数的解释:
//input_clock 方法函数的一些解释
//创建,初始化
input_clock_New();
//释放,反初始化
void input_clock_Delete( input_clock_t *cl )
//输出型函数
//es_out.c 中调用
//This functions will return a deadline used to control the reading speed.
//输入线程中,通过这个来获取一个 系统的绝对时间点,然后wait等到这个时间点才来调用
//解码器的 demux 函数获取一个数据
input_clock_GetWakeup();
//控制输入型函数
//重置,参考点也会被设置为空
//比如在 EsOutChangePostion() 修改播放时间点之后,会重设。等下一次调用 input_clock_Update
//更新时间点时就会利用新的时间点来建立参考点。
void input_clock_Reset( input_clock_t *cl )
//输入型函数
//修改两时间的线性关系倍速关系,比如两倍速播放后,即 2.0 rate
input_clock_ChangeRate( p_sys->pgrm[i]->p_clock, p_sys->i_rate );
//输入型函数
//暂停、继续播放
input_clock_ChangePause();
input_clock_GetSystemOrigin();//不重要
//输入型函数
input_clock_ChangeSystemOrigin(); //有需要 //修改基准时间
//重要, 这个是输出型函数
//在decoder.c DecoderFixTs中调用,将解码后的时间戳修正,对应到系统时间
//可以同时修正两个时间戳 pi_ts0 和 pi_ts1
input_clock_ConvertTS( vlc_object_t *, input_clock_t *, int *pi_rate,
mtime_t *pi_ts0, mtime_t *pi_ts1, mtime_t i_ts_bound );
//获取状态,参考起始时间点,和当前时间距离参考点的偏差
input_clock_GetState( input_clock_t *,
mtime_t *pi_stream_start, mtime_t *pi_system_start,
mtime_t *pi_stream_duration, mtime_t *pi_system_duration );
//输入型函数
//设置抖动?? //在es_out.c 中 其中的第三个参数,i_cr_average ,是用来设置漂移平均值的样本数的
//case ES_OUT_SET_PCR:
//case ES_OUT_SET_GROUP_PCR:
//被调用设置, 并且同时,在这里调用 input_clock_Update
void input_clock_SetJitter( input_clock_t *cl,
mtime_t i_pts_delay, int i_cr_average )
mtime_t input_clock_GetJitter( input_clock_t * );
// 输入型函数 重要
//外部周期性调用这个函数,就会周期性地 控制系统时间和流时间 线性增长
//一旦两者的增长率 不一致,不再保持线性平行关系,就计算出一个时间漂移出来 AvgGet( &cl->drift )获取时间漂移值。这是一个动态变化的值
// 在转换时间关系时,会应用这个时间漂移 来作为余数来 补偿。
//比如 input_clock_ConvertTS()函数, *pi_ts0 = ClockStreamToSystem( cl, *pi_ts0 + AvgGet( &cl->drift ) );
//再比如 input_clock_GetWakeup() 也会用到
//该函数内部,如果出现断流,就会重新强制更新参考点,这个时候时间漂移 也就重置为0 了
void input_clock_Update( input_clock_t *cl, vlc_object_t *p_log,
bool *pb_late,
bool b_can_pace_control, bool b_buffering_allowed,
mtime_t i_ck_stream, mtime_t i_ck_system )
抽取出的源码:clock.h clock.c //添加了部分宏定义等,可以直接在ubuntu 中编译, #gcc clock.c -lpthread
/*****************************************************************************
* clock.h: clocks synchronisation
*****************************************************************************
* Copyright (C) 2008 VLC authors and VideoLAN
* Copyright (C) 2008 Laurent Aimar
* $Id: dc4c8bd9352ed669930fe4b36dcd601783d6026c $
*
* Authors: 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.
*****************************************************************************/
#ifndef LIBVLC_INPUT_CLOCK_H
#define LIBVLC_INPUT_CLOCK_H 1
//#include <vlc_common.h>
//#include <vlc_input.h> /* FIXME Needed for input_clock_t */
#include<stdio.h>
#include<stdlib.h>
typedef long int64_t;
typedef unsigned long uint64_t;
typedef int64_t mtime_t;
typedef int bool;
#define INT64_MAX (0xefffffffffffffff)
#define false 0
#define true 1
#define VLC_TS_INVALID (0)
#define VLC_TS_0 (1)
#define CLOCK_FREQ (1000000)
#if 1
#define msg_Info(p_this, ...) \
printf("%p VLC_MSG_INFO",p_this);
#define msg_Warn(p_this, ...) \
printf("%p VLC_MSG_WARN",p_this);printf
#define msg_Err(p_this, ...) \
printf("%p VLC_MSG_ERR",p_this);printf
#define msg_Dbg(p_this, ...) \
printf("%p VLC_MSG_DBG",p_this);printf
#else
#define msg_Info(p_this, ...)
#define msg_Warn(p_this, ...)
#define msg_Err(p_this, ...)
#define msg_Dbg(p_this, ...)
#endif
/*****************************************************************************
* Error values (shouldn't be exposed)
*****************************************************************************/
#define VLC_SUCCESS (-0) /**< No error */
#define VLC_EGENERIC (-1) /**< Unspecified error */
#define VLC_ENOMEM (-2) /**< Not enough memory */
#define VLC_ETIMEOUT (-3) /**< Timeout */
#define VLC_ENOMOD (-4) /**< Module not found */
#define VLC_ENOOBJ (-5) /**< Object not found */
#define VLC_ENOVAR (-6) /**< Variable not found */
#define VLC_EBADVAR (-7) /**< Bad variable value */
#define VLC_ENOITEM (-8) /**< Item not found */
/**
* Default rate value
*/
#define INPUT_RATE_DEFAULT 1000
/**
* Minimal rate value
*/
#define INPUT_RATE_MIN 32 /* Up to 32/1 */
/**
* Maximal rate value
*/
#define INPUT_RATE_MAX 32000 /* Up to 1/32 */
typedef pthread_mutex_t vlc_mutex_t;
/* __MAX and __MIN: self explanatory */
#ifndef __MAX
# define __MAX(a, b) ( ((a) > (b)) ? (a) : (b) )
#endif
#ifndef __MIN
# define __MIN(a, b) ( ((a) < (b)) ? (a) : (b) )
#endif
#define VLC_UNUSED(x) (void)(x)
/** @struct input_clock_t
* This structure is used to manage clock drift and reception jitters
*
* XXX input_clock_GetTS can be called from any threads. All others functions
* MUST be called from one and only one thread.
*/
typedef struct input_clock_t input_clock_t;
/**
* This function creates a new input_clock_t.
* You must use input_clock_Delete to delete it once unused.
* pram: b_support_lock_drift: default value false
*/
input_clock_t *input_clock_New( int i_rate);
/**
* This function destroys a input_clock_t created by input_clock_New.
*/
void input_clock_Delete( input_clock_t * );
/**
* This function will update a input_clock_t with a new clock reference point.
* It will also tell if the clock point is late regarding our buffering.
*
* \param b_buffering_allowed tells if we are allowed to bufferize more data in
* advanced (if possible).
*/
void input_clock_Update( input_clock_t *, void *p_log,
bool *pb_late,
bool b_can_pace_control, bool b_buffering_allowed,
mtime_t i_clock, mtime_t i_system );
/**
* This function will reset the drift of a input_clock_t.
*
* The actual jitter estimation will not be reseted by it.
*/
void input_clock_Reset( input_clock_t * );
/**
* This functions will return a deadline used to control the reading speed.
*/
mtime_t input_clock_GetWakeup( input_clock_t * );
/**
* This functions allows changing the actual reading speed.
*/
void input_clock_ChangeRate( input_clock_t *, int i_rate );
/**
* This function allows changing the pause status.
*/
void input_clock_ChangePause( input_clock_t *, bool b_paused, mtime_t i_date );
/**
* This function returns the original system value date and the delay for the current
* reference point (a valid reference point must have been set).
*/
void input_clock_GetSystemOrigin( input_clock_t *, mtime_t *pi_system, mtime_t *pi_delay );
/**
* This function allows rebasing the original system value date (a valid
* reference point must have been set).
* When using the absolute mode, it will create a discontinuity unless
* called imediatly after a input_clock_Update.
*/
void input_clock_ChangeSystemOrigin( input_clock_t *, bool b_absolute, mtime_t i_system );
/**
* This function converts a pair of timestamp from stream clock to system clock.
*
* If pi_rate is provided it will be filled with the rate value used for
* the conversion.
* p_ts0 is a pointer to a timestamp to be converted (in place) and must be non NULL.
* p_ts1 is a pointer to a timestamp to be converted (in place) and can be NULL.
*
* It will return VLC_EGENERIC if i_ts_bound is not INT64_MAX and if the value *p_ts0
* after conversion is not before the deadline mdate() + i_pts_delay + i_ts_bound.
* It will also return VLC_EGENERIC if the conversion cannot be done successfully. In
* this case, *p_ts0 and *p_ts1 will hold an invalid timestamp.
* Otherwise it will return VLC_SUCCESS.
*/
int input_clock_ConvertTS( void *, input_clock_t *, int *pi_rate,
mtime_t *pi_ts0, mtime_t *pi_ts1, mtime_t i_ts_bound );
/**
* This function returns the current rate.
*/
int input_clock_GetRate( input_clock_t * );
/**
* This function returns current clock state or VLC_EGENERIC if there is not a
* reference point.
*/
int input_clock_GetState( input_clock_t *,
mtime_t *pi_stream_start, mtime_t *pi_system_start,
mtime_t *pi_stream_duration, mtime_t *pi_system_duration );
/**
* This function allows the set the minimal configuration for the jitter estimation algo.
*/
void input_clock_SetJitter( input_clock_t *,
mtime_t i_pts_delay, int i_cr_average );
/**
* This function returns an estimation of the pts_delay needed to avoid rebufferization.
* XXX in the current implementation, the pts_delay will never be decreased.
*/
mtime_t input_clock_GetJitter( input_clock_t * );
#endif
/*****************************************************************************
* clock.c: Clock/System date convertions, stream management
*****************************************************************************
* Copyright (C) 1999-2008 VLC authors and VideoLAN
* Copyright (C) 2008 Laurent Aimar
* $Id: e1cc0fc0aea89f57c5fe1aede2abef352700b7c9 $
*
* 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 <vlc_input.h>
#include "clock.h"
#include <assert.h>
#include <sys/time.h>
#include <time.h>
#include <pthread.h>
#include <unistd.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
*****************************************************************************/
/* Maximum gap allowed between two CRs. */
#define CR_MAX_GAP (60 * CLOCK_FREQ)
/* Latency introduced on DVDs with CR == 0 on chapter change - this is from
* my dice --Meuuh */
#define CR_MEAN_PTS_GAP (300000)
/* 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 (60000000)
/* Due to some problems in es_out, we cannot use a large value yet */
#define CR_BUFFERING_TARGET (100000)
mtime_t mdate (void)
{
struct timespec ts;
if (clock_gettime (CLOCK_MONOTONIC, &ts) != 0)
abort ();
clock_gettime(CLOCK_MONOTONIC, &ts);
return (1000000 * ts.tv_sec) + (ts.tv_nsec / 1000);
}
void vlc_mutex_init( vlc_mutex_t *p_mutex )
{
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
#ifdef NDEBUG
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_DEFAULT);
#else
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_ERRORCHECK);
#endif
pthread_mutex_init (p_mutex, &attr);
pthread_mutexattr_destroy( &attr );
}
void vlc_mutex_lock (vlc_mutex_t *p_mutex)
{
pthread_mutex_lock( p_mutex );
//VLC_THREAD_ASSERT ("locking mutex");
}
int vlc_mutex_trylock (vlc_mutex_t *p_mutex)
{
int val = pthread_mutex_trylock( p_mutex );
// if (val != EBUSY)
// VLC_THREAD_ASSERT ("locking mutex");
return val;
}
void vlc_mutex_unlock (vlc_mutex_t *p_mutex)
{
int val = pthread_mutex_unlock( p_mutex );
//VLC_THREAD_ASSERT ("unlocking mutex");
}
void vlc_mutex_destroy (vlc_mutex_t *p_mutex)
{
int val = pthread_mutex_destroy( p_mutex );
// VLC_THREAD_ASSERT ("destroying mutex");
}
/*****************************************************************************
* Structures
*****************************************************************************/
/**
* This structure holds long term average
*/
typedef struct
{
mtime_t i_value;
int i_residue;
int i_count;
int i_divider;
} average_t;
static void AvgInit( average_t *, int i_divider );
static void AvgClean( average_t * );
static void AvgReset( average_t * );
static void AvgUpdate( average_t *, mtime_t i_value );
static mtime_t AvgGet( average_t * );
static void AvgRescale( average_t *, int i_divider );
/* */
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;
}
/* */
#define INPUT_CLOCK_LATE_COUNT (3)
/* */
struct input_clock_t
{
/* */
vlc_mutex_t lock;
/* Last point
* It is used to detect unexpected stream discontinuities */
clock_point_t last;
/* Maximal timestamp returned by input_clock_ConvertTS (in system unit) */
mtime_t i_ts_max;
/* Amount of extra buffering expressed in stream clock */
mtime_t i_buffering_duration;
/* Clock drift */
mtime_t i_next_drift_update;
average_t drift;
/* Late statistics */
struct
{
mtime_t pi_value[INPUT_CLOCK_LATE_COUNT];
unsigned i_index;
} late;
/* Reference point */
clock_point_t ref;
bool b_has_reference;
/* External clock drift */
mtime_t i_external_clock;
bool b_has_external_clock;
/* Current modifiers */
bool b_paused;
int i_rate;
mtime_t i_pts_delay;
mtime_t i_pause_date;
};
static mtime_t ClockStreamToSystem( input_clock_t *, mtime_t i_stream );
static mtime_t ClockSystemToStream( input_clock_t *, mtime_t i_system );
static mtime_t ClockGetTsOffset( input_clock_t * );
/*****************************************************************************
* input_clock_New: create a new clock
*****************************************************************************/
input_clock_t *input_clock_New( int i_rate )
{
input_clock_t *cl = malloc( sizeof(*cl) );
if( !cl )
return NULL;
vlc_mutex_init( &cl->lock );
cl->b_has_reference = false;
cl->ref = clock_point_Create( VLC_TS_INVALID, VLC_TS_INVALID );
cl->b_has_external_clock = false;
cl->last = clock_point_Create( VLC_TS_INVALID, VLC_TS_INVALID );
cl->i_ts_max = VLC_TS_INVALID;
cl->i_buffering_duration = 0;
cl->i_next_drift_update = VLC_TS_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->i_rate = i_rate;
cl->i_pts_delay = 0;
cl->b_paused = false;
cl->i_pause_date = VLC_TS_INVALID;
return cl;
}
/*****************************************************************************
* input_clock_Delete: destroy a new clock
*****************************************************************************/
void input_clock_Delete( input_clock_t *cl )
{
AvgClean( &cl->drift );
vlc_mutex_destroy( &cl->lock );
free( cl );
}
static FILE *fp_clock_log = NULL;
/*****************************************************************************
* input_clock_Update: manages a clock reference
*
* i_ck_stream: date in stream clock
* i_ck_system: date in system clock
*****************************************************************************/
void input_clock_Update( input_clock_t *cl, void *p_log,
bool *pb_late,
bool b_can_pace_control, bool b_buffering_allowed,
mtime_t i_ck_stream, mtime_t i_ck_system )
{
bool b_reset_reference = false;
assert( i_ck_stream > VLC_TS_INVALID && i_ck_system > VLC_TS_INVALID );
vlc_mutex_lock( &cl->lock );
do{
if( !cl->b_has_reference )
{
/* */
b_reset_reference= true;
}
else if( cl->last.i_stream > VLC_TS_INVALID &&
( (cl->last.i_stream - i_ck_stream) > CR_MAX_GAP ||
(cl->last.i_stream - i_ck_stream) < -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" );
cl->i_ts_max = VLC_TS_INVALID;
/* */
msg_Warn( p_log, "feeding synchro with a new reference point trying to recover from clock gap" );
b_reset_reference= true;
}
/* */
if( b_reset_reference )
{
cl->i_next_drift_update = VLC_TS_INVALID;
AvgReset( &cl->drift );
/* Feed synchro with a new reference point. */
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 ) );
cl->b_has_external_clock = false;
}
/* 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 mtime_t i_converted = ClockSystemToStream( cl, i_ck_system );
AvgUpdate( &cl->drift, i_converted - i_ck_stream );
cl->i_next_drift_update = i_ck_system + CLOCK_FREQ/5; /* 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 mtime_t i_duration = __MAX( i_ck_stream - cl->last.i_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_stream, i_ck_system );
/* It does not take the decoder latency into account but it is not really
* the goal of the clock here */
const mtime_t i_system_expected = ClockStreamToSystem( cl, i_ck_stream + AvgGet( &cl->drift ) );
const mtime_t i_late = ( i_ck_system - cl->i_pts_delay ) - i_system_expected;
*pb_late = i_late > 0;
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;
}
}while(0);
vlc_mutex_unlock( &cl->lock );
}
/*****************************************************************************
* input_clock_Reset:
*****************************************************************************/
void input_clock_Reset( input_clock_t *cl )
{
vlc_mutex_lock( &cl->lock );
cl->b_has_reference = false;
cl->ref = clock_point_Create( VLC_TS_INVALID, VLC_TS_INVALID );
cl->b_has_external_clock = false;
cl->i_ts_max = VLC_TS_INVALID;
vlc_mutex_unlock( &cl->lock );
}
/*****************************************************************************
* input_clock_ChangeRate:
*****************************************************************************/
void input_clock_ChangeRate( input_clock_t *cl, int i_rate )
{
vlc_mutex_lock( &cl->lock );
if( cl->b_has_reference )
{
/* Move the reference point (as if we were playing at the new rate
* from the start */
cl->ref.i_system = cl->last.i_system - (cl->last.i_system - cl->ref.i_system) * i_rate / cl->i_rate;
}
cl->i_rate = i_rate;
vlc_mutex_unlock( &cl->lock );
}
/*****************************************************************************
* 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 )
{//to resum
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 );
}
/*****************************************************************************
* input_clock_GetWakeup
*****************************************************************************/
mtime_t input_clock_GetWakeup( input_clock_t *cl )
{
mtime_t i_wakeup = 0;
vlc_mutex_lock( &cl->lock );
/* Synchronized, we can wait */
if( cl->b_has_reference )
i_wakeup = ClockStreamToSystem( cl, cl->last.i_stream + AvgGet( &cl->drift ) - cl->i_buffering_duration );
vlc_mutex_unlock( &cl->lock );
printf("wangtest wakeup %ld [%d%s]\n",i_wakeup,__LINE__,__FUNCTION__);
return i_wakeup;
}
/*****************************************************************************
* input_clock_ConvertTS
*****************************************************************************/
int input_clock_ConvertTS( void *p_object, input_clock_t *cl,
int *pi_rate, mtime_t *pi_ts0, mtime_t *pi_ts1,
mtime_t i_ts_bound )
{
assert( pi_ts0 );
vlc_mutex_lock( &cl->lock );
if( pi_rate )
*pi_rate = cl->i_rate;
if( !cl->b_has_reference )
{
vlc_mutex_unlock( &cl->lock );
msg_Err(p_object, "Timestamp conversion failed for %"PRId64": "
"no reference clock", *pi_ts0);
*pi_ts0 = VLC_TS_INVALID;
if( pi_ts1 )
*pi_ts1 = VLC_TS_INVALID;
return VLC_EGENERIC;
}
/* */
const mtime_t i_ts_buffering = cl->i_buffering_duration * cl->i_rate / INPUT_RATE_DEFAULT;
const mtime_t i_ts_delay = cl->i_pts_delay + ClockGetTsOffset( cl );
/* */
if( *pi_ts0 > VLC_TS_INVALID )
{
*pi_ts0 = ClockStreamToSystem( cl, *pi_ts0 + AvgGet( &cl->drift ) );
if( *pi_ts0 > cl->i_ts_max )
cl->i_ts_max = *pi_ts0;
*pi_ts0 += i_ts_delay;
}
/* XXX we do not update i_ts_max on purpose */
if( pi_ts1 && *pi_ts1 > VLC_TS_INVALID )
{
*pi_ts1 = ClockStreamToSystem( cl, *pi_ts1 + AvgGet( &cl->drift ) ) +
i_ts_delay;
}
vlc_mutex_unlock( &cl->lock );
/* Check ts validity */
if (i_ts_bound != INT64_MAX && *pi_ts0 > VLC_TS_INVALID) {
if (*pi_ts0 >= mdate() + i_ts_delay + i_ts_buffering + i_ts_bound) {
msg_Err(p_object,
"Timestamp conversion failed (delay %"PRId64", buffering "
"%"PRId64", bound %"PRId64")",
i_ts_delay, i_ts_buffering, i_ts_bound);
return VLC_EGENERIC;
}
}
return VLC_SUCCESS;
}
/*****************************************************************************
* 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;
}
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, bool b_absolute, mtime_t i_system )
{
vlc_mutex_lock( &cl->lock );
assert( cl->b_has_reference );
mtime_t i_offset;
if( b_absolute )
{
i_offset = i_system - cl->ref.i_system - ClockGetTsOffset( cl );
}
else
{
if( !cl->b_has_external_clock )
{
cl->b_has_external_clock = true;
cl->i_external_clock = i_system;
}
i_offset = i_system - cl->i_external_clock;
}
cl->ref.i_system += i_offset;
cl->last.i_system += i_offset;
vlc_mutex_unlock( &cl->lock );
}
void input_clock_GetSystemOrigin( input_clock_t *cl, mtime_t *pi_system, mtime_t *pi_delay )
{
vlc_mutex_lock( &cl->lock );
assert( cl->b_has_reference );
*pi_system = cl->ref.i_system;
if( pi_delay )
*pi_delay = cl->i_pts_delay;
vlc_mutex_unlock( &cl->lock );
}
#warning "input_clock_SetJitter needs more work"
void input_clock_SetJitter( input_clock_t *cl,
mtime_t i_pts_delay, int i_cr_average )
{
vlc_mutex_lock( &cl->lock );
/* Update late observations */
const mtime_t i_delay_delta = i_pts_delay - cl->i_pts_delay;
mtime_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.i_divider != i_cr_average )
AvgRescale( &cl->drift, i_cr_average );
vlc_mutex_unlock( &cl->lock );
}
mtime_t input_clock_GetJitter( input_clock_t *cl )
{
vlc_mutex_lock( &cl->lock );
#if INPUT_CLOCK_LATE_COUNT != 3
# error "unsupported INPUT_CLOCK_LATE_COUNT"
#endif
/* 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 mtime_t *p = cl->late.pi_value;
mtime_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]);
mtime_t i_pts_delay = cl->i_pts_delay ;
vlc_mutex_unlock( &cl->lock );
return i_pts_delay + i_late_median;
}
/*****************************************************************************
* ClockStreamToSystem: converts a movie clock to system date
*****************************************************************************/
static mtime_t ClockStreamToSystem( input_clock_t *cl, mtime_t i_stream )
{
if( !cl->b_has_reference )
return VLC_TS_INVALID;
return ( i_stream - cl->ref.i_stream ) * cl->i_rate / INPUT_RATE_DEFAULT +
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 +
cl->ref.i_stream;
}
/**
* It returns timestamp display offset due to ref/last modfied on rate changes
* It ensures that currently converted dates are not changed.
*/
static mtime_t ClockGetTsOffset( input_clock_t *cl )
{
return cl->i_pts_delay * ( cl->i_rate - INPUT_RATE_DEFAULT ) / INPUT_RATE_DEFAULT;
}
/*****************************************************************************
* Long term average helpers
*****************************************************************************/
static void AvgInit( average_t *p_avg, int i_divider )
{
p_avg->i_divider = i_divider;
AvgReset( p_avg );
}
static void AvgClean( average_t *p_avg )
{
VLC_UNUSED(p_avg);
}
static void AvgReset( average_t *p_avg )
{
p_avg->i_value = 0;
p_avg->i_residue = 0;
p_avg->i_count = 0;
}
static void AvgUpdate( average_t *p_avg, mtime_t i_value )
{
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++;
}
static mtime_t AvgGet( average_t *p_avg )
{
return p_avg->i_value;
}
static void AvgRescale( average_t *p_avg, int i_divider )
{
const mtime_t i_tmp = p_avg->i_value * p_avg->i_divider + p_avg->i_residue;
p_avg->i_divider = i_divider;
p_avg->i_value = i_tmp / p_avg->i_divider;
p_avg->i_residue = i_tmp % p_avg->i_divider;
}
/*--------------以下个人添加,用以测试研究----------------------------
*---------------------------------------------------*/
void clock_inter_AVG_test()
{// clock 内部有一个avg 即 average 平均值
//这个算法,得到的是一个近似的算术并均值, 实测当输入数据个数还未满 样本数时候,计算是不准的 BUG
/*
* This structure holds long term average
*typedef struct
*{
* mtime_t i_value; //最新的平均值
* int i_residue; //最新的 余数
*
* int i_count;//总共已经输入的个数
* int i_divider;//求平均值的数据个数,样本量
* } average_t;
*/
// 用来计算drift时间漂移的平均值的, 其中的 i_divider, 即统计的个数,比如只计算10个最近的输入数的平均值
//有这么些函数:1.0 AvgInit( average_t *, int i_divider ); AvgClean(average_t *)
//2.0 AvgUpdate( average_t *, mtime_t i_value );
//3.0 AvgGet( average_t * );
//4.0 AvgRescale( average_t *, int i_divider );
//6.0 AvgReset( average_t * );
//使用: 先初始化,AvgInit( average_t *, int i_divider ) 第二个参数表示求平均值的数据个数
// 用AvgUpdate() 输入数据, 第二个参数就是输入的数据 同时,可以用AvgGeg()获取到最新的平均值
// AvgRescale() 可以重新修改 样本量
printf("start:%s\n",__FUNCTION__);
average_t s_avg;
AvgInit(&s_avg,4);
mtime_t data[]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
int i =0;
for(i=0; i<sizeof(data)/sizeof(data[0]); i++)
{
AvgUpdate(&s_avg,data[i]);
printf("input %8dth data:%8ld, avrge:%8ld\n",i,data[i],AvgGet(&s_avg));
//printf("------[avg.i_value %ld , i_residue %d ]\n\n",s_avg.i_value,s_avg.i_residue);
}
}
#define NEW_TIME(x) (x)*100*10000
int test_sample()
{
#if 1
// 简单线性增长测试,不持续 pcr,没有时钟漂移
//pcr ::::: postion comparison reference
printf("start:%s\n",__FUNCTION__);
int i =0;
input_clock_t *p_sClock = NULL;
p_sClock = input_clock_New(INPUT_RATE_DEFAULT);//速率 1000ms
mtime_t stream_time[]={1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,
31,32,33,34,35,36,37,38,39,40,};
for(i=0; i<sizeof(stream_time)/sizeof(stream_time[0]); i++)
{
stream_time[i]= NEW_TIME(stream_time[i]);
}
//更新一次,使其有参考点
int b_late =false;
input_clock_Update( p_sClock, NULL,
&b_late,
false, true,
stream_time[0], mdate());
for(i=0; i<sizeof(stream_time)/sizeof(stream_time[0]); i++)
{
mtime_t time_temp = stream_time[i];
input_clock_ConvertTS( NULL, p_sClock, NULL, //pi_rate ,这是一个输出型参数,返回clock的速率
&time_temp, NULL, INT64_MAX );//可以同时支持两个时间戳转换,pts dts,
printf("stream_time:%8ld-----system_time:%8ld\n",stream_time[i],time_temp);
}
#endif
}
int test_drift()
{//周期 更新pcr, 时钟漂移动态变化
#if 1
//pcr ::::: postion comparison reference
printf("start:%s\n",__FUNCTION__);
int i =0;
input_clock_t *p_sClock_Drift = NULL;
input_clock_t *p_sClock_Norm = NULL;
p_sClock_Norm = input_clock_New(INPUT_RATE_DEFAULT);//正常速率 1000ms INPUT_RATE_DEFAULT == 1000
p_sClock_Drift = input_clock_New(INPUT_RATE_DEFAULT);//正常速率 1000ms INPUT_RATE_DEFAULT == 1000
mtime_t stream_time[]={1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30};
for(i=0; i<sizeof(stream_time)/sizeof(stream_time[0]); i++)
{// 让输入流的时间戳 是 1fps , 每秒1帧均匀增长
stream_time[i]= NEW_TIME(stream_time[i]);
}
//更新一下不计drift的clokc,使有一个参考点
int b_late =false;
input_clock_Update( p_sClock_Norm, NULL,
&b_late,
false, true,
stream_time[0], mdate());
mtime_t last_system = 0;
for(i=0; i<sizeof(stream_time)/sizeof(stream_time[0]); i++)
{
//模拟 1s + [-100~100]*2ms左右时间才收到一帧。
int igap =(rand()%200 - 100)*2000 + 100*10000;
usleep(igap);
if(!(i%4))
{//每隔3帧,更新一下dritf clock的参考时间线
int b_late=false;
input_clock_Update( p_sClock_Drift, NULL,
&b_late,
false, true,
stream_time[i], mdate());
}
mtime_t time_temp = stream_time[i];
input_clock_ConvertTS( NULL, p_sClock_Drift, NULL, //pi_rate ,这是一个输出型参数,返回clock的速率
&time_temp, NULL, INT64_MAX );//可以同时支持两个时间戳转换,pts dts,
printf("stream_time:%8ld-----system_time:%8ld increas: %8ld",stream_time[i],time_temp,time_temp-last_system);
//下面是不计算drift的情况下,对同样的 strem_time进行 转换成系统时间的值,作为对比。
last_system = time_temp;
mtime_t time_temp2 = stream_time[i];
input_clock_ConvertTS( NULL, p_sClock_Norm, NULL,
&time_temp2, NULL, INT64_MAX );
printf("--------normal_system %8ld\n",time_temp2);
}
#endif
}
int main()
{
printf("hello world [%d%s]\n",__LINE__,__FUNCTION__);
//clock_inter_AVG_test();
//test_sample();
test_drift();
}