Timmy's Blog

使用VMAF分数优化视频压制参数

2.9千字 预计阅读 8 分钟 - 次浏览

Background

Immich推荐参数
使用Immich推荐参数压出来的视频糊成一坨被对象嫌弃了,经过不断调整参数依然无法实现速度、质量、体积的三角平衡,能给我干出来很糊还巨大无比的视频,最终下定决心使用科学的方法调参
开盲盒呢

什么是VMAF

视频压制速度、视频体积都可以被轻松量化,那么如何评估压制视频的质量呢?总不能靠我一个个看过去吧,既慢又不客观。
VMAF(Video Multimethod Assessment Fusion)是由 Netflix 提出的感知视频质量评估算法,结合了多种技术来预测人眼对压缩后视频质量的主观感受,提供 0-100 分的评分(越高越好)。

测试方法

压制参数:

  • 分辨率:最大4K / 最大1440P / 最大1080P / 最大720P / 原尺寸
  • 编码器:h264 / hevc
  • 预设(preset):[1, 3, 5, 7] (越小越慢)
  • 质量(global_quality,注意crf参数不适用于qsv):[20, 24, 28, 32] (越小越好)

对以上参数进行排列组合,使用每种参数压制样本视频,记录耗时、文件大小和VMAF分数,也就是速度、体积和质量

服务器:Intel Core i5-8500T (Coffee Lake), Intel UHD Graphics 630,支持h264和h265的编解码和vp9的解码,最高4K 60Hz,支持Intel QuickSync Video (QSV) 技术加速

测试集:在Immich library里选取了6条典型视频作为样本,覆盖1080p/4k、h264/hevc、不同码率等规格,相机制造商覆盖Oneplus、Xiaomi和Sony。这基本上能代表我库里的大部分视频。

filedurationresolutionframeratevideo codecbitratesize
VID_01ad2796-46e5-49ad-b556-09a53e866bdb.mp447.823s1920x108030.03 fpshevc10.90 Mbps65.14 MB
VID_1c6fd81a-a1b9-469b-b83f-9ca3d1380601.mp430.481s3840x216060.01 fpshevc57.29 Mbps218.30 MB
VID_43b903f6-5fe7-4747-bdd8-236e9220648f.mp450.121s3840x216029.94 fpsh26450.40 Mbps315.79 MB
VID_55ecb93e-0a66-4d97-86f3-d0b19fd9025c.mp439.779s1920x108030.04 fpshevc16.13 Mbps80.19 MB
VID_ae33a87c-ff68-4f66-a635-6330a020333f.mp456.000s3280x246460.00 fpshevc22.72 Mbps159.01 MB
VID_d8c56ed1-fb06-4f86-8393-4e1e71e0bf3c.mp463.360s1920x108050.00 fpsh26428.60 Mbps226.53 MB

考虑到服务器处理器羸弱的性能,每条视频截取前20s。有没有好心人捐助我一台工作站
以上参数组合下,共960个任务。去除比原视频更高的分辨率档位后,共608个任务。
vmaf需要两条视频的分辨率和帧率完全相同,所以对于改变分辨率的压制,重新缩放到与原视频一致的大小。
其余ffmpeg参数参考immich容器里实际用于压缩视频的命令

Text
pgrep -a ffmpeg
3967527 /usr/bin/ffmpeg -hwaccel qsv -hwaccel_output_format qsv -async_depth 4 -noautorotate -qsv_device /dev/dri/renderD128 -threads 1 -i /data/library/admin/2023/2023-09/VID_3ae20959-d982-4dec-a94f-b58122cfda4c.mp4 -y -c:v hevc_qsv -c:a aac -movflags faststart -fps_mode passthrough -map 0:0 -map_metadata -1 -map 0:1 -bf 7 -refs 5 -g 256 -tag:v hvc1 -v verbose -preset 1 -global_quality:v 28 /data/encoded-video/7b922d26-805a-4160-82d9-f30521f08eea/3a/e2/3ae20959-d982-4dec-a94f-b58122cfda4c.mp4

Python
encode_command = [
    "ffmpeg", "-hide_banner", "-y", "-v", "verbose", "-hwaccel", "qsv", "-hwaccel_output_format", "qsv", "-async_depth", "4", "-noautorotate", "-qsv_device", "/dev/dri/renderD128", "-threads", "1", "-t", f"{clip_seconds:.3f}", "-i", source_path, "-movflags", "faststart", "-fps_mode", "passthrough", "-map", "0:v:0", "-map_metadata", "-1",
]
if job.has_audio:
    encode_command.extend(["-map", "0:a:0", "-c:a", "aac"])
if scale_needed:
    encode_command.extend(["-vf", f"scale_qsv=w={job.target_width}:h={job.target_height}"])
