视频压到 25 MB 以内,码率要算、音轨要扣、余量要留

把一段视频压到某个体积以内是个常见的小任务。毕设演示视频要塞进有 25 MB 上限的邮件附件,或者作品集的上传入口写着单个文件不超过 25 MB。这些都是这种场景。我是边做边记笔记的人。这次把「按目标大小压视频」拆成了十几步,每一步都拿真实文件量一次字节。哪里超了、超在哪里都留了数。这篇没有一招通吃的结论,更像一份能跟着做的清单。先把码率公式算出来,再看它为什么超,然后一点点收回误差。中间有几处我没搞懂的地方,会直接写明。

摘要

把 60 秒的 1080p 动作片压到 25 MB 以内,直接用「目标体积×8÷时长」算出 3333 kbps 交给 x264。成品是 25,785,503 字节,超了 3.14%。多出来的 785,503 字节里,音轨占 971,835 字节、容器占 66,834 字节,视频本身反而少用了 253,166 字节。扣掉 128 kbps 的音轨以后单遍编码低 0.66%,两遍编码却超了 0.28%。两遍并没有更准,要主动留余量。给目标留 2% 之后,两遍落在 24,577,768 字节。换成一段合成录屏,同样的做法在 10 MB 档上两遍超了 12.0%。余量不能照搬。

文中也写了图映 ImgIng 这类现成工具的做法。它没有按目标大小压的功能。25 MB 要靠「高清均衡加最长边 1280」凑出 25,938,497 字节,按 1,000,000 字节算会略超。

版本声明

本文所有数字来自 2026-10-23 在一台 Apple M4 的 Mac 上的实测,每项只跑了一次。环境是 FFmpeg 7.1(含 libx264)和 HandBrake 1.11.2,网页工具用的是 Chromium 149。素材只有两段,下文称动作片 A 和合成录屏 B。网页工具的页面行为以 2026-10-23 当天为准。剪映、CapCut、Clideo 我只读了官网说明,查阅日期同为 2026-10-23。我没有实际用它们压过视频。这几个工具的免费限制随时会改,我只记下 2026-10-23 当天官网上看得到的写法,用之前请自己再看一眼。

适用边界

这篇的结论只在下面这个范围里成立:x264 的 medium 预设、AAC 128 kbps 音轨、60 秒的片子、两段指定素材。换成 H.265、更长的视频或者真实的手机录屏,误差会是多少我没测。不要直接套用文中的百分比。合成录屏 B 比真实录屏更静也更干净,它的结论只代表这一段合成素材。

文章目录

一、视频要压进 25 MB 为什么先算码率

  • 1.1 体积等于码率乘时长
  • 1.2 先问清 MB 指哪一种

二、动手前要准备哪些东西

  • 2.1 两段素材和一套固定参数
  • 2.2 每一步都要量实际字节

三、码率公式怎么变成一条命令

  • 3.1 先用脚本算出两个码率
  • 3.2 直接除的结果超了 3.14%

四、多出来的 785,503 字节去了哪里

  • 4.1 音轨占了约 97 万字节
  • 4.2 容器占了约 6.7 万字节
  • 4.3 视频自己反而少用了

五、扣掉音轨后怎么再核对一遍

  • 5.1 扣掉音轨后单遍低了 0.66%
  • 5.2 10 MB 档扣音轨后也没超

六、两遍编码是不是更准

  • 6.1 两遍在 25 MB 上超了目标
  • 6.2 CRF 二分没超但慢得多

七、余量留多少才够用

  • 7.1 留余量后两遍低了 1.69%
  • 7.2 余量只在这段素材上验证过

八、换一段素材还靠得住吗

  • 8.1 合成录屏上两遍超了 12.0%
  • 8.2 25 MB 档录屏根本用不满

九、整套做法收成清单要几步

  • 9.1 清单一共六步
  • 9.2 预算紧的时候码率花在哪

十、现成工具里按大小压是什么样

  • 10.1 图映没有按目标大小压
  • 10.2 8mb.video 和桌面软件怎么做
  • 10.3 只看了官网说明的工具

十一、适用边界与风险提示

十二、测量方法自身的坑

十三、常见问题

  • 目标写 25 MB 要不要少算一点
  • 视频压不动了该先降什么
  • 两遍编码到底值不值得跑
  • 网页工具压出来的体积为什么对不上
  • 音轨码率能不能再降一点

参考资料

一、视频要压进 25 MB 为什么先算码率

1.1 体积等于码率乘时长

视频文件的大小可以粗略看成各条轨道的码率乘上时长。一条 60 秒的片子要算视频轨每秒花多少比特和音轨每秒花多少比特。两者相加再除以 8 就是字节数的主体。剩下的是封装用的索引和文件头。整篇文章都在用这层关系。没有它后面的数就没有来处。

有一点要先认清:命令里写的 -b:v 是平均码率。编码器做的是「整段平均下来大约这么多」,并不保证每一秒都不超。想把峰值也压住的话 x264 另有 maxrate 和 bufsize 这类参数,我这次没有试过。下文所有结果都只用平均码率这一个旋钮。

「压到指定大小」换个说法,就是把目标体积倒推成一个码率交给编码器让它尽量别超。能直接选目标大小的在线工具,背后怎么算的我不清楚。自己动手要做的就是这个倒推的步骤,加上倒推之后的核对。

只靠调画质参数碰运气是另一条路。CRF 这类按质量编码的方式事先不知道体积,压完才看结果。第六节我会拿它当对照。它能压得很贴近代价是每试一次都要把整段视频重新编码一遍。

1.2 先问清 MB 指哪一种

动手之前先把单位说清楚,不然下面所有的偏差百分比都没意义。本文的 MB 一律是 1,000,000 字节。目标 25 MB 就是 25,000,000 字节,目标 10 MB 就是 10,000,000 字节。

