ffplay源码分析(1)
1音视频同步基础
因为音视频解码,输出都是在不同线程中完成的,且有些片源音视频本身的pts 就存在飘动, 因此需要引入音视频同步机制。用一句话来总结音视频同步就是"慢了等,快了丢"。
在ffplay 中, 需要时刻将音频,视频时间与系统时间对齐(set_clock)。通过引入pts_drift 变量,记录系统时间与音频,视频时间的差值。 当set_clock() 时,pts_drift = pts - os time。
当get_clock() 时,得到的pts = os_time + pts_drift.。
在ffplay 中, 音视频同步策略有三种:
1 视频同步到音频:将视频的pts 与master clock比较,慢了等,快了丢。
2 音频同步到视频, 将音频的pts 与master clock 做比较,通过重采样库,控制样本输出。
3 音视频同步到外部时钟:复用了前两种的策略,等效与前两种的叠加。
什么是master clock?
以谁为准的时间, 比如视频同步到音频, 那就以音频的pts 作为master clock.
/* get the current master clock value */
static double get_master_clock(VideoState *is)
{
double val;
switch (get_master_sync_type(is)) {
case AV_SYNC_VIDEO_MASTER:
val = get_clock(&is->vidclk);
break;
case AV_SYNC_AUDIO_MASTER:
val = get_clock(&is->audclk);
break;
default:
val = get_clock(&is->extclk);
break;
}
return val;
}
2 视频同步到音频
视频同步到音频是在ffplay.c 中的video_refresh() 函数中完成的。通过引入frame_timer 来决定当前帧是否够了,慢了等,快了丢
/* compute nominal last_duration */
last_duration = vp_duration(is, lastvp, vp);//计算上一帧应该显示的时长
delay = compute_target_delay(last_duration, is);//上一帧显示时长 + 音视频pts 的diff
time= av_gettime_relative()/1000000.0;
if (time < is->frame_timer + delay) { //查看上一帧显示时间是否够了
*remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
goto display;// 不够,慢了等,继续上一帧显示
}
is->frame_timer += delay;//够了, 更新当前帧显示时间
............
/*下面为丢帧逻辑*/
if (frame_queue_nb_remaining(&is->pictq) > 1) {
Frame *nextvp = frame_queue_peek_next(&is->pictq);
duration = vp_duration(is, vp, nextvp);
//如果系统时间 > 当前帧显示时间 + 当前帧显示的duration, 则丢弃
if(!is->step && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){
is->frame_drops_late++;
frame_queue_next(&is->pictq);//快了丢,跳到下一帧,丢弃当前帧
goto retry;//retry,当前帧不再显示
}
3 音视同步到视频
与视频同步到音频逻辑不同, 音频同步到视频并不能简单的慢了等,快了丢,因为人耳对声音特别敏感。 因此音频同步到视频,通过重采样库,控制音频样本的输出时间。
在ffplay中, 音频同步到视频逻辑在audio_decode_frame().
static int audio_decode_frame(VideoState *is)
{
......
//与video clk 比较,计算应该数据的样本数
wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);
//判断是否需要重采样
if (af->frame->format != is->audio_src.fmt ||
dec_channel_layout != is->audio_src.channel_layout ||
af->frame->sample_rate != is->audio_src.freq ||
(wanted_nb_samples != af->frame->nb_samples && !is->swr_ctx)) {
swr_free(&is->swr_ctx);
//重新设置重采样参数
is->swr_ctx = swr_alloc_set_opts(NULL,
is->audio_tgt.channel_layout, is->audio_tgt.fmt, is->audio_tgt.freq,
dec_channel_layout, af->frame->format, af->frame->sample_rate,
0, NULL);
i
}
..........
//利用重采样库,实现对样本的插入删除
len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);
............
}
4 音视频同步到外部时钟
我们可以自由的选择音视频同步的方式,其实仔细看代码可以发现,音视频同步的选择,其实就是选择不同的clock 来作为master clock 。当选择外部时钟时,就会get_clock(&is->extck)。然后音频,视频输出就与extck 进行比较。因此同步到外部时钟 相当于 视频同步到音频,音频同步到视频的叠加。
/* get the current master clock value */
static double get_master_clock(VideoState *is)
{
double val;
switch (get_master_sync_type(is)) {
case AV_SYNC_VIDEO_MASTER:
val = get_clock(&is->vidclk);
break;
case AV_SYNC_AUDIO_MASTER:
val = get_clock(&is->audclk);
break;
default:
val = get_clock(&is->extclk);
break;
}
return val;
}