2.4.3 视频同步到音频
视频同步到音频是 ffplay 的默认同步方式。在视频播放线程中实现。
视频播放线程中有一个很重要的函数 video_refresh(),实现了视频播放(包含同步控制)核心步骤,理解起来有些难度。
相关函数关系如下:
1 2 3 4 5 6 7 8 main() --> player_running() --> open_video() --> open_video_playing() --> SDL_CreateThread(video_playing_thread, ...) 创建视频播放线程 video_playing_thread() --> video_refresh()视频播放线程源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static int video_playing_thread(void *arg) { player_stat_t *is = (player_stat_t *)arg; double remaining_time = 0.0; while (1) { if (remaining_time > 0.0) { av_usleep((unsigned)(remaining_time * 1000000.0)); } remaining_time = REFRESH_RATE; // 立即显示当前帧,或延时remaining_time后再显示 video_refresh(is, &remaining_time); } return 0; }video_refresh() 函数源码如下:
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 /* called to display each frame */ static void video_refresh(void *opaque, double *remaining_time) { player_stat_t *is = (player_stat_t *)opaque; double time; static bool first_frame = true; retry: if (frame_queue_nb_remaining(&is->video_frm_queue) == 0) // 所有帧已显示 { // nothing to do, no picture to display in the queue return; } double last_duration, duration, delay; frame_t *vp, *lastvp; /* dequeue the picture */ lastvp = frame_queue_peek_last(&is->video_frm_queue); // 上一帧:上次已显示的帧 vp = frame_queue_peek(&is->video_frm_queue); // 当前帧:当前待显示的帧 // lastvp和vp不是同一播放序列(一个seek会开始一个新播放序列),将frame_timer更新为当前时间 if (first_frame) { is->frame_timer = av_gettime_relative() / 1000000.0; first_frame = false; } // 暂停处理:不停播放上一帧图像 if (is->paused) goto display; /* compute nominal last_duration */ last_duration = vp_duration(is, lastvp, vp); // 上一帧播放时长:vp->pts - lastvp->pts delay = compute_target_delay(last_duration, is); // 根据视频时钟和同步时钟的差值,计算delay值 time= av_gettime_relative()/1000000.0; // 当前帧播放时刻(is->frame_timer+delay)大于当前时刻(time),表示播放时刻未到 if (time < is->frame_timer + delay) { // 播放时刻未到,则更新刷新时间remaining_time为当前时刻到下一播放时刻的时间差 *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time); // 播放时刻未到,则不播放,直接返回 return; } // 更新frame_timer值 is->frame_timer += delay; // 校正frame_timer值:若frame_timer落后于当前系统时间太久(超过最大同步域值),则更新为当前系统时间 if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX) { is->frame_timer = time; } SDL_LockMutex(is->video_frm_queue.mutex); if (!isnan(vp->pts)) { update_video_pts(is, vp->pts, vp->pos, vp->serial); // 更新视频时钟:时间戳、时钟时间 } SDL_UnlockMutex(is->video_frm_queue.mutex); // 是否要丢弃未能及时播放的视频帧 if (frame_queue_nb_remaining(&is->video_frm_queue) > 1) // 队列中未显示帧数>1(只有一帧则不考虑丢帧) { frame_t *nextvp = frame_queue_peek_next(&is->video_frm_queue); // 下一帧:下一待显示的帧 duration = vp_duration(is, vp, nextvp); // 当前帧vp播放时长 = nextvp->pts - vp->pts // 当前帧vp未能及时播放,即下一帧播放时刻(is->frame_timer+duration)小于当前系统时刻(time) if (time > is->frame_timer + duration) { frame_queue_next(&is->video_frm_queue); // 删除上一帧已显示帧,即删除lastvp,读指针加1(从lastvp更新到vp) goto retry; } } // 删除当前读指针元素,读指针+1。若未丢帧,读指针从lastvp更新到vp;若有丢帧,读指针从vp更新到nextvp frame_queue_next(&is->video_frm_queue); display: video_display(is); // 取出当前帧vp(若有丢帧是nextvp)进行播放 }视频同步到音频的基本方法是:如果视频超前音频,则不进行播放,以等待音频;如果视频落后音频,则丢弃当前帧直接播放下一帧,以追赶音频。
本文转载自网络,感谢原作者的分享,转载仅为分享干货知识,如有侵权欢迎联系作者进行删除处理。