525 行变成 480 行,和 PAL 是同一套算法。差别在于它的帧率不是整数。 29.97 的精确值是 30000/1001,千分之一的偏差会累积成麻烦。
2.8.2 把 PAL 算到底,这一节用同一套方法算 NTSC,正好对比着看。 本章到此结束,第三章开始逐个细讲三个工具的全部选项。
一、525 行变成 480 行
算法和 PAL 完全一样:总行数扣掉消隐,剩下的就是有效行。
项 PAL NTSC 差别
扫描行 625 525 少 100 行 有效行 576 480 少 96 行 宽度 720 720 一样 帧率 25 29.97 快约 20%
第三行的「宽度一样」很有意思:720 是数字取样标准定的,跟模拟行数无关。 所以两套完全不同的扫描体系,最后都用了 720 这个宽度。
这也意味着「720×576」和「720×480」看着只差高度,实际背后是两套体系。 把 PAL 素材当 NTSC 处理,画面会被拉长;反过来会被压扁。
二、29.97 到底是多少
这个数字有三种写法,用哪个场合不一样:
写法 值 该用在哪
分数 30000/1001 精确值,ffmpeg 内部就用它 小数 29.9699… 四舍五入成 29.97 是给人看的 与 30 的比 0.999001 差了约千分之一
第一行是关键:ffmpeg 的参数里写 29.97 会被自动转成接近的分数,
但要精确匹配必须写 30000/1001。两个写法在某些场合结果不一样。
什么时候会踩这个坑?做帧精确的剪切、做长时间的时间码统计、 或者要把两段素材无缝拼接时——差一帧都会露出来。
三、千分之一累积成什么样
单帧差 0.03 毫秒,感觉可以忽略。但播出是连续几小时的:
时长 按 30 算 按 29.97 算 差多少帧
1 小时 108000 107892 108 1 天 2592000 2589410 2590 30 天 77760000 77682317 77683
一小时差 108 帧就是 3.6 秒。一天差 86 秒,够让字幕和画面彻底错开。 所以广播行业必须有「丢帧时间码」这种东西。
名字容易误会:它丢的是时间码的编号,不是画面。 画面一帧都没少,只是每过几分钟就跳过几个编号,让时间码追上真实时间。
这也能解释一个日常现象:为什么剪 29.97 的片子, 时间线上显示的时间和实际钟表时间总是差一点——它在补偿这个千分之一。
四、动手 Demo:29.97 这个怪数字的来历
先把 525 行到 480 行的换算走一遍,再算清 30000/1001 这个分数, 最后量一下千分之一的偏差在一天、一个月里会累积成多少帧。
# NTSC 与 PAL 的换算对比
for name, lines, active in ((u"PAL", 625, 576), (u"NTSC", 525, 480)):
print(u"%s 扫描 %d 有效 %d 折损 %d 行"
% (name, lines, active, lines - active))
# 29.97 的精确形式
num, den = 30000, 1001
fps = float(num) / den
print(u"30000/1001 = %.6f" % fps)
print(u"和 30 的比 = %.6f" % (fps / 30))
# 偏差累积
for label, hours in ((u"1 小时", 1), (u"1 天", 24), (u"30 天", 720)):
a = 30 * 3600 * hours
b = fps * 3600 * hours
print(u"%s 按30算 %.0f 帧 按29.97算 %.0f 帧 差 %.0f 帧 / %.1f 秒"
% (label, a, b, a - b, (a - b) / 30.0))
# 同族帧率
for label, n, d in ((u"23.976", 24000, 1001), (u"29.97", 30000, 1001),
(u"59.94", 60000, 1001)):
print(u"%s 就是 %d/%d = %.4f" % (label, n, d, float(n) / d))
实跑输出:
--------------------------------------------------------------------
NTSC 制式——29.97 这个怪数字的来历
--------------------------------------------------------------------
**① 525 行怎么变成 480,和 PAL 是同一套算法**
项 PAL NTSC 差别
------------------------------------------
扫描行 625 525 少 100 行
有效行 576 480 少 96 行
宽度 720 720 一样
帧率 25 29.97 快 20%
**有意思的是宽度都是 720——那是数字取样标准定的,和模拟行数无关。**
**所以「720×576」和「720×480」看着只差高度,其实背后是两套完全不同的扫描体系。**
**② 29.97 到底是多少**
写法 值 说明
------------------------------------------------------
分数形式 30000/1001 这是精确值
小数形式 29.9699… 四舍五入到 29.97
和整数 30 的比 0.999001 差了约千分之一
**所以看到 29.97 就要想到它不是一个「随便定的数」,而是 30000/1001。**
**ffmpeg 里写 29.97 会被自动转成这个分数,但精确匹配要用 30000/1001。**
**③ 千分之一的偏差,累积起来有多少**
时长 按 30 算的帧数 按 29.97 算的帧数 差多少帧
----------------------------------------------------------------
1 小时 108000 107892 108
1 天 2592000 2589410 2590
30 天 77760000 77682317 77683
**一小时差 108 帧,也就是 3.6 秒。一天差 86 秒。**
**所以广播行业必须用「丢帧时间码」:跳过一些编号,让时间码追上真实时间。**
**注意它丢的是编号,不是画面——画面一帧都没少。**
**④ 59.94 也是同一个道理**
**59.94 = 2 × 29.97,23.976 = 24 ÷ 1.001。**
**凡是和 NTSC 沾边的帧率,都带着这个 1001 的尾巴。**
**所以 ffmpeg 里 24000/1001、30000/1001、60000/1001 这三个分数会经常见到。**
第一段那个「折损」一列,PAL 是 49 行、NTSC 是 45 行,比例差不多。 两套体系的消隐比例接近,说明这个折扣是扫描原理带来的,不是各自的特殊设计。
第二段的 30000/1001 是核心:它既是定义也是实现。
写 29.97 只是这个分数的近似,很多场合够用,做帧精确就得用分数。
最后一段那三个分数是 NTSC 家族的全部成员。 23.976 是电影转电视用的(24 除以 1.001),59.94 是高帧率的双倍。 凡是和 NTSC 沾边的帧率,都带着这个 1001 的尾巴。
所以 ffmpeg 里最常打交道的三个帧率分数就是 24000/1001、30000/1001、60000/1001。 记住这三个,以后看到一堆奇怪的小数就不会发懵。
30 秒实验
- 算一次:用 Python 算
30000/1001和29.97的差,看差在哪个小数位。 - 验一次:
ffprobe -show_streams找r_frame_rate,很多真素材写的就是30000/1001。 - 比一次:
ffmpeg -i in.mp4 -r 30 out.mp4,把 29.97 转成 30,看它怎么处理那一帧的差。
常见误区
- 以为 NTSC 是 30 帧。名义 30,实际 30000/1001。做帧精确操作时必须用分数写法。
- 以为「丢帧时间码」丢的是画面。它只跳编号不跳画面,画面一帧都没少。
- 把 720×480 当 720×576 处理。两套体系高度不同,混用会让画面被拉伸或压扁。
自测三问(点开对答案)
1. 29.97 的精确值是什么?为什么不是整数 30? —— 精确值是 30000/1001。当年为了在黑白信号带宽里塞进彩色信息,把行频减慢了千分之一。 2. 千分之一的偏差,一天能累积多少? —— 约 2590 帧,也就是 86 秒多。所以广播行业必须用丢帧时间码来补偿。 3. 23.976、29.97、59.94 这三个数有什么共同点? —— 都是「整数除以 1.001」:24000/1001、30000/1001、60000/1001。凡是 NTSC 家族的帧率都带这个尾巴。- ☐ (本章完,下一章 3.1 ffmpeg 工具简介)