麦克风测试

点一下按钮才会向浏览器申请录音权限,随后实时显示音量条、频谱图与峰值保持,并给出当前采样率和这台设备真正能分辨的频率间隔。

−60−48−36−24−120 dBFS
−15.0 dBFS(演示值,尚未开始检测)

上面这一条频谱和音量读数都是写在页面里的演示数据,**不是**你的真实录音;点「开始检测麦克风」之后才会换成实时值。本页不录音、不保存、不上传,音频只在浏览器内存里流动。

采样率48,000 Hz(演示)
频率分辨率23.4 Hz(演示)
时域窗口42.67 ms(演示)
当前电平−15.0 dBFS(演示)

提示:必须再点一次按钮才会出现浏览器的录音授权弹窗——打开页面时不会自动弹出,也不会自动占用麦克风。测完请点「停止并释放麦克风」,否则标签页上的录音标记会一直亮着。

使用方法

  1. 点「开始检测麦克风」。浏览器会弹出授权提示,选择「允许」;如果选了「阻止」,页面会告诉你被拒绝以及怎么改回来,而不是一直转圈。
  2. 授权通过后,音量条立刻随声音起伏,频谱图显示各频段的能量分布,黑色竖线是峰值保持标记——它记录你这一段时间喊到的最大音量,按设定速度缓慢回落。
  3. 对着麦克风用正常音量说话,让读数稳定在 −24 到 −12 dBFS 之间比较合适;一直贴着 0 dBFS 说明过载,录出来会破音;低于 −60 dBFS 基本等于没声音。
  4. 换设备时,先授权,「输入设备」下拉框才会列出真正的设备名(未授权前浏览器只给一个空白项)。选中另一个设备后检测会自动重启。
  5. 「FFT 点数」决定频率分辨率:点数越大,频谱越细,但时间窗口也越长、对瞬态反应越迟钝。默认 2048 是常规折中。
  6. 「峰值保持衰减」控制黑线回落速度,单位是 dB/帧;填 0 则峰值停在最大值不再回落,适合做最大音量标定。
  7. 测完点「停止并释放麦克风」。这一步会真正调用 track.stop() 关掉采集,而不是仅仅停止画图。

这个麦克风测试能解决什么问题

一是确认麦克风到底有没有被系统认到。值不值得怀疑硬件之前,先用这里看一眼:有波形说明设备正常,授权成功却始终是一条直线,问题多半在系统输入音量被拉到 0,或者是笔记本的物理静音键被按下了。

二是找出「对方听不到我」的位置。开会时听不到自己,可能是选错了输入设备。授权之后设备名会真正列出来(这一点很关键,下面会解释为什么),换成另一个再说话,音量条有没有反应一目了然。

三是把麦克风音量调到合适的挡位。看着 dBFS 读数说话,把系统输入增益调到正常说话时落在 −24 到 −12 dBFS,就不用靠「听起来差不多」来猜了。

四是快速判断有没有持续的底噪或电流声。安静环境下如果频谱在某一两个频点长期亮着一根细柱,通常是电源噪声或设备自激;宽带铺满一层则更像环境底噪。麦克风测试只做呈现和判断,不替你做降噪。

本页不做这些事:不录音、不生成音频文件、不分析音高、不做回声消除、不上传任何音频数据。它只回答一个问题——这台设备现在收到了多大的声音、分布在哪些频段。

检测原理:getUserMedia、AnalyserNode 与 dBFS

链路只有四步:getUserMedia 拿到音频流 → AudioContext 建立音频图 → AnalyserNode 每帧取样 → 换算成音量与频谱。每一步都有必须先满足的前提。

第一步,权限必须先手动触发,而且页面必须在安全上下文里。navigator.mediaDevices.getUserMedia({audio:true}) 只在 HTTPS 或 localhost 下可用;用 file:// 直接打开本地 HTML,或者用 http:// 访问一个真实域名,navigator.mediaDevices 根本就是 undefined,连请求都发不出去。所以本页把调用放在按钮的点击处理里:既是浏览器「必须由用户手势触发」的要求,也能避免一打开页面就弹权限框。授权成功后拿到的 MediaStream 还必须在测完主动释放——stream.getTracks().forEach(t => t.stop()),否则浏览器标签上的录音图标会一直亮着。

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

第三步,AudioContext 要在用户手势后 resume()。现代浏览器为防止自动播放噪音,新建的 AudioContext 处于 suspended 状态,必须调用 resume() 才会走时钟。本页在按钮点击的同一个手势里创建并恢复上下文,不会出现「接了线但图不动」的情况。

第四步,音量怎么算成 dBFS。每次绘制前调用 analyser.getByteTimeDomainData(buf),拿到的是一串 0~255 的字节,静音的中值是 128。先归一化:v = (byte − 128) / 128,范围落在 −1 到 1。然后求均方根 rms = sqrt(Σv² / n),最后换算成分贝:

