摄像头测试

点一下按钮才向浏览器申请视频权限,随后实时预览,并把设备真正给出的分辨率、帧率、宽高比与朝向读出来,还能在线切换前后摄像头。

打开页面时不会申请任何权限,也不会占用摄像头。

这里会显示摄像头画面。上面这块是打开页面时的静态占位,不是你的摄像头;点「开始测试摄像头」并允许权限后才会换成实时画面。本页不录像、不拍照、不上传。

实际分辨率1280×720(演示)
实际帧率30.0 fps(演示)
设备朝向前置(演示)
设备名称内置摄像头(演示)
项getSettings() 当前实际值getCapabilities() 能力上限
分辨率1280×720(演示)640×480 – 1920×1080(演示)
帧率30.0 fps(演示)30 – 60 fps(演示)
宽高比1.7778(演示)—
朝向 facingModeuser(演示)user, environment(演示)
设备 deviceId(授权后显示)(演示)—

提示:上方表格是写在页面里的演示值,不是你的设备读数;允许权限后会被真实值替换。测完请点「停止并释放摄像头」,它会调用 track.stop();只关掉预览而不释放,系统指示灯或标签页上的录制圆点会一直亮着。

使用方法

  1. 点「开始测试摄像头」。浏览器会弹出授权提示,选「允许」;如果点了「阻止」,页面会直接指出被拒绝以及怎么改回来,而不是一直转圈。
  2. 授权通过后画面立刻出现,下面四张卡片会换成设备真实值:实际分辨率、实际帧率、朝向和设备名。
  3. 「请求分辨率」「请求帧率」是你希望设备给出的值,不是保证值。设备给了别的档位时,卡片里显示的才是真正生效的那一个,两者不一致是正常的。
  4. 换设备时先授权,「摄像头设备」下拉框才会列出真正的设备名;选中另一颗后会重新取流。
  5. 带前后置的设备点「切换前后摄像头」,页面会对当前视频轨道调用 applyConstraints 在线换镜头,不会重新弹权限框。
  6. 点「复制设备报告」,可以把当前的分辨率、帧率、宽高比、朝向和能力上限一次性复制成文本,用来贴到工单里。
  7. 测完点「停止并释放摄像头」,它会调用 track.stop(),系统的指示灯或标签页上的录制圆点会随之熄灭。

这个摄像头测试能解决什么问题

一是确认摄像头到底有没有被系统认到。开会前先看一眼:能出画面说明设备正常;授权成功却始终黑屏,问题多半在系统相机隐私开关被关,或者被别的软件占用了。

二是找出"对方看不到我"的原因。可能是选错了设备。授权之后设备名才会真正列出来(下面会解释为什么),换成另一颗再看画面有没有出现,一目了然。

三是核对这台设备真正给到了什么画质。你请求 1080p,设备可能只给 720p;卡片里的实际分辨率和帧率才是真话,用它来判断该不该换摄像头或调整设置。

四是确认指示灯有没有真的熄灭。很多人以为关掉预览就没事了,其实只要视频轨道还活着,指示灯就一直亮。本页把释放做成一个明确的动作,点了就是 track.stop()。

本页不做这些事:不录像、不拍照、不生成文件、不上传任何画面,也不做美颜和滤镜。它只回答两个问题——这颗摄像头现在能不能用、它实际给了多大的画面和多快的帧率。

检测原理:getUserMedia、getSettings 与 applyConstraints

这个摄像头测试的链路只有三步:getUserMedia 拿到视频流 → 把流挂到 <video> 上预览 → 从视频轨道读出参数。每一步都有必须先满足的前提。

第一步,页面必须在安全上下文里,权限必须由用户手势触发。navigator.mediaDevices.getUserMedia({ video: true }) 只在 HTTPS 或 localhost 下可用;用 file:// 直接打开本地 HTML,或者用 http:// 访问一个真实域名,navigator.mediaDevices 根本就是 undefined,连请求都发不出去。这也是本页把调用放进按钮点击处理里的原因:浏览器要求权限必须由用户手势触发,同时也避免一打开页面就弹权限框。

第二步,enumerateDevices() 在授权前拿不到设备名。这是最容易误解的一点。未授权时调用它,返回的数组里 label 是空字符串、deviceId 也是空的,你只会看到几项没有名字的占位。只有授权之后再次枚举,才能读到"HD Webcam"这样的真实名字。所以本页的设备下拉框是在授权回调里才去填充,而不是在页面加载时。

第三步,getSettings() 和 getCapabilities() 读的是两件不同的事。track.getSettings() 返回此刻真正生效的参数:width、height、frameRate、aspectRatio、deviceId、facingMode;track.getCapabilities() 返回这颗设备的能力上限:width 与 height 的 {min, max} 区间、frameRate 的区间,以及 facingMode 支持哪些取值。两者都是普通对象而不是 Promise,直接取用即可,但只有在流建立、轨道存活期间才有意义。想知道"为什么我要 1080p 却给了 720p",把两者一对比就清楚了:如果 getCapabilities().height.max 只有 720,那是设备上限,不是浏览器在偷懒。

第四步,切换摄像头靠 applyConstraints。对已有的视频轨道调用 track.applyConstraints({ facingMode: { exact: 'environment' } }),就要求这条轨道改用后置摄像头。用 exact 是因为多数手机只有在精确匹配时才会真的换镜头;写成 ideal 或不写,设备可能觉得"当前这颗也还行"而不动。约束里还有一层取舍:{ width: { ideal: 1280 } } 表示"尽量给 1280,给不了就退到最接近的一档",不会报错;而 { width: { exact: 1280 } } 表示"必须是 1280",设备没有这个精确尺寸就抛 OverconstrainedError,采集直接失败。所以日常用 ideal,需要严格标定时才用 exact。约束之间也有优先级:deviceId 与 facingMode 决定"用哪颗镜头",分辨率与帧率只在选定镜头后再谈。

