用整数运算换算音视频时间戳
FFmpeg 将时间戳按源、目标时间基换算为整数,并在乘除过程中处理舍入与溢出。
相关概念
工程问题#
音频采样、视频编码和封装使用不同时间基。直接计算 a × b / c 可能在中间乘法溢出;转成浮点数又可能丢失大时间戳的精度。
实际实现#
av_packet_rescale_ts 对有效的 PTS、DTS 和正数时长调用 av_rescale_q。后者把两个时间基组合成整数比例,再交给 av_rescale_rnd;当比例分子、分母不超过 INT_MAX 时,把 a 拆成商与余数,较大比例则使用分段乘法与逐位除法。
处理流程#
- 跳过 AV_NOPTS_VALUE,保留未定义的 PTS 和 DTS
- 将两个 AVRational 时间基组合成整数比例 b/c
- 根据舍入方式计算偏移量,并按参数范围选择换算分支
- 检查结果能否用 int64_t 表示,再更新数据包时间字段
源码片段#
固定版本 · 原文摘录libavutil/mathematics.cn7.1 · L77–L90
77 if (rnd == AV_ROUND_NEAR_INF)78 r = c / 2;79 else if (rnd & 1)80 r = c - 1;8182 if (b <= INT_MAX && c <= INT_MAX) {83 if (a <= INT_MAX)84 return (a * b + r) / c;85 else {86 int64_t ad = a / c;87 int64_t a2 = (a % c * b + r) / c;88 if (ad >= INT32_MAX && b && ad > (INT64_MAX - a2) / b)89 return INT64_MIN;90 return ad * b + a2;av_rescale_rnd
先按舍入模式设置偏移 r。在 b、c 不超过 INT_MAX 的分支,把较大的 a 拆为商 ad 和余数对应项 a2,合并前检查结果上限;较大的 b、c 由后续未展示的分段乘法分支处理。
libavcodec/packet.cn7.1 · L533–L541
533void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)534{535 if (pkt->pts != AV_NOPTS_VALUE)536 pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb);537 if (pkt->dts != AV_NOPTS_VALUE)538 pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb);539 if (pkt->duration > 0)540 pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb);541}av_packet_rescale_ts
完整函数只换算有效的 PTS、DTS 和正数 duration。它调用 av_rescale_q,并保留 AV_NOPTS_VALUE;这说明整数换算函数如何用于实际数据包接口。
第一段是整数比例较小的计算分支,完整函数还处理无效参数、负数、特殊值与宽乘法;第二段为实际调用该换算逻辑的完整数据包函数。
收益与取舍#
工程分析带来的收益
- 避免直接 a*b 造成的中间结果溢出
- 不同调用点共用明确的整数舍入规则
付出的代价
- 目标时间基更粗时仍会量化,不能把整数换算理解为无误差
- 合法范围和舍入方向必须由调用方确定;不可表示的结果以 INT64_MIN 返回
可比较的选择
浮点时间戳换算表达简单,但大整数转换可能丢失精度,舍入也需要额外约定。
直接使用更宽的整数类型可简化中间乘法,但依赖编译器和目标平台对宽整数的支持,仍需检查结果范围。
版本与适用范围#
FFmpeg n7.1 的时间基换算。展示的是数值运算与数据包字段处理,不是音频信号重采样或视频帧率转换。av_rescale_q 默认使用就近舍入,恰好一半时远离零。
参考来源#
源码、官方文档与原始论文01
FFmpeg · av_rescale_rnd / av_rescale_q
源码n7.1libavutil/mathematics.c · av_rescale_rnd / av_rescale_q