ffplay源码分析__音视频同步compute_target_delay()

一、为什么需要音视频同步

1、视频显示和音频播放运行在两个不同的线程,线程调度由操作系统控制,所以计算帧的播放时长时难以精准控制。

2、音视频解码及渲染的耗时不同,可能造成每一帧输出有细微差距,长久累计,不同步会越来越明显。

二、如何音视频同步?

为了解决音视频同步问题,引入了时间戳。首先选择一个参考时钟(要求参考时钟上的时间是线性递增的)。编码时依据参考时钟给每个音视频数据块都打上时间戳;播放时,再根据音视频时间戳及参考时钟,来调整播放。所以,视频和音频的同步实际上是一个动态的过程,同步是暂时的,不同步则是常态。以参考时钟为标准,放快了就减慢播放速度;播放快了就加快播放的速度。     

一般来说有以下三种同步策略:

将视频同步到音频上:就是以音频的播放速度为基准来同步视频,ffplay默认的同步机制。
将音频同步到视频上:就是以视频的播放速度为基准来同步音频,一般不常用。
将视频和音频同步到外部的时钟上:选择一个外部时钟为基准,视频和音频的播放速度都以该时钟为标准。
当播放源比参考时钟慢,则加快其播放速度,或者丢弃;快了,则延迟播放。

这三种是最基本的策略,考虑到人对声音的敏感度要强于视频,频繁调节音频会带来较差的观感体验,且音频的播放时钟为线性增长,所以一般会以音频时钟为参考时钟,视频同步到音频上。在实际使用时基于这三种策略会做一些优化调整。

例如:调整策略可以尽量采用渐进的方式,因为音视频同步是一个动态调节的过程,一次调整让音视频PTS完全同步,没有必要,也不现实,且可能导致异常播放较为明显。实际操作中,一般保持音视频差异在一个范围内即可认为同步,这个范围被称为同步阈值。  

三、时钟的概念

可以把音频时钟和视频时钟看作两个手表,音频时钟的更新是在sdl_audio_callback()中,每次回调一次函数更新一下音频时钟的pts,当然不更新pts时手表也是在跑的。视频时钟的更新是在video_refresh()中,默认每隔0.01秒检查一次两块手表的差异,来不断调整视频的时钟,使两者的差异控制在同步阈值内。

1、时钟结构体Clock

typedef struct Clock {
    double	pts;            // 时钟基础, 当前帧(待播放)显示时间戳,播放后,当前帧变成上一帧
    // 当前pts与当前系统时钟的差值, audio、video对于该值是独立的
    double	pts_drift;      // clock base minus time at which we updated the clock
    // 当前时钟(如视频时钟)最后一次更新时间,也可称当前时钟时间
    double	last_updated;   // 最后一次更新的系统时钟
    double	speed;          // 时钟速度控制,用于控制播放速度
    // 播放序列,所谓播放序列就是一段连续的播放动作,一个seek操作会启动一段新的播放序列
    int	serial;             // clock is based on a packet with this serial
    int	paused;             // = 1 说明是暂停状态
    // 指向packet_serial
    int* queue_serial;      //指向对应PacketQueue的序列号  用于过时时钟检测
} Clock;

Clock结构体记录了时钟的各个字段,用于设置时钟和读取时钟。以下是各字段的含义

pts:帧的显示时间戳,单位是秒(代码中根据时间基换成秒)

pts_drift:重要字段,c->pts - c->last_updated,是个很大的负数。理解为pts相对更新时间(系统时间)的偏移值。这个值的作用是在读取时间时,计算pts更新后经过了多少秒,来预估当前的时钟值。比如系统时间10点时更新了pts为10秒,那么过了5秒后去读取时钟,时钟应该为15秒。下面是ffplay读取时钟时的计算逻辑,c->speed默认为1。

static double get_clock(Clock *c)
{	
	if (*c->queue_serial != c->serial)
		return NAN;
    if (c->paused) {
        return c->pts;
    } else {
        double time = av_gettime_relative() / 1000000.0;
        return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed);
    }
}

last_updated:最近一次更新时钟的系统时间戳         

c->pts_drift为c->pts - last_updated(最近一次更新时钟的系统时间戳),c->speed初始为1.0,所以上面的代码简化为c->pts_drift + time= c->pts - last_updat

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值