encode_command.extend(
    [
        "-c:v", job.encoder, "-bf", "7", "-refs", "5", "-g", "256", "-preset", str(job.preset), "-global_quality:v", str(job.global_quality),
    ]
)
if job.codec == "hevc":
    encode_command.extend(["-tag:v", "hvc1"])
encode_command.append(output_path)

环境配置

vmaf

GitHub repo
仓库里提供了vmaf命令行工具、C库libvmaf、Python库,和ffmpeg滤镜。其中release里的vmaf命令行工具只提供了Win x64和Linux arm64两个架构,和我的环境不符,我也懒得重新编译了,所以在这次实验中,使用ffmpeg计算vmaf分数。
测试发现mac homebrew和debian源里的ffmpeg编译时没有启用libvmaf,所以去下个完整构建的,使用ffmpeg-master-latest-linux64-gpl.tar.xz
release里提供了非常多的变体:

选:ffmpeg-master-latest-linux64-gpl.tar.xz最省心,原因是:

  • 功能最全
  • 一般包含更多常用编码器
  • 非 shared 通常更适合直接拿来跑命令行

简单区分:

  • gpl:功能更多,但许可证更严格
  • lgpl:功能相对少一点,但许可证更宽松
  • shared:给程序链接 .so 动态库用,适合开发/集成
  • 非 shared:更像直接可用的独立二进制,适合普通用户

版本:

  • master-latest:开发版,最新代码快照
  • n7.1:7.1 正式发布系列
  • n8.1:8.1 正式发布系列

运行ffmpeg -filters | grep vmaf可以看到有libvmaf过滤器了

Text
./ffmpeg-master-latest-linux64-gpl/bin/ffmpeg -filters | grep vmaf
ffmpeg version N-124085-g162ad61486-20260423 Copyright (c) 2000-2026 the FFmpeg developers
  built with gcc 15.2.0 (crosstool-NG 1.28.0.23_185f348)

 .. libvmaf           VV->V      Calculate the VMAF between two video streams.
 .. vmafmotion        V->V       Calculate the VMAF Motion score.

测试命令

Text
./ffmpeg-master-latest-linux64-gpl/bin/ffmpeg -i compressed.mp4 -i original.mp4 -lavfi libvmaf -f null -

[Parsed_libvmaf_6 @ 0x7fe8a00111c0] VMAF score: 95.064836

另外有个vmaf-cuda的仓库,对n卡有加速。那我们i卡只能慢慢用cpu算了。(还有个思路是用服务器环境压完之后,到mac上跑测评,但估算了一下数据量太大了,有点搞,于是放弃)

ffmpeg

不理解,前述静态编译的ffmpeg与deb源的ffmpeg都不能正常使用核显进行加速,但是跑在docker里的immich和jellyfin容器都是正常的。推测可能因为它们用的是ffmpeg-jellyfin,带有特定的patch,和Intel媒体驱动配合更好。

项目包含libvmaf能编码视频
deb源ffmpeg
github下载的静态构建ffmpeg
immich和jellyfin容器里的ffmpeg-jellyfin

不管了,反正为了还原真实环境,就用immich容器里的ffmpeg吧,把测试视频复制进容器压缩后再复制出来,用宿主机的ffmpeg计算vmaf分数。

测试程序 benchmark_qsv_vmaf.py

第一轮测试从2026-04-24T02:58:25+08:00开始,到2026-04-24T09:41:39+08:00结束,共成功转码并测试384/608个任务。剩余224个是由于VID_ae33a87c-ff68-4f66-a635-6330a020333fVID_55ecb93e-0a66-4d97-86f3-d0b19fd9025c这两个样本为竖屏视频,rotate=-90,在vmaf阶段被自动旋转,导致参考视频与失真视频不一致。在vmaf命令行里添加-noautorotate参数后重新运行,全部任务测试成功,2026-04-24T10:39:29+08:00~2026-04-24T15:52:48+08:00。
所有608个任务总用时10:39:29(38,369s),平均64.2s处理一个任务,其中压制占13.1s、VMAF测试51.1s。

Results

原始数据 results.csv

使用Excel、pandas+matplotlib处理数据

Findings

fig.1 质量-比特率 权衡
fig.1 质量-比特率 权衡
  • GQ24->GQ20,码率显著增加,而VMAF只有小幅提升
    • GQ24是甜点位
  • max1080p 在码率上的收益明显强于 max1440psource。相同VMAF分数时,max1080p的码率更低
  • max720p是一个明确的低码率区间,适合体积敏感场景,但质量下降也更明显

结论:GQ24 是默认甜点位,max1080p 是更实用的默认分辨率上限

fig.2 速度-质量 权衡
fig.2 速度-质量 权衡

realtime factor = 处理消耗时间/处理的视频时间,在此处取倒数,越高越快

  • h264_qsv 快于 hevc_qsv
  • 两种格式在中间档位上的质量差距并不大,但速度差距非常大