另一种算法是 1 MB 等于 1,048,576 字节,严格的名字叫 MiB。同样是 25,000,000 字节,换成这种算法只有 23.84 MiB。反过来说入口写的「25 MB」如果其实是 25 MiB,上限就是 26,214,400 字节。这比我这里宽了 1,214,400 字节。第十节那个网页工具界面里显示的 MB 就是 1,048,576 字节这一种。这件事会影响后面对成品的判断。

对方的上限到底按哪种算我没办法替你查。要压到贴着上限,先确认。拿不准就按小的那个算。按 25,000,000 字节算更保守,压出来的成品在两种算法下都不会超。

二、动手前要准备哪些东西

2.1 两段素材和一套固定参数

素材 A 是维基共享资源上 Isak Zvegelj 拍的街头吉他表演,原题是 Ljubljana Street Country Guitar Performance,授权 CC BY 3.0 需要署名。我截了 60 秒。画面是 1920×1080、30 fps,原始文件 122,317,449 字节。H.264 视频码率约 16.1 Mbps,音轨 AAC 约 195 kbps。这段是按手机级码率重编码出来的,不是真正的手机原片。目标 25 MB 对它来说是压到原来的约五分之一。

拿到素材先看一眼流信息很有用:一条视频轨、一条音轨、时长刚好 60 秒,这样才敢在后面把「容器」当成唯一的剩余项。片子里如果还夹着字幕轨或章节信息,它们也会占字节而且拆账时要多记一项。

素材 B 是我合成的静止录屏。它同样是 60 秒、1920×1080、30 fps,大小 45,881,903 字节。它用代码逐帧画出讲义页面、鼠标和滚动。比真实录屏更静也更干净,不能代表你手上的录屏。它在第八节才登场。

选 60 秒还有个顺手的好处:换算很干净。25,000,000 字节乘 8 再除以 60 秒,正好是 3,333,333 比特每秒。128 kbps 的音轨放 60 秒,正好是 960,000 字节。这些整数让我在后面看到不对劲的数时,能直接心算出差在哪。

编码参数固定成一套:x264 的 medium 预设和 AAC 128 kbps 音频(B 是单声道)。固定参数的好处是后面看到体积变化时可以确定是码率估算的问题,不是我顺手改了别的东西。

另外我用的是图映 ImgIng(imging.cn/)的视频压缩页,在电脑... Chromium 149 里跑,HandBrake 1.11.2 和 8mb.video 也拿来做了对照,机器还是那台 M4 Mac。这些对照放到第十节再讲。

2.2 每一步都要量实际字节

这篇的核心习惯只有一条:每压出一个成品就立刻量它的实际字节数,和目标相减再算百分比。别看文件管理器里的「约 24 MB」,也别看编码器打印的预估。那两个数都经过了四舍五入或口径转换。先把几个会反复出现的名词摆在一起,后面看到就不用回头找。

名词 在这篇里的意思
码率 每秒钟用多少比特描述画面或声音,单位 kbps 是每秒千比特
平均码率 编码器整段视频平均下来大约花的码率,不是每一秒都不超
单遍编码 编码器只扫一遍视频,边走边调整码率
两遍编码 第一遍只统计不输出,第二遍按统计结果真正编码
CRF 按画质目标压的模式,体积事先不知道
容器 MP4 文件里装视频和音频样本的外壳,含索引与文件头
moov MP4 里的索引盒子,放时间表与样本位置
偏差 成品实际字节减去目标字节,再除以目标字节
MiB 1,048,576 字节为一个单位,常被写成 MB

我写了个很小的核对脚本来做这件事,第三节会贴出来。它只做一件事:取文件字节数、换成 MiB 方便对照,再算出相对目标的偏差。每一节的结论都出自它的输出。没有哪个数是我估出来的。不想写脚本的话在终端里用 ls -l 或 stat 看字节数也行。关键是看到整数字节不要看带小数的 MB。后面要追的是几万字节级别的偏差,近似值根本看不出来。

三、码率公式怎么变成一条命令

3.1 先用脚本算出两个码率

第一步不碰编码器,只算数:公式很简单:目标字节数乘 8 除以时长秒数再除以 1000 得到 kbps。时长我不想手填让 FFmpeg 读出来更稳。同一个脚本里把两种码率都算出来。一种是直接除,另一种是再减去音轨的 128 kbps。

python 复制代码
import re, subprocess, sys

target_mb = float(sys.argv[1])
limit = int(target_mb * 1_000_000)
info = subprocess.run(["ffmpeg", "-hide_banner", "-i", "clip.mp4"],
                      capture_output=True, text=True).stderr
h, m, s = re.search(r"Duration: (\d+):(\d+):([\d.]+)", info).groups()
secs = int(h) * 3600 + int(m) * 60 + float(s)

naive = limit * 8 / secs / 1000
audio_kbps = 128
video = naive - audio_kbps
print(f"时长 {secs:.2f} 秒,上限 {limit:,} 字节")
print(f"直接除:{naive:.0f} kbps")
print(f"扣掉音轨:{video:.0f} kbps")

运行 python3 budget.py 25 和 python3 budget.py 10,输出是这样的。

复制代码
时长 60.00 秒,上限 25,000,000 字节
直接除:3333 kbps
扣掉音轨:3205 kbps
时长 60.00 秒,上限 10,000,000 字节
直接除:1333 kbps
扣掉音轨:1205 kbps

代码说明:脚本里的 ffmpeg -i 只读文件信息,没有输出文件因此会返回报错码。我只取它打印到标准错误里的 Duration 一行。clip.mp4 就是素材 A。25 MB 档直接除得 3333 kbps,扣音轨后是 3205 kbps。差的 128 就是音轨码率。10 MB 档同理:1333 对 1205。

3.2 直接除的结果超了 3.14%

接下来先照最朴素的做法,把 3333 kbps 交给 x264 做单遍编码然后用核对脚本看结果。核对脚本放在同一段里后面几节都会复用。我顺手也把扣音轨后的 3205 kbps 压了一遍,放在一起对照。

python 复制代码
import os, sys

