看到好多人对时间戳这个概念不明白啊;简单写一下我的理解
第一,时间戳是什么
时间戳就是一个能够表示一个事物发生时间的东西,她有个单位,比如秒,毫秒等
第二,timebase是个什么
timebase是个有点抽象的东西, 在这里不说抽象的概念,你就把它当成时间的单位;
例如25帧的视频,如果不存在timebase这个东西, 我们打时间戳应该是这样的,0-40-80-120-以此类推,40毫秒一帧图像
可是出现了timebase之后就不能这么处理了;
例如,同样25帧的视频, timebase设置为1/25, 那这个1/25是什么意思呢? 就是把1秒分成25份, 你的时间戳每增加1,就代表增加了1/25秒;
视频是25帧的, 也就是每帧之间的间隔恰好就是1/25秒, 那么,我们的时间戳就可以每次递增1了;
同理,timebase设置为1/50, 那么我们的时间戳就可以每次递增2了;
上面三句话非常重要,是时间戳的核心原理;
第三,如何去确定编码器的timebase
那怎么去确定timebase是多少合适呢? 2/25可以吗?3/456可以吗?
理论上来说都是可以的,但是有个精度问题;
例如,同样25帧的视频,我们设置timebase为2/25;
假设如果第一帧是时间: 0毫秒,
换算成1/25的时间戳:0*(2/25) = 0;
再还原成原始时间就是0/(2/25) = 0;
在经过40毫秒后产生第二帧,这一帧的时间是40毫秒,换算成时间戳:
40*(2/25)=3.2,由于ffmpeg记录时间戳的变量类型为long long ,所以下取整后为3
这时候我们在还原一下原始时间:
3/(2/25) = 37.5毫秒,下取整后为37,这就与原始时间40产生了3毫秒的误差
实际上2/25就是1/12.5,也就是把一秒分成了12.5份,如果timebase的num/den不是帧率的整数倍都会产生误差;
所以一般建议都设置成 1/帧率
注意下,这个误差是帧间误差
第四,上面为什么要计算还原后的误差?
编码->解码这是一个永恒不变的过程,任何编码后的数据,都需要逆操作,也就是解码来复原;
在图像中,为了更好的还原图像,以及音视频的同步,就要保证尽可能高精度的时间戳;这样我们才能更精确的知道某一幅图像应该在什么时候显示;
第五,解码时间戳是个什么东西?
前面说的都是编码时间戳,那么解码时间戳是什么? 从计算过程上来说,与编码时间戳是一样的;只不过这个时间代表了他应该何时被送入解码器;
编码器会去设置这个值,用户不需要关心,只需要按编码的输出顺序送入解码器即可;这部分也不属于本文的内容,不再敖述;
第六,第一帧图像的时间戳一定0么?
不一定,可能是任意值,但一定是通过timebase计算得来的; 否则无法正确的知道解码后的显示顺序和时间
第七,如果timebase是1/25,时间戳只需要每次递增1就可以吗?
不是的,可能由于调用层卡顿,转入后台等情况导致帧率不稳定现象,也就是说很可能实际帧率是达不到设置的帧率的,如果你的时间戳依旧按照每次+1来设置,图像就会渐渐的与实际时间越拉越大;
第八,音频的timebase是如何确定的?
与视频类似, 只是音频换做采样率这个概念,采样率代表麦克风一秒有多少个采样产生,这个频率是极为精确的.所以我的一般做法是设置为 1/采样率; 时间戳每次递增输入的采样数;
第九,音频和视频如何去确定起始的时间戳
虽说时间戳不必从0开始,但是为了调试或者强迫症,我还是尽量保证从0开始...
我的做法是这样的;
不管音视频,上层都使用绝对的毫秒时间,编码层收到后,记录下第一个时间(音频和视频,那个早取哪个)
例如,
音频采样率: 44100
视频帧率 :25
第一个音频采样时间:140029123,记做AT1
第一个视频帧时间 :140029143,记做VT1
取140029123作为FRISTTIME;
然后每次都用下面的公式进行计算即可以得出时间戳:
用 (FRISTTIME - ATn) * (1/44100) ;
用 (FRISTTIME - VTn) * (1/25);
第十,ffmpeg里面的计算方式
//目标时间戳 = f(原时间戳, 原timebase, 目标timebase, 取整方式);
dst = av_rescale_q_rnd(src, src->time_base, dst->time_base, AV_ROUND_NEAR_INF);
如果src是毫秒时间, 那么src的timebase是多少呢?
考虑下上面的,1/25表示1/25秒, 1毫秒的话是1/多少?
应该是1/1000,也就是 AVRational{1, 1000}
当然,你应该能想到,颠倒几个参数就能还原回去,虽说还是有点点误差....
不同模块之间的转换也是使用这个方式
第十一,播放时如何去同步?
同样的, 解码后,不管音视频,解码后拿到时间戳,通过前面所说的计算公式还原回去,然后都以第一个为基准, 后面的按照这个时间来计算应该显示的时间;