结论:h264_qsv 更快,实际运营价值更高

fig.3 预设影响
fig.3 预设影响
  • h264_qsvpreset=1->preset=7VMAF下降了1.06%(可以忽略不计),但速度提升了38.3%
  • hevc_qsv,预设对 VMAF 几乎没有影响,但对速度提升非常显著(378%)
  • preset=3preset=5 的质量和速度相等

结论:preset 更像“速度旋钮”而不是“质量旋钮”。preset=5没有意义。

fig.5 vmaf热力图
fig.5 vmaf热力图
  • global_quality20->32VMAF 单调下降
  • sourcemax1440pmax1080p 在质量上相对接近,max720p 是明显更低的一档
    • GQ24 附近,max1440pmax1080p 之间的质量差距极小
  • hevc_qsv 在大多数点位上略高于 h264_qsv,领先幅度大约在 0.32.5 VMAF分之间
fig.6 码率热力图
fig.6 码率热力图

这张图是图5 的码率对应

  • 分辨率上限对码率的影响明显大于编码的影响
  • max1080p 相比 sourcemax1440p 能带来更显著的码率下降
  • 只有 max720p 能稳定进入更低的码率区间
  • 在本次实验中,hevc_qsv 并没有表现出更优秀的体积,很多点位上反而比 h264_qsv 略大
fig.7 编码差值热力图
fig.7 编码差值热力图
  • preset=3 下,hevc_qsv 相对 h264_qsv
    • mean VMAF 提升范围约为 +0.29+2.48
    • harmonic VMAF 的提升范围约为 +0.27+2.15
  • 码率比值范围约为 0.94x1.22x,在大多数情况下hevc_qsv反而比h264_qsv更大
  • 速度比值范围约为 0.36x0.51x,说明hevc_qsv的编码速度只有h264_qsv的一半甚至更低

结论:HEVC 的优势主要是“略高一些的感知质量”而不是“更小的文件”。这部分质量收益是用显著的吞吐损失换来的。

fig.8 样本vmaf分数分布
fig.8 样本vmaf分数分布
  • d8c56ed1ae33a87c1c6fd81a01ad2796 的分布较健康,整体质量表现较稳定
  • 55ecb93e43b903f6 在大多数参数组合下都很难压,不是单纯由个别极端参数造成的问题
  • 调和平均分布面板比普通平均面板的差异更大,再次说明局部最差片段的敏感性高度依赖素材本身
fig.9 质量阶梯收益
fig.9 质量阶梯收益
  • h264_qsvGQ20->GQ24 平均节省 42.82% 码率,只损失 2.35 VMAF
  • hevc_qsvGQ20->GQ24 平均节省 39.52% 码率,只损失 1.80 VMAF
  • h264_qsvGQ24->GQ28 虽然还能节省 46.42% 码率,但会损失 4.68 VMAF
  • hevc_qsvGQ24->GQ28 节省 43.79% 码率,同时损失 3.50 VMAF。
  • GQ28->GQ32 基本不适合作为默认配置的进一步下探,尤其在 HEVC 上,平均VMAF损失已经达到5.88

结论:GQ24 是最干净的拐点。它享受大部分码率收益,同时没有进入后面更陡峭的质量损失区间

fig.12 帕累托(Pareto)前沿
fig.12 帕累托(Pareto)前沿

在多目标优化中,帕累托前沿是所有帕累托高效解的集合。这张图展示在“码率、画质、速度”三维意义下的非支配配置

  • 一共有48个 Pareto 有效聚合配置,其中 H.264 为26个,HEVC 为22个
  • 前沿最密集的区域在 max720p,因为这里码率非常接近,速度差异会变得更重要
  • 第二个重要区域在 max1080p/sourceGQ24GQ20 一带,这里质量明显更高,但码率上升也更快
  • 这张图明确说明不存在单一的最优参数,只能在不同目标之间做取舍

结论:

  • 如果把码率放在第一位,前沿会从 max720p 开始
  • 如果追求综合平衡,前沿会很快转向 max1080p GQ24
  • 如果追求更高画质,前沿终点则落在 source GQ20 一带

Quantified Score

质量分:将vmaf分数区间映射到0-100
速度分:将实时因素区间映射到0-100
体积分:将压缩后大小区间映射到0-100

我更在意质量和体积,所以我的打分权重如下:

项目权重
质量分-mean VMAF40%x65%
质量分-harmonic VMAF40%x35%
体积分40%
速度分20%
总分100%

同时为了避免低质量高速度的样本干扰结果,限制质量分>=50

分数排行
分数排行

为了适当提高质量,我选择第13行的配置,即h264_qsvmax720pGQ24preset=3的组合,而不是总分最高的那个。

评论

评论加载中…