返回博客
2026年8月13日Sergei Solod13 分钟阅读

我如何用 H.264 把实际运行中的一个视频从约 280 MB 压到约 50 MB

实际运行环境中的一个视频,在我围绕 CRF 28、x264 veryslow、720p 级分辨率上限、真正有用的帧率以及适度的解码要求重新设计 H.264 配置后,从约 280 MB 降到了约 50 MB。更早的阶段里,同一个例子已经从约 350 MB 降到 238 MB;对旧媒体库的检查还表明,每秒数兆比特的 H.264 文件并不少见。

H.264FFmpegx264视频压缩网页性能媒体优化

真正让这次优化变得具体的数字很简单:我跟踪的一个实际运行中的真实视频,在采用新的 H.264 策略后,从大约 280 MB 降到了 50 MB。也就是大约缩小 5.6 倍,节省约 230 MB,相当于原始文件大小的约 82%

这个结果并不是靠迁移到 AV1、HEVC 或 VP9 得到的。实际运行输出 仍然是 MP4 中的 H.264。变化的是 编解码器 周围的整个策略:去掉不必要的 像素,减少没有意义的 时间采样,采用明显更激进的 质量目标,在一次性的 编码 阶段投入更多 CPU,同时把 解码约束 有意限制在较保守的范围内。

这是一个非常具体的 使用场景:短篇插画和动画内容,约 80% 的 流量 来自 移动端,带宽 是持续发生的成本,而 离线编码 的时间成本相比反复发送过大的文件要便宜得多。

旧策略和新策略大致如下:

旧配置
H.264 Main @ Level 4.0
CRF 19
预设 slow
最高 1920x1080 / 1080x1920
30 fps
refs = 3
B 帧 = 3
GOP ≈ 2 秒
VBV ≈ 10M / 20M

新配置
H.264 Main @ Level 3.1
CRF 28
预设 veryslow
720p 级为上限,不放大
有用的 CFR,通常 <= 30 fps
refs = 4
B 帧 = 5
GOP ≈ 5 秒
VBV ≈ 4M / 8M
yuv420p / avc1 / faststart

实测结果:约 280 MB 降到约 50 MB

我有几组实际运行环境中的真实实测,但它们并不是同一种 实验。把这些类别分开,比单纯挑一个看起来最大的百分比更重要。

测量项修改前修改后缩减
同一个具体视频~280 MB~50 MB约缩小 5.6× / 减少约 82%
同一文件的早期阶段~350 MB~238 MB约缩小 1.47× / 减少 32%

第一行是标题最直接、最干净的证据。它就是同一条具体视频在新策略前后的结果:大约 280 MB 变成大约 50 MB。我没有在恢复出来的旧日志里找到这对结果对应的精确 码率/时长 行,所以不会编造。单凭 大小 的变化已经足够:这一个具体文件确实缩小了约 5.6 倍。

~350→238 MB 是同一个文件在更早阶段的一次优化。当时的 输出 大约是 1264×720、30 fps、约 500 秒、无 音频、约 3.8 Mbps。数学上也与观察到的文件大小吻合:3.8 Mbps 持续约 500 秒,大约就是 238 MB。相比 ~350 MB 已经节省了 32%,但对我的 带宽 目标来说依旧太大。

旧 媒体库 的 检查 也说明,过大的 H.264 并不是某一个奇怪的 异常值。一次 检查 中共有 238 个实际运行视频,总计 6.37 GB,其中 117 条 H.264、121 条 AV1。101 个文件至少 20 MB,34 个至少 50 MB。一些较大的 H.264 如下:

大小时长平均码率
121.0 MB4:253.83 Mbps
101.2 MB5:192.659 Mbps
92.78 MB4:442.735 Mbps
90.10 MB3:553.206 Mbps
89.74 MB4:412.676 Mbps

这些文件对应的是不同内容,因此这张表只能提供背景,不能算严格的前后对照测试。但它至少说明旧方案中 3.8 Mbps 并不是偶然的异常值:旧媒体库里确实有多个较大的 H.264 文件处在约 2.6–3.8 Mbps 的范围。

最重要的优化并不是某个 FFmpeg 参数

最大的变化其实是我看待成本的方式。

编码只发生一次。文件每被请求一次,就要传输一次。

对于实时视频,为了省一点码率而多花很多 CPU 可能并不划算。我的文件是在离线环境中编码,然后被反复提供给用户。在这种模式下,如果更快的编码会让今后的每次请求都多传一些数据,那么省下十分钟编码时间在经济上几乎没有意义。

所以 -preset veryslow 对我很合理。只要 x264 能用额外 CPU 找到更高效的表示方式,我愿意一次性付出这笔成本。浏览器不会重复编码器做过的搜索;它只负责解码最终的比特流。

于是规则变得很简单:把计算成本花在只做一次的步骤上,把字节省在会重复发生的步骤上

为什么我没有追逐最新编解码器,而是继续用 H.264

我并不是说 H.264 是目前压缩效率最高的编解码器。它不是。如果分发系统可以保留多个版本,并针对不同客户端选择最合适的版本,那么更新的编解码器会很有吸引力。

