校准 Intel Quick Sync HEVC:28 与 30 之间的悬崖,和一个毫无作用的 preset
一个我回答不了的问题:到底该出货哪个数字?
我的转码队列按文件逐个决定用哪个编码器:源文件本来就高效就用 copy、GPU 走 NVENC、CPU 走 x265——现在又多了一个 Intel Quick Sync。每一档都有两个决定质量的旋钮,除了 QSV,其他每一档我都有在自己片库上量过的数字。
QSV 一个都没有。而代码拒绝猜:出货默认值是 qsv_quality = 0 / qsv_preset = "",含义是「没人标定过这一档」,引擎把它当成硬排除——于是 QSV 可以存在于代码里,却在任何机器上都不会被选中,包括真的有这块显卡的那台。
所以任务不是「把 QSV 打开」,而是「为两个旋钮产出量过的数字,让这个编码器能被诚实地选中」。
问题一:硬件不在跑这个程序的机器里
队列跑在 Windows 11 虚拟机里。那台 VM 直通了一张 RTX 3060,没有任何 Intel 核显——这正是一直没量过 QSV 的原因:最顺手的主意就是在本地跑一次、把数字读出来。
核显在宿主上(unRaid,i9-13900K),而且空闲、无人占用:
00:02.0 VGA compatible controller: Intel Raptor Lake-S GT1 [UHD Graphics 770]
driver: i915 refcount: 0
/dev/dri/card0 /dev/dri/renderD128 (存在,全局可读)
没有任何运行中的容器声明 dri 设备
virsh dumpxml Win11: hostdev = 01:00.0, 01:00.1(3060 + 它的 HDMI 音频)
两个结论都很有用。VM 里那句 Error creating a MFX session 是预期行为,不是一个要去查的 bug——那台 VM 永远看不到这块核显。而量 QSV 的唯一办法,是在宿主上、在容器里、用 Linux 侧去量。
用量尺的那个人自己的闸门来量,而不是用 ffmpeg
这是让上面这些数字真正有价值的部分。我没有自己写 ffmpeg 命令、自己算 SSIM——整场标定跑的是将来真正干活的那个引擎本体,带着它自己的校验和自己的判收闸门:
python3 library_run.py --work-dir /work --tier qsv --force-tier qsv \
--qsv-quality Q --qsv-preset P --path <真原件> --keep-local --overwrite
--keep-local 的意思是:编码、校验、过闸门,但不落盘——我收集的是判决,不是一批新文件。素材通过已有的 SMB 挂载只读进入,而且每个源档在编码前后都做了哈希核对:未变。
镜像是 linuxserver/ffmpeg(ffmpeg 9.0.2、iHD 26.3.5、oneVPL 2.17)加 python3,再加 numpy——后者不是可选项。没有它闸门会打 flat-frame scan skipped: No module named 'numpy',悄悄改变近平坦帧的判定方式,也就改变了判决。
-global_quality 在 hevc_qsv 上对应 RateControlMethod: ICQ,这就是被标定的旋钮。
质量阶梯:在瘦源上,低 ICQ 会把文件变大
第一轮,源是一支真实的 1080p25、951 kbps(bpp 0.0184——已经很瘦、榨过一遍的那种):
| ICQ | 引擎判决 | mean SSIM | 视频 = 源 |
|---|---|---|---|
| 18 | FAIL size — 176.8% | — | — |
| 20 | FAIL size — 141.8% | — | — |
| 22 | FAIL size — 107.6% | — | — |
| 24 | ok | 0.9967 | 81.1% |
| 26 | ok | 0.9959 | 61.0% |
| 28 | ok | 0.9951 | 46.4% |
| 30 | FAIL quality | 0.9942 | — |
| 32 | FAIL quality | 0.9942 | — |
这张表掉出三个结论。
第一个反直觉的:低 ICQ 不等于安全。ICQ 18–22 产出的文件比源还大,于是被引擎的体积闸门(源视频码率的 100%)拒掉。这不是 QSV 独有的毛病——NVENC 对瘦输入用低 cq 也是同一个形状——但这里的边界更靠上:可用区间从 24 起,不是 18。
第二个,28 与 30 之间有一道悬崖。30 和 32 都质量失败,而且失败在首次尝试。这句话很重要,因为引擎在质量失败时会用 global_quality - 2 重试一次——ICQ 更低、质量更高——并把重试写进日志。一行写着「ok at quality+2」的日志其实是首次尝试失败,不是通过。我第一遍在自己的笔记里把这条读反了;规矩是读首次尝试,不读救济结果。
第三个,地板决定取舍:在 0.995 的 mean SSIM 下限下,q28 的余量是 0.0001,q26 的余量是 0.0010。
完整文件确认了这张阶梯——而且有一个捷径站得住
摘录(180 秒无损切片)是「不用跑十次完整编码就能排出候选顺序」的办法。它只有在与完整文件结果一致时才成立,所以我把真原件端到端跑了一遍:
| 源 | ICQ | preset | 视频 = 源 | 文件 | SSIM min / mean | 编码 |
|---|---|---|---|---|---|---|
| 254 MB,2023 秒,872 kbps | 26 | medium | 61.0% | 168,435,266 B(66%) | 0.9938 / 0.9960 | 360 秒 ≈ 5.6× 实时 |
| 同一支 | 28 | medium | 46.3% | 54% | 0.9927 / 0.9952 | 330 秒 |
| 315 MB,3280 秒,546 kbps | 26 | medium | 50.4% | 60% | 0.9965 / 0.9977 | 600 秒 ≈ 5.5× 实时 |
第一支的 180 秒摘录量到 mean 0.9959,完整文件量到 0.9960。这个接近程度足以让人相信「摘录用来排质量档」这条路——而且现在这是有证据的,不是靠希望。
那个毫无作用的 preset
摘录那一层给出的故事很干净:preset 只影响速度——veryfast 30 秒 / 61.6%、fast 30 秒 / 61.0%、medium 60 秒 / 61.0%、slow 与 veryslow 60 秒。结论似乎就是选 veryfast,每支文件省 30 秒。
然后完整文件否掉了它。同一支源、同一个 ICQ 26,一次用 fast、一次用 medium:
fast: 168,435,266 B 编码 360 秒 mean SSIM 0.9960
medium: 168,435,266 B 编码 360 秒 mean SSIM 0.9960
逐字节同大小、同编码时间、同质量。摘录层的 30 秒对 60 秒,是每一轮固定开销造成的差别,不是编码速度——这正是那种「如果只走到一步就停」会写进配置文件、再写进一篇博客的数字。
所以:摘录只能排质量,永远不能排速度。 速度必须在完整文件上量,固定开销在那里才会消失。
最后落了什么
qsv_quality = 26、qsv_preset = medium——源视频码率的 61.0%,mean SSIM 0.9960(min 0.9938),约 5.6× 实时;在片库里最瘦的一支上是 50.4%、mean 0.9977。q28 相对下限的余量只有 0.0002,而一次质量失败的代价是两次整片编码再加一次搁置,所以那多出来的 15 个百分点不值得换这条尾部风险。
另外两条值得记下来的量测,它们回答的是「到底是 QSV 的问题还是文件的问题」:
- 跨编码器对照。 一支 720p、节奏不均匀的源,QSV q24 量到 mean 0.9154、QSV q28 量到 0.9146、x265 crf26 量到 0.9164——引擎的对齐搜索同时报
no alignment explains the mismatch。这条拒绝属于源档,不属于 Quick Sync。 - 并发。 引擎拒绝在共享 work root 上起第二个编码器:
another transcode is already running (pid 7, /work/run-qsv.lock, 3 s old)。换成各自独立的 work root,两个作业能跑——但墙钟 201 秒对单人 107 秒。两倍时间,零吞吐收益。QSV 一次只跑一个作业。
四个坑,每个都花掉一轮
- 永远不要把共享片库挂到容器的
/lib。 我把 SMB 共享挂进了容器的/lib——而那是容器自己的动态加载器目录。八个作业全在 1 秒内死掉,报exec /bin/bash: no such file or directory,读起来像镜像坏了,而不是挂载点错了。挂/library就正常。 - 合成源会给出与事实相反的 ICQ 结论。
hevc_qsv的码率控制对rgb24输入会被驱动拒绝(Selected ratecontrol mode is unsupported),所以用testsrc/mandelbrot自检会得出相反的结论。要么用真文件,要么加format=nv12。 - 已经是 HEVC 的源根本没法标定。 闸门会回
SKIP already HEVC hvc1,--overwrite也不解除。这个片库里肥档 1080p H.264 原件早就转完了,剩下还在肥档的都是 HEVC——所以那一档已经没有可量的素材了。这是缺输入,不是漏做任务。 - 读首次尝试,不读重试(见上面那道悬崖)。
如果重来一次
- 速度要在你真正会跑的那个产物上量,不要在它的一片切片上量。整件事里我唯一报错的数字,就是信了摘录的墙钟。
- 先让机器证明它有没有这块硬件,再排测试计划。 六分钟的
lspci加virsh dumpxml,就把「为什么 QSV 在 VM 里失败」这个悬了几周的问题结掉了。 - 把闸门留在回路里。 因为每一次运行都由引擎自己的校验判收,上面这些表格是决策,不是观点——将来在生产里会拒掉坏文件的,正是这个把我的 ICQ 30 拒掉的闸门。
- 把诚实的适用范围写进文档。 这些数字是 UHD 770 上 Linux iHD 26.3.5 + oneVPL 2.17 的数;生产目标是同一块硅片上的 Windows MSDK 路径,我不能声称两者一致。这里站得住的是:命令在真硬件上跑得通、质量↔体积曲线的位置、以及锁的行为——留给将来的,只是「在 Windows+Intel 上按同一张表复测一次」,而不是又一个下午。
结果
QSV 从「存在于代码里、永远选不到」变成了一个带公开数字的已标定档,安全规则也跟着改了:原来那条钉住「出货默认值必须代表未标定」的测试,现在钉的是「出货默认值必须是量过的值」,同时保留「空值 = 硬排除」的全部断言。在没装 Intel 显卡的机器上,doctor 那行也从信息级的「等硬件」变成了警告——这本身是另一个小教训:一个已标定的编码器自检失败该长什么样,以及为什么机器上不存在的硬件应该进一行汇总,而不是进警告。