四时宝库

程序员的知识宝库

音视频同步基础(音视频同步基础视频)

看这篇文章前,可以看看前面的文章。

FFmpeg的FFplay框架分析

FFplay超详细数据结构分析

超详细解析FFplay之数据读取线程

超详细解析FFplay之音视频输出模块

超详细解析FFplay之视频输出和尺寸变换模块

超详细解析FFplay之音视频解码线程

1.?视频同步基础

为什么要做音视频同步呢?

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

在ffplay中,?频(audio)和视频(video)有各?的输出线程,其中?频的输出线程是sdl的?频输出回调线程video的输出线程是程序的主线程(自己设计可以把video的输出线程放在子线程比较好)。

2.?视频同步策略

?视频的同步策略,?般有如下?种:

(1)AUDIO_MASTER,以?频为基准,同步视频到?频。音频正常播,视频处理基础策略为以下2点:

第一,视频慢了则丢掉部分视频帧,但是这个时候视觉上会有画面跳帧。

第二,视频快了,则继续渲染上一帧。

(2)VIDEO_MASTER,以视频为基准,同步?频到视频。视频正常播,音频处理基础策略为以下3点:

第一,?频慢了则加快播放速度,或丢掉部分音频帧,丢帧极容易听出来断?,这也是为什么选择AUDIO_MASTER的原因。

第二,?频快了则放慢播放速度,可能要多播放一些数据,如重复上一帧,这时候对于人耳来说,体验也不是很好。

第三,音频改变播放速度时涉及到重采样(因为重采样可以改变播放数据是多还是少)。

(3)EXTERNAL_CLOCK,以外部时钟为基准,同步?频和视频到外部时钟。根据外部时钟改变播放速度。音频和视频都同步到外部时钟。

(4)视频和?频各?输出,即不作同步处理(FREE RUN)。


同步策略的选型思路:

由于??对于声?变化的敏感度?视觉?,因此,?般采样的策略是将视频同步到?频,即对画?进?适当的丢帧重复以追赶或等待?频。特殊地,有时候会碰到?些特殊封装(或者有问题的封装),此时就不作同步处理各?为主时钟,进?播放

注意:比较成熟的商用播放器,一般为了兼容各种类型的复杂情况,三种方式都会有。video master有助于快速显示第一帧图片,比如在机顶盒切换台时,切换节目,快速出图。audio master还是为主。再就是外部时钟。根据不同情况进行切换。

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

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

也可以通过命令行,设置:

ffplay source.200kbps.768x320.flv -sync video,比如,这里设置video master。


?视频同步概念

在深?代码了解其实现前,需要先简单了解下?些结构体和概念。

(1)DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这?帧的数据。

(2)PTS(Presentation Time Stamp):即显示时间戳,这个时间戳?来告诉播放器该在什么时候显示这?帧的数据。

(3)timebase 时基:pts的值的真正单位。

(4)ffplay中的pts,ffplay在做?视频同步时使?秒为单位,使?double类型去标识pts,在ffmpeg内部不会?浮点数标记pts

(5)Clock 时钟

当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是?致的。但存在B帧的时候两者的顺序就不?致了。

(6)pts是presentation timestamp的缩写,即显示时间戳,?于标记?个帧的呈现时刻,它的单位由timebase决定。timebase的类型是结构体AVRational(?于表示分数):

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

如 timebase={1, 1000} 表示千分之?秒(毫秒),如果pts=1000,即为pts*1/1000 = 1秒,那么这?帧就需要在第?秒的时候呈现。

将AVRatioal结构转换成double。

static inline double av_q2d(AVRational a){

return a.num / (double) a.den;

}

计算时间戳

timestamp(秒) = pts * av_q2d(st->time_base)

计算帧时?

time(秒) = st->duration * av_q2d(st->time_base)。表示这个视频有多长。

不同时间基之间的转换

int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)

在ffplay中,将pts转化为秒,?般做法是: pts * av_q2d(timebase)。

(7)在做同步的时候,我们需要?个"时钟"的概念,?频、视频、外部时钟都有??独?的时钟,各?set各?的时钟,以谁为基准(master), 其他的则只能get该时钟进?同步,ffplay定义的结构体是Clock:

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

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

第一,需要不断“对时”。对时的?法 set_clock_at(Clock *c, double pts, int serial,double time) ,需要?pts、serial、time(系统时间)进?对时。

第二,获取的时间是?个估算值。估算是通过对时时记录的pts_drift估算的。pts_drift是最精华的设计,?定要理解。如下图,能够更好的理解。


图中央是?个时间轴(time是?直在按时间递增),从左往右看。?先我们调? set_clock 进??次对时,假设这时的 pts 是落后时间 time 的,那么计算 pts_drift = pts - time ,计算出pts和time的相对差值。

接着,过了?会?,且在下次对时前,通过 get_clock 来查询时间,因为set_clock时的 pts 已经过时,不能直接拿set_clock时的pts当做这个时钟的时间。不过我们前?计算过 pts_drift (这个差值基本是定值),也就是 pts和 time 的差值,所以我们可以通过当前时刻的时间来估算当前时刻的pts: pts = time +pts_drift 。

注意:一般time会取CLOCK_MONOTONIC(单调递增的时钟),即系统开机到现在的时间。

