客户有批 23.976fps 的电影素材,要在 PAL 制式的电视渠道播出(25fps)。
我当时的做法很直接:
bash ffmpeg -i film.mov -r 25 -c:v libx264 out.mp4出来的文件帧率确实是 25,但有两个问题:时长短了 4%(90 分钟变成 86 分 20 秒),而且声音变尖了(音调升高)。客户那边排期按 90 分钟排的,直接对不上。
后来才搞明白:帧率转换不是"改个标签",它有三种完全不同的语义——改变时长(加速)、保持时长(增删帧)、或者生成新帧(插值)。而 24→25 在广播行业有一套约定俗成的做法(加速 4%,音高也跟着升,观众已经习惯了)。
TL;DR:三种转换方式:
-r/fps滤镜(增删帧,保持时长,画面可能顿挫)、setpts(改时间戳,改变时长,等于加速/减速)、minterpolate(运动补偿生成新帧,最平滑但慢)。24→25(PAL 加速)的标准做法:setpts=PTS/1.0427+ 音频atempo=1.0427。关键区别:atempo变速不变调(time-stretch,推荐);asetrate变速且变调(模拟磁带加速,传统 PAL 加速就是这个效果)。另外:29.97 和 30 差 0.1%——一小时累积 3.6 秒,长节目必须处理,短视频可以忽略。
目录
- 一、先解释 23.976 和 29.97 这两个怪数字
- 二、三种转换方式
- 三、24 → 25:PAL 加速
- 四、atempo 和 asetrate:变不变调的区别
- 五、29.97 ↔ 30:那 0.1% 要不要管
- 六、50/60 → 25/30:最简单的一种
- 七、慢动作与快动作
- 八、验证
- 九、命令速查表
- 十、坑清单
一、先解释 23.976 和 29.97 这两个怪数字
故事的起点:1950 年代美国给黑白电视加彩色信号。为了不破坏已有的黑白接收机,彩色副载波要"塞进"现有频谱,结果帧率被微调了 0.1%:
30 fps → 30 × 1000/1001 = 29.970 fps
24 fps → 24 × 1000/1001 = 23.976 fps(严格写法 24000/1001)
常见帧率对照:
| 名称 | 精确值 | 用途 |
|---|---|---|
| 23.976 | 24000/1001 | 电影、北美流媒体 |
| 24 | 24/1 | 部分数字制作 |
| 29.97 | 30000/1001 | 北美电视(NTSC 遗留) |
| 25 | 25/1 | 欧洲/中国电视(PAL) |
| 50 / 59.94 | 50 / 60000/1001 | 高清电视 |
这个 0.1% 会累积:一小时的 29.97fps 内容若按 30fps 处理,时间轴会差 3.6 秒。
二、三种转换方式
| 方式 | 命令 | 时长 | 画面 | 适合 |
|---|---|---|---|---|
| ① 增删帧 | -r 25 或 fps=25 |
不变 | 重复/丢帧,可能顿挫 | 保持时长优先 |
| ② 改时间戳 | setpts=PTS/1.0427 |
变短(加速) | 完全流畅 | PAL 加速、需要"变快" |
| ③ 插值 | minterpolate=fps=60 |
不变 | 生成新帧,最平滑 | 慢动作、升帧率 |
① 增删帧(保持时长)
ffmpeg -i in.mp4 -r 25 -c:v libx264 out.mp4 # 输出选项
ffmpeg -i in.mp4 -vf "fps=25" -c:v libx264 out.mp4 # 滤镜形式,更可控
23.976 → 25 用这种方式会产生重复帧(每秒多约 1 帧),快速运动时有轻微顿挫。优点:时长不变,音频不用动,音画天然同步。
② 改时间戳(改变时长)
-vf "setpts=PTS/1.0427" # 加速(时长变短)
-vf "setpts=PTS*1.0427" # 减速(时长变长)
画面完全流畅(没有重复帧),但时长变了,所以音频必须同步处理。
③ 插值(最平滑,最慢)
-vf "minterpolate=fps=60:mi_mode=mci:mc_mode=aobmc:vsbmc=1"
运动补偿生成中间帧。代价:比普通转码慢 10~50 倍,快速运动/遮挡处有伪影。只在真正需要平滑慢放时用。
三、24 → 25:PAL 加速
ffmpeg -i film_23976.mov \
-vf "setpts=PTS/1.0427" \
-af "atempo=1.0427" \
-c:v libx264 -crf 18 -preset slow \
-c:a aac -b:a 192k \
film_pal_25.mp4
系数怎么算:
| 源 | 系数(25/源) |
|---|---|
| 23.976(24000/1001) | 1.042709 |
| 24(24/1) | 1.041667 |
先确认源的实际帧率再选系数:
ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate,avg_frame_rate -of csv=p=0 film.mov
# 24000/1001,24000/1001
视频和音频必须用同一个系数,否则每秒差一点,几分钟后就明显不同步。
四、atempo 和 asetrate:变不变调的区别
这是最容易搞混的一对:
| 滤镜 | 原理 | 音调 | 时长 |
|---|---|---|---|
atempo=X |
时间拉伸(WSOLA) | 不变 | 变为 1/X |
asetrate=R |
改采样率标记 | 变(按 R/原采样率升高) | 变为 原/R×原采样率 |
atempo=1.0427:快 4.27%,音调不变(现代做法,推荐);asetrate=48000*1.0427:又快又尖——这就是传统 PAL 加速的效果(音调升高约 0.7 个半音)。
| 做法 | 音调 | 什么时候用 |
|---|---|---|
| atempo(现代) | 不变 | 一般交付的默认选择 |
| asetrate(传统) | 升高 | 客户明确要求"与播出版本一致"时 |
atempo 范围限制 0.5~2.0,超出要串联:
-af "atempo=2.0,atempo=2.0" # 4 倍速
-af "atempo=0.5,atempo=0.5" # 0.25 倍速
asetrate 的另一个用途(变调不变速,配 atempo 反向拉回):
# 升高一个半音(×1.0595),时长保持不变
-af "asetrate=48000*1.0595,atempo=1/1.0595"
五、29.97 ↔ 30:那 0.1% 要不要管
| 场景 | 要不要处理 |
|---|---|
| 短视频(< 10 分钟) | 不用(误差 < 0.6 秒) |
| 长节目(> 1 小时) | 必须(字幕会跑偏) |
| 多机位/字幕对齐 | 必须 |
| 上传到视频平台 | 一般不用(平台会处理) |
处理方式(保持时长):
ffmpeg -i in.mp4 -vf "fps=30" -c:v libx264 out.mp4
最常见的坑是字幕:字幕按 29.97 时间轴做、视频按 30 处理,一小时后字幕偏 3.6 秒。长节目一定要确认视频和字幕用同一帧率基准。
六、50/60 → 25/30:最简单的一种
整数倍减半,直接丢帧,无副作用:
ffmpeg -i in_50.mp4 -vf "fps=25" -c:v libx264 out_25.mp4
音频完全不用动,画面也不会顿挫(只是运动细腻度下降)。
七、慢动作与快动作
# 2 倍慢放(不插值,有效帧率降低会顿)
-vf "setpts=PTS*2" -af "atempo=0.5"
# 平滑慢放(先插到高帧率再减速)
-vf "minterpolate=fps=120:mi_mode=mci,setpts=PTS*4" -af "atempo=0.5,atempo=0.5"
# 4 倍快放
-vf "setpts=PTS/4" -af "atempo=2.0,atempo=2.0"
八、验证
# 1. 时长
ffprobe -v error -show_entries format=duration -of csv=p=0 out.mp4
# 2. 实际帧率
ffprobe -v error -select_streams v:0 -show_entries stream=avg_frame_rate -of csv=p=0 out.mp4
# 3. 音视频时长是否一致(差 < 0.5 秒)
ffprobe -v error -select_streams v:0 -show_entries stream=duration -of csv=p=0 out.mp4
ffprobe -v error -select_streams a:0 -show_entries stream=duration -of csv=p=0 out.mp4
最实用的主观检查:看结尾的口型——系数错了的话开头看不出来,结尾会明显偏。
九、命令速查表
| 需求 | 视频 | 音频 |
|---|---|---|
| 23.976→25 加速(不变调) | setpts=PTS/1.0427 |
atempo=1.0427 |
| 23.976→25 加速(传统变调) | setpts=PTS/1.0427 |
asetrate=48000*1.0427 |
| 25→23.976 | setpts=PTS*1.0427 |
atempo=0.959 |
| 29.97→30(保持时长) | fps=30 |
无 |
| 30→29.97(保持时长) | fps=30000/1001 |
无 |
| 60→30 | fps=30 |
无 |
| 2 倍慢放 | setpts=PTS*2 |
atempo=0.5 |
| 4 倍快放 | setpts=PTS/4 |
atempo=2.0,atempo=2.0 |
十、坑清单
- 只用
-r 25做 PAL 加速 → 时长没变且有重复帧。要用 setpts + 音频同步。 - 视频加速了音频没动 → 音画失步。
- 视频和音频系数不同 → 缓慢失步,结尾最明显。
- 23.976 用了 24 的系数 → 每小时差 3.6 秒。先 probe 确认。
- 以为 atempo 会变调 → 不会;要变调用 asetrate。
- atempo 超范围 → 报错,串联多个。
- 慢放只用 setpts → 有效帧率降低、画面顿,要插值。
- minterpolate 用于快速运动 → 伪影明显,先小段评估。
- 忘了字幕时间轴 → 长节目字幕跑偏。
- 变速后没复核时长 → 客户排期对不上。
- 多次累积转换 → 每次重编码累积画质损失。从原始素材一次转到位。
-r和fps的效果混淆 →-r是输出选项(也可能触发帧重采样),fps是滤镜;行为在不同版本略有差异,实测确认。- 变速后关键帧间隔变了 → ABR 档位可能不对齐,要重设
-g。 - 音频编码参数没跟上 → 变速后重编码音频时注意码率。
- 交付说明里没写"已做 PAL 加速" → 客户拿到后发现时长变了会疑惑。写进交付文档。
最后说说这次的体会。
这类"历史遗留"问题在音视频领域特别多。23.976 这个数字本身就是 1950 年代的技术妥协,它不合理,但它无处不在。类似的还有:
- 丢帧时间码(第 43 篇讲过);
- 隔行扫描(PAL/NTSC 的遗产);
- 44.1kHz(CD 的遗产);
- 响度标准的地区差异(第 42 篇)。
理解这些"不合理"的由来,比记住它们的数值重要——因为记住数值你只会照抄,而理解了由来,你才能判断"这个场景要不要管它"。
比如 0.1% 这个事:
- 知道它是 NTSC 彩色的遗留 → 知道它只在北美体系出现;
- 知道它会累积(每小时 3.6 秒)→ 就能判断"短视频不用管、长节目必须管"。
不需要每次都查规范,只要理解了原理,判断就是自然的。
还有一点很实际:帧率转换的验收一定要看结尾。开头几秒的差异太小看不出来,但累积误差在结尾最明显。我现在检查任何"时间轴相关"的处理,都是开头、中间、结尾各抽一段看——这是那次排期事故留下的习惯。