【VLC核心二】clock管理流程

一、前言

clock管理是vlc播放音视频的重要部分,从live555收流到decoder解码到render渲染,整个播放过程中均需依赖clock机制。

二、涉及的类文件

src\input\input.c

modules\access\live555.cpp

src\input\es_out.c

src\input\decoder.c

src\input\clock.c

三、clock核心点备注

1、live555::CmdExecuteControl(ES_OUT_SET_PCR,p_sys->i_pcr+1)

注:p_sys->i_pcr+1作为input_clock_Update的i_ck_stream参数传入。p_sys->i_pcr在live555的StreamRead函数中赋值

2、clock中i_cr_average值的来源

clock中i_cr_average=配置文件中读取默认40ms * i_pts_delay / DEFAULT_PTS_DELAY;
DEFAULT_PTS_DELAY = 3*CLOCK_FREQ/10

3、当准备收流或回放拖动时,会触发设置PCR动作,对应ES_OUT_SET_PCR,继而调用clock::input_clock_Update(i_pcr, mdate()),更新clock机制,其核心处理如下

a、判断当前流时戳与上一帧时戳差值是否大于MAX_GAP,如果大于MAX_GAP,说明收到的帧已经跳变过大,则重置clock值ResetClock。(MAX_GAP宏的值为60s,可以调整宏代码,此值过大,应该调整为1s以内,否则在回放拖动时有bug,后续文章详解);

b、每隔20ms调用AvgUpdate计算一次 stream clock 和system clock间的漂移;

typedef struct
{
    mtime_t i_value;
    int     i_residue; //残余

    int     i_count;	
    int     i_divider; // 分割;分配
} average_t;

AvgUpdate详解:
//将当前mdate时间转换为流时戳
const mtime_t i_converted = ClockSystemToStream( cl, i_ck_system );
//用转换的流时戳-真实流时戳作为ivalue
AvgUpdate( &cl->drift, i_converted - i_ck_stream );

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 );//i_f0记录当前已存值的个数
    const int i_f1 = p_avg->i_divider - i_f0;//剩余个数
	//统计总值=已存个数*平均value + 剩余个数*新来i_value + 余数
	// 好处是:未存满值的时候,剩余个数均使用新的i_value填充
    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++;
}

c、计算当前帧是否来晚了

当前系统时戳即mdate的当前时间- (当前系统时戳将流时戳+平均偏移,转换为系统时戳)。如果大于零,说明改帧比期望它到的时间来晚了。来晚了就更新到数组里。VLC缓存了三个late的值,为后续GetJitter获取的时候可以计算平均的late值。

摘录:如果late,存储到数组中
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;
    }

摘录:input_clock_GetJitter
寻找中间的late值,这种方法可以规避掉bad values
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 ;

d、如果晚了,在es_out.c中,调用clock::input_clock_GetJitter统计抖动,并调用clock:: input_clock_Reset和input_clock_SetJitter重置clock,重新调节计算漂移的参数。这样就可以重新缓存待解码的数据。


4、缓存数据的核心流程

a、每次es_out.c中SET_PCR中调用EsOutDecodersStopBuffering 如果是缓冲状态, 判断是否缓冲完?

1)计算流缓存时间:调用input_clock_GetState检查i_stream_duration值得到流缓存的时间
2)计算预设缓存时间:
const mtime_t i_buffering_duration = p_sys->i_pts_delay + i_preroll_duration + p_sys->i_buffering_extra_stream - p_sys->i_buffering_extra_initial;
实测中i_buffering_duration  = p_sys->i_pts_delay = 1000;其它值为0,待研究什么情况下其它值不为零。
3)如果i_stream_duration<i_buffering_duration,则继续缓冲,否则已缓冲满。

b、input_DecoderWaitBuffering缓冲完通知decoder模块。解码模块队列中循环解码,可参考文章《【VLC核心一】播放流程梳理


5、clock在live555收流拼帧部分相关工作

a、拼帧完成后送SteamRead,SteamRead中的处理

int64_t i_pts = (int64_t)pts.tv_sec * INT64_C(1000000) +(int64_t)pts.tv_usec;
i_pts &= INT64_C(0x00ffffffffffffff);

if( p_sys->i_pcr < i_pts )
{
    p_sys->i_pcr = i_pts;
}

四、核心流程时序图

【VLC核心二】clock管理流程

上一篇:Windows平台使用VS2013编译VLC源码


下一篇:[译]Godot系列教程六 - 简单的二维游戏