写点什么

想做直播的你,这些热门的音视频如何绝对同步的。你 get 了嘛?(1)

用户头像
Android架构
关注
发布于: 9 小时前

//更新 video clock//视频同步音频时没作用 SDL_LockMutex(is->pictq.mutex);if (!isnan(vp->pts))update_video_pts(is, vp->pts, vp->pos, vp->serial);SDL_UnlockMutex(is->pictq.mutex);


//……


//丢帧逻辑 if (frame_queue_nb_remaining(&is->pictq) > 1) {Frame *nextvp = frame_queue_peek_next(&is->pictq);duration = vp_duration(is, vp, nextvp);//当前帧显示时长 if(time > is->frame_timer + duration){//如果系统时间已经大于当前帧,则丢弃当前帧 is->frame_drops_late++;frame_queue_next(&is->pictq);goto retry;//回到函数开始位置,继续重试(这里不能直接 while 丢帧,因为很可能 audio clock 重新对时了,这样 delay 值需要重新计算)}}}


代码只保留了同步相关的部分,完整的代码可以参考 ffmpeg 源码


这段代码的逻辑在上述流程图中有包含。主要思路就是一开始提到的如果视频播放过快,则重复播放上一帧,以等待音频;如果视频播放过慢,则丢帧追赶音频。实现的方式是,参考 audio clock,计算上一帧(在屏幕上的那个画面)还应显示多久(含帧本身时长),然后与系统时刻对比,是否该显示下一帧了。


这里与系统时刻的对比,引入了另一个概念——frame_timer。可以理解为帧显示时刻,如更新前,是上一帧的显示时刻;对于更新后(is->frame_timer += delay),则为当前帧显示时刻。


上一帧显示时刻加上 delay(还应显示多久(含帧本身时长))即为上一帧应结束显示的时刻。具体原理看如下示意图:



这里给出了 3 种情况的示意图:


  • time1:系统时刻小于 lastvp 结束显示的时刻(frame_timer+dealy),即虚线圆圈位置。此时应该继续显示 lastvp

  • time2:系统时刻大于 lastvp 的结束显示时刻,但小于 vp 的结束显示时刻(vp 的显示时间开始于虚线圆圈,结束于黑色圆圈)。此时既不重复显示 lastvp,也不丢弃 vp,即应显示 vp

  • time3:系统时刻大于 vp 结束显示时刻(黑色圆圈位置,也是 nextvp 预计的开始显示时刻)。此时应该丢弃 vp。###delay 的计算那么接下来就要看最关键的 lastvp 的显示时长 delay 是如何计算的。


这在函数 compute_target_delay 中实现:


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) {/ if video is slave, we try to correct big delays byduplicating or deleting a frame */diff = get_clock(&is->vidclk) - get_master_clock(is);


/* skip or repeat frame. We take into account thedelay to compute the threshold. I still don't knowif it is the best guess */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)delay = FFMAX(0, delay + diff);else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD)delay = delay + diff;else if (diff >= sync_threshold)delay = 2 * delay;}}


av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f\n",delay, -diff);


return delay;}


上面代码中的注释全部是源码的注释,代码不长,注释占了快一半,可见这段代码重要性。


这段代码中最难理解的是 sync_threshold,画个图帮助理解:



图中坐标轴是 diff 值大小,diff 为 0 表示 video clock 与 audio clock 完全相同,完美同步。图纸下方色块,表示要返回的值,色块值的 delay 指传入参数,结合上一节代码,即 lastvp 的显示时长。


从图上可以看出来 sync_threshold 是建立一块区域,在这块区域内无需调整 lastvp 的显示时长,直接返回 delay 即可。也就是在这块区域内认为是准同步的。


如果小于-sync_threshold,那就是视频播放较慢,需要适当丢帧。具体是返回一个最大为 0 的值。根据前面 frame_timer 的图,至少应更新画面为 vp。


如果大于 sync_threshold,那么视频播放太快,需要适当重复显示 lastvp。具体是返回 2 倍的 delay,也就是 2 倍的 lastvp 显示时长,也就是让 lastvp 再显示一帧。


如果不仅大于 sync_threshold,而且超过了 AV_SYNC_FRAMEDUP_THRESHOLD,那么返回 de


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


lay+diff,由具体 diff 决定还要显示多久(这里不是很明白代码意图,按我理解,统一处理为返回 2*delay,或者 delay+diff 即可,没有区分的必要)


至此,基本上分析完了视频同步音频的过程,简单总结下:


  • 基本策略是:如果视频播放过快,则重复播放上一帧,以等待音频;

  • 如果视频播放过慢,则丢帧追赶音频。

  • 这一策略的实现方式是:引入 frame_timer 概念,标记帧的显示时刻和应结束显示的时刻,再与系统时刻对比,决定重复还是丢帧。

  • lastvp 的应结束显示的时刻,除了考虑这一帧本身的显示时长,还应考虑了 video clock 与 audio clock 的差值。

  • 并不是每时每刻都在同步,而是有一个“准同步”的差值区域。

关于我

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
想做直播的你,这些热门的音视频如何绝对同步的。你get了嘛?(1)