视频编码原理:为什么 1 小时电影只有几百 MB

204 阅读11分钟

导读:上一篇我们了解了播放器的工作流程,这一篇深入视频压缩的魔法世界。你会发现,1 小时电影从 500GB 压缩到 1GB,背后竟然藏着如此精妙的数学和心理学。 音视频开发全景图:播放器是怎样炼成的音视频开发全景图:播放器是怎样炼成的 🎬 开场:点击播放按钮,究竟发生了什么? 想 - 掘金


🤔 开场:一个惊人的数字

让我们算一笔账:

未压缩视频的大小 = 分辨率 × 帧率 × 色彩深度 × 时长

1080p 视频(1 小时):
= 1920 × 1080 像素              (分辨率)
× 30 帧/秒                       (帧率)
× 24 bit/像素                    (RGB  8 位)
× 3600                         (1 小时)
= 5,970,432,000,000 bit
 746 GB 😱

但你下载的电影通常只有 1-2 GB,压缩比高达 400 倍

这是怎么做到的?


🎯 核心思想:视频中 95% 的信息是冗余的

视频编码器像一个聪明的打包工,会扔掉大部分"废话":

冗余类型 1:空间冗余(同一帧内)

观察这张图片

1764138093344.png

发现了吗?

  • 天空的蓝色,左边和右边几乎一样
  • 草地的绿色,相邻像素差别很小

传统存储

像素1: RGB(135, 206, 250)  蓝色
像素2: RGB(135, 206, 250)  蓝色
像素3: RGB(136, 206, 250)  蓝色(几乎一样)
... 重复 100 万次

聪明的做法

"这一片天空都是 RGB(135, 206, 250),只有第 3 个像素稍微亮一点 (+1)"

节省了 90% 的存储!这就是 帧内预测(Intra Prediction)


冗余类型 2:时间冗余(相邻帧之间)

观察这两帧

1764138175009.png

发现了吗?

  • 30fps 视频,相邻两帧间隔只有 0.033 秒
  • 除了移动的人物,背景完全一样

传统存储

第 100 帧: 存储完整画面(2MB)
第 101 帧: 存储完整画面(2MB)
第 102 帧: 存储完整画面(2MB)

聪明的做法

100 帧: 存储完整画面(2MB)
第 101 帧: "背景不变,人物向右移动 10 像素"5KB)
第 102 帧: "背景不变,人物向右移动 10 像素"5KB)

节省了 99.75% 的存储!这就是 帧间预测(Inter Prediction)


冗余类型 3:视觉冗余(人眼察觉不到的)

人眼有个特点:对细节不敏感

1764138209168.png

编码器的策略

  • 保留主要轮廓明显颜色变化
  • 丢弃细微纹理高频噪声

这就像 MP3 音频删掉人耳听不见的高频,H.264 视频删掉人眼看不清的细节。


🧩 H.264 编码的三大武器

H.264(也叫 AVC)是目前最流行的视频编码标准,它用三大技术实现压缩:

graph LR
    A[原始视频<br/>746 GB] --> B[帧内预测<br/>Intra]
    B --> C[帧间预测<br/>Inter]
    C --> D[熵编码<br/>Entropy]
    D --> E[压缩视频<br/>1.5 GB]
    
    style A fill:#ffebee
    style B fill:#fff3e0
    style C fill:#e8f5e9
    style D fill:#e1f5ff
    style E fill:#f3e5f5

🔧 武器 1:帧内预测(Intra Prediction)

目标:压缩单张图像内的空间冗余。

原理:预测 + 差值

步骤

  1. 把图像分成 4×4 或 16×16 的小块(宏块)
  2. 已编码的相邻块预测当前块的内容
  3. 只存储预测值和实际值的差

1764138271667.png

举例

已编码块(左边): 平均亮度 = 128
当前块预测值: 128(假设和左边一样)
当前块实际值: 130
只需存储: +2(差值)

9 种预测模式

模式编号模式名称适用场景
0DC(平均值)平坦区域(天空、墙壁)
1Horizontal水平纹理(百叶窗)
2Vertical垂直纹理(栅栏)
3-8Diagonal斜向纹理(斜坡)

