3 / 32 课程导览与音视频基础

第 2 课:视频的本质就是连续的图片

# 第 2 课:视频的本质就是连续的图片

本课目标:把视频最底层的四个概念 —— 像素、分辨率、帧、帧率 —— 用大白话讲透, 再回到 libobs/media-io/libobs/obs.h,看 OBS 是怎么用结构体把「一帧画面」精确描述出来的。 这是整条视频链路的第一块砖。

前两课我们学会了把 OBS 跑起来、在源码里找路。从这一课开始进入正题。先说一个听起来反直觉、却能解放思想的事实:

视频里其实没有「视频」这种东西,只有一堆图片。 所谓「视频」,不过是一叠静止图片,按时间顺序快速翻看而已。

顺着这句话,我们把它拆成四个概念。前三个回答「一张图片是什么」,最后一个回答「怎么让图片动起来」。


# 2.1 像素:图像的「原子」

📍我们在哪:流水线的第一站是「采集」,可要看懂采集出来的画面,得先认识它的最小零件 —— 像素。

拿出任意一张照片,在屏幕上不停放大、放大、再放大,最终你会看到画面变成一个个方形的小色块:

像素:放大后看到一个个色点

这些方块,就是像素(pixel,源自 picture element「图像元素」)。它是图像里最小的、不可再分的单位 —— 一个像素就是一个带着某种颜色的点。

把无数像素排成一张网格,就是一张图片。上图那个爱心,其实就是一张 11 × 10 的网格,总共 110 个像素,有的染成红色、有的留白。任何图片,本质都是这样一张「色点网格」。

至于「一个像素的颜色,在电脑里到底用几个字节、怎么存」—— 这是个大话题,我们专门留到第 3 课(RGB / YUV)。本课你只需记住:像素 = 一个带颜色的点,图像 = 像素网格。


# 2.2 分辨率:这张网格有多大

📍承上启下:像素是一个点,那把点排成网格、这张网格有多大,就是分辨率 —— 顺手也埋下「越清晰越占空间」的伏笔。

既然图片是像素网格,那「网格有多少行多少列」就决定了它的精细程度。这个「宽 × 高」,就是分辨率(resolution),单位是像素。

你一定见过这些数字:

分辨率阶梯:720p / 1080p / 4K

俗称 分辨率(宽 × 高) 像素总数
720p 1280 × 720 ≈ 92 万
1080p(全高清) 1920 × 1080 ≈ 207 万
4K(2160p) 3840 × 2160 ≈ 830 万

几个要点:

  • 名字里的「p」是 progressive(逐行扫描)的意思,表示每一帧都是一整张完整画面。(与之相对的是「i」= interlaced 隔行扫描,把奇偶行拆开交替显示,是老电视时代的技术;OBS 里有一整套「去隔行 / deinterlace」处理就是为它准备的,我们后面会遇到。)
  • 像素越多 → 画面越清晰,但数据量也越大。 注意上表:4K 的宽和高都是 1080p 的 2 倍,所以像素总数是它的 2 × 2 = 4 倍。分辨率每「升一级」,数据往往是成倍地涨。

这个「越清晰越大」的矛盾,先记在心里 —— 它是后面「为什么要编码」的一半理由。


# 2.3 帧与帧率:让图片动起来

📍承上启下:前面都在说「一张图片」;这一节让图片动起来 —— 一张叫帧,每秒放多少张叫帧率,视频就是这么来的。

到这里我们都还在说「一张图片」。视频怎么来的?把许多张图片按顺序快速播放。 其中每一张静止画面,就叫一帧(frame)。

帧:视频是一叠快速翻看的静止图片

就像小时候在书角画小人、快速翻页就「动」起来一样。你的眼睛有个特性叫视觉暂留(persistence of vision):一个画面消失后,视网膜还会短暂保留它的影像。于是当画面切换得足够快,大脑就把一连串静止图片脑补成了连续的运动。

那「足够快」是多快?这就引出帧率(frame rate),即每秒播放多少帧,单位是 fps(frames per second):

帧率对比:15 / 30 / 60 fps

  • 24 fps —— 电影的经典帧率,够骗过眼睛,还带点「电影感」;
  • 30 fps —— 早年视频、直播的常见值;
  • 60 fps —— 游戏直播的主流,运动画面明显更顺滑。

