同是 1080P,为什么有的视频糊?6 档码率压缩 PSNR 实测
同一张 1920×1080 的画面,压成 6 档码率,差距不用放大镜就能看出来。
1 Mbps 那一档只剩 3.7 KB/帧,PSNR 24.09 dB;32 Mbps 有 130.2 KB/帧,PSNR 33.45 dB。

两档标称分辨率完全一样,都是 1080P。
变的是每一帧能分到多少字节。
分辨率决定画布有多大,码率决定画布里能装多少细节。
为什么这件事值得看
视频平台给每个视频分配的码率不一样。
同一个 1080P 标签,可能是 1 Mbps 的会议录屏,也可能是 30 Mbps 的相机原片。
码率不够时,编码器先丢掉高频细节,再放大色块。
画面先变软,接着出现方块,最后连边缘都糊成一片。
看懂这件事,至少能回答三个日常问题。
为什么下载的“高清”电影只有 2 GB。
为什么手机拍的视频发到群里就糊。
1 小时 4K 视频到底要占多少硬盘。
准备环境:进入码道 Web
浏览器打开码道 Web 版:https://devcloud.cn-north-4.huaweicloud.com/chat?source=dmzntgwlb&sourcead=dmzntgwlb1&invitecode=INV-IXhxk5bwWx_qKSP1FcgSuiOEf6ruDXtjvLFiCH9nMs5dZGLapaSSylwt-cKRj5LoT3MlxikWxy8VhqplfvtQjefQOq31Tbp3R3wYP3K535Cn4XTaUzqqExKPYTWvvw4LijV14IS1torXdTGp7oyXb6SR5VxgYTg50jM,登录后直接在对话窗口输入需求。
码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。
我让码道做了什么
需求写死了六件事:内置 1080P 测试画面、6 档码率、真实 JPEG 字节数、逐像素 PSNR、可拖动分屏对比、8 条自检。
码道交付的是一个单文件 index.html,没有外部依赖。
页面左边是控制区,右边是结果、曲线、换算器和自检面板。
内置画面由 Canvas 画出来,有天空渐变、树叶、水波和噪点,细节足够多,压缩损失看得见。
每次点“开始压缩”,6 档全部重算,实测耗时 1.37 秒。
验证一:6 档数据都是当场算的
每一档都记录真实字节数,再反推实际码率和 bpp。
PSNR 是把压缩图解码回 1920×1080 后,与原图逐像素比较算出来的。
| 目标码率 | 实际字节/帧 | 实际码率 | JPEG q | 编码尺寸 | PSNR | 实际 bpp |
|---|---|---|---|---|---|---|
| 1 Mbps | 3.7 KB | 0.91 Mbps | 0.064 | 672×378 | 24.09 dB | 0.015 |
| 2 Mbps | 8.3 KB | 2.04 Mbps | 0.050 | 1152×648 | 24.04 dB | 0.033 |
| 4 Mbps | 15.5 KB | 3.81 Mbps | 0.054 | 1632×918 | 24.48 dB | 0.061 |
| 8 Mbps | 30.4 KB | 7.47 Mbps | 0.082 | 1920×1080 | 27.36 dB | 0.120 |
| 16 Mbps | 64.0 KB | 15.72 Mbps | 0.194 | 1920×1080 | 30.67 dB | 0.253 |
| 32 Mbps | 130.2 KB | 32.00 Mbps | 0.504 | 1920×1080 | 33.45 dB | 0.514 |
先看低三档。
1 Mbps 目标每帧只有 4.17 KB,1080P 全尺寸 JPEG 即使 q=0.05 也远不止这个数。
所以它先把编码尺寸降到 672×378,再用 q=0.064 压到 3.7 KB,最后放大回 1080P 算 PSNR。
2 Mbps 用 1152×648,4 Mbps 用 1632×918。
8 Mbps 以上才回到全尺寸 1080P。
这里有个反直觉的细节:2 Mbps 的 PSNR 比 1 Mbps 低 0.05 dB。
不是算错,是两档踩在不同的分辨率台阶上,损失的结构不一样。
自检对这条曲线给了 ±0.5 dB 容差,并把这个前提写在了结果里。

把 1 Mbps 那档放大,左边原图还能数清树叶,右边已经是一块一块的色斑。

