视频就是一秒钟放几十张静止画面,人眼把它们连成运动。 体积由三个数乘出来:像素数 × 帧率 × 每像素字节数——压缩只是在这个乘积上打个折。
这是全书第一节,先把「视频到底是多少数据」这件事算清楚。 后面八章讲的所有播放器、所有 API,处理的对象都是这个乘积: 解封装拆的是它,解码还原的是它,渲染画到屏幕上的还是它。 要对照编码侧,看 🎬 音视频原理及应用。
一、视觉暂留其实是个误称
教科书常说「人眼有视觉暂留,所以连续的静止画面看起来是动的」。 这个说法不准确:真正起作用的是大脑的运动知觉——它把时间上连续、 位置上渐变的图案解释成同一个物体的移动。
由此得到两个实用结论: 第一,帧率低到一定程度,运动就会「跳」——这个门槛叫临界闪烁频率,多数人在 50 到 60 赫兹之间。 第二,同样 24 fps,有运动模糊的电影看着顺、没有模糊的游戏看着顿,差别不在帧率, 在每帧里有没有「运动轨迹」。
所以「多少帧才够」没有统一答案:电影 24、电视 25 或 30、游戏 60 起步、VR 要 90 以上。 帧率是体验参数,不是物理常数。
二、分辨率与像素
一帧画面是一张位图,被切成横竖两个方向的小方格,一格就是一个像素。 1920×1080 的意思是横 1920 格、竖 1080 格,一共两百多万格。
| 叫法 | 像素网格 | 像素数 | 俗称 |
|---|---|---|---|
| 360p | 640×360 | 230400 | 小窗 |
| 720p | 1280×720 | 921600 | 高清 |
| 1080p | 1920×1080 | 2073600 | 全高清 |
| 2160p | 3840×2160 | 8294400 | 4K |
那个 p 不是像素的意思,它是逐行扫描(progressive); 对应的 i 是隔行(interlaced),老电视用的就是隔行。 分辨率翻一倍,像素数是四倍,不是两倍——因为横竖各翻一倍。这条常被忽略,却是算带宽时最常见的错。
三、帧率:每秒几张
帧率就是每秒的画张数,单位 fps。 它决定「运动顺不顺」,不决定「画面清不清」。
两个方向的需求不一样: 清晰度由分辨率与压缩质量决定,跟帧率无关。 流畅度由帧率决定,跟分辨率无关。
所以「降分辨率保帧率」和「降帧率保分辨率」是两种完全不同的取舍: 前者让画面糊但动作连贯,后者让画面清但动作卡顿。网络差的时候选哪一个, 取决于内容——看球赛选前者,看文档选后者。
四、为什么是 YUV 而不是 RGB
屏幕上的每个像素本来该是红绿蓝三个分量,那是 RGB。 但视频里存的几乎都是 YUV,原因有两个,一老一新。
老的原因是兼容:彩色电视刚出来时,黑白电视还在用。 YUV 把「亮度」单独拎出来当 Y,黑白电视只收 Y 就能看,彩色电视再补上 UV 两个色度分量。 一套信号,两代设备都能用。
新的原因更实在:人眼对色度不敏感。 所以色度可以少存一点——四个像素共用一组 UV,这就是 YUV420,平均下来一个像素占一点五字节, 比 RGB 的三字节省了一半。
这个「省一半」在整条链路上都会被继承:解码器输出 yuv420p、sws_scale 把它转成 rgb24 给屏幕、 转换这一步的耗时也正好是「一个半字节变三个字节」的代价。
五、码率:三个数乘出来
未压缩的码率 = 像素数 × 帧率 × 每像素字节数 × 8。 1080p30 的 YUV420 就是 2073600 × 30 × 1.5 × 8,约 746 Mbps。
这个数字的意义在于:它是一条物理底线。 任何压缩算法都只能在这个数上打折,不能超过它。
打折打到多少,取决于内容和画质要求: 电影压到 20 Mbps,压缩比约 37 比 1。 网络视频压到 5 Mbps,压缩比约 149 比 1。 会议软件压到 1 Mbps,画面几乎只剩轮廓。
压缩比越高,丢掉的越是「人眼不太在意」的部分——高频细节、快速变化处的精度、色度的精细度。 所以「同码率下谁更清楚」比的其实是「谁更懂人眼」。
六、动手 Demo:一帧 1080p 到底有多大
这段先按四个常见分辨率把账算一遍,再真编一段视频看压缩比。 注意最后一列「一小时」——那是把码率乘上三千六百秒的容量。
for name, px in ((u"1920×1080", 1920 * 1080),):
b = px * 3 // 2 # YUV420 平均 1.5 字节每像素
T([name, u"%d" % px, u"%d" % b,
u"%.0f Mbps" % (b * 30 * 8 / 1e6),
u"%.0f GB" % (b * 30 * 3600 / (1024.0 ** 3))],
[0, 0, 0, 0, 0])
# 真跑:编 320×240、30 帧,比较 rawvideo 与 libx264
raw = 320 * 240 * 3 // 2 * 30
n = _make_video(path, 320, 240, 30, 30)
print(raw / float(n)) # 压缩比
实跑输出:
--------------------------------------------------------------------
一帧 1080p 到底有多大
--------------------------------------------------------------------
**视频的体积先被「像素数 × 帧率 × 每像素字节数」定死,跟压缩算法无关。**
YUV420 里 Y 是每像素一个字节,UV 每四个像素共用一组,平均下来一像素一点五字节。
分辨率 像素数 每帧字节 30 fps 码率 一小时
------------------------------------------------------------------
640×360 230400 345600 83 Mbps 35 GB
1280×720 921600 1382400 332 Mbps 139 GB
1920×1080 2073600 3110400 746 Mbps 313 GB
3840×2160 8294400 12441600 2986 Mbps 1251 GB
**1080p30 生的就是 746 Mbps,一小时 313 GB。**
**所以不压缩根本存不下,也传不动。**
**真跑一段 320×240、30 帧**:
rawvideo 的账算出来是 3456000 字节,也就是 3.30 MiB。
libx264 压完,文件实际 11371 字节,**压缩比 303.9 比 1**。
这块测试图几乎是纯色块加一条平移的边,好压得离谱。
**别拿这个数去估真实素材**——1080p 电影压到 20 Mbps,压缩比只有 37 比 1。
**所以「码率」这个参数的本质是:拿多少比特去换多少画质。**
**给少了细节糊,给多了带宽扛不住。**
真跑那一段的压缩比是三百多比一,看着惊人,但别拿去估真实素材。 因为测试图几乎是纯色块加一条平移的边——这种画面在编码器眼里几乎是「一句话就能描述完」, 所以压得极狠。
真实素材的压缩比看内容:静态访谈能到一百比一以上,运动比赛只有二三十比一,雪花噪声几乎压不动。
所以规格书上「支持 1080p」这句话几乎没用——它没说码率。 同样 1080p,5 Mbps 和 20 Mbps 是两个世界。看规格要看「1080p 多少帧、多少码率」, 三个数必须同时给。
30 秒实验
- 打开你手机拍的任意一段视频,看它的分辨率、帧率、码率三个数。
- 用「像素数 × 帧率 × 1.5 × 8」算未压缩码率,再和实际码率相除,得到压缩比。
- 换一段运动剧烈的视频再算一次,比较两个压缩比差多少。
常见误区
- 把 1080p 当成「画质好」。 分辨率只是网格密度, 糊成一片的 1080p 到处都是——真正决定观感的是码率与编码器。
- 以为分辨率翻倍数据量也翻倍。 横竖各翻一倍,像素数是四倍,带宽和存储都按四倍算。
- 以为 YUV 是「比 RGB 差」的格式。 它是把人眼不敏感的那部分省掉了, 省下来的空间可以还给亮度和细节,同码率下往往更好看。
自测三问(点开对答案)
1. 1920×1080 的 YUV420 一帧多少字节? —— 1920×1080×1.5 = 3110400 字节,约 2.97 MiB。 2. 为什么彩色电视要选 YUV 而不是直接送 RGB? —— 因为要兼容还在用的黑白电视——只送 Y 就能让黑白机正常显示,彩色机再额外解 UV。 3. 同样 1080p,5 Mbps 和 20 Mbps 差在哪? —— 差在编码器能保留多少细节。 码率是预算,预算少就得砍高频细节、砍色度精度,画面在大动态与细纹理处先糊。下一节把声音补上:同样是一秒钟,声音的数据量比画面小两三个数量级,但它有自己的三个乘数。
- ☐ (本节完,下一节 01.01.02 音频简介)