美文网首页
ijkplayer学习笔记(八)——音视频同步

ijkplayer学习笔记(八)——音视频同步

作者: 程序媛的程 | 来源:发表于2021-09-23 19:55 被阅读0次

第六篇:视频渲染的学习过程中,我了解到video_refresh中所做的事情,并涉及到一些音视频同步的内容,这一篇将深入学习这一部分内容。以下是video_refresh这个函数的流程图:

video_refresh.jpg

说到同步,就要有一个参考对象,这里称为参考时钟源,选择参考时钟源有多种方式:(注意时间戳和时间的区分,需要转换)

  • 视频时间戳作为参考时钟源
  • 音频时间戳作为参考时钟源
  • 外部时间作为参考时钟源
    通常在存在音频的情况下,优先选择音频作为主时钟源,ijkplayer在默认情况下也是使用音频作为参考时钟源。

video_refresh

 // 上一帧
 lastvp = frame_queue_peek_last(&is->pictq);
 // 当前帧
 vp = frame_queue_peek(&is->pictq);
 ......
  /* compute nominal last_duration */
  // 根据当前帧和上一帧的pts,计算出来上一帧的显示时长
 last_duration = vp_duration(is, lastvp, vp);
  // 计算显示当前帧需要等待的时间(即上一帧真正的持续时长)
 delay = compute_target_delay(ffp, last_duration, is)
 //取系统时刻
 time= av_gettime_relative()/1000000.0;
 //如果上一帧显示时长未满,重复显示上一帧
 if (time < is->frame_timer + delay) {
      *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
      goto display;
 }
  //frame_timer更新为上一帧结束时刻,也是当前帧开始时刻
  is->frame_timer += delay;
  //如果与系统时间的偏离太大,则修正为系统时间
  if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)
      is->frame_timer = time;
  ......
  //丢帧逻辑
  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 && (ffp->framedrop > 0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration) {
          frame_queue_next(&is->pictq);
          //回到函数开始位置,继续重试(这里不能直接while丢帧,因为很可能audio clock重新对时了,这样delay值需要重新计算)
          goto retry;
      }
}

ffplay中将视频同步到音频的主要方案是,如果视频播放过快,则重复播放上一帧,以等待音频;如果视频播放过慢,则丢帧追赶音频。

音视频同步.png
理解上图之前,我们先清楚几个概念:
  • lastvp:上一帧
  • vp:当前帧
  • nextvp:下一帧
  • frame_timer:帧显示的时刻
  • delay:帧还应显示多久(包含帧本身显示时长)
  • frame_timer += delay:frame_timer更新为上一帧结束时刻,也是当前帧开始显示时刻

示意图中给出了三种情况:
time1:系统时刻小于lastvp结束显示的时刻(frame_timer+dealy),即虚线圆圈位置。此时应该继续显示lastvp
time2:系统时刻大于lastvp的结束显示时刻,但小于vp的结束显示时刻(vp的显示时间开始于虚线圆圈,结束于黑色圆圈)。此时既不重复显示lastvp,也不丢弃vp,即应显示vp
time3:系统时刻大于vp结束显示时刻(黑色圆圈位置,也是nextvp预计的开始显示时刻)。此时应该丢弃vp

compute_target_delay

static double compute_target_delay(FFPlayer *ffp, 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 by
           duplicating or deleting a frame */
        diff = get_clock(&is->vidclk) - get_master_clock(is);

        /* skip or repeat frame. We take into account the
           delay to compute the threshold. I still don't know
           if it is the best guess */
        sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));
        /* -- by bbcallen: replace is->max_frame_duration with AV_NOSYNC_THRESHOLD */
        if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) {
            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;
}
image.png

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

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

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

如果不仅大于sync_threshold,而且超过了AV_SYNC_FRAMEDUP_THRESHOLD,那么返回delay+diff,由具体diff决定还要显示多久。

参考转自:https://zhuanlan.zhihu.com/p/44615401

相关文章

网友评论

      本文标题:ijkplayer学习笔记(八)——音视频同步

      本文链接:https://www.haomeiwen.com/subject/vhkogltx.html