编码器会自动选择误差最小的模式。


🎬 武器 2:帧间预测(Inter Prediction)

目标:利用相邻帧的相似性,只存储变化部分。

关键技术:运动估计(Motion Estimation)

核心思想:这一帧的内容,可能只是上一帧的"平移版本"。

1764138325339.png

步骤

  1. 参考帧中搜索最匹配的块
  2. 记录运动矢量(偏移量)
  3. 只存储运动矢量 + 残差

示例

当前帧的块: 小球在坐标 (100, 50)
参考帧中找到: 小球在坐标 (90, 50)
存储: 运动矢量 (+10, 0) + 少量残差

I/P/B 帧:三种不同的编码帧

H.264 把视频分成三种帧类型:

graph TD
    A[I 帧<br/>Intra Frame<br/>关键帧] --> B[完整图像<br/>不依赖其他帧]
    C[P 帧<br/>Predicted Frame<br/>预测帧] --> D[参考前面的 I/P 帧<br/>存储运动矢量]
    E[B 帧<br/>Bidirectional Frame<br/>双向帧] --> F[参考前后的 I/P 帧<br/>压缩率最高]
    
    style A fill:#ffcdd2
    style C fill:#c8e6c9
    style E fill:#bbdefb

详细对比

帧类型中文名依赖关系压缩率大小比例解码难度
I 帧关键帧不依赖其他帧1.0×简单
P 帧前向预测帧依赖前面的 I/P 帧0.2-0.3×中等
B 帧双向预测帧依赖前后的 I/P/B 帧0.1-0.15×复杂

1764138372557.png

举例

帧序列: I P B P B P I P B P B P
大小:    100KB 20KB 10KB 20KB 10KB 20KB 100KB ...

平均每帧: (100 + 20×3 + 10×2) / 630KB
vs 原始: 2MB/帧
压缩比: 67

GOP(Group of Pictures):关键帧间隔

GOP 是从一个 I 帧到下一个 I 帧之间的所有帧。

常见 GOP 结构

GOP = 12, M = 3
I B B P B B P B B P B B I ...
↑                       ↑
└───── 12 帧 ──────────┘

参数含义

  • GOP 大小:多少帧出现一个 I 帧(常见 12、30、60)
  • M 参数:多少个 B 帧夹在两个参考帧之间

权衡

GOP 越大:
  优点: 压缩率更高(更多 P/B 帧)
  缺点: Seek 慢(必须从上一个 I 帧开始解码)
  
GOP 越小:
  优点: Seek 快(I 帧更多)
  缺点: 文件更大(I 帧占空间)

实际应用

  • 直播:GOP = 1-2 秒(30-60 帧),快速响应
  • 点播:GOP = 2-10 秒,追求压缩率
  • 广播:GOP = 0.5 秒(12 帧),方便剪辑

📊 武器 3:熵编码(Entropy Coding)

目标:进一步压缩数据的表示形式。

原理:高频出现的数据用短编码,低频数据用长编码。

CAVLC vs CABAC

H.264 提供两种熵编码方式:

特性CAVLCCABAC
全称Context-Adaptive VLCContext-Adaptive Binary AC
压缩率中等高(多 10-15%)
复杂度简单复杂
解码速度
适用场景实时编码离线编码

类比

原始数据: AAAAABBBCCCCCCCDE
频率统计: A=5, B=3, C=7, D=1, E=1

霍夫曼编码:
  C → 0       (最常见,1 位)
  A → 10      (较常见,2 位)
  B → 110     (3 位)
  D → 1110    (4 位)
  E → 1111    (4 位)

压缩后: 0010101010...
vs 原始: AAAAA... (每个字符 8 位)
节省: 约 60%

🎨 完整的编码流程

让我们把三大武器串起来:

sequenceDiagram
    participant Input as 原始帧<br/>2MB
    participant Predict as 预测模块
    participant Transform as 变换模块
    participant Quantize as 量化模块
    participant Entropy as 熵编码
    participant Output as 压缩数据<br/>5KB
    
    Input->>Predict: 输入宏块
    Predict->>Predict: 帧内/帧间预测
    Predict->>Transform: 残差数据
    Transform->>Transform: DCT 变换
    Transform->>Quantize: 变换系数
    Quantize->>Quantize: 量化(丢弃高频)
    Quantize->>Entropy: 量化系数
    Entropy->>Entropy: CABAC 编码
    Entropy->>Output: 比特流

详细步骤

  1. 预测(Prediction)

    • 选择帧内或帧间预测模式
    • 计算残差:实际值 - 预测值
  2. 变换(Transform)

    • DCT(离散余弦变换)
    • 把空间域转为频域
  3. 量化(Quantization)

    • 丢弃人眼不敏感的高频信息
    • 这一步是有损压缩的关键
  4. 熵编码(Entropy Coding)

    • CABAC 或 CAVLC
    • 无损压缩数据表示
  5. 打包(Bitstream)

    • 加上 NAL 单元头
    • 写入 SPS/PPS 参数集

💾 码率(Bitrate)与画质的平衡

码率:单位时间的数据量(单位:Kbps 或 Mbps)。

常见码率参考

分辨率帧率推荐码率(H.264)文件大小(1小时)
480p30fps1-2 Mbps450-900 MB
720p30fps2-4 Mbps900-1.8 GB
1080p30fps4-8 Mbps1.8-3.6 GB
1080p60fps8-12 Mbps3.6-5.4 GB
4K30fps15-25 Mbps6.8-11.3 GB
4K60fps25-40 Mbps11.3-18 GB

三种编码模式

  1. CBR(Constant Bitrate) - 固定码率 优点: 文件大小可预测 缺点: 静态场景浪费码率,动作场景画质差 适用: 直播流

  2. VBR(Variable Bitrate) - 可变码率 优点: 画质更稳定 缺点: 文件大小不可预测 适用: 视频点播

  3. CRF(Constant Rate Factor) - 恒定质量 优点: 保证视觉质量 参数: 0-51(18-28 最常用) 适用: 离线编码


🛠️ 实战:FFmpeg 编码命令

实验 1:对比不同码率的效果

# 原始视频信息
ffprobe input.mp4

# 低码率编码(1 Mbps)
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -c:a aac output_1M.mp4

# 中等码率编码(4 Mbps)
ffmpeg -i input.mp4 -c:v libx264 -b:v 4M -c:a aac output_4M.mp4

# 高码率编码(10 Mbps)
ffmpeg -i input.mp4 -c:v libx264 -b:v 10M -c:a aac output_10M.mp4

# 查看文件大小
ls -lh output_*.mp4

预期结果

output_1M.mp4  →  60 MB  (画质一般,压缩块明显)
output_4M.mp4  →  240 MB (画质良好,推荐)
output_10M.mp4 →  600 MB (画质优秀,但体积大)

实验 2:使用 CRF 模式(推荐)

# CRF 模式(自动调节码率,保证质量)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset medium output_crf23.mp4

# 不同 CRF 值对比
ffmpeg -i input.mp4 -c:v libx264 -crf 18 -preset slow output_crf18.mp4   # 高质量
ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset fast output_crf28.mp4   # 低质量

CRF 值建议

0-17:  视觉无损(文件极大)
18-23: 高质量(推荐用于存档)
23-28: 中等质量(推荐用于网络分发)
28-51: 低质量(不推荐)

实验 3:调整 GOP 大小

# 默认 GOP(通常 250 帧)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 output_default.mp4

# 小 GOP(30 帧 = 1 秒)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -g 30 output_gop30.mp4

# 大 GOP(300 帧 = 10 秒)
ffmpeg -i input.mp4 -c:v libx264 -crf 23 -g 300 output_gop300.mp4

# 测试 Seek 速度
ffmpeg -ss 00:01:30 -i output_gop30.mp4 -frames:v 1 frame_gop30.jpg
ffmpeg -ss 00:01:30 -i output_gop300.mp4 -frames:v 1 frame_gop300.jpg

观察

  • gop30.mp4 文件更大,但 Seek 更快
  • gop300.mp4 文件更小,但 Seek 需要解码更多帧