帧率和「每帧间隔」是倒数关系:60 fps 意味着每 1/60 秒(≈ 16.7 毫秒)就要换一帧。同样一秒钟,帧率越高,塞进去的帧越多,画面越丝滑 —— 但要处理和传输的数据也成倍增加。又一次,「更好」和「更大」绑在了一起。

🗒️ 术语速记:像素=画面最小的一个色点;分辨率=一帧有多少像素(如 1920×1080);=某一瞬间的一整幅画面;帧率(fps)=每秒多少帧。


# 2.4 一个小谜题:为什么会有 29.97 fps?

📍接上一节:刚说完帧率,这一节抓一个反常的帧率 29.97 fps 当谜题,顺带看 OBS 为什么把帧率存成分数而不是小数。

如果你翻过视频软件的设置,可能见过 29.9759.94 这种别扭的帧率,而不是整齐的 30、60。这不是 bug,而是一段历史遗留:

当年美国的彩色电视(NTSC)要兼容已有的黑白电视信号,工程师不得不把帧率从 30 略微下调到 30 ÷ 1.001 ≈ 29.97,好给彩色信息腾出位置又不干扰伴音。这个 1.001 的尾巴就这么一直留到了今天。

问题来了:29.97 是个除不尽的小数(30000 ÷ 1001),用浮点数存会有精度误差,日积月累音画就会对不齐。OBS 怎么办?它干脆不存小数,而是把帧率存成一个分数 —— 分子和分母分开存。 看源码:

// libobs/media-io/frame-rate.h:7
struct media_frames_per_second {
	uint32_t numerator;    // 分子,如 30000
	uint32_t denominator;  // 分母,如 1001
};

// 真正的 fps 值 = 分子 ÷ 分母
static inline double media_frames_per_second_to_fps(struct media_frames_per_second fps)
{
	return (double)fps.numerator / fps.denominator;
}
1
2
3
4
5
6
7
8
9
10
11

于是 29.97 在 OBS 里被精确地表示成 numerator = 30000, denominator = 1001,59.94 则是 60000 / 1001,毫无精度损失。一个小小的结构体,藏着一段电视史 —— 这正是读真实工程源码的乐趣。


# 2.5 源码印证:OBS 眼里的「一帧」和「一条视频流」

📍承上启下:概念讲够了,回源码兑现 —— 看 OBS 用哪两个结构体,把本课的像素、分辨率、帧率精确地装进代码。

现在把本课四个概念,和 OBS 的真实数据结构对上。

# 一条视频流的「说明书」:video_output_info

OBS 用 struct video_output_info 来描述「一条视频流长什么样」。注意看,本课的概念几乎全在里面:

// libobs/media-io/video-io.h:127
struct video_output_info {
	const char *name;

	enum video_format format;   // 像素格式:颜色怎么存(第 3 课)
	uint32_t fps_num;           // 帧率的分子  ┐ 帧率
	uint32_t fps_den;           // 帧率的分母  ┘ (本课 2.3 / 2.4)
	uint32_t width;             // 宽  ┐ 分辨率
	uint32_t height;            // 高  ┘ (本课 2.2)
	size_t cache_size;
	...
};
1
2
3
4
5
6
7
8
9
10
11
12

分辨率(width/height)、帧率(fps_num/fps_den)、像素格式(format),三个概念在这一个结构体里齐活了。以后你在 OBS 设置里调的「输出分辨率」「FPS」,最终就是填进这几个字段。

# 具体某一帧:obs_source_frame

而当一个「源」(摄像头、屏幕…)真正产出某一张具体画面时,它交给 OBS 的是 struct obs_source_frame。本课的每个概念,都是它的一个字段:

obs_source_frame 字段 → 概念映射

// libobs/obs.h:283
struct obs_source_frame {
	uint8_t *data[MAX_AV_PLANES];      // 像素数据本身(一大块字节)
	uint32_t linesize[MAX_AV_PLANES];  // 每一行占多少字节
	uint32_t width;                    // 宽 ┐ 分辨率
	uint32_t height;                   // 高 ┘
	uint64_t timestamp;                // 时间戳:这帧该在何时显示
	enum video_format format;          // 像素格式(第 3 课)
	...
};
1
2
3
4
5
6
7
8
9
10
  • data / linesize —— 像素数据本身:所有像素的颜色字节,一行一行排好;linesize 记录「一行有多少字节」,方便逐行读取。
  • width / height —— 分辨率,本课主角。
  • timestamp —— 时间戳,这一帧应该在什么时刻显示。它和帧率直接相关,更是模块 4「音画同步」的关键 —— 声音帧和画面帧都靠各自的 timestamp 来对齐。
  • format —— 像素格式,下一课的主题。

