ffplay音视频同步分析——基础概念

转自:https://zhuanlan.zhihu.com/p/44615185

由于音频和视频的输出不在同一个线程,而且,也不一定会同时解出同一个pts的音频帧和视频帧。更有甚者,编码或封装的时候可能pts还是不连续的,或有个别错误的。因此,在进行音频和视频的播放时,需要对音频和视频的播放速度、播放时刻进行控制,以实现音频和视频保持同步,即所谓的音视频同步。

在ffplay中,音频(audio)和视频(video)有各自的输出线程,其中音频的输出线程是sdl的音频输出回调线程,video的输出线程是程序的主线程。(参考:https://zhuanlan.zhihu.com/p/44139512 和 https://zhuanlan.zhihu.com/p/44122324

音视频的同步策略,一般有如下几种:

  • 视频同步到音频,即音频为主时钟
  • 音频同步到视频,即视频为主时钟
  • 视频、音频同步到外部时钟,即外部时钟(系统时间)为主时钟
  • 视频和音频各自输出,即不作同步处理,或称之为各自为主时钟

由于人耳对于声音变化的敏感度比视觉高,因此,一般采样的策略是将视频同步到音频,即对画面进行适当的丢帧或重复以追赶或等待音频。

特殊地,有时候会碰到一些特殊封装(或者有问题的封装),此时就不作同步处理,各自为主时钟,进行播放。

在ffplay中实现了上述前3种的同步策略。由sync参数控制:

{ "sync", HAS_ARG | OPT_EXPERT, { .func_arg = opt_sync }, "set audio-video sync. type (type=audio/video/ext)", "type" },

 

在深入代码了解其实现前,需要先简单了解下一些结构体和概念。读者也可以选择在后文阅读中回头查阅本节。

  • pts
  • timebase
  • ffplay中的pts
  • Clock

pts是presentation timestamp的缩写,即显示时间戳,用于标记一个帧的呈现时刻。

它的单位由timebase决定。timebase的类型是结构体AVRational(用于表示分数):

typedef struct AVRational{
    int num; ///< Numerator
    int den; ///< Denominator
} AVRational;

timebase={1, 1000}表示千分之一秒,那么pts=1000,即为1秒,那么这一帧就需要在第一秒的时候呈现在ffplay中,将pts转化为秒,一般做法是:pts * av_q2d(timebase)

ffplay的很多自定义结构体中也有pts字段,只不过是double类型,其实就是已经转化为秒为单位的pts值。

在做同步的时候,我们需要一个"时钟"的概念,ffplay定义的结构体是Clock:

typedef struct Clock {
    double pts;           /* clock base */
    double pts_drift;     /* clock base minus time at which we updated the clock */
    double last_updated;
    double speed;
    int serial;           /* clock is based on a packet with this serial */
    int paused;
    int *queue_serial;    /* pointer to the current packet queue serial, used for obsolete clock detection */
} Clock;

这个时钟的工作原理是这样的:

  1. 需要不断“对时”。对时的方法set_clock_at(Clock *c, double pts, int serial, double time),需要用pts、serial、time(系统时间)进行对时。
  2. 获取的时间是一个估算值。估算是通过对时时记录的pts_drift估算的。

可以看这个图来帮助理解:

 

图中央是一个时间轴,从左往右看。首先我们调用set_clock_at进行一次对时,假设这时的pts是落后系统时间time的,那么计算pts_drift = pts - time

接着,过了一会儿,且在下次对时前,通过get_clock来查询时间,因为这时的pts已经过时,不能直接拿pts当做这个时钟的时间。不过我们前面计算过pts_drift,也就是ptstime的差值,所以我们可以通过当前时刻的系统时间来估算这个时刻的pts:pts = time + pts_drift.

当然,由于pts_drift是一直在变动的(drift与漂移、抖动的意思),所以get_clock是估算值,真实的pts可能落在比如图示虚线圆的位置。

一般time会取CLOCK_MONOTONIC,即系统开机到现在的时间,一般都有几个小时;而pts是节目的播放时刻,比如从0开始,播放了10分钟,就是600s。所以,真实情况下pts_drift可能要比图示的大。

在了解了这些基础概念后,就可以开始阅读音视频同步的代码了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值