我的限制条件不同:一个 URL、一个文件、一个编解码器,并且要让以移动端为主的用户尽可能少遇到播放问题。

对于这个目标,MP4 中的 H.264 仍然是很稳妥的基线。Apple 目前建议 网页开发者在 Safari 中为静态视频使用 H.264 编码的 MP4 文件。Android 当前文档列出了 MP4 中的 H.264,并从 Android 6.0 起要求支持 Main 配置档 解码;它的 H.264 播放建议还列出了 1280×720、30 fps 的 HD 配置,同时注明并非所有设备都支持 HD。参见 Android 支持的媒体格式

这不代表现代设备只能处理 Main 配置档 或 级别 3.1。例如,Apple 的 HLS 指南通常更偏向 High 配置档,而不是 Main 或 Baseline 配置档。我选择 Main@3.1,是因为单个静态 MP4 文件需要一个刻意保守的解码复杂度范围,并不是因为 Apple 强制要求这样做。

我不再编码那些没有必要存在的像素

分辨率是影响最大的因素之一。我的上限大致是:横向 1280×720、纵向 720×1280,方形或混合方向素材约 960×960

更重要的规则是:绝不要为了碰到上限而把素材放大

如果源素材是 900×600,把它变成 1280×720 并不会恢复任何细节,只会制造更多需要编码器描述的采样点。1920×1080 的源素材可以缩到 720p 级别,而 900×600 的素材完全可以继续保持在大约 900×600。上限是最大值,不是目标值。

这听起来很简单,但去掉不必要的像素,往往比许多冷门的编码器微调更有价值。

这个上限并不是为了选一个好看的整数。1920×1080 一帧包含 2,073,600 个像素,而 1280×720 包含 921,600 个。也就是说,从 1080p 降到 720p,在编码器开始做压缩决策之前就已经去掉了约 55.6% 的空间采样。

我也考虑过把 540p 作为统一默认值。但 960×540 只有 518,400 个像素,比 1280×720 少 43.75%,也就是只保留 720p 的 56.25% 采样。对于插画类内容,这些采样承载的是细线、眼睛、头发、手指、脸部以及锐利轮廓。如果还需要更小的文件,我宁愿先测试稍高一点的 CRF,也不会在没有测量的情况下直接再丢掉 43.75% 的空间信息。量化强度可以在下一次编码时调整,而缩小分辨率时被删掉的细节已经无法恢复。

因此,720p 级是我谨慎采用的统一上限,并不是说 540p 不好。对某个具体文件来说,经过测量的 540p 版本完全可能更合适。我只是不在没有数据的情况下,把这种不可逆的空间裁减变成整个媒体库的规则。

我不再为源素材里并不存在的帧付带宽成本

帧率也是一个乘数。如果一段动画每秒实际上只有大约 16 个有意义的视觉状态,把它存成 30 或 60 fps 并不会自动让运动变得更好。很多时候只是增加了重复或合成出来的时间采样,而这些数据仍然需要被编码。

我的策略是保留源素材真正有用的节奏,并且通常保持在 30 fps 或以下。对于这类素材,只要确实符合源素材,12、15、16、18、20、24、25 或 30 fps 都可能是合理的选择。

生成文件时我也更喜欢干净的恒定帧率。VFR 本身没有问题;只是 CFR 能让我的流程更容易处理时间戳、帧数、时长检查、跳转以及后续验证。

比某个具体 FPS 更重要的是这个原则:不要为源素材里根本没有的时间信息付带宽成本

CRF 28 是针对我的使用场景做的选择,不是魔法数字

我不想强迫所有片段都靠近同一个目标码率。几乎静止的插画和运动复杂的场景,并不需要相同数量的比特才能看起来可接受。

因此我使用 x264 的 CRF 模式。对于这种带宽优先的插画类素材,我最终选在大约 -crf 28。FFmpeg 将 libx264 中的 CRF 记录为恒定质量的码率控制方式;参见 FFmpeg 编解码器文档

CRF 28 是刻意偏激进的选择。我不会把它直接套用到有胶片颗粒的内容、噪点很多的相机视频、屏幕上的极小文字,或者任何比带宽更重视保真度的场景。

我也没有一个通用的感知指标可以证明 CRF 28 是“透明”的。我能针对自己的素材说得更有限:文件明显小了很多,而正常播放时在我看来仍然自然。这是实际观察,不是说 CRF 28 在视觉上无损。

veryslow 增加的是编码器成本,不会自动增加解码器成本

我的预设是 -preset veryslow。更慢的预设让 x264 有更多机会搜索更高效的预测和编码决策,代价是编码端需要更多 CPU 和时间。

关键区别是:编码器付出的计算量和解码器的复杂度不是一回事

我可以让 x264 花很多时间搜索,同时单独限制最终比特流。我的保守输出约束是:

H.264 Main 配置档
Level 3.1
8 位 yuv420p
avc1
refs = 4
B 帧 = 5
B-pyramid = normal
开放 GOP = 关闭