limit = int(float(sys.argv[1]) * 1_000_000)
for name in sys.argv[2:]:
    size = os.path.getsize(name)
    gap = size - limit
    print(f"{name}: {size:,} 字节 = {size / 1048576:.2f} MiB,"
          f"相对 {limit:,} 偏差 {gap / limit * 100:+.2f}%({gap:+,} 字节)")
less 复制代码
ffmpeg -y -hide_banner -loglevel error -i clip.mp4 -c:a aac -b:a 128k -c:v libx264 -b:v 3333k -preset medium -movflags +faststart naive.mp4
ffmpeg -y -hide_banner -loglevel error -i clip.mp4 -c:a aac -b:a 128k -c:v libx264 -b:v 3205k -preset medium -movflags +faststart deduct.mp4
python3 verify.py 25 naive.mp4 deduct.mp4
makefile 复制代码
naive.mp4: 25,785,503 字节 = 24.59 MiB,相对 25,000,000 偏差 +3.14%(+785,503 字节)
deduct.mp4: 24,835,380 字节 = 23.68 MiB,相对 25,000,000 偏差 -0.66%(-164,620 字节)

代码说明:verify.py 的第一个参数是目标 MB 数,后面可以跟任意多个文件。-b:v 给视频轨设平均码率。-movflags +faststart 把索引放到文件开头方便网页边下边播,不改变内容本身。-y -hide_banner -loglevel error 只是覆盖旧文件并去掉进度刷屏。

直接除的那一版超出 785,503 字节,百分比是 +3.14%。这个数放在有硬上限的入口里就是上传失败。扣掉音轨的那一版低了 164,620 字节,看起来已经安全。可我对这两个数的来龙去脉还没搞懂,所以接着拆。

顺带把命令里每个参数的意思记一下,免得过几个月自己也看不懂。-c:v libx264 指定用 x264 编码视频。-preset medium 是速度和压缩效率的折中档。越慢的预设同样码率下画质越好,耗时也越长。我全程没动它。-c:a aac -b:a 128k 把音轨重新编码成 128 kbps 的 AAC。四个参数里只有 -b:v 在变。其余都固定两条命令之间的差别才能完全归到码率估算上。

四、多出来的 785,503 字节去了哪里

4.1 音轨占了约 97 万字节

第一个怀疑对象是音轨:AAC 128 kbps 的音轨放 60 秒,纯数学上是 128,000×60÷8 等于 960,000 字节。我把成品里的音频样本字节数单独量出来,是 971,835 字节。比纯算多了 11,835 字节。这多出来的部分是什么我没有细查,只是照实记下。

放到 25 MB 的目标里,971,835 字节占 3.9%。放到 10 MB 的目标里,占 9.7%。目标越小音轨越是一块躲不开的固定开销。我一开始把音轨当成顺带的小东西,其实在 10 MB 档它是最大的一项。

那能不能把音轨码率降到 64 kbps,多省出几十万字节给画面?从算术上说 128 降到 64 能省下约 48 万字节,在 10 MB 档里就是近 5%。但我没有实际试过,也没有听过降码率后的声音。这里只当成一个没验证的想法记下来。讲解类、人声为主的视频大概值得试。音乐表演像素材 A,我会先看声音的损失能不能接受。

4.2 容器占了约 6.7 万字节

第二个怀疑对象是封装:MP4 除了视频和音频的样本,还有 moov(索引和时间表)、ftyp 和 mdat 头。我自己写了个小解析按 MP4 的盒子结构把文件拆开。容器开销等于文件总字节减去视频样本字节,再减去音频样本字节。素材 A 这一版是 66,834 字节,其中 moov 一项就有 66,786 字节。几乎是全部。

6.7 万字节放在 25 MB 里只占 0.27%,放在 10 MB 里占 0.67%。它比音轨小一个数量级但同样是固定的。不会因为你选了更低的码率就消失。

4.3 视频自己反而少用了

把三项加起来就能对账:对 25 MB 目标的单遍直除版本,总超出 785,503 字节。音轨贡献 +971,835、容器贡献 +66,834、视频自己是 −253,166。三项相加正好等于总超出。也就是说视频轨比我给它的预算还少用了 253,166 字节,约占目标的 1.0%。

样本与目标 总超出(字节) 视频码率控制误差 音轨 AAC 128k 容器开销
A 25 MB 单遍直除 +785,503 −253,166 +971,835 +66,834
A 10 MB 单遍直除 +917,084 −121,585 +971,835 +66,834

10 MB 档也对得上:总超出 917,084 字节,等于 −121,585 加 971,835 再加 66,834。这张表说明在这段动作片上超出的主要原因是音轨,其次是容器。编码器的码率控制不仅没添乱,还帮了点忙。

为什么视频轨会少用 1% 到 1.2%,我一开始没有头绪。后来翻了编码器收尾时打印的统计,才算看到一点线索。同一条命令跑完,FFmpeg 最后会打印一行汇总。里面列出视频和音频各占多少 KiB 以及「muxing overhead」。直除那一版的汇总里视频是 24167 KiB、音频是 949 KiB,封装开销是 0.2599%。x264 自己报的平均码率是 3299.48 kb/s。我要求的是 3333 kb/s,低了大约 1.0%。这和我自己拆出来的 −253,166 字节是同一个方向、同一个量级,互相印证。

这个汇总行比自己写解析省事得多。只想快速知道音轨占多少的话不用写任何代码,看清命令末尾那一行就行。换算时记得它的 KiB 是 1024 字节。至于编码器为什么一开始就把平均码率压在要求值以下,我没有查到原因。我推断是单遍编码为了不超出码率而偏保守,但没有做对照去验证。

五、扣掉音轨后怎么再核对一遍

5.1 扣掉音轨后单遍低了 0.66%

既然主因是音轨,修法就很直白:给视频轨的预算里先把音轨的 128 kbps 扣掉。25 MB 档的直除码率 3333 减 128 得到 3205 kbps,这就是第三节里 deduct.mp4 用的值。

