FFmpeg 时间戳全解:从 av_rescale_q 到时间基黑洞

目录

[1. AVRational ------ 一切的起点](#1. AVRational —— 一切的起点)

[2. av_rescale_q ------ 最核心的函数](#2. av_rescale_q —— 最核心的函数)

实际例子:从输入流时间基转输出流时间基

什么时候用?

致命坑:负数

[3. av_rescale_q_rnd ------ 带舍入策略的版本](#3. av_rescale_q_rnd —— 带舍入策略的版本)

为什么需要舍入?

音频场景的关键组合

[4. av_rescale ------ 最简单的缩放](#4. av_rescale —— 最简单的缩放)

[5. av_rescale_rnd ------ av_rescale 的舍入版](#5. av_rescale_rnd —— av_rescale 的舍入版)

[6. av_add_stable ------ 你可能没听过但很重要](#6. av_add_stable —— 你可能没听过但很重要)

为什么需要它?

实际用法

[7. av_compare_ts ------ 比较两个时间戳](#7. av_compare_ts —— 比较两个时间戳)

典型场景:音视频同步

[8. av_get_time_base_q ------ 一个常量](#8. av_get_time_base_q —— 一个常量)

[9. 实战:从解码到编码的完整时间戳流转](#9. 实战:从解码到编码的完整时间戳流转)

解码端

[转码时 rescale](#转码时 rescale)

编码端自己生成时间戳

[10. 常见坑汇总(血泪教训)](#10. 常见坑汇总(血泪教训))

[坑 1:忘记 rescale duration](#坑 1:忘记 rescale duration)

[坑 2:B-frame 导致 dts > pts](#坑 2:B-frame 导致 dts > pts)

[坑 3:AV_NOPTS_VALUE](#坑 3:AV_NOPTS_VALUE)

[坑 4:音频 pts 步进不对](#坑 4:音频 pts 步进不对)

[坑 5:time_base 设错了](#坑 5:time_base 设错了)

[11. 一张速查表](#11. 一张速查表)

写在最后


如果你写过 FFmpeg,大概率在某个深夜对着 ptsdtstime_base发呆过。尤其是第一次遇到音画不同步,顺着代码追到 av_rescale_q(),然后发现文档像天书一样。

今天这篇,咱们把 FFmpeg 时间戳相关的函数从头到尾捋一遍。不搞半吊子解释,直接讲清楚每个函数干什么、什么时候用、坑在哪


1. AVRational ------ 一切的起点

FFmpeg 里的时间不是毫秒,不是微秒,是一个分数

复制代码
typedef struct AVRational {
    int num; ///< 分子
    int den; ///< 分母
} AVRational;

比如:

  • 1/25→ 每帧 40ms(PAL 视频)

  • 1/44100→ 每个音频采样约 22.7μs

  • 1/90000→ MPEG-TS 的标准时间基(≈11.1μs)

时间基(time_base)的含义1 / time_base= 每秒多少个时间单位。

复制代码
真实时间(秒) = pts × av_q2d(time_base)

举个直观的例子:

复制代码
AVRational tb = {1, 90000};
int64_t pts = 90000;
// 真实时间 = 90000 × (1/90000) = 1 秒

坑点 :很多人以为 time_base 越小精度越高,于是拼命缩小 den。但实际上 den 太大可能导致 int64_t溢出,尤其是在 rescale 的时候。后面会讲到。


2. av_rescale_q ------ 最核心的函数

复制代码
int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq);

作用 :把数值 a从一个时间基 bq转换到另一个时间基 cq

数学公式:

复制代码
result = a × bq / cq

但 FFmpeg 的实现是无损整数运算,不会先除后乘导致精度丢失。

实际例子:从输入流时间基转输出流时间基

复制代码
// 输入流 time_base = 1/90000
// 输出流 time_base = 1/48000
int64_t input_pts = 180000;  // 2 秒处(按 1/90000 算)

AVRational in_tb  = {1, 90000};
AVRational out_tb = {1, 48000};

int64_t output_pts = av_rescale_q(input_pts, in_tb, out_tb);
// = 180000 × (1/90000) ÷ (1/48000)
// = 2 × 48000 = 96000

什么时候用?

场景 是否要用
从解码器拿到 pts,往编码器送 ✅ 必须
从输入 AVStream 转输出 AVStream ✅ 必须
自己构造 pts(如实时采集) ✅ 必须
同一 time_base 内计算 delta ❌ 没必要

致命坑:负数

av_rescale_q()不处理负数 。如果你的 pts 可能是 -9223372036854775808(INT64_MIN),直接传进去会溢出。

FFmpeg 后来补了一个安全版本:

复制代码
int64_t av_rescale_q_rnd(int64_t a, AVRational bq, AVRational cq,
                          enum AVRounding rnd);

3. av_rescale_q_rnd ------ 带舍入策略的版本

复制代码
enum AVRounding {
    AV_ROUND_ZERO     = 0, ///< 向零舍入(截断)
    AV_ROUND_INF      = 1, ///< 向无穷舍入
    AV_ROUND_DOWN     = 2, ///< 向下舍入
    AV_ROUND_UP       = 3, ///< 向上舍入
    AV_ROUND_NEAR_INF = 5, ///< 四舍五入(默认最常用)
    AV_ROUND_PASS_MINMAX = 8192, ///< 特殊:MIN/MAX 直接透传
};

为什么需要舍入?

因为时间基转换时经常除不尽

复制代码
// 44.1kHz 音频,time_base = 1/44100
// 转成 1/1000 ms 时间基
// 1 个采样 = 1000/44100 ≈ 0.02267573696 ms

int64_t pts_ms = av_rescale_q(1, {1, 44100}, {1, 1000});
// = 0 (直接截断,丢了!)

AV_ROUND_NEAR_INF

复制代码
int64_t pts_ms = av_rescale_q_rnd(1, {1, 44100}, {1, 1000},
                                   AV_ROUND_NEAR_INF);
// = 0 还是 0... 但至少语义对了

音频场景的关键组合

复制代码
// 音频帧转时间戳
int64_t pts = av_rescale_q_rnd(
    nb_samples,
    {1, codec_ctx->sample_rate},
    stream->time_base,
    AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX
);

AV_ROUND_PASS_MINMAX的意思是:如果 aINT64_MININT64_MAX,直接返回,不计算。防止 B-frame 场景下 pts 为 AV_NOPTS_VALUE时炸掉。


4. av_rescale ------ 最简单的缩放

复制代码
int64_t av_rescale(int64_t a, int64_t b, int64_t c);

数学公式:a × b / c

和 av_rescale_q 的区别

  • av_rescale只认整数,不认 AVRational

  • 没有时间基语义,纯粹算数

什么时候用?当你已经有整数倍关系的时候:

复制代码
// 已知:1 个视频帧 = 1024 个音频采样
int64_t audio_pts = av_rescale(video_pts, 1024, 1);

不要用 av_rescale 做时间基转换 ,除非你非常确定分母不会溢出。用 av_rescale_q,它内部做了溢出保护。


5. av_rescale_rnd ------ av_rescale 的舍入版

复制代码
int64_t av_rescale_rnd(int64_t a, int64_t b, int64_t c, enum AVRounding rnd);

av_rescale_q_rnd的关系,就像 av_rescaleav_rescale_q的关系。

实际用途:在做非时间基的整数比例换算时用,比如像素格式转换、采样率换算的中间步骤。


6. av_add_stable ------ 你可能没听过但很重要

复制代码
int64_t av_add_stable(AVRational ts_tb, int64_t ts,
                       AVRational tb, int64_t inc);

作用 :在时间基 ts_tb下,给 ts加上 inctb单位的时间,并且保证结果是"稳定的"(不会因为舍入误差漂移)。

为什么需要它?

想象你有一个 29.97fps 的视频(30000/1001):

复制代码
// 每一帧的时间增量
AVRational frame_duration = {1001, 30000};  // ≈ 33.366... ms

// 第 n 帧的 pts
pts = n × frame_duration

如果每次都用 av_rescale_q算,浮点误差会累积。1000 帧之后,误差可能大到音画不同步。

av_add_stable的做法是:记录分数部分的余数,下次累加,类似定点小数。

实际用法

复制代码
int64_t next_pts = av_add_stable(stream->time_base,
                                  last_pts,
                                  codec_ctx->time_base,
                                  1);

这在自己生成 pts 的编码场景(摄像头采集、屏幕录制)里几乎是必用的。


7. av_compare_ts ------ 比较两个时间戳

复制代码
int av_compare_ts(int64_t ts_a, AVRational tb_a,
                   int64_t ts_b, AVRational tb_b);

返回值:

  • < 0:ts_a 更早

  • = 0:同时

  • > 0:ts_a 更晚

典型场景:音视频同步

复制代码
int cmp = av_compare_ts(audio_pts, audio_tb,
                         video_pts, video_tb);
if (cmp < 0) {
    // 音频慢了,丢音频或加速
} else if (cmp > 0) {
    // 视频慢了,丢帧或延迟显示
}

内部实现:先把两个时间戳 rescale 到同一个时间基再比较,避免溢出。


8. av_get_time_base_q ------ 一个常量

复制代码
AVRational av_get_time_base_q(void);

返回 {1, AV_TIME_BASE},其中 AV_TIME_BASE = 1000000(微秒)。

这是 FFmpeg 内部用来表示绝对时间的时间基。比如:

复制代码
int64_t now_us = av_gettime_relative();  // 微秒
AVRational tb = av_get_time_base_q();     // 1/1000000

9. 实战:从解码到编码的完整时间戳流转

这是最让人头大的地方,咱们走一遍完整流程。

解码端

复制代码
AVPacket pkt;
av_read_frame(fmt_ctx, &pkt);

AVStream* in_stream = fmt_ctx->streams[pkt.stream_index];
// pkt.pts 的单位是 in_stream->time_base

转码时 rescale

复制代码
AVStream* out_stream = ...;

// 关键一步:转换时间基
pkt.pts = av_rescale_q_rnd(pkt.pts,
                            in_stream->time_base,
                            out_stream->time_base,
                            AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);

pkt.dts = av_rescale_q_rnd(pkt.dts,
                            in_stream->time_base,
                            out_stream->time_base,
                            AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);

pkt.duration = av_rescale_q(pkt.duration,
                             in_stream->time_base,
                             out_stream->time_base);

编码端自己生成时间戳

复制代码
AVFrame* frame = ...;
frame->pts = next_pts;
next_pts = av_add_stable(enc_ctx->time_base,
                          next_pts,
                          enc_ctx->time_base,
                          1);

10. 常见坑汇总(血泪教训)

坑 1:忘记 rescale duration

复制代码
// 只转了 pts/dts
pkt.pts = av_rescale_q(pkt.pts, in_tb, out_tb);
pkt.dts = av_rescale_q(pkt.dts, in_tb, out_tb);
// duration 忘了转 → 播放器算错帧率

// 记得转 duration
pkt.duration = av_rescale_q(pkt.duration, in_tb, out_tb);

坑 2:B-frame 导致 dts > pts

H.264 的 B 帧会让 dts 乱序。FFmpeg 内部会处理,但你往外推 packet 的时候,dts 可能不是单调递增的。如果你自己缓存 packet,必须按 dts 排序。

坑 3:AV_NOPTS_VALUE

复制代码
#define AV_NOPTS_VALUE ((int64_t)UINT64_C(0x8000000000000000))

这是 FFmpeg 表示"没有时间戳"的特殊值。不要对它调用 av_rescale_q ,用 AV_ROUND_PASS_MINMAX或者直接判断:

复制代码
if (pkt.pts != AV_NOPTS_VALUE) {
    pkt.pts = av_rescale_q_rnd(pkt.pts, in_tb, out_tb,
                                AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX);
}

坑 4:音频 pts 步进不对

音频帧的 pts 应该按采样数递增,不是按帧数:

复制代码
// 正确
frame->pts += frame->nb_samples;

// 错误
frame->pts += 1;  // 一帧可能有 1024 个采样!

坑 5:time_base 设错了

复制代码
// 视频编码器
enc_ctx->time_base = {1, 25};  // 25 fps

// 音频编码器
enc_ctx->time_base = {1, sample_rate};  // 不是 {1, 25}!

11. 一张速查表

函数 用途 什么时候用
av_rescale_q 时间基转换 跨流传递时间戳
av_rescale_q_rnd 带舍入的时间基转换 音频、需要精确舍入时
av_rescale 整数比例缩放 已知整数比例的非时间运算
av_rescale_rnd 带舍入的整数缩放 同上,但要控制舍入方向
av_add_stable 稳定递增时间戳 自己生成 pts(录制、采集)
av_compare_ts 比较时间戳 音视频同步
av_get_time_base_q 获取微秒时间基 绝对时间计算

12.总结

FFmpeg 的时间戳系统本质上是一个有理数算术系统,设计目标是在不引入浮点误差的前提下,优雅地处理各种帧率、采样率的转换。

理解了 av_rescale_q就是理解了 FFmpeg 时间的灵魂。剩下的,无非是搞清楚谁产生时间戳、谁消费时间戳、中间经过了几层 rescale

下次再遇到音画不同步,别慌。先打印 pts × time_base,看看真实时间对不对。90% 的问题,答案就在那里。

相关推荐
AI创界者15 小时前
AIGC进阶】Sulphur-2 视频生成大模型离线实战:文生视频/图生视频本地一键部署整合包解压即用与调优指南
人工智能·aigc·音视频
ldsweet18 小时前
HarmonyOS NEXT 音频播放器开发:AVPlayer 封装、播放列表与后台播放实战
华为·音视频·harmonyos
林澈在路上18 小时前
2026 AI 写歌 APP 推荐:国产软件哪个好用实测
大数据·人工智能·aigc·音视频·音频
湿滑路面19 小时前
VideoPipe中集成多模态大模型做视频(图片)分析
人工智能·算法·音视频
数字会议深科技21 小时前
自主音视频会议核心技术沉淀:两项发明专利赋能专业会务设备稳健运行
人工智能·音视频·音频·国际会议·会议解决方案·会议讨论系统·高端会议
2zcode21 小时前
抑郁症多模态音视频与脑电信号数据集
音视频·情绪识别·抑郁症·心理健康分析
FlightYe1 天前
音视频修炼之视频基础(一):视频基础理论
android·c++·vscode·音视频·androidx·android runtime
中微极客1 天前
2026上半年AI视频模型技术突破:从生成到“导演级”控制的工程实践
人工智能·深度学习·音视频
EasyGBS1 天前
国标GB28181视频平台EasyGBS集群SIP网关模式:大并发场景下如何解决信令绑定难题?
音视频
开开心心就好1 天前
内存清理工具定时自动清理开机自启动
java·开发语言·elasticsearch·ocr·excel·音视频·big data