ffplay使?ffmpeg提供的av_gettime_relative()函数。

如下图:

pts相对相对系统时间就是pts_drift=PTS1-time1,这个pts_drift基本上是定值,计算下一帧的计算公式就是pts_drift+time2。经过公式推导,同时也可以使用PTS1+duration计算PTS2,这个duration=time2-time1,这也是为什么使用相对时间av_gettime_relative()的精髓。

这里的公式:


由于这个是双缓冲,上面的公式需要按照如下图去理解。


(1)lastvp表示上一次刷新的帧,目前还在显存,lastvp持续播放时间,相邻帧的pts。

(2)vp准备要显示帧。

(3)nextvp,表示接下来的帧。

last_duration = vp_duration(is,lastvp,vp);这个只是表示静态的计算。

delay = compute_target_delay(last_duration,is);这里表示动态的计算。返回的是当前帧真正能够播放多久。比如frame_timer记录播放的起始时间为100ms,当到130ms,如果每帧是40ms,理论上剩下播放时间还有10ms,由于其他原因,可能会延迟会加快,那就是根据公式,frame_timer+delay与time的比较。如下图。

如果超过了当前时间,那就要考虑是否使用丢帧策略。


audio master与video master区别:

(1)audio master设置的是音频是主时钟。video master设置的是视频是主时钟。

(2)audio master的情况下,视频是否显示取决于音视频同步的差值。diff = get_video_clock - master_clock(实际是audio的时钟)。使用diff值去决定。video master的情况下,音频是根据视频同步的差值决定,播快(减少音频的采样点)与播慢(增加音频的采样点)有采样点的各种计算。在ffplay中synchronize_audio与swr_set_compensation可以参与计算。


FFmpeg中的时间单位

AV_TIME_BASE

定义为:#define AV_TIME_BASE 1 000 000。

时间基:ffmpeg中的内部计时单位。

AV_TIME_BASE_Q

定义为:#define AV_TIME_BASE_Q (AVRational){1, AV_TIME_BASE}。

ffmpeg内部时间基的分数表示,实际上它是AV_TIME_BASE的倒数

时间基转换公式

timestamp(ffmpeg内部时间戳,这个单位就是us) = AV_TIME_BASE * time(秒)。

time(秒) = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)。


?视频时间换算的问题

标准时间 秒(seconds)

标准时间 微秒(microsecond)

?定义时间单位 (a/b 秒)

以?频AAC?频帧举例,如果pts以1/采样率为单位,?如44.1khz, 则时间单位是1/44100,因此PTS表示:

(1)第?帧 PTS1 = 0。

(2)第?帧 PTS2 = 1024。

(3)第三帧 PTS2 = 2048。

(4)播放的时候要将PTS换算成秒的单位,则PTS1 = 0 *1/44100, PTS2 = 1024*1/44100, PTS3 =2048*1/44100= 0.046439秒。注意以下这种时间基数的转换。

不同结构体的time_base/duration分析

ffmpeg存在多个时间基准(time_base),对应不同的阶段(结构体),每个time_base具体的值不?样,ffmpeg提供函数在各个time_base中进?切换

AVFormatContext

duration:整个码流时?,获取正常时?的时候要除以AV_TIME_BASE,得到的结果单位是

AVStream:

time_base:单位为,?如AAC?频流,他可能是{1,44100},TS流,按{1, 90khz}。

duration:表示该数据流的时?,以AVStream->time_base 为单位。

AVStream的time_base是在demuxer或者muxer内设置的,以TS,FLV,MP4为例?:

TS

avpriv_set_pts_info(st, 33, 1, 90000) (mpegts.c和mpegtsenc.c)。

FLV

avpriv_set_pts_info(st, 32, 1, 1000) (flvdec.c)

avpriv_set_pts_info(s->streams[i], 32, 1, 1000) (flvenc.c)

MP4

avpriv_set_pts_info(st, 64, 1, sc->time_scale); (mov.c)

avpriv_set_pts_info(st, 64, 1, track->timescale); (movenc.c)


不同结构体的PTS/DTS分析

不同结构体下,pts和dts使?哪个time_base来表示?

AVPacket

pts:以AVStream->time_base为单位。

dts:以AVStream->time_base为单位。

duration:以AVStream->time_base为单位。


AVFrame

pts:以AVStream->time_base为单位。

pkt_pts和pkt_dts:拷??AVPacket,同样以AVStream->time_base为单位。

duration:以AVStream->time_base为单位。


ffplay中PTS的转换流程分析

Video Frame PTS的获取

PTS校正

frame->pts = frame->best_effort_timestamp;

这?为什么不?AVFrame中的pts来直接计算呢?其实?多数情况下AVFrame的pts和best_effort_timestamp值是?样的。

Audio Frame PTS的获取

ffplay有3次对于Audio的pts进?转换。

第?次将其由AVStrean->time_base转换为(1/采样率)

frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);

第?次 将其由(1/采样率)转换为秒

af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);

第三次 根据实际拷?给sdl的数据?度做调整,这里实际不是时间单位的调整,实际是一种时间计算。

audio_pts = is->audio_clock -(double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec;

本篇文章就分析到这里,欢迎关注,点赞,转发,收藏。


发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接