核对结果前面已经出现过:成品 24,835,380 字节,低于目标 164,620 字节。相对偏差 −0.66%。从 +3.14% 变成 −0.66%,只改了一个减法。我觉得这是整篇性价比最高的一步。它不用多编一遍也不用换工具,只是承认文件里还有一条音轨。

有一点要提醒自己:这里的 128 是我自己指定的音频码率,我知道音轨是 128 kbps 才能准确地扣。如果原片音轨是别的码率而你又直接拷贝过去,就要扣原来的那个码率。动作片 A 的原音轨约 195 kbps。我压的时候统一重编成了 128 kbps,才有了这个整齐的数。

还有一个细节值得算一下。容器的 66,834 字节摊到 60 秒里只相当于约 8.9 kbps,我没有单独去扣它,因为这个量级已经被后面留余量的做法吸收了。音轨的 971,835 字节摊到 60 秒是约 129.6 kbps,比我指定的 128 多了一点点。两个数都是用字节数乘 8 再除以 60 秒和 1000 算出来的。你可以拿自己的成品对着算一遍,看看音轨实际占的码率和指定值差多少。

5.2 10 MB 档扣音轨后也没超

同样的做法放到 10 MB 档。直除的 1333 kbps 单遍得到 10,917,084 字节,超 9.17%。扣掉音轨用 1205 kbps,得到 9,965,325 字节。低 34,675 字节,偏差 −0.35%。

对比一下就能看出差别:目标越小,直除的偏差越大。25 MB 档超 3.14%,10 MB 档超 9.17%。原因是那条固定的 97 万字节音轨在小目标里占的比例更高。扣音轨之后两个档的偏差都落到 1% 以内:一个 −0.66%,一个 −0.35%。

这里还有一个我没搞懂的地方:10 MB 档视频只剩 1205 kbps,对 1080p、30 fps 的动作画面来说其实很紧。成品依然能压出来,只是细节会糊。糊到什么程度我没有用统一的标尺量过。第九节和第十节只写了各工具在分辨率和帧率上的取舍。

到这里我手上有了一条可以复述的步骤:算直除码率、减去音轨码率、用得到的值做单遍编码,再量字节。在动作片 A 上它两次都落在目标之下。不过「落在目标之下」只是这段素材、这一次的事。下一节要看的两遍编码就没这么听话。

六、两遍编码是不是更准

6.1 两遍在 25 MB 上超了目标

很多教程会说要贴着目标大小压就用两遍编码。第一遍先扫一遍整片,记下哪里难压哪里好压。第二遍再按这份记录分配码率。听起来应该比单遍更准,我也是带着这个预期去试的。

vbscript 复制代码
ffmpeg -y -hide_banner -loglevel error -i clip.mp4 -an -c:v libx264 -b:v 3205k -preset medium -pass 1 -passlogfile p1 -f null /dev/null
ffmpeg -y -hide_banner -loglevel error -i clip.mp4 -c:a aac -b:a 128k -c:v libx264 -b:v 3205k -preset medium -pass 2 -passlogfile p1 -movflags +faststart twopass.mp4
python3 verify.py 25 twopass.mp4
makefile 复制代码
twopass.mp4: 25,069,942 字节 = 23.91 MiB,相对 25,000,000 偏差 +0.28%(+69,942 字节)

代码说明:第一遍用 -an 丢掉音频,输出写到空设备只留下 p1 开头的统计日志。第二遍读这份日志。码率同样是 3205 kbps,再加上音频和 faststart。两遍的码率参数完全一样,区别只在第二遍多了一份统计。

结果是 25,069,942 字节,超了目标 69,942 字节。偏差 +0.28%。同样扣了音轨,单遍是 −0.66%。两遍却超了。按「两遍更准」的说法,我以为它至少该离目标更近。从离零的距离看+0.28% 确实比 −0.66% 近。可对有硬上限的入口来说,会不会超才算数。在这一项上两遍反而输了。

10 MB 档上的表现更明显:扣音轨后的两遍是 10,174,131 字节,偏差 +1.74%。单遍是 −0.35%。因此我不会把「两遍更准」写进结论。在这个样本上它离目标是近是远,要看哪一档。

把两遍成品的账也拆开看。25 MB 档的视频轨是 24,031,524 字节,3205 kbps 对应的预算是 24,037,500 字节,几乎没偏。多出来的 69,942 字节,主要是约 6.7 万字节的容器,再加上音轨比我扣掉的 960,000 字节多出的 11,835 字节。也就是说两遍把视频码率控得很准,超出来自我没扣容器。10 MB 档不太一样:视频轨是 9,135,713 字节,比 1205 kbps 对应的 9,037,500 字节多了 98,213 字节,约 1.1%,再叠上容器和音轨多出来的部分,才凑成 +1.74%。这一档视频轨为什么多出 1.1%,我没有查原因。上限不能超的话,这个拆法比「哪个更准」重要得多。

6.2 CRF 二分没超但慢得多

另一条路是不指定码率,改用 CRF。先选一个质量值让编码器按质量压,体积事后再看。要压到目标以内,就对 CRF 值做二分查找。试 7 轮并且每轮整片编码一次,留下不超目标且数值最低的那个。

在 A 上 25 MB 档二分到 CRF 25.27,成品 24,785,445 字节。低 0.86%。10 MB 档二分到 CRF 33.37,成品 9,854,805 字节。低 1.45%。偏差和单遍差不多都落在目标之下一点点。

代价是时间:单遍编码大约 23 秒,两遍大约 34 秒。二分 7 轮在这台机器上要 157 到 217 秒,是单遍的 7 到 9 倍。这些耗时都是本机、单次的数,我不写成固定倍数。要在脚本里批量压很多段视频,这个时间会累积得很快。

二分的逻辑也有个值得留意的小地方。我把搜索范围设成 14 到 51,每轮取中点编码。体积不超就往更低的 CRF 走,超了就往更高的走。7 轮之后取不超目标且数值最低的那一个。这样得到的结果永远不会超。但最后落在哪里取决于 7 次折半之后区间有多窄。精度不是无限的,这也解释了它为什么总比目标低一点。

