设为首页收藏本站

安徽论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 75823|回复: 0

音视频同步与帧率控制

[复制链接]

63

主题

507

回帖

959

积分

高级会员

Rank: 4

积分
959
发表于 2022-3-26 10:24:55 | 显示全部楼层 |阅读模式
网站内容均来自网络,本站只提供信息平台,如有侵权请联系删除,谢谢!
原文:https://github.com/rockcarry/fanplayer/wiki/%E9%9F%B3%E8%A7%86%E9%A2%91%E5%90%8C%E6%AD%A5%E4%B8%8E%E5%B8%A7%E7%8E%87%E6%8E%A7%E5%88%B6

音视频同步

音视频同步是一个播放器要处理的基本问题,音视频同步的好坏直接影响到播放效果。
解码后的音频片段和视频片段,都分别带有 pts 时间戳信息。回放时需要做的,就是尽量保证 apts(音频时间戳)和 vpts(视频时间戳),之间的差值是最小的。为了达到这个目的,就需要在 adev 和 vdev 进行渲染的时候进行控制。控制的方法就是 delay。
由于音频在回放时,我们必须保证连续性,就是说两个时间上连续的音频片段是不允许有 delay 的。如果有了 delay 人的耳朵可以很明显的分辨出来,给人的主观感受就是声音卡顿。所以通常情况下,声音都是连续播放,然后在视频渲染的时候做 delay,将视频同步到音频。
fanplayer 中,VDEV_COMMON_MEMBERS 中定义了
  1. int64_t   apts;int64_t   vpts;
复制代码
这两个变量,用于记录当前的 apts 和 vpts。apts 是在 adev 中,由 waveout 的 callback 函数进行更新的,代表着当前音频 pts 时间。vpts 是在 vdev 的视频渲染线程中更新的,代表着当前视频 pts 时间。在视频渲染线程中,有一段控制算法:
  1. //++ frame rate & av sync control ++//DWORD   tickcur  = GetTickCount();int     tickdiff = tickcur - c->ticklast;int64_t avdiff   = apts - vpts - c->tickavdiff;c->ticklast = tickcur;if (tickdiff - c->tickframe >  2) c->ticksleep--;if (tickdiff - c->tickframe < -2) c->ticksleep++;if (apts != -1 && vpts != -1) {    if (avdiff > 5) c->ticksleep-=2;    if (avdiff ticksleep+=2;}if (c->ticksleep < 0) c->ticksleep = 0;if (c->ticksleep > 0) Sleep(c->ticksleep);av_log(NULL, AV_LOG_INFO, "d3d d: %3lld, s: %d\n", avdiff, c->ticksleep);//-- frame rate & av sync control --//
复制代码
这一段代码,就实现了音视频同步和帧率控制。
这里:
  1. int64_t avdiff   = apts - vpts - c->tickavdiff;
