美文网首页
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