我也不会因此写「CRF 最好」。在这两个目标上它没有比单遍扣音轨的版本更贴近。它只是不会超同时每次都慢好几倍。哪种做法值得看你更怕超还是更怕慢。

七、余量留多少才够用

7.1 留余量后两遍低了 1.69%

既然两遍会略超最直接的补救就是主动给目标打个折,也就是留余量。我试了一个很具体的数:把目标体积按 98% 算,也就是 24,500,000 字节来倒推码率。算法和前面一样,只是 25,000,000 换成 24,500,000。再扣音轨得到 3139 kbps。

r 复制代码
ffmpeg -y -hide_banner -loglevel error -i clip.mp4 -an -c:v libx264 -b:v 3139k -preset medium -pass 1 -passlogfile p2 -f null /dev/null
ffmpeg -y -hide_banner -loglevel error -i clip.mp4 -c:a aac -b:a 128k -c:v libx264 -b:v 3139k -preset medium -pass 2 -passlogfile p2 -movflags +faststart margin2.mp4
ffmpeg -y -hide_banner -loglevel error -i clip.mp4 -c:a aac -b:a 128k -c:v libx264 -b:v 3139k -preset medium -movflags +faststart margin1.mp4
python3 verify.py 25 margin2.mp4 margin1.mp4
makefile 复制代码
margin2.mp4: 24,577,768 字节 = 23.44 MiB,相对 25,000,000 偏差 -1.69%(-422,232 字节)
margin1.mp4: 24,343,500 字节 = 23.22 MiB,相对 25,000,000 偏差 -2.63%(-656,500 字节)

代码说明:margin2.mp4 是两遍,margin1.mp4 是单遍。两者码率相同。两遍落在 24,577,768 字节,低了 422,232 字节。单遍是 24,343,500 字节,低了 656,500 字节。两个都稳稳地躲在 25,000,000 之下。

这一组是在前面几节之外单独补跑的。数字本身很干净。但它只回答了「留 2% 够不够」这一个问题。在素材 A、25 MB 档、本机跑一次的条件下,够了。

7.2 余量只在这段素材上验证过

留余量的代价也要看清:2% 的余量让两遍的成品低了 1.69%。这相当于白白让出 422,232 字节,约 0.4 MB。上限是死的话这点浪费完全值得。只是想让成品尽量大一点、画质尽量好一点的话,这个数可以再调。

我只验证了 2% 在 A 上 25 MB 档管用。10 MB 档上两遍超了 1.74%。2% 的余量应该也能盖住,但我没有补跑,只能写成推断。到了素材 B 上两遍超了 12.0%。2% 远远不够。余量不是个通用常数,它跟素材有关。

换个角度看这个 2%。25 MB 的 2% 是 500,000 字节,而两遍那一版的超出量只有 69,942 字节,余量大约是超出量的七倍。这个倍数是我事后算出来的,不是什么经验值,也不能保证对别的素材同样宽松。如果你只想要一个起点,可以先给目标留 2%,压完量过字节以后再决定是收紧还是放宽。

比较稳妥的做法是把「留余量」和「量实际字节」绑在一起。先按估算码率压一遍,量字节。超了就把码率往下调再压,低得太多就往上调。这是笨办法但每一步都有数。下一节讲的就是为什么不能只靠一个固定的余量比例。

八、换一段素材还靠得住吗

8.1 合成录屏上两遍超了 12.0%

素材 B 是合成的静止录屏,我拿同样的做法去压它。10 MB 档直除的单遍是 9,930,771 字节,低 0.69%。扣音轨的单遍是 9,134,627 字节,低 8.65%。扣音轨的两遍是 11,195,510 字节,超出 12.0%。CRF 二分是 9,838,907 字节,低 1.6%。

在动作片 A 上扣音轨之后,三种做法的偏差都在 1.8% 以内。到了 B 上,同样的步骤一个超 12.0%、一个低 8.7%,差了一个数量级。我另外画过逐秒码率曲线,B 的两遍在场景切换处有尖峰(这张图本文没放)。我推断两遍编码在这些地方没有分配均匀,才出现了那 12.0% 的超出。但这只是推断,没有做对照去验证。单遍扣音轨在 B 的 10 MB 档上低了 8.65%,没有超,只是浪费了一点预算。两遍则超了 12.0%。真实录屏会是什么样我没测,用在别的素材上之前,仍然要先量字节。

下面这张图把三种做法在四组条件下的偏差并排放在一起,是整篇最想让你看一眼的东西。

这张图要看什么:四组柱子里,左边两组(动作片 25 MB、10 MB)的三根柱子都贴近零线。最高的是两遍的 +0.3% 和 +1.7%。右边的静止录屏 10 MB 组里,橙色的两遍柱子伸到 +12.0%、蓝色单遍在 −8.7%。中间的录屏 25 MB 组三根柱子都拉到 −40% 以下。图下方的小字写着素材 A 来自 Isak Zvegelj(CC BY 3.0,维基共享资源)。B 是合成的课程录屏。

8.2 25 MB 档录屏根本用不满

再看 B 的 25 MB 档,三种做法的偏差是 −43.9%、−42.7%、−55.8%。这不是编码器没压准,而是这段录屏本身不需要那么多字节。单遍和两遍的成品分别是 14,020,677 和 14,326,893 字节,都远小于 25,000,000。

CRF 二分那一次我还踩了个小坑。第一轮的搜索范围沿用了 14 到 51。结果停在 4,111,034 字节,因为二分碰到了范围下限,再也不能往更高画质走。把范围放宽到 0 到 51 重跑,CRF 一路走到 0.4。成品是 11,053,804 字节,仍然只有目标的 44% 左右。

