帧率指纹:刷新率与视频节奏
requestAnimationFrame 时序、屏幕刷新率和视频播放节奏如何成为指纹信号,以及 BotBrowser 如何分别设置显示 FPS 和视频 FPS。
BotBrowser Team
requestAnimationFrame 的回调间隔会暴露显示器刷新率,页面无需任何提示即可读取。BotBrowser 可以通过 --bot-fps 让该时序跟随配置文件、固定数值或宿主,并用 --bot-video-fps 降低视频节奏;二者都是 ENT Tier2 控制,没有启用 Video FPS Control 的配置文件会忽略视频参数,降低后的视频节奏也正是截图和像素采样看到的内容。
要点摘要
除非有意测试另一类显示器,否则让 --bot-fps 保持 profile,并使用 60、120、144 这类真实显示器具备的数值。把 --bot-video-fps 当作独立的工作负载选择:1:30 适合后台媒体,截图和逐帧比较则需要更高的实际值或不使用该参数。
本文目录
requestAnimationFrame 如何暴露刷新率
页面调用 requestAnimationFrame(callback) 时,浏览器会在下一次重绘之前运行回调,并传入一个标记该帧起点的 DOMHighResTimeStamp。MDN 文档描述了这一约定,并指出回调通常与屏幕刷新率一致。把相邻时间戳相减得到帧间隔,用一秒除以该间隔就得到刷新率。
可靠的估算只需要一小段采样。收集几十个连续时间戳并对间隔取平均,可以抹平个别慢帧,整个测量在不到一秒内完成,访问者毫无察觉。测量必须在可见标签页中进行,因为浏览器通常会暂停或放慢后台页面的 requestAnimationFrame。
实际测量带有噪声。繁忙的页面偶尔会丢一帧,表现为一串正常间隔里夹着一个很长的间隔,单次采样可能恰好落在这个异常值上。取中位数或对更长的采样取平均可以得到稳定的数字,这个数字通常接近 60、90、120 或 144 Hz 这类常见值。正是这种稳定性,让这个数字对想要识别回访设备的网站有用。
CSS 动画和过渡在同一个显示周期上推进。一个时长一秒的 @keyframes 动画大致每次刷新产生一帧,所以在 144 Hz 屏幕上的步进比在 60 Hz 屏幕上更细。CSS 规范并不承诺逐帧精确,但动画推进的节奏仍然跟随屏幕。这一点在后文很重要,因为页面可以分别从 requestAnimationFrame 和某个动画读出刷新率,并期待两个读数一致。
可变刷新率带来另一个维度。支持 G-Sync、FreeSync 或 ProMotion 的屏幕可以随内容调整刷新率,例如在画面几乎不动时降到较低的速率。观察足够久的页面看到的不只是基础刷新率,还有刷新率变化的方式,而这种模式不同于固定刷新率面板的稳定节奏。
多显示器环境还有最后一个细节。当窗口从 60 Hz 的显示器移到 144 Hz 的显示器时,回调间隔会在那一刻改变。在会话期间持续跟踪间隔的页面可以注意到这次移动,这也透露了浏览器窗口背后的硬件布局。
刷新率为什么是硬件信号
刷新率属于物理屏幕以及驱动它的合成器。清除 Cookie、打开隐私窗口或切换网络都不会改变它。同一台机器的访问者可能报告相近的节奏,但负载、可变刷新率和屏幕变化都可能让不同会话的观察间隔发生移动。
这个信号携带的信息也比过去更多。不久前,几乎所有消费级显示器都运行在 60 Hz,这个数字几乎不能告诉网站什么。如今面板有 60、75、90、120、144、165、240 和 360 Hz 等多种规格,带有 ProMotion 这类自适应刷新率的手机和笔记本进一步增加了差异。使用 165 Hz 显示器的访问者所在的群体比使用 60 Hz 的小得多,因此这个值有助于缩小一次访问可能来自哪些设备。移动浏览器同样如此:60、90 和 120 Hz 的屏幕都存在,许多新手机在运行时还会在不同速率之间切换。
刷新率是若干硬件相关显示信号之一,单独不能证明身份。它的分量来自组合。与屏幕尺寸、色深、设备像素比和 GPU 输出放在一起,它构成的硬件描述比任何单一数值都具体得多。屏幕与窗口指纹指南介绍了与之相邻的显示信号。
浏览器的多个功能暴露了相关的显示信号。requestAnimationFrame 间隔和 CSS 动画进度跟随显示时序,而浏览器报告的显示属性描述窗口与屏幕的周边状态。只改动其中一项的隐私措施,可能让其余信号指向不同的硬件环境。
同样的原则对在混合硬件上运行浏览器的团队也很重要。无头主机没有物理显示器,虚拟机和容器报告的时序往往由主机负载决定,而不是由任何真实显示器决定。配置文件描述的是具有特定刷新率的设备,页面可见的时序若与配置文件不符,就是所声明设备与其观察到的行为之间的不一致。回调节奏为 60 Hz 的 120 Hz 手机配置文件就是一个例子。
为什么脚本包装和扩展留下缺口
帧率是浏览器在自己的显示周期内测量的本地硬件属性,因此作用在别处的工具碰不到它。VPN 或其他网络工具改变的是请求的路径,对回调间隔没有影响。隐私浏览改变的是数据的存放位置,而不是屏幕的刷新方式,所以隐私窗口中的测量结果与普通窗口一致。
修改 requestAnimationFrame 的扩展和页面脚本可以做的事情更多,但也会碰到限制。常见做法有三种,每一种都会留下可见的缺口。
- 限制回调频率。 脚本可以包装该 API,以较低的目标速率调用原始回调。这样正常的动画会出现卡顿,而 CSS 动画仍以真实的屏幕刷新率运行,两个时序来源因此互相矛盾。
- 改写时间戳。 修改传给回调的
DOMHighResTimeStamp可以模仿另一个速率,但performance.now()、Date.now()和 CSS 过渡时序仍会报告原来的节奏。 - 屏蔽该 API。 禁用
requestAnimationFrame会破坏许多依赖它绘制画面的网页应用。
共同的问题是,这些工具位于 JavaScript 层,而屏幕刷新率会出现在多个地方。只改一处而不改其他地方,就会产生不一致,而这种不一致会与配置文件所描述的设备相矛盾。
一些浏览器的开发者设置可以限制帧率。限制能约束浏览器的渲染速度,但它是上限而不是选定的目标。你无法按会话指定某个速率,限制也不能把速率提高到主机屏幕所能提供的水平之上,而且这种节流可能表现为可见的瑕疵。声明 120 Hz 的配置文件无法通过这种方式在 60 Hz 主机上匹配。
设置显示帧时序
BotBrowser 通过 --bot-fps 支持设置显示帧时序,该参数接受 profile、real 或一个数字。这是 ENT Tier2 控制项,需要配置文件具备 ENT Tier2 启用状态。设置之后,requestAnimationFrame 时序跟随所选速率,而不是主机屏幕碰巧运行的速率。
配置文件模式
在默认的 profile 模式下,BotBrowser 读取已加载配置文件中保存的刷新率,并使 requestAnimationFrame 时序与之匹配。
chromium-browser \
--bot-profile="path/to/profile.enc" \
--bot-fps=profile
配置文件描述的是目标设备。从 60 Hz 硬件采集的配置文件报告 60 Hz 的节奏,从 120 Hz 或 144 Hz 设备采集的配置文件则报告更快的节奏,与主机屏幕实际运行的速率无关。对大多数工作而言这是正确的选择,因为刷新率与设备描述的其余部分保持关联,无需每次运行再做决定。
固定数值
数字会设定一个确切的速率,帧时序和渲染间隔在任何主机硬件上都会遵循它。
chromium-browser \
--bot-profile="path/to/profile.enc" \
--bot-fps=60
当机群同时包含高刷新率显示器和原生时序不稳定的无头主机,而你希望各处节奏一致时,可以使用它。请选择真实显示器所具有的数值,例如 60、75、90、120、144、165 或 240。像 73 或 137 这样的数值不对应访问者可能拥有的任何显示器,所以会与配置文件所描述的显示器相矛盾。
真实模式
real 使用主机屏幕的原生刷新率。
chromium-browser \
--bot-profile="path/to/profile.enc" \
--bot-fps=real
它适合主机硬件已经与配置文件目标一致的情况,也是一个方便的诊断手段。用 real 和 profile 分别运行同一个页面,可以看出不稳定来自主机的渲染负载还是配置文件的设置。
--bot-fps 参数只管理显示和运行时时序。视频播放有自己的控制项,下一节介绍,因此页面可以保持 60 Hz 的显示节奏,同时让视频元素更新得更少。
视频播放节奏与 CPU
即使工作流只读取文本、网络响应、DOM 状态或偶尔的截图,视频元素仍会持续解码并绘制画面。在监控页面、队列看板或媒体密集的目录页上,这种解码可能是每个标签页中最大的单项成本,并且随标签页数量增长。
针对这种情况,BotBrowser 支持 --bot-video-fps=<actual>[:<reported>]。它是 ENT Tier2 控制项,在启用了 Video FPS Control 的配置文件上生效。没有启用的配置文件会忽略该参数。
chromium-browser \
--bot-profile="path/to/profile.enc" \
--bot-video-fps=1:30
该参数有三种形式。
--bot-video-fps=1以接近 1 FPS 解码并渲染视频,并报告 1 FPS。--bot-video-fps=1:30以接近 1 FPS 渲染视频,而媒体报告使用 30 FPS。--bot-video-fps=1:real以接近 1 FPS 渲染视频,并在可用时报告媒体自身报告的节奏。
第一个值是实际的视觉节奏,决定视频画面多久更新一次。第二个值是媒体报告策略,省略时会报告实际值。real 设置使报告值与媒体所报告的节奏保持一致,而不是与被降低后的实际节奏一致。
像素输出跟随实际节奏,这是需要规划的唯一限制。按帧采样视频像素的工作流,或需要截图显示最新视频内容的工作流,会看到降低后的节奏。对这些工作流,请选择更高的实际值,例如 5:30 或 10:30,或者不使用该参数。截图中视频以较低节奏显示,是配置后的预期行为,不是故障。
同样的道理适用于任何评判播放质量的检查。统计丢帧、验证播放是否流畅或比较相邻视频帧的测试,测量的是实际节奏,因此需要完整速率,应当在不带 --bot-video-fps 的情况下运行。该参数适合相反的情形:视频只是背景素材,真正的工作发生在 DOM、网络或页面文本上。
BotBrowser 的公开基准测试衡量了它对视频密集型工作负载的影响。测试使用 Linux x64 官方构建,以无头模式运行,每个元素都是本地 AVC 视频,分辨率 640 乘 360 像素、30 FPS,预热 10 秒,采样窗口 30 秒,每种设置重复 3 次。基线策略 30:30 与 1:30 进行了对比。
| 并发视频数 | 基线平均 CPU | 1:30 平均 CPU | 降幅 |
|---|---|---|---|
| 8 | 33.96% | 9.70% | 71.43% |
| 16 | 61.20% | 12.10% | 80.24% |
| 24 | 76.49% | 13.81% | 81.95% |
这些数据描述的是一个平台上的一种工作负载。CPU 使用量会随主机、驱动、编解码器和页面内容而变化,因此应把 71% 到 82% 的范围当作一次视频密集型测试的参考结果,那次测试中较低的视觉节奏是可以接受的,而不是到处都能期待的节省。完整的测试设置见 Video FPS Control 基准测试。
两个控制项一起使用时,显示运行时保持 60 Hz 的节奏,而视频元素更新得更少。
chromium-browser \
--bot-profile="path/to/profile.enc" \
--bot-fps=60 \
--bot-video-fps=1:30
选择数值并检查结果
请把显示 FPS 和视频 FPS 当作由不同对象决定的两个选择。显示 FPS 是设备描述的一部分,所以除非你有意测试另一类显示器,否则应与已加载的配置文件一致。60 Hz 的办公笔记本配置文件、120 Hz 的移动配置文件和高刷新率桌面配置文件不应随意混用,因为帧时序与屏幕尺寸、设备像素比、GPU 输出和动画时序紧挨在一起。配置文件管理指南介绍了配置文件如何存放和轮换。
视频 FPS 是运维层面的选择。先从保守的值开始:偶尔更新画面就够用时用 1:30,截图需要更新鲜的画面时用更高的实际节奏。按帧比较视频像素的场景会排除过低的数值。
对于机群,请按工作负载写下策略。为需要完整视觉播放的页面设一套,为携带媒体的后台页面设一套,为截图工作流再设一套。书面策略便于日后审查性能变化,也能把工作负载调优与配置文件一致性的决定区分开。
要检查结果,请在可见标签页中测量。收集大约六十个连续 requestAnimationFrame 回调的时间戳,对间隔取平均,并把得到的速率与配置值比较。然后再与从 CSS 动画读出的速率比较。对于视频,请在使用和不使用 --bot-video-fps 的情况下比较 CPU 使用量和可见节奏。无头和有头运行在负载下可能不同,所以请用同一个配置文件和参数验证两者。无头与有头一致性指南介绍了这些差异。
将时序样本变成故障检查
这个采样器记录连续动画回调的 120 个间隔。保持标签页可见,在页面的 DevTools 控制台中运行它;中位数估计常规节奏,第 95 百分位显示平均值可能掩盖的延迟帧尾部。
const intervals = [];
let previous;
let valid = document.visibilityState === 'visible';
if (!valid) console.log('sample invalid: open the page in a visible tab');
document.addEventListener('visibilitychange', () => {
if (document.visibilityState !== 'visible') {
valid = false;
console.log('sample invalid: tab became hidden');
}
});
function sample(timestamp) {
if (!valid) return;
if (previous !== undefined) intervals.push(timestamp - previous);
previous = timestamp;
if (intervals.length < 120) {
requestAnimationFrame(sample);
return;
}
const sorted = [...intervals].sort((a, b) => a - b);
const middle = sorted.length / 2;
const medianIntervalMs = (sorted[middle - 1] + sorted[middle]) / 2;
const p95IntervalMs = sorted[Math.ceil(sorted.length * 0.95) - 1];
console.log(
JSON.stringify({
visible: valid && document.visibilityState === 'visible',
intervalCount: intervals.length,
medianIntervalMs: Number(medianIntervalMs.toFixed(2)),
p95IntervalMs: Number(p95IntervalMs.toFixed(2)),
estimatedHz: Number((1000 / medianIntervalMs).toFixed(1)),
})
);
}
if (valid) requestAnimationFrame(sample);
请把样本当作分支检查,而不是设备结论:
| 观察结果 | 它说明什么 | 下一项检查 |
|---|---|---|
| 标签页在完成 120 个间隔前变为隐藏 | 后台节流使本次样本无效 | 保持标签页可见,重新执行相同采集 |
| 中位数接近选择的速率,但 p95 明显更长 | 常规节奏存在,但尾部有延迟帧 | 在空闲主机上重跑,并在更换配置文件前检查 CPU/GPU 负载 |
中位数跟随 real,却不跟随配置文件声明的速率 | 主机时序与配置文件时序不一致 | 确认生效的 --bot-fps 参数和配置文件显示速率 |
requestAnimationFrame 一致,但 1:30 下视频帧推进缓慢 | 显示节奏与实际视频节奏按配置分离 | 逐帧像素或播放质量检查时移除视频限制 |
现有排障列表覆盖了速率漂移和视频看起来缓慢的情况。这几行新增了一种可复现的方式,在修改任一控制项前区分无效的后台样本、抖动尾部、配置文件与主机不一致,以及有意设置的视频限制。
大多数问题属于以下几种模式。
- 测得的速率与配置值有偏差:检查主机的 CPU 和 GPU 是否饱和,因为繁重的渲染负载会在目标速率附近带来抖动。
- 配置文件写的是 120 Hz,但测量结果接近 60:确认
--bot-fps没有被固定为60,并检查正在运行的进程的启动参数。 - 视频报告 30 FPS,但看起来很慢:使用
--bot-video-fps=1:30时这是预期现象,因为报告 FPS 与视觉节奏在设计上就是分开的。 - 视频参数没有效果:确认配置文件已启用 Video FPS Control,因为没有启用的配置文件会忽略该参数。
帧时序只是页面能够观察到的执行时序的一部分。性能时序指南介绍了与之相邻的其他时序信号。
BotBrowser 支持用 --bot-fps(profile、real 或固定数值)设置显示帧时序,并在启用了 Video FPS Control 的配置文件上用 --bot-video-fps 设置视频播放节奏,因此 requestAnimationFrame 时序跟随所选配置文件,视频密集的页面也能占用更少的 CPU。对你来说,这意味着刷新率与你选定的设备一致,视频很多的页面 CPU 开销更低,显示 FPS 与视频 FPS 可以分别决定。BotBrowser 不会在没有 ENT Tier2 配置文件启用的情况下提供这些控制项,不会在没有 Video FPS Control 的配置文件上应用 --bot-video-fps,不能保证在测试负载之外获得同样的 CPU 节省,也不能替代选择一个显示、GPU 和时序信号彼此一致的配置文件。
相对参考文档新增的内容
参考页描述显示控制。本节增加对比,在配置文件、固定和真实模式下分别记录 requestAnimationFrame 间隔与视频节奏。如果页面动画稳定而视频不稳定,应排查媒体管线和主机负载,而不是修改显示数值。
公开来源
- MDN:Window.requestAnimationFrame():回调时序与
DOMHighResTimeStamp参数。 - MDN:使用 CSS 动画:CSS 动画如何推进。
- BotBrowser FPS Control:
--bot-fps模式与--bot-video-fps行为。 - BotBrowser 基准测试:Video FPS Control 的 CPU 测量。
实用结论
显示 FPS 与视频 FPS 分开决定:显示时序应与配置文件一致,视频节奏则按工作负载制定策略。验证时在可见标签页中平均约六十个 requestAnimationFrame 间隔,并与 CSS 动画的读数对比。动画稳定而视频不稳定时,应排查媒体管线和宿主负载,而不是修改显示数值。
本文属于指南: 什么是浏览器指纹?原理、风险、信号与隐私边界