🎬 第一章 音视频及流媒体播放原理
📄 一句话概括

视频就是一秒钟放几十张静止画面,人眼把它们连成运动。 体积由三个数乘出来:像素数 × 帧率 × 每像素字节数——压缩只是在这个乘积上打个折。

📝 本节和前面几节的关系

这是全书第一节,先把「视频到底是多少数据」这件事算清楚。 后面八章讲的所有播放器、所有 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. 打开你手机拍的任意一段视频,看它的分辨率、帧率、码率三个数。
  2. 用「像素数 × 帧率 × 1.5 × 8」算未压缩码率,再和实际码率相除,得到压缩比。
  3. 换一段运动剧烈的视频再算一次,比较两个压缩比差多少。

常见误区

  1. 把 1080p 当成「画质好」。 分辨率只是网格密度, 糊成一片的 1080p 到处都是——真正决定观感的是码率与编码器。
  2. 以为分辨率翻倍数据量也翻倍。 横竖各翻一倍,像素数是四倍,带宽和存储都按四倍算。
  3. 以为 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 差在哪? —— 差在编码器能保留多少细节。 码率是预算,预算少就得砍高频细节、砍色度精度,画面在大动态与细纹理处先糊。

下一节把声音补上:同样是一秒钟,声音的数据量比画面小两三个数量级,但它有自己的三个乘数。

相关笔记