这一段最值得带走的是一个方向:预算给得再大内容本身压不出那么多字节就不会贴着上限。这时候「压到 25 MB 以内」其实已经是空话,成品大小由素材决定。要不要回头去提高画质或者保留更高的分辨率,是另一个问题。下面这张表把前面几节的结果收到一起。单遍和两遍都已扣掉音轨,CRF 不走码率。括号里是实际字节,偏差相对各自的目标。

做法 动作片 A 25 MB 动作片 A 10 MB 合成录屏 B 25 MB 合成录屏 B 10 MB
单遍 −0.66%(24,835,380) −0.35%(9,965,325) −43.9%(14,020,677) −8.65%(9,134,627)
两遍 +0.28%(25,069,942) +1.74%(10,174,131) −42.7%(14,326,893) +12.0%(11,195,510)
CRF 二分 −0.86%(24,785,445) −1.45%(9,854,805) −55.8%(11,053,804) −1.6%(9,838,907)

读这张表我只记住两件事:正的数字只出现在两遍那一行,也就是超了目标的只有两遍。录屏 25 MB 那一列整列都是大负数,是预算用不满。

九、整套做法收成清单要几步

9.1 清单一共六步

跑完这些之后我把整个过程收成了六步。第一步,读出视频时长和现有的音轨设置并确认文件里有几条轨道。第二步,把目标写成字节数。先问清对方说的 MB 是 1,000,000 字节还是 1,048,576 字节。拿不准就取小的那个。第三步,用「目标乘 8 除以时长」算出直除码率,再减去音轨码率得到视频轨的预算。

第四步,按这个预算做一遍编码。立刻量实际字节,算出和目标的偏差。这一步最容易被省略,也最该保留。前面所有的估算都只是估算。第五步,看偏差。已经落在目标之下且差距不大,就收工。超了或者低得太多,就按偏差的比例把视频码率调一下再压一遍。第六步,入口是死上限的话就在算预算的时候给目标留一点余量。我在素材 A 上用的是 2%。

第五步的调法给个算术示例,我没有再跑这一步。两遍那一版超了 69,942 字节,约 0.28%。视频码率往下调 0.3% 左右,也就是从 3205 kbps 调到约 3196 kbps。再压一遍并量字节,应当会落回目标之内。这是按比例推出来的数,实际落点要以量出来的字节为准。

这六步里真正需要手动算的只有第三步和第六步,其余都是读数和对照。我自己更看重第四步的习惯:压完先量字节再决定要不要动。哪怕前面的估算全错了,这一步也能兜住。清单里用到的几个算式,我另外收成了一张速查表,例子都取自素材 A 的 25 MB 档。

要算的东西 写法 例子
目标字节数 目标 MB 乘 1,000,000 25,000,000 字节
直除码率 目标字节数乘 8 除以时长秒数再除以 1000 3333 kbps
视频轨预算 直除码率减去音频码率 3333 减 128 得 3205 kbps
留 2% 余量的预算 目标字节数乘 0.98 重新倒推再减音频码率 3139 kbps
偏差 实际字节数减目标字节数再除以目标字节数 两遍 +0.28%
某条轨道实际占的码率 样本字节数乘 8 除以时长秒数再除以 1000 音轨约 129.6 kbps

9.2 预算紧的时候码率花在哪

清单走到第五步会遇到一个现实问题。10 MB 档视频轨的预算只剩一千出头 kbps,再往下压画质就要垮。这时候能做的不是继续压码率,而是决定这点码率花在多少像素和多少帧上。

我手上的几个对照结果正好摆出了这个取舍。10 MB 档里 HandBrake 的 1080p30 两遍是 1216 kbps,720p30 两遍是 1197 kbps。体积优先档是 1268 kbps,8mb.video 是 1113 kbps。视频码率都集中在 1100 到 1270 kbps 这一带。差别在于这一点码率摊给了多大的画面。

HandBrake 的 1080p 预设和 FFmpeg 保留了 1920×1080 和 30 fps。8mb.video 统一降到 1280×720。体积优先档降到 1080×608 和 24 fps。哪个取舍更好看要看你的片子。画面里有小字的话,比如演示视频里的代码,我猜降分辨率会先伤到可读性。运动画面则可能是降帧率让动作变得不顺。这两条都只是我的猜测,我没有用统一的标尺给这些取舍打分。只想把一件事摆清楚:预算固定的时候省下来的字节必然来自画面大小或帧数。这是一个交换,不是魔法。

十、现成工具里按大小压是什么样

10.1 图映没有按目标大小压

先说明白,图映 ImgIng 的视频压缩页没有「按目标大小压」这个功能。它给的是几档压缩方式,加上分辨率和帧率的选项。电脑浏览器里就能用,页面上写明视频在浏览器本地处理。我这轮 11 次压缩任务抓包看,没有出现上传视频的请求。这里只写我抓到的事实,不替它写成「全部不上传」。手机上不能用,这一条本轮没有重测。官网还声明了批量压缩。我没有测,不写效果。

在素材 A 上,它几个档位的落点是 85,012,782、57,160,300 和 10,487,546 字节。依次对应视觉近无损、高清均衡和体积优先。体积优先档是 1080×608、24 fps,耗时 3.8 秒。要压到 25 MB 附近我用的是高清均衡,再把最长边设成 1280。得到 1280×720、30 fps 的成品。体积是 25,938,497 字节,耗时 5.0 秒。

这个 25,938,497 字节有个微妙的地方。按它界面的 MB(1,048,576 字节)算是 24.74,没超 25。按本文的 1,000,000 字节算,它比 25,000,000 多了 938,497 字节。入口的 25 MB 到底是哪一种,决定了它能不能过。我没法替你判断。要压到上限边上的话这种凑出来的落点要自己量字节。

智能推荐这一项在动作片 A 上没有被采用。候选结果 104.6 MB,只比原片小 10.3%。没到 12% 的门槛,保留了原片。在合成录屏 B 上它被采用了。成品 9,351,418 字节,分辨率仍是 1920×1080。比原片小了 79.6%。这是单段合成录屏的结果不代表真实录屏。

10.2 8mb.video 和桌面软件怎么做

