ffplay源码分析
-------------------------------------------------------------------音视频同步部分
1、音视频同步 main-> event_loop-> refresh_loop_wait_event-> video_refresh
主函数里面,event_loop阻塞等待GUI操作发来的事件,在阻塞里面会有wait方法,再调用video_refresh进行音视频同步,并显示视频帧。
static void video_refresh(void *opaque, double *remaining_time)
{
VideoState *is = opaque;
double time;
Frame *sp, *sp2;
if (is->video_st) {
retry:
if (frame_queue_nb_remaining(&is->pictq) == 0) {
// nothing to do, no picture to display in the queue
} else {
double last_duration, duration, delay;
Frame *vp, *lastvp;
//取得上一已播放的帧
lastvp = frame_queue_peek_last(&is->pictq);
//取得本帧(待播放的第一帧)
vp = frame_queue_peek(&is->pictq);
/*如果本帧的serial和视频packet队列的serial不想等,继续找下一帧,直到相等。
也就是如果有seek操作,要显示seek操作后的帧
*/
if (vp->serial != is->videoq.serial) {
frame_queue_next(&is->pictq);
goto retry;
}
/*
如果本帧和上一帧的serial不想等,则使is->frame_timer等于当前系统时间。
is->frame_timer是上一帧的播放时刻。
*/
if (lastvp->serial != vp->serial)
is->frame_timer = av_gettime_relative() / 1000000.0;
//如果暂停,去显示上一帧
if (is->paused)
goto display;
//计算vp.pts - lastvp.pts,也就是上一帧的播放时长
last_duration = vp_duration(is, lastvp, vp);
/*
计算delay值,通过调节delay值来达到提前或延后播放本帧。
如果视频落后于音频进度,则通过减小delay值,提前播放本帧(没等到last_duration的间隔,就播放),具体还要结合下面代码来判断。
如果视频超前音频进度,则增加delay值,延后播放本帧(也就是要超过last_duration),先暂时显示上一帧。
*/
delay = compute_target_delay(last_duration, is);
time= av_gettime_relative()/1000000.0;
/*
当前时间 < (上一帧播放时刻 + delay),也就是当前时间小于本帧播放时刻,意味着本帧的播放时刻未到,
则修改remaining_time,先显示上一帧
*/
if (time < is->frame_timer + delay) {
*remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
goto display;
}
//更新本帧播放时刻,更新后变为上一帧播放时刻
is->frame_timer += delay;
//如果当前时间和上一帧播放时刻差值超过阈值,则更新frame_timer为当前时间
if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)
is->frame_timer = time;
//更新视频时钟,目的是用于compute_target_delay函数中比较视频时钟和音频时钟上一帧的差值。
SDL_LockMutex(is->pictq.mutex);
if (!isnan(vp->pts))
update_video_pts(is, vp->pts, vp->pos, vp->serial);
SDL_UnlockMutex(is->pictq.mutex);
//如果丢帧策略生效(framedrop),并且视频落后(下一帧的播放时刻 < 当前时间),则丢弃本帧
if (frame_queue_nb_remaining(&is->pictq) > 1) {
Frame *nextvp = frame_queue_peek_next(&is->pictq);
duration = vp_duration(is, vp, nextvp);
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;
}
}
//移动读索引rindex,使本帧变为上一帧
frame_queue_next(&is->pictq);
is->force_refresh = 1;
if (is->step && !is->paused)
stream_toggle_pause(is);
}
display:
/* 显示视频帧*/
if (!display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown)
video_display(is);
}
is->force_refresh = 0;
}
static double compute_target_delay(double delay, VideoState *is)
{
double sync_threshold, diff = 0;
/* update delay to follow master synchronisation source */
if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) {
//时钟差值 = 最后播放的视频帧的pts - 主时钟最后播放的帧的pts(这里仅分析音频时钟)
diff = get_clock(&is->vidclk) - get_master_clock(is);
//阈值
sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
if (!isnan(diff) && fabs(diff) < is->max_frame_duration) {
if (diff <= -sync_threshold) //视频落后于音频,并且超过阈值
/*
diff是小于0的,delay是大于0的。
如果delay + diff = 本帧的pts,小于0(仍未追上音频),则delay=0,加速追赶音频。
否则 delay = delay + diff,表示经过delay时间,正好视频赶上音频
*/
delay = FFMAX(0, delay + diff);
else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD)
delay = delay + diff;
else if (diff >= sync_threshold)
//视频超前音频,大于阈值,简单扩大2倍delay,等待音频
delay = 2 * delay;
}
}
av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f\n",
delay, -diff);
//如果diff未超过sync_threshold阈值,可以认为音视频仍然处于同步状态,直接返回delay值。
return delay;
}
能力有限,前有大神分析到位,可以详看https://www.cnblogs.com/leisure_chn/p/10307089.html