时间基与时间戳是 FFmpeg 音视频同步、有序播放的核心机制,也是推流开发中最容易踩坑的环节。所有音视频帧的播放顺序、音画对齐、封装传输,都依赖统一的时间刻度规则与转换逻辑。
一、FFMPEG 时间基、 时间戳
1.1 时间基(time_base):
时间基也称之为时间基准,它代表的是每个刻度是多少秒。比方说:视频帧率是**25FPS,**那它的时间刻度是{1,25}。相当于1s内划分出25个等分,也就是每隔1/25秒后显示一帧视频数据。具体的如下图所示:

在FFMPEG中时间基准都是用AVRational结构体来表示:
typedef struct AVRational{
int num; // 分子
int den; // 分母
} AVRational;
num : 它是numerator的缩小,代表的是分子
den : 它是denominator的缩小,代表的是分母
它的物理含义是:1 个时间刻度 = num/den 秒。
在视频时间基都是以帧率为单位,比方说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,flv格式的视频流timebase为1000
tbc **:**表示视频流codec timebase,这个值一般为帧率的两倍。比方说:帧率是25fps,则tbc是50
核心原则:时间戳的数值本身没有意义,必须搭配对应的时间基才能换算出真实的时间。同一个真实时刻,用不同的时间基表示,数值完全不同。
1.2 时间戳(PTS、DTS):

首先时间戳它指的是在时间轴里面占了多少个格子,时间戳的单位不是具体的秒数,而是时间刻度。只有当时间基和时间戳结合在一起的时候,才能够真正表达出来时间是多少。
比方说:
有一把尺子pts = 25个刻度,time_base = {1,25} 每一个刻度是1/25厘米
所以这把尺子的长度 = pts * time_base = 25* 1/25= 1厘米
FFmpeg 中每个音视频帧(AVPacket/AVFrame)都带有两个核心时间戳,单位是对应模块的时间基刻度:
1.2.1 PTS :
全称是Presentation Time Stamp(显示时间戳),它主要的作用是度量解码后的视频帧什么时候显示出来。标记这一帧应该被渲染 / 播放的真实时刻,决定了用户看到画面、听到声音的时间点。
视频PTS计算:n为第n帧视频帧,timebase是{1,framerate},fps是framerate
pts = n *(( 1 / timebase) / fps):
pts = pts++;
举例子: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/ timebase) / num_pkt)
pts = pts+1024
举例子:n = 1, pts = 1024
n = 2, pts = 2048
n = 3, pts = 3072
1.2.2 DTS :
解码时间戳,标记这一帧应该被送入解码器解码的时刻,决定了解码器的工作顺序。在没有B帧的情况下PTS 等于 DTS。假设编码的里面引入了B帧,则还要计算B帧的时间。
没有B帧:dts = pts
存在B帧:dts = pts + b_time
- 无 B 帧的场景(绝大多数监控、直播场景) :解码顺序和播放顺序完全一致,因此
PTS = DTS,这也是你 RV1126 硬件编码场景的默认情况。 - 有 B 帧的场景:B 帧需要参考前后帧才能解码,解码顺序和播放顺序不一致,因此 DTS 和 PTS 会有差值,需要额外处理
二、FFmpeg 不同层级的时间基
FFmpeg 的不同模块有各自独立的时间基,这是新手最容易混淆的点。在推流场景下,重点关注三个层级的时间基:
1. 编码器层:AVCodecContext->time_base
- 所属模块:编码器内部使用的时间刻度,是编码侧计算帧间隔、控制码率的基准。
- 软编码场景下,送入编码器的 AVFrame 的 pts 必须以这个时间基为单位。
- 本项目是硬件编码场景,编码由 VENC 硬件完成,FFmpeg 不参与编码,因此这个时间基不需要关注。
2. 流层:AVStream->time_base
- 所属模块:封装层(AVFormat/AVStream)使用的时间刻度,是推流开发最核心的时间基。
- 硬性规则:所有写入封装器的 AVPacket,它的 pts/dts 必须以对应流的 time_base 为单位。
- 这是我们开发中需要重点对齐的参数:硬件输出的时间戳必须转换为该时间基后,再赋值给 pkt->pts/dts。
3. 容器 / 协议层
- 所属模块:具体封装格式或传输协议自带的时间刻度,比如 FLV/RTMP 固定为毫秒精度、MPEG-TS 固定为 90kHz。
- 开发者一般不需要手动处理:FFmpeg 的封装器内部会自动把
AVStream->time_base的时间戳,转换成容器 / 协议要求的刻度,不需要手动换算。
工程简化原则:推流开发时,我们只需要保证 AVPacket 的 pts/dts 和
AVStream->time_base对应即可,底层协议的时间转换由 FFmpeg 自动完成。
三、时间转换的原理
时间基转换不能手写乘除,直接计算容易出现整数溢出、精度误差。FFmpeg 提供了标准转换函数 av_rescale_q,是所有时间基转换的唯一标准写法。
在FFMPEG中由于不同的复合流,时间基是不同的,比方说:flv的时间基time_base= {1,1000 },假设一个视频time_base = {1,25 },我们需要合成mpegts文件,它就需要把time_base = {1,25} 占的格子转换成time_base = {1,1000}占的格子。

3.1 在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转换的例子来说明这个转换时间基的用法:
3.2 视频H264时间基转换成MPEGTS时间基:
**DST_VIDEO_PTS = VIDEO_PTS * VIDEO_TIME_BASE / DST_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
3.3 音频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去计算我们真实推流的音频时间戳。