8mb.video 是我对照里唯一一个能直接填目标大小的在线站,免登录。目标选 10 MB 或 25 MB 之后,文件会分块传到它的服务器,处理完再下载。素材 A 上,10 MB 档的成品是 9,872,167 字节,25 MB 档是 24,555,009 字节,都没超过所选的目标。输出的分辨率统一是 1280×720,音轨保留了 193 kbps。它的耗时包含上传,没法和本地软件直接比,我就不列。

HandBrake 和 FFmpeg 都在本地跑。HandBrake 1.11.2 用 Fast 1080p30 预设加两遍码率。25 MB 档得到 25,019,191 字节,10 MB 档得到 10,120,027 字节。FFmpeg 两遍分别是 25,069,942 和 10,174,131 字节。这四个数都比各自的目标大一点,超出 19,191 到 174,131 字节不等。这正好呼应第六节:按码率压的话,音轨和容器要自己算进去,还要留余量。

HandBrake 的两遍码率我是用和 FFmpeg 同一个思路填的,也就是目标换成码率、扣掉音轨再填进去。它的超出比 FFmpeg 小一些,在 25 MB 档是 19,191 字节对 69,942 字节。要留意 HandBrake 用的是 Fast 预设,FFmpeg 用的是 medium,两边并不是严格同参数的对照,所以差别可能来自预设,也可能来自各自处理音频和封装的方式。我没有进一步拆开。这里只列数字,不下结论。几个对照结果放在一起是这样:字节数都是实际量出来的。最后一行的两个数分别来自体积优先档和高清均衡加最长边 1280。

工具与设置 10 MB 档字节 25 MB 档字节 输出规格 是否上传
HandBrake Fast 1080p30 两遍码率 10,120,027 25,019,191 1920×1080 / 30 fps 否
FFmpeg x264 两遍码率 10,174,131 25,069,942 1920×1080 / 30 fps 否
8mb.video 选目标大小 9,872,167 24,555,009 1280×720 / 30 fps 是
图映 体积优先 / 高清均衡加最长边 1280 10,487,546 25,938,497 1080×608 / 24 fps 与 1280×720 / 30 fps 本轮抓包无上传请求

表里最后一行在 10 MB 那一档也比 10,000,000 字节大了 487,546 字节。它这两个落点都不是「按目标压出来的」,只是离目标近的一档。

HandBrake 还带一个「Social 25 MB」预设,用的是两遍加 1280×720。成品 23,596,928 字节,低于 25,000,000。它没有超。代价是分辨率定在 720p,比我手动做的 1080p 版本少了像素。我不拿画质分数给这几个排名。它们保留的分辨率和帧率不一样:体积优先档是 24 fps,桌面软件保留了 30 fps。各自取舍不同数字放在一起排名不公平。

10.3 只看了官网说明的工具

剪映、CapCut 和 Clideo 我没有实际压过视频,只读了官网说明。我是在 2026-10-23 读的它们的官网。Clideo 的定价页写着免费版会带水印、最高 720p、上传文件不超过 500 MB。剪映和 CapCut 的官网我没有读到关于压缩的免费限制说明。是否有,我不知道。

这几个工具对我的意义是提醒了一件事。在线工具和桌面软件的限制不一样。有的限制分辨率、有的限制上传大小、有的会加水印。片子本来就是 1080p、原文件又很大的话,选工具之前先看这三项。这类限制改得快,上面几条只代表 2026-10-23 官网当天的写法。

自己核对这类限制,可以先打开官网的定价页或帮助页,找到写着时长、分辨率、文件大小和水印的那几行,再记下查阅日期。我读 Clideo 时只看了定价页,没有登录也没有上传文件,因此不知道实际压出来的成品有没有别的差别。

十一、适用边界与风险提示

这篇讲的做法有几处边界需要摆出来,免得读者照抄了出问题。

第一是素材范围:所有数字只来自两段 60 秒的素材:一段动作片,一段合成录屏。真实的手机录屏、屏幕录制、转场很多的剪辑片,码率曲线和这两段都不一样。我没有测也不知道结果。

第二是编码设置:x264 的 medium 预设和 AAC 128 kbps 的音轨是我固定下来的。换成更慢的预设、H.265 或别的音频码率,音轨和容器的占比会变。偏差也会变。H.265 我这轮没有测,不能拿来说。

第三是样本次数:每一项只跑了一次,没有重复。偏差在 0.3% 这种量级时重复跑可能差几个字节。是否稳定我没有验证。写「超了 69,942 字节」是这一次的结果,不是保证。

第四是工具与环境:我用的是 Apple M4 的 Mac、FFmpeg 7.1、HandBrake 1.11.2 和 Chromium 149。换了机器或版本耗时和落点可能不同。耗时也只是这台机器跑一次的数,不能写成倍数。

第五是上限的口径:入口的「25 MB」可能是 1,000,000 字节,也可能是 1,048,576 字节。邮件附件通常还要经过编码,实际占用比原文件大。这一点我没有测,只是提醒你把上限留得宽松些。

第六是上传链路:有的入口除了看文件大小,还会限制时长、分辨率或者格式。我这篇只处理了体积这一项。入口拒收的话先看提示的具体原因,别默认就是体积超了。

第七是转发和二次处理。成品交出去以后如果要经过聊天软件、网盘或者邮件系统再转一手,对方拿到的文件可能已经被再次处理过。这一点我没有测,只是列在这里提醒你在最终的入口上再量一次字节,别只信自己电脑上的数字。

最后是风险方面:成品一旦超了上限入口会拒收,需要重压。留余量比省那点画质划算。反过来余量留太多会浪费画质。任务对画质敏感的话应该把余量压小并且多量几次字节,而不是靠一次预估。为了方便核对,下面列出文中各类数字的出处。没有实际使用过的工具,我只写了官网说明。