顺带看一眼「一帧到底占多少内存」是怎么算出来的。libobs/media-io/video-frame.h:32 有这么个函数:

void video_frame_init(struct video_frame *frame,
                      enum video_format format,
                      uint32_t width, uint32_t height);
1
2
3

它按 格式 × 宽 × 高 算出需要多少字节,然后一次性分配好内存。换句话说:一帧的大小 = 分辨率 × 每像素字节数。 记住这个公式,下一节我们就用它。


# 2.6 把第 0 课的账,精确地再算一遍

📍收束:带着新学的概念,把第 0 课那笔「裸流有多大」的账再算一遍 —— 这回每个数字你都说得清来历,也为「编码」攒足了动机。

第 0 课我们粗略算过「1080p60 的裸流有多大」,当时很多数字是「先给你一个结果」。学完本课,你已经能说清每个数字的来历了:

   1920 × 1080      ← 分辨率(2.2):一帧有这么多像素
 ×        3 字节    ← 每个像素约 3 字节(RGB,第 3 课细讲)
 ───────────────
 ≈ 6.2 MB           ← 一帧的大小(正是 video_frame_init 要分配的内存)
 ×       60         ← 帧率(2.3):每秒 60 帧
 ───────────────
 ≈ 356 MB / 秒
1
2
3
4
5
6
7

每一个乘数,现在都对应本课一个明确的概念。而结论没变,而且更触目惊心了:一秒钟约 356 MB,一分钟就是 20 多 GB。 这样的裸数据,既存不下也传不出去。

所以视频链路走到「采集」之后,必须有一步把它压小 —— 那就是模块 5 的「编码」。本课为它备好了全部前置概念。


# 2.7 本课小结

  • 视频 = 一叠按时间快速播放的静止图片。 拆成四个概念:
    • 像素:图像最小单位,一个带颜色的点;图像 = 像素网格。
    • 分辨率:宽 × 高(像素数),如 1080p = 1920 × 1080;像素越多越清晰、数据越大(4K 像素是 1080p 的 4 倍)。
    • :一张静止画面;靠视觉暂留,连续播放就成了运动。
    • 帧率(fps):每秒帧数,24/30/60;越高越流畅、数据越大。
  • 帧率在 OBS 里存成分数(media_frames_per_secondnumerator/denominator),从而精确表示 29.97 = 30000/1001 这类值。
  • 这些概念在源码里都有落点:video_output_info(一条流的分辨率/帧率/格式)、obs_source_frame(具体一帧的 width/height/data/timestamp/format)。
  • 一帧大小 = 分辨率 × 每像素字节数;裸视频体积大到必须压缩 —— 为模块 5「编码」埋好了动机。

# 2.8 动手 / 观察(本课作业)

  1. 在 OBS 界面里找概念:打开 OBS → 设置 → 视频,看「基础(画布)分辨率」「输出(缩放)分辨率」和「常用 FPS 值」三个选项 —— 它们正是本课的分辨率帧率,直接对应 video_output_info 的字段。
  2. 亲手看见像素:用任意看图 / 画图软件打开一张图,放大到 800% 以上,直到你能看见一个个方形色块。
  3. 读两段源码:打开 libobs/media-io/video-io.h:127,数一数 video_output_info 有哪些字段、各对应本课哪个概念;再打开 libobs/media-io/frame-rate.h,理解 fps 为什么要存成分数。

# 下一课预告

我们一直回避的那个问题 —— 「一个像素的颜色,到底怎么存进电脑?」 —— 下一课揭晓。

第 3 课:颜色是怎么被存进电脑的 —— 讲 RGB 与 YUV、亮度与色度,以及一个关键问题:为什么直播和视频几乎都用 YUV,而不是你更熟悉的 RGB? 答案又一次和「省数据」有关。我们会回到 libobs/media-io/video-io.h 里那一长串 VIDEO_FORMAT_*,看 OBS 到底支持多少种颜色存法。


📁 本课配图:imgs/02-01 ~ imgs/02-05 📌 源码锚点:libobs/media-io/frame-rate.h:7libobs/media-io/video-io.h:127libobs/obs.h:283libobs/media-io/video-frame.h:32

上次更新: 2026/07/14, 19:37:40

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>