本节聊一聊ffpmeg时间概念的讲解,主要介绍 FFMPEG 的时间概念,包括时间基、时间戳、时间转换、时间比较等知识点。这些知识点对于我们了解推流至关重要,因为音视频合成本质上是各种时间转换的过程。
二. FFMPEG 时间基、时间戳的讲解
2.1 时间基(Time Base)
时间基,也称为时间基准,代表每个刻度是多少秒。例如,视频帧率是 30 FPS,那么时间基就是 {1, 30}
,也就是说每 1 秒钟划分为 30 个等分,视频每隔 1/30 秒显示一帧。
在 FFMPEG 中,时间基使用 AVRational
结构体表示:
num
:分子,代表刻度的分子den
:分母,代表刻度的分母
在视频时间基都是以帧率为单位,比方说 50 帧。FFMPEG 就以 AVRational video_timebase= {1, 50}来表示。
在音频时间基都是以采样率为单位,比方说音频采样率是 48000HZ。FFMPEG 就以
AVRational audio_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,这个值一般为帧率的两倍。比方说:帧率是 30fps,则 tbc
是 60
时间戳(PTS 和 DTS):
首先时间戳它指的是在时间轴里面占了多少个格子,时间戳的单位不是具体的秒数,而是时
间刻度。只有当时间基和时间戳结合在一起的时候,才能够真正表达出来时间是多少。比如钱,单单一个数字1是没有如何意义的。后面要有单位,比如是美元还是人民币,这样才有意义。
比方说:
有一把尺子 pts = 30 个刻度,time_base = {1,30} 每一个刻度是 1/30 厘米
所以这把尺子的长度 = pts * time_base = 30 * 1/30 = 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 是采样率
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
DTS:表示的是压缩解码的时间戳,在没有 B 帧的情况下 PTS 等于 DTS。假设编码
的里面引入了 B 帧,则还要计算 B 帧的时间。
没有 B 帧:dts = pts
存在 B 帧:dts = pts + b_time
时间转换的原理:
在 FFMPEG 中由于不同的复合流,时间基是不同的,比方说:ts 的时间基 time_base=
{1,90000},假设一个视频 time_base = {1,30},我们需要合成 mpegts 文件,它就需要把
time_base = {1,30}占的格子转换成 time_base = {1,90000}占的格子。
在 FFMPEG 中用以下的 API 进行时间基转换:
转换的函数已经ffmpeg已经准备好了,我们不需要手动转换,但是我们要搞明白转换的原理是什么。
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 时间基:公式
H264 {1,30} MPEGTS {1,90000}
pts = 1 pts = 3000
pts = 2 av_packet_rescale_ts pts = 6000
pts = 3 pts = 9000
pts = 4 pts = 12000
音频 AAC 时间基转换成 MPEGTS 时间基:公式
AAC {1,48000} MPEGTS {1,90000}
pts =1024 pts = 1920
pts =2048 av_packet_rescale_ts pts = 3840
pts =3072 pts = 5760
pts =4096 pts = 7680
从上述推导的结果可以看出来,如果使用 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 去计算我们真实推流的音频时间
戳。
FFMPEG 时间戳的比较:
所谓的时间转换,就是把音频视频的时间,用ts留的时间基作为标准,然后风别计算他们的时间,看谁的时间快,就有限处理谁的,就先把它写到复合流里面
int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b) 这些都是成对出现的,1,2是参数,34是参数。
- 第一个参数:ts_a 它指的是当前相对 tb_a 的时间戳
- 第二个参数:ts_a 相对应的时间基
- 第三个参数:ts_b 它指的是当前相对 tb_a 的时间戳
- 第四个参数:ts_b 相对应的时间基
返回值判断:
- 当 ret == -1, ts_a 的时间戳快过 ts_b 时间戳。
- 当 ret == 1, ts_a 的时间戳慢过 ts_b 时间戳。
- 当 ret == 0, ts_a 的时间戳等于 ts_b 时间戳
av_compare_ts 它的主要作用是进行时间戳进行实时比较,它能够实时保证当前的时间戳是
准确无误的。它不会出现时间戳混乱的情况,所谓混乱的情况就相当于:视频时间戳当成音
频时间戳处理,音频时间戳当成视频时间戳处理。
下面这张图是编码视频、音频然后进行时间戳比较然后合成复合流的流程:
视频时间戳参考视频帧率进行比较、音频时间戳进行比较(这里我们默认 tb_a 时间基是视频
时间基、tb_b 时间基是音频时间基)。所以当比较的结果 ret <= 0 的时候,则要取出视频数
据,否则就取出音频数据。取出视频数据后则利用 av_packet_rescale_ts 进行时间转换、同
样 取 出 音 频 数 据 后 也 要 对 其 进 行 时 间 转 换 。 音 视 频 数 据 进 行 时 间 转 换 后 , 则 用av_interleaved_write_frame 对复合流进行写入操作。