FFmpeg 把 CRF、预设、调优、配置档 限制、参考帧和 B 帧分开控制。我也按同样的方式看待它们:让编码器尽可能认真地搜索,但让播放端保持普通和可预测。

GOP 和 VBV 是护栏,不是主要的画质控制手段

对于这些短篇渐进式播放片段,我使用大约五秒的最大 GOP:30 fps 时约 -g 150,24 fps 时 -g 120,16 fps 时 -g 80

这是针对我的使用场景做的选择,不是普遍规则。自适应流媒体有不同约束;例如 Apple 的 HLS 制作指南建议每两秒出现一个 IDR。我不会把这条 HLS 规则原样套到短篇静态渐进式 MP4 文件上。

我还大致使用下面这些值:

-maxrate:v 4M
-bufsize:v 8M

这些值只是用来限制异常码率尖峰的上限,并不意味着“所有内容都按 4 Mbps 编码”。正常的比特分配仍然由 CRF 决定,因此容易压缩的片段依旧可以非常小。

我也把 MP4 容器做得尽量普通

我会明确使用 avc1。Apple 当前的 HLS 文档建议使用 avc1 这类 样本格式,而不是 avc3。这并不是文件变小的原因,但符合生成传统 MP4 中的 H.264 文件的目标。

我也使用 -movflags +faststart。FFmpeg 的格式文档说明,faststart 会把 MP4 的 moov 索引移动到文件开头。Android 的 HTTP 流媒体要求同样指出,对于 MPEG-4,moov 必须在 ftyp 之后、mdat 之前。

ftyp
moov
mdat

Faststart 不会改善压缩率。它只是让渐进式 HTTP 播放更顺畅。

对于普通 SDR 输出,我使用 8 位 yuv420p,并标记 BT.709 和 有限视频范围。如果片段没有音频,我不会凭空制造音轨。对于这类插画内容,我还使用 -tune animation;我把它视为针对内容类型的选择,而不是通用兼容性约束的一部分。

核心 FFmpeg 配置

对于 30 fps 的插画类源素材,命令的核心部分大致如下:

ffmpeg -i input \
  -c:v libx264 \
  -preset veryslow \
  -tune animation \
  -crf 28 \
  -profile:v main \
  -level:v 3.1 \
  -pix_fmt yuv420p \
  -tag:v avc1 \
  -refs 4 \
  -bf 5 \
  -g 150 \
  -maxrate:v 4M \
  -bufsize:v 8M \
  -x264-params "open-gop=0:b-pyramid=normal:nal-hrd=none" \
  -color_range tv \
  -color_primaries bt709 \
  -color_trc bt709 \
  -colorspace bt709 \
  -an \
  -movflags +faststart \
  output.mp4

缩放和帧率阶段在这里故意没有写死。900×600 的源素材不应该仅仅因为上限是 1280×720 就被放大;天然低帧率的动画也不应该仅仅因为示例使用了 -g 150 就被强制转换成 30 fps。

这条命令只是策略的一种实现,并不等于策略本身。

为什么文件能缩小数倍

没有什么神奇参数。

文件缩小是多个决定叠加后的结果,每个决定都去掉了一类浪费:不必要的像素、不必要的帧、过于保守的质量目标、偏向编码速度而不是效率的设置、过于频繁的关键帧,以及我根本不需要的数据流。

所以,“这个文件是 H.264”其实几乎说明不了它有多大。同一个源素材的两次 H.264 编码可能相差很大,因为编解码器名称并不会告诉你分辨率、帧率、码率控制、预设、GOP 结构、配置档 或源素材如何预处理。

在我的场景里,改变这些围绕编解码器的决策,比更换编解码器本身更重要。

这个结果不能证明什么

我没有在受控实验中逐项隔离每个设置,因此不能诚实地说节省的空间分别有多少百分比来自 veryslow、CRF 28、降低分辨率或降低帧率。

我也不能说所有 CRF 28 输出在感知上都“透明”。“没有明显画质下降”只是我对这类插画素材在常见观看尺寸下的观察,不是对任意视频的科学保证。

我同样不是说一个 H.264 文件就是所有网站都该采用的架构。多版本、自适应流媒体、HDR、4K 和编解码器协商都会改变取舍。

我真正能主张的范围更窄:对于一个带宽优先、移动端用户为主的短篇插画和动画素材库,在编码时间便宜而稳定播放很重要的情况下,这个配置让我的文件缩小了数倍,同时正常播放时仍然看起来自然。

这个结果如何改变了我的优化思路

过去我主要把视频优化看成编码器设置问题。现在我更把它看成整个文件生命周期的成本问题。

编码器也许只运行一次,但这些字节可能会通过网络传输成千上万甚至数百万次。

这会改变“昂贵”这个词的含义。

我很愿意一次性多花 CPU,但我不愿意在今后的每次请求里发送因放大而产生的像素、没有增加有效运动信息的帧,或内容根本不需要的码率。

编解码器还是那个普通选择:MP4 中的 H.264。真正的优化发生在它周围。

对于这种使用场景,最重要的经验比任何单独的 FFmpeg 参数都更清楚:优化你会反复支付的成本,而不是只支付一次的成本