第五步,释放必须落到 track 上。浏览器的摄像头指示灯(或标签页上的红色圆点)绑定的是处于 live 状态的视频轨道,不是页面。把 <video> 的 srcObject 置空、或者把元素从页面里删掉,都不会让指示灯熄灭;只有 track.stop() 让轨道进入 ended 状态,采集才真正停止。本页的「停止并释放摄像头」做的就是这件事,另外还监听了 pagehide,在离开页面时兜底释放一次。

完整示例(数值仅作演示)

页面打开时的默认条件:请求分辨率 1280×720、请求帧率 30 fps、镜像开、自动停止 0。这个摄像头测试给出的下面这组演示数值,与页面上的演示卡片、演示表格和对照表三处一致:

把这几组数对上页面:演示卡片里的"实际分辨率 1280×720、实际帧率 30.0 fps"就取自这一档;演示表格里 1280×720、30.0 fps、1.7778 也来自同一组;对照表 1280×720 那一行的 0.92 MP、16:9、1.32 MiB、39.6 MB/s 与上面逐项对应。数值仅作演示:真实值必须以 getSettings() 现场读到的为准。

分辨率、像素与原始带宽对照表

下表每一行都能用同一条公式复核:像素 = 宽 × 高,YUV420 单帧 = 像素 × 1.5 字节,30 fps 带宽 = 单帧 × 30。带宽是未压缩的粗算值,实际传输会因为设备与浏览器使用压缩而小得多。

分辨率像素数宽高比YUV420 单帧30 fps 原始带宽
320×24076,8004:30.11 MiB3.3 MB/s
352×288101,37611:90.15 MiB4.4 MB/s
640×480307,2004:30.44 MiB13.2 MB/s
800×600480,0004:30.69 MiB20.6 MB/s
1,024×768786,4324:31.13 MiB33.8 MB/s
1,280×720(默认)921,60016:91.32 MiB39.6 MB/s
1,280×8001,024,0008:51.46 MiB43.9 MB/s
1,280×9601,228,8004:31.76 MiB52.7 MB/s
1,600×9001,440,00016:92.06 MiB61.8 MB/s
1,920×1,0802,073,60016:92.97 MiB89.0 MB/s
2,560×1,0802,764,80064:273.96 MiB118.7 MB/s
2,560×1,4403,686,40016:95.27 MiB158.2 MB/s
3,440×1,4404,953,60043:187.09 MiB212.6 MB/s
3,840×2,1608,294,40016:911.87 MiB356.0 MB/s
4,096×2,1608,847,360256:13512.66 MiB379.7 MB/s

别只看"像素更多就更清楚":4:3 的 640×480(0.31 MP)与 16:9 的 1280×720(0.92 MP),宽高比相差 0.4444。如果传感器是 4:3 而你要 16:9,浏览器会按较大者拉伸并居中裁剪,画面上下或左右会被切掉,视野变窄——这正是"换了分辨率后人只剩半个脸"的常见原因。1280×720 与 1920×1080 都是 16:9(都是 1.7778),互切不会裁剪。

边界与权限说明

常见问题

点了「开始测试摄像头」没反应,或者提示拿不到接口?

先看地址栏:必须是 https:// 或 localhost。用 file:// 双击打开的本地网页、或用 http:// 访问真实域名,浏览器都不会提供 navigator.mediaDevices,这与浏览器版本无关,是安全上下文的规定。

授权了,但设备下拉框还是空的?

这正是 enumerateDevices() 的特性:授权前拿不到 label,只能拿到空的占位项。授权成功后本页会重新枚举一次并填上下拉框。如果授权成功仍是空的,检查操作系统是否把它列成了"未插入设备"。

提示「设备被占用」或「无法读取」怎么办?

说明这颗摄像头已经被别的程序独占了,常见的是另一个浏览器标签、会议软件或录制软件。把占用它的程序关掉再点一次即可。部分驱动也允许共享,但独占模式下会返回 NotReadableError。

我要 1080p,卡片却显示 720p,是坏了吗?

不是。先点开页面里那张演示表格看真实上限:如果 getCapabilities().height.max 就只有 720,那这颗设备最高只能给 720p,请求 1080 也只是 ideal,浏览器会退到最接近的一档而不报错。想强制要 1080 得写 exact,但设备做不到时采集会直接失败。

为什么画面是黑的,指示灯却亮着?

说明轨道已经建立、设备确实被占用了,只是没有出图。常见原因有三个:系统的相机隐私开关把画面遮掉了、镜头前的物理遮挡没打开、或者设备被另一个程序以独占方式占用。指示灯亮本身就证明"浏览器已经拿到了这颗摄像头"。

点了「切换前后摄像头」没反应?

这个按钮走的是 track.applyConstraints({ facingMode: { exact: … } }),只有设备支持按朝向切换才有效。台式机的外接摄像头通常没有前后置概念,调用会被拒绝,页面会提示你改用「摄像头设备」下拉框手动选另一颗。手机上如果系统把它当成两颗独立设备,用下拉框切换同样有效。

测完直接关掉标签页可以吗?

可以,但更稳妥的是先点「停止并释放摄像头」。关闭标签页时浏览器通常也会释放采集,但如果你只是切到别的页面继续用,采集中会一直占用设备并让指示灯亮着。本页除了按钮,还在 pagehide 时兜底释放一次。

我的画面会被上传或保存吗?

不会。这个摄像头测试只把视频流连接到一个 <video> 元素,数据既不写入磁盘也不发请求。想自己验证,打开开发者工具的 Network 面板,点「开始测试摄像头」,可以看到没有任何请求发出。

相关工具