音视频同步理解

音视频同步理解

最近学习FFmpeg音视频同步,着实很难理解,本文记录学习过程中的领悟知识点,如有不对,还望指正!

为何要进行音视频同步?

正常情况来说,在对视频 编码 时,大致是这个样子的:
在这里插入图片描述
编码时,以时间线为轴,依次给编码后的数据打上正确的时间pts,如果这个地方pts时间打错了,播放时无路如何也同步不了的;

解码播放时,正常来说,以上图为例,我们只需要在0.1s时播放解码后第一个视频包vp1和音频包ap1,在0.2s时播vp2和ap2,依次播放即可;

但是实际情况,可能会这样:
在这里插入图片描述
音频包和视频包播放时对不上,也就会出现画面和声音对不上,也就是卡了

为什么会出现这种情况?

我认为主要原因来自两类:

  • 网络原因
  • 播放器逻辑原因

网络原因

网络原因如网络卡顿,导致流媒体中的包丢失、延迟到来等,如0.2s的时候该播放vp1和ap2,但是这个时候vp2这个packet还没有从网络中传递过来,无法播放方面,只能重复上一个画面,这样肯定是同步不了的

播放器逻辑原因

播放器中涉及解码、播放的逻辑,大多都是在不同的线程中,会在不同的时间得到解码的包,然后拿去播放线程播放,这样的话,如果不对音视频同步,解码后想要使音视频在正确的时间去播放是很难的

如何进行音视频同步

针对上面两个原因,解决视频不卡顿的办法就是进行音视频同步,选择一个参考时钟,另一个时钟参考时钟为基准,不停对比两者之间差异,超前就让自己重复上一个画面,延后就加快播放自己的播放进度,大致原理是这样

以音频时钟作为主时钟参考

以下图为例说说大致原理:
在这里插入图片描述
当前时刻为0.35s,理论来说在0.3s时应该播放vp3和ap3,但是由于各种原因导致vp2重复播放,使两者没有同步播放,试想,为了修复两者出现的不同步问题,我们该怎么做?
就是在0.35s出尽可能马上播放vp3,然后在0.4s处播放vp4,修复两者播放的差异;那我们这个修复过程是怎么处理的呢?

摘自ffplay.c源码,根据源码来讲最好:

double MediaSync::calculateDelay(double delay)  {
    double sync_threshold, diff = 0;
    // 如果不是同步到视频流,则需要计算延时时间
    if (playerState->syncType != AV_SYNC_VIDEO) {
        // 计算差值
        diff = videoClock->getClock() - getMasterClock();
        // 用差值与同步阈值计算延时
        sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
        ALOGI("diff frame_timer %f %f %f", frameTimer, delay, sync_threshold);
        if (!isnan(diff) && fabs(diff) < maxFrameDuration) {
            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;
            }
        }
    }
    return delay;
}

逻辑分析
此函数为修正上个vp的播放时长函数,
入参delay: 理论vp的播放时长,按照上图例子delay = vp3-vp2 = 0.1s
diff:两个时钟差异,实质就是上一个播放的vp和ap的差异,也就是vp2-ap3 = -0.1s
sync_threshold: 同步阈值,超过这个同步阈值就认为要进行音视频同步,sync_threshold在0.1<x<0.04之间
在if条件中,满足第一个条件,最后delay修正后为0;
函数执行完后,会进入以下逻辑

  // 计算上一次显示时长
  lastDuration = calculateDuration(lastFrame, currentFrame);
  // 根据上一次显示的时长,计算延时 主要是视频
  delay = calculateDelay(lastDuration);
  // 处理超过延时阈值的情况
  if (fabs(delay) > AV_SYNC_THRESHOLD_MAX) {
      if (delay > 0) {
          delay = AV_SYNC_THRESHOLD_MAX;
      } else {
          delay = 0;
      }
  }
  // 获取当前时间
  time = av_gettime_relative() / 1000000.0;
  if (isnan(frameTimer) || time < frameTimer) {
      frameTimer = time;
  }
  // 如果当前时间小于帧计时器的时间 + 延时时间,则表示还没到当前帧,需要返回在休眠一段时间
  if (time < frameTimer + delay) {
      *remaining_time = FFMIN(frameTimer + delay - time, *remaining_time);
      break;
  }

  // 更新帧计时器
  frameTimer += delay;
  // 帧计时器落后当前时间超过了阈值,则用当前的时间作为帧计时器时间
  if (delay > 0 && time - frameTimer > AV_SYNC_THRESHOLD_MAX) {
      frameTimer = time;
  }
  播放业务逻辑   省略........

time为当前系统时间,对应到我们例子中也就是0.35s,frameTimer记录上一个vp的实际播放时间,在例子中vp2是在0.2s时播放,0.3s时也只是重复而已,所以frameTimer是0.2s,frameTimer + delay也还是0.2s,小于当前时间0.35s,所以不会进入break条件,直接进行后续的播放逻辑,直接播放也就达到我们最初设想的立即播放;当然后面的播放逻辑有丢包策略,这里就不阐述了

这只是一个视频时钟落后的例子,还有视频时钟超前的情况,按照上面分析举一反三即可

posted @ 2019-10-09 18:35  帅气好男人_jack  阅读(9)  评论(0编辑  收藏  举报  来源