一、前言
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; }
四、核心流程时序图