复制代码
计算出了当前 apts 和 vpts 之间的差值 avdiff。如果 avdiff 为负数,说明 video 的渲染比 audio 快了,所以视频渲染线程的延时需要增加;如果 avdiff 为正数,说明 audio 跑得比 video 快了,所以要减少延时:
  1. if (apts != -1 && vpts != -1) {    if (avdiff > 5) c->ticksleep-=2;    if (avdiff ticksleep+=2;}
复制代码

这段代码在动态运行时,实时计算当前 avdiff 值,并且实时调整 c->ticksleep,使得 avdiff 值趋向于收敛,正常情况下 avdiff 的绝对值可达到 ticklast; // c->ticklast 是上一次的 tickc->ticklast = tickcur;[/code] 在线程的 while 循环中,每次执行到 int tickdiff = tickcur - c->ticklast; 这一行代码时,我们就计算出了上一次执行到这行代码和这次执行到这行代码时,极为精确的时间差值,精确到 1ms。
理论上这个 tickdiff 值,就应当等于 1000ms / 帧率,所以我们有了这样的动态控制算法:
  1. if (tickdiff - c->tickframe >  2) c->ticksleep--;if (tickdiff - c->tickframe < -2) c->ticksleep++;// tickdiff 为实际的实时的当前的视频渲染前后帧的时间差值// c->tickframe 为 1000ms / 帧率// c->ticksleep 为渲染线程的 sleep 延时时间
复制代码
原理就是当 tickdiff 过大,就减少延时时间,当 tickdiff 过小就增加延时时间。这也是一个实时的动态的控制过程,在运行过程中会自动实时调整 ticksleep 值,使得 tickdiff 收敛于 tickframe,即保证视频渲染的帧率恒定。
帧率控制的动态算法和音视频同步的动态算法,最终的计算结果,其实就是 c->ticksleep 这样一个延时时间。说白了,音视频同步和帧率控制,就是在视频渲染线程中通过做延时来实现的,而控制的关键变量,就是 ticksleep 这个延时时间。整个算法的最终目的,就是动态计算和调整 ticksleep,来保证 tickdiff 和 avdiff 两个指标趋于收敛。这段代码原理就是如此(算是比较简单),十几行代码就轻松解决了音视频同步和帧率控制两个关键问题。
当然目前的实现,只是考虑了将视频同步到音频,这样足以应付绝大多数多媒体文件。对于部分视频文件,其音频和视频的 pts 可能是不连续的,怎么办?这就需要将音视频的 pts 同步到系统的 clock 上。什么是系统的 clock,其实就是 GetTickCount 出来的系统 tick 值。在音视频渲染的开始,记录下当时的 tick 值,之后每次渲染音视频的时候,都重新获取当前的 tick 值,就能计算出系统的 clock 过去了多少 ms。然后比较系统 clock 和 apts/vpts 时间的差值,来决定延时时间。这就是所谓的同步音视频到系统 clock 的原理。
以下结合代码,注释说明 fanplayer 中实现的音视频收敛算法:
  1. //++ frame rate & av sync control ++//tickframe   = 100 * c->tickframe / c->speed; // 从多媒体文件实际帧率和变速播放速度计算出来的帧间隔时间tickcur     = av_gettime_relative() / 1000;  // 当前的 tick 时间tickdiff    = (int)(tickcur - c->ticklast);  // 当前的帧间隔,即这次渲染和上次渲染的 tick 差值c->ticklast = tickcur;// 帧率稳定的目标,就是 tickdiff 要收敛到 tickframe,这就保证了帧率稳定// re-calculate start_pts & start_tick if neededif (c->start_pts == AV_NOPTS_VALUE) {    c->start_pts = c->vpts;    c->start_tick= tickcur;}// 这里的 start_tick 和 start_pts 用于计算系统时钟// 这一行代码,是计算系统时钟sysclock= c->start_pts + (tickcur - c->start_tick) * c->speed / 100;// 这一行代码,计算当前 apts 和 vpts 之间的差值avdiff  = (int)(c->apts  - c->vpts - c->tickavdiff); // diff between audio and video pts// 这一行代码,计算当前 apts 和 系统时钟的差值scdiff  = (int)(sysclock - c->vpts - c->tickavdiff); // diff between system clock and video pts// 如果 apts 是无效的,比如没有音频流// 或者 avdiff 的值太差,就是说 apts 与 vpts 差距太大// 那么就将视频同步到音频if (c->apts   5) c->ticksleep--;if (tickdiff - tickframe < -5) c->ticksleep++;// 这一段代码,用于进行音视频同步的收敛,收敛的目标就是 avdiff 收敛到 0// 原理就是根据 avdiff 去计算 ticksleep// 而 ticksleep 就是当前视频帧渲染后要做的 sleep 延时if (c->vpts >= 0) {    if      (avdiff >  500) c->ticksleep  = 0;    else if (avdiff >  50 ) c->ticksleep -= 1;    else if (avdiff < -500) c->ticksleep += 2;    else if (avdiff < -50 ) c->ticksleep += 1;}if (c->ticksleep < 0  ) c->ticksleep = 0;   // 这两行用于保证延时的范围 [0, 500] ms if (c->ticksleep > 500) c->ticksleep = 500;//-- frame rate & av sync control --//
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
免责声明
1. 本论坛所提供的信息均来自网络,本网站只提供平台服务,所有账号发表的言论与本网站无关。
2. 其他单位或个人在使用、转载或引用本文时,必须事先获得该帖子作者和本人的同意。
3. 本帖部分内容转载自其他媒体,但并不代表本人赞同其观点和对其真实性负责。
4. 如有侵权,请立即联系,本网站将及时删除相关内容。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表