数字或说法 出处
成品字节数与偏差 本机 FFmpeg 压出的文件加上核对脚本的输出
音轨、容器、视频三项拆账 自己写的 MP4 解析加上 FFmpeg 收尾汇总行互相印证
图映各档位的体积 2026-10-23 在 Chromium 149 里压出来的成品字节数
图映没有上传请求 11 次压缩任务的抓包结果
8mb.video 的体积与规格 上传后下载回来的成品
Clideo 免费限制 官网定价页 2026-10-23 查阅 未实际使用
剪映与 CapCut 官网说明 2026-10-23 查阅 未实际使用

十二、测量方法自身的坑

写到这里我发现测量本身也有几处容易看走眼的地方,值得单列。

第一处是单位:同一个 24,835,380 字节的文件,按 1,000,000 进位是 24.84 MB;按 1,048,576 进位是 23.68 MiB。两个数差了一个多 MB。各个系统的文件管理器默认用哪一种我没有逐个去核对,因此坚持用整数字节再自己换算。

第二处是编码器说的和文件实际的:x264 的 -b:v 是平均码率目标,不是体积保证。我量的是写完之后文件的字节数,不是它打印的预估。核对前要确认编码已经结束、文件已经完整落盘。否则量到的是半截文件。

第三处是容器开销的算法:我用的是文件字节减去视频样本和音频样本的字节。这个差值包含 moov、ftyp 和 mdat 头。文件里如果还有别的轨道或元数据,它们也会被算进「容器」。素材 A 里只有一条视频和一条音频,我才敢把 66,834 字节全记成容器。

第四处是拆账的口径:第四节的三项相加等于总超出,是因为我把「视频码率控制误差」定义成了总超出减去音轨和容器的余数。它是个倒推出来的差,不是单独量到的一项。我说视频轨少用 1%,实际上也是这个余数。不要把它当成编码器自己报的误差。

第五处是耗时:我记录的是命令开始到结束的墙钟时间。文中耗时只用来看量级,不拿来比大小。

第六处是画质:这轮我还算过画质分数(VMAF),是本机、单次、每 5 帧抽 1 帧算的。体积优先档输出是 24 fps,抽帧会对不上。分数偏低有一部分是这个原因。我没有拿它给工具排名,也不能写成「图映画质差」。想看画质不如把成品放大了和原片同一帧并排看,再对照分辨率和帧率的取舍。

十三、常见问题

目标写 25 MB 要不要少算一点

看你的上限怎么定义:入口写 25 MB 而你不确定是哪一种进位,就按 25,000,000 字节倒推码率。再留 2% 左右的余量。成品大约落在 24.5 MB 附近,在两种算法下都不会超。这个余量是我在素材 A 的 25 MB 档上验证的。其他素材要自己量字节再调。

视频压不动了该先降什么

我没有做系统的对比,只能说这轮看到的现象。预算很紧的时候先看分辨率和帧率。体积优先档把素材 A 降到 1080×608 和 24 fps,才压到 10.49 MB。8mb.video 统一降到 1280×720。哪个取舍更合适要看你的片子主要看什么。

两遍编码到底值不值得跑

在素材 A 上两遍耗时是单遍的 1.1 到 1.5 倍,却不见得更接近目标:25 MB 档离零近了一点,10 MB 档反而更远,两档还都略超。我不会因此说两遍没用。它的好处可能在画质分配上,这轮没有测。结论只限于「想不超体积」这一件事。两遍不是保险。要留余量,要量字节。

网页工具压出来的体积为什么对不上

最先要排除的是单位:图映界面的 MB 是 1,048,576 字节,我的 25,938,497 字节在它那里显示 24.74。这和我按 1,000,000 算的数字对不上。这并不是谁算错了。其次要看输出的分辨率和帧率有没有被工具改掉,8mb.video 就会统一输出 1280×720。最后才是码率估算本身。

音轨码率能不能再降一点

从算术上看可以:10 MB 档里 128 kbps 的音轨占 9.7%,降一半就能省出约 5% 的体积。但这一条我没有实测,也没有对比过声音的差别。不能说「降了没问题」。我的做法是先按 128 kbps 走完整个流程。量出偏差之后预算仍然紧,再回头单独试音频并且自己听一遍。

参考资料

  • FFmpeg 官方 Wiki,H.264 视频编码指南(CRF 与两遍编码的说明):trac.ffmpeg.org/wiki/Encode...
  • HandBrake 官方文档,预设与两遍编码说明(对应 HandBrake 1.11.2 版本)。
  • 素材 A:Isak Zvegelj,Ljubljana Street Country Guitar Performance,维基共享资源,CC BY 3.0。
  • 素材 B:作者合成的课程录屏,非真实录屏。
  • Clideo 定价页、剪映与 CapCut 官网(2026-10-23 查阅):只读官网说明,未实际压制。
相关推荐
北冥有鱼33 分钟前
发版后动态路由点击报 `Failed to fetch dynamically imported module`」问题上的完整排查与修复过程
前端·vue.js
用户372037492601839 分钟前
把 10MB 中文语义模型塞进浏览器:STSB 做到教师模型的 93%,但线上 top1 只有 42%
前端
光锥智能1 小时前
海康机器人举行2026移动机器人新品发布会:箱式存拣升级,「明智」系列发布
java·前端·机器人
计算机魔术师1 小时前
Anthropic 上市文件最吓人的一幕,不是亏损 807 亿
前端
alsmile1 小时前
AI友好的可视化图形引擎自我革命:Meta2d.js 2.0 历时一年全新重写,性能与多人协作全面升级
前端·开源·数据可视化
行思理1 小时前
Nginx 跨站请求(CORS)配置完全指南:从原理到生产实践
java·前端·nginx
粥里有勺糖1 小时前
视野修炼-技术周刊第137期 | 编程已经面目全非
前端·github·ai编程
IT_陈寒2 小时前
Python的列表推导式用不好真的会爆内存你知道吗
前端·人工智能·后端
moshuying2 小时前
块扩散 + GDN 改造:Clover 把「并行生成」塞进了 Agent 的长上下文里
前端·算法