dBFS = 20 × log10(rms),满量程记作 0 dBFS。这里有个反直觉的点:满幅的单频正弦波只有 −3.01 dBFS,不是 0,因为它的 rms 是峰值的 1/√2 = 0.7071,而 20 × log10(0.7071) = −3.01。所以看到读数稳定在 −3 dBFS 左右,说明已经贴着满幅了。另外振幅每减半就是 −6.02 dB:rms 从 1 降到 0.5 是 −6.02,降到 0.25 是 −12.04。真正的数字静音让 log10(0) 变成负无穷,代码里必须钳位到 −60,否则读数是 -Infinity。

频谱那一块靠 getByteFrequencyData。它给出 fftSize / 2 个频点(本页默认 1024 个),第 i 个频点对应的中心频率是 i × sampleRate / fftSize。这就是频率分辨率的来源:分辨率 = 采样率 ÷ FFT 点数。48 kHz 采样配 fftSize 2048,分辨率 = 48000 / 2048 = 23.4 Hz,意思是两个频率相差不到 23.4 Hz 就没法在这张图上分开;换成 4096 点,分辨率细到 11.7 Hz,代价是时域窗口从 42.67 毫秒拉长到 85.33 毫秒,响应变钝。这也解释了为什么不能用 setInterval 去轮询——采样应当跟着屏幕刷新走,用 requestAnimationFrame 才既不掉帧又不空转。

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

页面打开时的默认条件:FFT 点数 2048、峰值衰减 0.5 dB/帧,演示用的一组读数按 48 kHz 采样率折算,与页面上的演示面板一致:

把这几组数对上页面:音量条按 −60 到 0 dBFS 线性铺开,−15.0 dBFS 对应 (−15.0 + 60) ÷ 60 = 75% 的宽度,正是演示音量条停在的位置;峰值黑线同宽,因为演示取的就是峰值本身。

采样率、FFT 点数与频率分辨率对照表

下表每一行都能直接套用公式 分辨率 = 采样率 ÷ FFT 点数、频点数 = 点数 ÷ 2、窗口 = 点数 ÷ 采样率 复核。同一台设备换不同采样率,能分辨的间隔会跟着变。

采样率FFT 点数频率分辨率频点数时域窗口
44,100 Hz102443.07 Hz51223.22 ms
44,100 Hz204821.53 Hz1,02446.44 ms
44,100 Hz409610.77 Hz2,04892.88 ms
48,000 Hz102446.88 Hz51221.33 ms
48,000 Hz(默认)2048(默认)23.44 Hz1,02442.67 ms
48,000 Hz409611.72 Hz2,04885.33 ms
16,000 Hz102415.63 Hz51264.00 ms
16,000 Hz20487.81 Hz1,024128.00 ms
8,000 Hz51215.63 Hz25664.00 ms
8,000 Hz10247.81 Hz512128.00 ms
96,000 Hz204846.88 Hz1,02421.33 ms
96,000 Hz409623.44 Hz2,04842.67 ms
96,000 Hz819211.72 Hz4,09685.33 ms
48,000 Hz81925.86 Hz4,096170.67 ms
44,100 Hz81925.38 Hz4,096185.76 ms
192,000 Hz409646.88 Hz2,04821.33 ms

注意 96,000 Hz 那一档:采样率翻倍、点数不变时分辨率反而变粗(2048 点从 23.4 Hz 变成 46.9 Hz),因为 Nyquist 上限被抬到了 48 kHz,同样的频点数量要摊到更宽的频率范围上。想在高采样率下保持细分辨率,只能把点数同步加倍。

边界与权限说明

常见问题

点了「开始检测麦克风」没反应,或者提示拿不到接口?

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

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

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

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

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

音量条一直在动但听不到自己的声音,是坏了吗?

不是。这个麦克风测试只采集、不回放,因为把麦克风直接接到扬声器会形成啸叫。想确认扬声器,请用别的工具;这里只负责告诉你「麦克风有没有在收声、收到多大」。

为什么读数最高只到 −3 dBFS 左右?

因为满幅正弦波的均方根是峰值的 1/√2 ≈ 0.7071,代入 20 × log10(0.7071) 得 −3.01 dBFS。也就是说,单频信号贴着满幅时读数就是 −3 上下,只有方波这类 rms 等于峰值的波形才可能读到 0 dBFS。看到 −3 就说明已经到顶了,别再加增益。

频率分辨率 23.4 Hz 是什么意思,够用吗?

意思是两个频率相差小于 23.4 Hz 时,在这张图上会被合并成同一个频点,分不开。它以 采样率 ÷ FFT 点数 计算(48 kHz ÷ 2048 = 23.4 Hz)。人声基频大约 85~255 Hz,在这个分辨率下只对应第 4 到第 11 根柱子,做「有没有声音、大概在哪个频段」够用;做精细的频谱分析要把点数加到 4096 甚至更高。

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

可以,但更稳妥的是先点「停止并释放麦克风」。关闭标签页时浏览器也会释放采集,但如果你只是切到别的页面继续用,采集中会一直占用设备并显示录音标记。点这个按钮会执行 track.stop()、断开节点并关闭 AudioContext。

我的录音会被上传或保存吗?

不会。音频流只连接到一个 AnalyserNode,数据在内存里的两个字节数组之间流动,既不写入磁盘也不发请求。想自己验证,打开开发者工具的 Network 面板,点「开始检测麦克风」,可以看到没有任何请求发出。

相关工具