PTS、DTS
如果视频里各帧的编码是按输入顺序(也就是显示顺序)依次进行的,那么解码和显示时间应该是一致的。
可事实上,在大多数编解码标准(如H.264或HEVC,当出现B帧的时候)中,编码顺序和输入顺序并不一致。
对于I帧和P帧,其解码顺序和显示顺序是相同的,但B帧不是,如果视频流中存在B帧,那么就会打乱解码和显示顺序。
先到来的 B 帧无法立即解码,需要等待它依赖的后面的 I、P 帧先解码完成,这样一来播放时间与解码时间不一致了,顺序打乱了,那这些帧该如何播放呢?
这时就需要我们来了解另外两个概念:DTS 和 PTS
PTS:Presentation-Time-Stamp。PTS主要用于度量解码后的视频帧什么时候被显示出来
DTS:Decode-Time-Stamp。DTS主要是标识读入内存中的比特流在什么时候开始送入解码器中进行解码。
FFmpeg中用AVPacket结构体来描述解码前或编码后的压缩包,用AVFrame结构体来描述解码后或编码前的信号帧。
对于视频来说,AVFrame就是视频的一帧图像。这帧图像什么时候显示给用户,就取决于它的PTS。
DTS是AVPacket里的一个成员,表示这个压缩包应该什么时候被解码。
需要注意的是:虽然 DTS、PTS 是用于指导播放端的行为,但它们是在编码的时候由编码器生成的。
当视频流中没有 B 帧时,通常 DTS 和 PTS 的顺序是一致的。但如果有 B 帧时,就回到了我们前面说的问题:解码顺序和播放顺序不一致了
比如一个视频中,帧的显示顺序是:I B B P,
现在我们需要在解码 B 帧时知道 P 帧中信息,因此这几帧在视频流中的顺序可能是:I P B B,
这时候就体现出每帧都有 DTS 和 PTS 的作用了。
DTS 告诉我们该按什么顺序解码这几帧图像,
PTS 告诉我们该按什么顺序显示这几帧图像。顺序大概如下
PTS: 480 640 560 520 600 800 720 680 760 960 ...
DTS: 400 440 480 520 560 600 640 680 720 760 ...
Stream: I P B B B P B B B P ...
播放序: 1 5 3 2 4 9 7 6 8 10 ...
PTS >= DTS
从流分析工具看,流中P帧在B帧之前,但显示确实在B帧之后
音视频的同步
音频的播放,也有 DTS、PTS 的概念,但是音频没有类似视频中 B 帧,不需要双向预测,所以音频帧的 DTS、PTS 顺序是一致的。
要实现音视频同步:
1. 通常需要选择一个参考时钟,参考时钟上的时间是线性递增的,
2. 编码音视频流时依据参考时钟上的时间给每帧数据打上时间戳。
3. 在播放时,读取数据帧上的时间戳,同时参考当前参考时钟上的时间来安排播放。
这里的说的时间戳就是我们前面说的 PTS。
所以,视频和音频的同步实际上是一个动态的过程,同步是暂时的,不同步则是常态。
以参考时钟为标准,放快了就减慢播放速度;播放快了就加快播放的速度。
实践中,同步策略:
1. 将视频同步到音频上
2. 将音频同步到视频上
4. 将视频和音频同步外部的时钟上:选择一个外部时钟为基准,视频和音频的播放速度都以该时钟为标准
当播放源比参考时钟慢,则加快其播放速度,或者丢弃;快了,则延迟播放。
考虑到人对声音的敏感度要强于视频,所以一般会以音频时钟为参考时钟,视频同步到音频上。
在实际使用基于这三种策略做一些优化调整,例如:
对于起播阶段,特别是TS实时流,由于视频解码需要依赖第一个I帧,而音频是可以实时输出,可能出现的情况是视频PTS超前音频PTS较多,
这种情况下进行同步,势必造成较为明显的慢同步。处理这种情况的较好方法是将较为多余的音频数据丢弃,尽量减少起播阶段的音视频差距
PTS和DTS的time_base
所谓time_base时间基表示的就是每个刻度是多少秒
FFmpeg中时间基的概念,也就是time_base。它也是用来度量时间的。
如果把1秒分为25等份,你可以理解就是一把尺,那么每一格表示的就是1/25秒。此时的time_base={1,25}
如果你是把1秒分成90000份,每一个刻度就是1/90000秒,此时的time_base={1,90000}
所谓时间基表示的就是每个刻度是多少秒
pts的值就是占多少个时间刻度(占多少个格子)。
它的单位不是秒,而是时间刻度。只有pts加上time_base两者同时在一起,才能表达出时间是多少。
pts=20
time_base={1,10} 每一个刻度是1/10
所以物体的长度=pts*time_base=20*1/10
在ffmpeg中。av_q2d(time_base)=每个刻度是多少秒
此时你应该不难理解 pts*av_q2d(time_base)才是帧的显示时间戳
根据pts来计算一桢在整个视频中的时间位置:
timestamp(秒) = pts * av_q2d(st->time_base)
time_base的转换
1. 为什么要有时间基转换。
首先,不同的封装格式,timebase是不一样的。
另外,整个转码过程,不同的数据状态对应的时间基也不一致
例如:拿mpegts封装格式25fps举例,
解码后的YUV数据,在ffmpeg中对应的结构体为AVFrame,它的time_base为AVCodecContext的time_base ,AVRational{1,25}
解码前的数据或者编码后的数据,即AVPacket,它对应的时间基为AVStream的time_base,AVRational{1,90000}
因为数据状态不同,时间基不一样,所以我们必须转换,在1/25时间刻度下占10格,在1/90000下是占多少格。这就是pts的转换。
2. ffmpeg时间基转换
duration和pts单位一样,duration表示当前帧的持续时间占多少格。或者理解是两帧的间隔时间是占多少格。一定要理解单位。
pts:格子数
av_q2d(st->time_base): 秒/格
计算视频长度:
time(秒) = st->duration * av_q2d(st->time_base)
ffmpeg内部的时间与标准的时间转换方法:
ffmpeg内部的时间戳 = AV_TIME_BASE * time(秒)
AV_TIME_BASE_Q=1/AV_TIME_BASE
av_rescale_q(int64_t a, AVRational bq, AVRational cq)函数
这个函数的作用是计算a*bq / cq来把时间戳从一个时间基调整到另外一个时间基。
在进行时间基转换的时候,应该首先这个函数,因为它可以避免溢出的情况发生。
函数表示在bq下的占a个格子,在cq下是多少
3. 音频pts
sample_rate : samples per second,即采样率,表示每秒采集多少采样点。
比如44100HZ,就是一秒采集44100个sample. 即每个sample的时间是1/44100秒
一个音频帧的AVFrame有nb_samples个sample,所以一个AVFrame耗时是 nb_samples *(1/44100) 秒
即标准时间下duration_s=nb_samples*(1/44100)秒
转换成AVStream时间基下
duration = duration_s / av_q2d(st->time_base)
基于st->time_base的num值一般等于采样率,所以duration=nb_samples.
pts = n*duration = n*nb_samples
避免音视频不同步现象有两个关键
1.
一是在生成数据流时要打上正确的时间戳。如果数据块上打的时间戳本身就有问题,那么播放时再怎么调整也于事无补
假如,视频流内容是从0s开始的,假设10s时有人开始说话,要求配上音频流,那么音频流的起始时间应该是10s,
如果时间戳从0s或其它时间开始打,则这个混合的音视频流在时间同步上本身就出了问题
打时间戳时,视频流和音频流都是参考参考时钟的时间,而数据流之间不会发生参考关系
也就是说,视频流和音频流是通过一个中立的第三方(也就是参考时钟)来实现同步的
2.
第二个关键的地方,就是在播放时基于时间戳对数据流的控制,也就是对数据块早到或晚到采取不同的处理方法
参考时钟时间在0-10s内播放视频流内容过程中,即使收到了音频流数据块也不能立即播放它,
而必须等到参考时钟的时间达到10s之后才可以,否则就会引起音视频不同步问题
基于时间戳的播放过程中,仅仅对早到的或晚到的数据块进行等待或快速处理,有时候是不够的。
如果想要更加主动并且有效地调节播放性能,需要引入一个反馈机制,
也就是要将当前数据流速度太快或太慢的状态反馈给“源”,让源去放慢或加快数据流的速度。
熟悉DirectShow的读者一定知道,DirectShow中的质量控制(Quality Control)就是这么一个反馈机制。
DirectShow对于音视频同步的解决方案是相当出色的
******以上内容均来自伟大的互联网,仅作记录使用,如有侵权,请联系删除,谢谢******