目录
[1. 时间基(time_base)](#1. 时间基(time_base))
[2. 时间戳(PTS、DTS)](#2. 时间戳(PTS、DTS))
1. 时间基(time_base)
时间基也称之为时间基准,它代表的是每个刻度是多少秒。比方说:视频帧率是**25FPS,**那它的时间刻度是{1,25}。相当于1s内划分出25个等分,也就是每隔1/25秒后显示一帧视频数据。具体的如下图所示:

在FFMPEG中时间基准都是用AVRational结构体来表示:

- num:它是numerator的缩小,代表的是分子
- den:它是denominator的缩小,代表的是分母
在视频时间基都是以帧率为单位,比方说25帧。FFMPEG就以AVRational video_timebase = {1, 25}来表示。
在音频时间基都是以采样率为单位,比方说音频采样率是48000HZ。FFMPEG就以AVRationalaudio_timebase = {1, 48000}来表示。
对于封装格式来说:flv 封装格式的 time_base 为{1,1000},ts 封装格式的 time_base 为{1,90000}

从上图ffplay的信息我们可以看到有很多关于时间基的信息:
- tbr:表示帧率,该帧率是一个基准,通常来说tbr和fps是一致的
- tbn:表示视频流timebase(时间基数值),容器时间基,比方说:TS格式的数据timebase是90000,fv格式的视频流timebase为1000
- tbc:表示视频流编解码器时间基codec timebase,它用于在解码或编码过程中,标记帧的显示顺序或解码顺序。这个值一般为帧率的两倍。比方说:帧率是25fps,则tbc是50
在 MPEG 编码标准(如 H.264)中,视频编码后的帧通常分为三种类型:I帧、P帧、B帧。
- B帧(双向预测帧) 有一个关键特性:它需要参考前后的帧才能解码。
- 这就导致在数据流的组织上,帧的存储顺序(解码顺序) 和显示顺序是不一致的。
举个例子:
假设一个视频的显示顺序是:I1 B2 B3 P4。
- 显示顺序: 先显示 I1,然后 B2,然后 B3,然后 P4。
- 解码顺序(因为 B 帧依赖 P 帧): 必须先解码 I1,然后必须解码 P4(因为 B2 和 B3 需要 P4 的信息),然后才能解码 B2 和 B3。所以实际存储顺序可能是:I1 P4 B2 B3。
这就产生了一个问题:如何同时标记解码时间和显示时间?
编码器需要两套时间戳:
- DTS(解码时间戳): 告诉解码器什么时候解码这个数据包(通常基于 tbc)。
- PTS(显示时间戳): 告诉播放器什么时候显示这个解码后的画面(通常基于 tbn)。
为了在时间基上精确区分 DTS 和 PTS,并且为了在半个帧间隔(比如处理场模式视频或 B 帧的精细偏移)时依然能精确标记,编解码器内部需要一个更细粒度的时间刻度。
- tbn(容器): 只需要精确到显示每一帧就够了,所以通常是帧率的倒数(例如 1/90000 或 1/1000)。
- tbc(编解码器): 因为需要标记"解码"和"显示"之间的偏移,为了操作整数方便,很多编码器(特别是 MPEG 系列)会将时间基设为帧率的两倍。这样,两个相邻显示帧之间的刻度就是 2,中间可以插入一个解码偏移值。
2. 时间戳(PTS、DTS)

时间戳它指的是在时间轴里面占了多少个格子,时间戳的单位不是具体的秒数,而是时间刻度。只有当时间基和时间戳结合在一起的时候,才能够真正表达出来时间是多少。
比方说:
有一把尺子pts = 25个刻度,time_base = {1,25} 每一个刻度是1/25厘米
所以这把尺子的长度 = pts * time_base = 25* 1/25= 1厘米
PTS
全称是Presentation Time Stamp(显示时间戳),它主要的作用是度量解码后的视频帧什么时候显示出来。
视频PTS计算:n为第n帧视频帧,timebase是{1,framerate},fps是framerate
timebase = 1/framerate
pts = (n-1) *(( 1 / timebase) / fps) = (n-1) *(timerate/fps) = n
pts = pts++;
举例子:
n = 0, pts = 0
n = 1, pts = 1
n = 2, pts = 2
n = 3, pts = 3
音频PTS计算:n为第n帧音频帧,nb_samples指的是采样个数(AAC默认1024),timebase是{1,samplerate},samplerate是采样率Samplerate = 48000, nb_sample=1024, timebase = {1,48000}
num_pkt = samplerate/nb_samples // 每秒多少个音频包
pts = (n-1) * ( ( 1/ timebase) / num_pkt) = n *1024
pts = pts+1024
举例子:
n = 0, pts = 0
n = 1, pts = 1024
n = 2, pts = 2048
n = 3, pts = 3072
DTS
表示的是压缩解码的时间戳,在没有B帧的情况下PTS 等于 DTS。假设编码的里面引入了B帧,则还要计算B帧的时间。
没有B帧:dts = pts
存在B帧:dts = pts + b_time
3.时间转换的原理
在FFMPEG中由于不同的复合流,时间基是不同的,比方说:flv的时间基time_base= {1,1000 },假设一个视频time_base = {1,25 },我们需要合成mpegts文件,它就需要把time_base = {1,25} 占的格子转换成time_base = {1,1000}占的格子。

在FFMPEG中用以下的API进行时间基转换:

void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
**第一个参数:**AVPacket结构体指针
**第二个参数:**源时间基
**第三个参数:**目的时间基
上面这个api的用法是,把AVPacket的时间基tb_src 转换成时间基tb_dst。
下面我们用H264和AAC时间基TS转换的例子来说明这个转换时间基的用法:
视频H264时间基转换成MPEGTS时间基:
DST_VIDEO_PTS = VIDEO_PTS * VIDEO_TIME_BASE / DST_TIME_BASE
本质:视频播放的时间相同,即
DST_VIDEO_PTS * DST_TIME_BASE = VIDEO_PTS * VIDEO_TIME_BASE
H264 {1,25} flv{1,1000}
pts = 1 pts = 40
pts = 2 av_packet_rescale_ts pts = 80
pts = 3 pts = 120
pts = 4 pts = 160
音频AAC时间基转换成FLV时间基:
DST_AUDIO_PTS = AUDIO_PTS * AUDIO_TIME_BASE / DST_TIME_BASE
AAC {1,48000} FLV{1,1000}
pts =1024 pts ~= 21.3
pts =2048 av_packet_rescale_ts pts ~= 42.6
pts =3072 pts ~= 64
pts =4096 pts ~= 85.3
从上述推导的结果可以看出来,如果使用av_packet_rescale_ts 的API对视频时间基进行转换,实际上是使用DST_VIDEO_PTS = VIDEO_PTS * VIDEO_TIME_BASE / DST_TIME_BASE去计算推流的视频时间戳。
同理用av_packet_rescale_ts 对音频时间基进行转换,实际上是使用DST_AUDIO_PTS = AUDIO_PTS * AUDIO_TIME_BASE / DST_TIME_BASE去计算我们真实推流的音频时间戳。