实验 4:查看帧类型分布

# 分析视频的帧类型
ffprobe -select_streams v -show_frames -show_entries frame=pict_type \
        -of csv output.mp4 | head -30

# 统计 I/P/B 帧数量
ffprobe -select_streams v -show_frames -show_entries frame=pict_type \
        -of csv output.mp4 | grep -v "frame" | sort | uniq -c

输出示例

frame,I
frame,P
frame,B
frame,B
frame,P
frame,B
frame,B
...

统计结果:
  45 I  (关键帧)
  680 P (预测帧)
  1275 B(双向帧)

📈 H.264 vs H.265 vs AV1

新一代编码器的对比:

特性H.264 (AVC)H.265 (HEVC)AV1
发布年份200320132018
压缩效率基准+50%+30%(相比H.265)
编码速度中等
解码速度中等
硬件支持广泛较广泛新设备
专利费用已过期高昂免费
适用场景通用4K/8KYouTube/Netflix

同等画质下的码率对比(1080p):

H.264: 8 Mbps
H.265: 4 Mbps  (节省 50%)
AV1:   3 Mbps  (节省 62.5%)

🧠 深入理解:为什么 B 帧压缩率最高?

示例场景:一个球从左飞到右

10 帧(I 帧): 球在左边
第 11 帧(B 帧): 球在中间
第 12 帧(P 帧): 球在右边

B 帧的选择:
  方案1: 参考第 10 帧(前向)→ 运动矢量 (+50, 0)
  方案2: 参考第 12 帧(后向)→ 运动矢量 (-50, 0)
  方案3: 同时参考两帧(双向)→ 平均预测,残差最小 ✅

编码器选择方案3,压缩率最高!

🎓 思考题

Q1:为什么直播流通常不使用 B 帧?

点击查看答案

因为 B 帧需要未来的帧作为参考,这会引入额外的编码延迟

不使用 B 帧:
  编码延迟: 1 帧(33ms)
  适合: 实时直播

使用 B 帧:
  编码延迟: 3-5 帧(100-167ms)
  适合: 点播视频

直播追求低延迟,所以通常只用 I 帧和 P 帧(GOP 结构:I P P P I P P P ...)。


Q2:如果视频是纯黑画面(全是 RGB(0,0,0)),理论上能压缩到多小?

点击查看答案

接近 0 字节!因为:

  1. 帧内预测:整个画面都是黑色,预测完美,残差为 0
  2. 帧间预测:每一帧都和前一帧一样,运动矢量为 0
  3. 熵编码:大量重复的 0 值,压缩率极高

实际测试:

# 生成 10 秒纯黑视频
ffmpeg -f lavfi -i color=c=black:s=1920x1080:d=10 -c:v libx264 black.mp4

# 文件大小约 10KB(几乎全是容器开销和元数据)
ls -lh black.mp4
# 10K black.mp4

对比:未压缩视频应该是 3.7 GB


Q3:为什么游戏录屏的视频文件比电影大得多?

点击查看答案

因为游戏画面变化剧烈时间冗余少

电影场景:
  - 大量静态镜头(对话、风景)
  - 镜头切换相对缓慢
  - P/B 帧压缩效率高

游戏录屏:
  - 快速移动、旋转(FPS 游戏)
  - 大量粒子特效(爆炸、烟雾)
  - 屏幕每一帧都有变化
  - P/B 帧压缩效率低,更多依赖 I 帧

实测对比(1080p 30fps):

电影: 4 Mbps → 1.8 GB/小时
游戏: 15 Mbps → 6.8 GB/小时(高 3.7 倍)

解决方案

  • 使用 NVENC(NVIDIA GPU 编码器)
  • 降低分辨率或帧率
  • 使用 H.265 或 AV1 编码

📚 下一篇预告

下一篇《音频编码原理:人耳听不见的频率都被删掉了》,我们将探索:

  • 音频采样和量化
  • 心理声学模型(为什么 MP3 只有原始大小的 1/10)
  • AAC 和 MP3 的区别
  • 如何用 FFmpeg 分析音频频谱

敬请期待!🎵


🔗 相关资源