引
可能和大部分小伙伴的习惯不同,我平时比较喜欢使用 B 站的客户端看视频,不喜欢网页端。可是有个问题一直卡了我很久,那就是 B 站的 Linux 客户端 bilibili-linux 项目一直不支持 VAAPI 硬件加速,导致我在看高清视频的时候 CPU 占用率非常高,视频也掉帧严重。
搜索后发现,和我有类似抱怨的用户还不少。近些年,这个项目的 issue 里也有至少 5 个讨论了这个问题,但是基本都是无果而终。
尝试
之前在安装 Arch Linux 的时候,我参考了极地萤火的 Arch Linux 安装笔记,笔记中提到,对于 Electron 应用,可以在启动参数中加入 --enable-features=AcceleratedVideoDecodeLinuxZeroCopyGL,AcceleratedVideoDecodeLinuxGL 来启用 VAAPI 硬件加速。这个参数对于 Edge 浏览器来说确实是有效的,实测在 ~/.config/microsoft-edge-stable-flags.conf 中加上这个参数后在 Edge 中观看 B 站视频时可以正常调用 VAAPI 加速。于是我就尝试在 B 站客户端的启动参数中也加入这个参数,却无果。
顺带一提,B 站客户端的启动参数可以写在 ~/.config/bilibili-flags.conf 中,也可以写在 ~/.config/bilibili/bilibili-flags.conf 中,两者都可以被识别到。前者是 bilibili 启动脚本中解析并追加到 Electron 命令行里的,后者是 bilibili 启动后内部的 electron-tool 模块在 Electron 主进程启动后读取的,因此两者都生效。
转机
既然 Edge 浏览器都能正常调用 VAAPI,说明我这套系统环境里的 VA-API 驱动、Mesa/libva 和硬件本身大概率是可用的,作为同样 Electron 出身的 B 站客户端也理应支持。
在翻阅 bilibili-linux 项目的 issue 时,我看到了一例成功案例。issue #193 中,有用户提到,可以在启动参数中加入如下参数:
--ignore-gpu-blocklist
--disable-gpu-driver-bug-workarounds
--enable-gpu-rasterization
--enable-zero-copy
--enable-native-gpu-memory-buffers
--enable-features=VaapiVideoDecodeLinuxGL,VaapiVideoEncoder,PlatformHEVCDecoderSupport,CanvasOopRasterization
--disable-features=UseChromeOSDirectVideoDecoder
可是我实测后发现,加上这些参数后我的 B 站客户端播放视频时直接花屏了!无论是我点开视频播放,还是鼠标在视频封面悬停预览,都会出现横向的条纹状的花屏,完全无法正常观看视频。但是打开 intel_gpu_top 一看,发现 Video 视频解码单元确实被调用了,说明这套参数确实让 B 站调用了 VAAPI,所以问题更可能出在显示 / 合成路径上。
思考片刻后,我意识到,这很可能是 Electron 的版本问题导致的。B 站客户端目前使用的 Electron 版本是 28,而主流的 Electron 应用再低也都是三十几了。bilibili-linux 的作者为了确保兼容性,也使用了 28 版本。那换上更高版本的 Electron 会不会解决这个问题呢?
我果断创建了一个新的 wrapper,将启动脚本里调用的 Electron 改成了 37,而启动参数里我只写了 --enable-features=AcceleratedVideoDecodeLinuxZeroCopyGL,AcceleratedVideoDecodeLinuxGL,和 Edge 一样的参数。结果一测试,视频可以正常播放了!intel_gpu_top 中的 Video 视频解码单元也确实被调用了,CPU 占用率也从之前的 90% 降到了 30% 左右,视频也不再掉帧了!
后来又测试了多个中间版本的 Electron,发现从 36 开始就不再花屏了。
原理
后来,我针对这一系列现象查阅了资料,也询问了 AI,得到了一些答案:
为什么最初的时候,同样的参数在 Edge 上有效,在 B 站客户端上却无效? 我一开始照着新版 Edge/Chromium 的写法,在 Electron 28/Chromium 120 上使用 AcceleratedVideoDecodeLinuxZeroCopyGL,AcceleratedVideoDecodeLinuxGL,但 Chromium 120 当时的 Linux VAAPI GL feature 还叫 VaapiVideoDecodeLinuxGL。因此把新版 flag 直接塞给 Electron 28,没法启用旧版的 VAAPI GL 解码路径。
为什么我加上了那些参数后虽然花屏了,但是却能成功调用 VAAPI? issue #193 中这组里包含 --ignore-gpu-blocklist、--disable-gpu-driver-bug-workarounds、--enable-zero-copy 等比较激进的开关,可能绕过了 Chromium 对 GPU / 驱动组合的一些保守判断。同时,它使用了 Chromium 120 能识别的旧 feature 名 VaapiVideoDecodeLinuxGL。因此,它能触发硬解路径。因此这套参数没错,只是我的核显型号太新了,被 Chromium 按保守路线降级了。
为什么加上了那些参数后会花屏? 这个花屏说明 VAAPI 解码路径被触发后,后续的视频帧导入、纹理上传、DMABUF modifier、GL 合成或显示后端链路里仍然存在兼容性问题。它不一定是「解码器不能解码」,更像是「解码后的图像没有被正确显示」。Chromium 131 把 VaapiVideoDecodeLinuxGL 统一改名为 AcceleratedVideoDecodeLinuxGL,旧 flag 失效,可是用了新 flag 后这个 bug 依旧存在。一直到 Chromium 136 之后画面才变得正常,所以我这套硬件升级到 Electron 36 就不会有这个问题了。
为什么换到 Electron 36 之后,两个简单的 flag 就够了? Electron 36 对应 Chromium 136,这个版本距 Chromium 120 跨越了整整 16 个版本。Linux VAAPI、ANGLE/GL、DMABUF、Wayland/EGL 以及 Intel 新平台相关代码都有持续变化。AcceleratedVideoDecodeLinuxGL 在新版 Chromium 里走的渲染路径已经能正确处理 MTL 的纹理格式。
结语
总的来说,这次折腾虽然有点曲折,但最终还是成功了。现在我终于可以在 B 站客户端上愉快地看高清视频了!希望这个经验也能帮助到其他有同样问题的小伙伴们。