换到 32 Mbps,分屏线两侧几乎看不出差别。
PSNR 33.45 dB 不算高,但在这个画面和这个观看尺寸下,肉眼已经够用。
再看 bpp。
32 Mbps 在 1080P、30fps 下是 0.514 bit/像素/帧。
1 Mbps 只有 0.015,差了 34 倍。
bpp 比 Mbps 更适合横向比较不同分辨率。
同样是 8 Mbps,1080P 是 0.129 bpp,4K 只有 0.032 bpp。
换算器算的是另一头。
4K、60fps、50 Mbps,一小时 20.95 GB,一部两小时电影 41.91 GB。
100 Mbps 宽带只够同时看 2 路。
这些数字按 1 GB = 1024 MB 计算,不含容器开销。

验证二:页面自检 8/8 通过
页面自带 8 条断言,点一次全部跑完。
- 相同画面 PSNR 为 ∞ 或 ≥ 60 dB
- 码率升高时 PSNR 不下降,容差 0.5 dB
- 实际字节不超过目标字节的 110%
- bpp 与 Mbps、像素数、帧率的关系一致
- 折线图数据点等于 6
- 压缩图解码后仍是 1920×1080
- 目标字节等于码率 ÷ 8 ÷ 帧率
- 实际 Mbps 等于字节 × 8 × 帧率
最后是 8/8 全过。
但第一次跑并不是。
真实踩坑:低码率根本压不到
第一版只搜 JPEG 质量 q。
结果 1 Mbps 实际字节是目标的 5.16 倍,2 Mbps 是 2.58 倍,4 Mbps 是 1.29 倍。
三档全都卡在 q=0.05,PSNR 都是 24.58 dB。
这不是实验,是同一个结果换了三个标签。
自检里的“实际字节 ≤ 目标字节的 110%”直接把它拦下来了。
修复办法是加一列分辨率台阶。
从 1.0 到 0.1 共 13 档,先找一档 q=0.05 能压进目标 110% 的尺寸。
找到后再二分搜索 q,让字节数贴近目标。
压完再放大回 1080P,和原图逐像素算 PSNR。
改完之后,6 档全部落在 110% 内,低三档也终于有了各自的损失曲线。
这不是给页面补一个假数字,而是把“码率不够时先降分辨率”的真实取舍写进了实验。
复现与失败路径
本地起一个静态服务,打开页面,点“开始压缩”即可。
python -m http.server 8000
自检 8/8,6 档总耗时 1.37 秒。
失败路径也测了。
帧率填 0 再点开始,页面给出红色提示:帧率必须大于 0,当前输入无效;已保留上一次有效结果。
6 档结果和曲线都还在,没有白屏。

使用码道的一点体会
这次最有用的一句需求,是“所有计算必须真实执行,不能写死假数据”。
码道按这句话把 JPEG 字节数、PSNR、bpp 都做成了运行时计算,自检也能查出问题。
但它一开始没想到 1080P JPEG 有码率下限。
所以自检不是装饰。
断言写对了,它能在发布前把实验设计的问题拦下来。
另外,需求里把 6 档、8 条断言、失败提示都写成可验收的句子,返工明显少。
边界:它不模拟完整视频编码
这是一次单帧帧内压缩实验,不是完整视频编码。
页面用 JPEG 模拟每一帧的帧内压缩,没有 H.264、VP9 的帧间预测、运动补偿和码率控制。
所以 PSNR 的绝对值不能直接和真实视频编码器对比。
它复现的是“字节不够时细节怎么丢”,不是“某个编码器在某个片源上的真实表现”。
测试画面也是程序生成的,换成真实拍摄素材,数字会变。
想更接近真实,可以把同一段视频用不同码率转码,再逐帧算 PSNR,那是下一步。
结论
1080P 只是分辨率,不是清晰度承诺。
同一分辨率下,码率决定每帧能分到多少字节。
bpp 决定这些字节够不够铺满像素。
下次看到“高清”两个字,先问一句:多少码率。
想自己试,可以打开码道复刻这个需求,也可以直接看公开示例:https://atomgit.com/deli007/demo_park/tree/main/codearts-video-bitrate-lab。
浙公网安备 33010602011771号