浏览器检测麦克风音量

article/2025/9/29 16:05:54

在这里插入图片描述

开发直播类的Web应用时在开播前通常需要检测设备是否正常,本文就来介绍一下如果如何做麦克风音量的可视化。

AudioWorklet出现的背景

做这个功能需要用到 Chrome 的 AudioWorklet。

Web Audio API 中的音频处理运行在一个单独的线程,这样才会比较流畅。之前提议处理音频使用audioContext.createScriptProcessor,但是它被设计成了异步的形式,随之而来的问题就是处理会出现 “延迟”。

所以 AudioWorklet 就诞生了,用来取代 createScriptProcessor。

AudioWorklet 可以很好的把用户提供的JS代码集成到音频处理的线程中,不需要跳到主线程处理音频,这样就保证了0延迟和同步渲染。

使用条件

使用 Audio Worklet 由两个部分组成: AudioWorkletProcessor 和 AudioWorkletNode.

  • AudioWorkletProcessor 代表了真正的处理音频的 JS 代码,运行在 AudioWorkletGlobalScope 中。

  • AudioWorkletNode 与 AudioWorkletProcessor 对应,起到连接主线程 AudioNodes 的作用。

编写代码

首先来写AudioWorkletProcessor,即用于处理音频的逻辑代码,放在一个单独的js文件中,命名为 processor.js,它将运行在一个单独的线程。

// processor.js
const SMOOTHING_FACTOR = 0.8class VolumeMeter extends AudioWorkletProcessor {static get parameterDescriptors() {return []}constructor() {super()this.volume = 0this.lastUpdate = currentTime}calculateVolume(inputs) {const inputChannelData = inputs[0][0]let sum = 0// Calculate the squared-sum.for (let i = 0; i < inputChannelData.length; ++i) {sum += inputChannelData[i] * inputChannelData[i]}// Calculate the RMS level and update the volume.const rms = Math.sqrt(sum / inputChannelData.length)this.volume = Math.max(rms, this.volume * SMOOTHING_FACTOR)// Post a message to the node every 200ms.if (currentTime - this.lastUpdate > 0.2) {this.port.postMessage({ eventType: "volume", volume: this.volume * 100 })// Store previous timethis.lastUpdate = currentTime}}process(inputs, outputs, parameters) {this.calculateVolume(inputs)return true}
}registerProcessor('vumeter', VolumeMeter); // 注册一个名为 vumeter 的处理函数 注意:与主线程中的名字对应。

封装成一个继承自AudioWorkletProcessor的类,VolumeMeter(音量表)。

主线程代码

// 告诉用户程序需要使用麦克风
function activeSound () {try {navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;navigator.getUserMedia({ audio: true, video: false }, onMicrophoneGranted, onMicrophoneDenied);} catch(e) {alert(e)}
}async function onMicrophoneGranted(stream) {// Initialize AudioContext objectaudioContext = new AudioContext()// Creating a MediaStreamSource object and sending a MediaStream object granted by the userlet microphone = audioContext.createMediaStreamSource(stream)await audioContext.audioWorklet.addModule('processor.js')// Creating AudioWorkletNode sending// context and name of processor registered// in vumeter-processor.jsconst node = new AudioWorkletNode(audioContext, 'vumeter')// Listing any message from AudioWorkletProcessor in its// process method here where you can know// the volume levelnode.port.onmessage  = event => {// console.log(event.data.volume) // 在这里就可以获取到processor.js 检测到的音量值handleVolumeCellColor(event.data.volume) // 处理页面效果函数}// Now this is the way to// connect our microphone to// the AudioWorkletNode and output from audioContextmicrophone.connect(node).connect(audioContext.destination)
}function onMicrophoneDenied() {console.log('denied')
}

处理页面展示逻辑

上面的代码我们已经可以获取到系统麦克风的音量了,现在的任务是把它展示在页面上。

准备页面结构和样式代码:

<style>.volume-group {width: 200px;height: 50px;background-color: black;display: flex;align-items: center;gap: 5px;padding: 0 10px;}.volume-cell {width: 10px;height: 30px;background-color: #e3e3e5;}
</style><div class="volume-group"><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div>
</div>

渲染逻辑:

/*** 该函数用于处理 volume cell 颜色变化*/
function handleVolumeCellColor(volume) {const allVolumeCells = [...volumeCells]const numberOfCells = Math.round(volume)const cellsToColored = allVolumeCells.slice(0, numberOfCells)for (const cell of allVolumeCells) {cell.style.backgroundColor = "#e3e3e5"}for (const cell of cellsToColored) {cell.style.backgroundColor = "#79c545"}
}

完整代码

下面贴上主线程完整代码,把它和processor.js放在同一目录运行即可。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>AudioContext</title><style>.volume-group {width: 200px;height: 50px;background-color: black;display: flex;align-items: center;gap: 5px;padding: 0 10px;}.volume-cell {width: 10px;height: 30px;background-color: #e3e3e5;}</style>
</head>
<body><div class="volume-group"><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div><div class="volume-cell"></div></div>
<script>function activeSound () {// Tell user that this program wants to use the microphonetry {navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;navigator.getUserMedia({ audio: true, video: false }, onMicrophoneGranted, onMicrophoneDenied);} catch(e) {alert(e)}}const volumeCells = document.querySelectorAll(".volume-cell")async function onMicrophoneGranted(stream) {// Initialize AudioContext objectaudioContext = new AudioContext()// Creating a MediaStreamSource object and sending a MediaStream object granted by the userlet microphone = audioContext.createMediaStreamSource(stream)await audioContext.audioWorklet.addModule('processor.js')// Creating AudioWorkletNode sending// context and name of processor registered// in vumeter-processor.jsconst node = new AudioWorkletNode(audioContext, 'vumeter')// Listing any message from AudioWorkletProcessor in its// process method here where you can know// the volume levelnode.port.onmessage  = event => {// console.log(event.data.volume)handleVolumeCellColor(event.data.volume)}// Now this is the way to// connect our microphone to// the AudioWorkletNode and output from audioContextmicrophone.connect(node).connect(audioContext.destination)}function onMicrophoneDenied() {console.log('denied')}/*** 该函数用于处理 volume cell 颜色变化*/function handleVolumeCellColor(volume) {const allVolumeCells = [...volumeCells]const numberOfCells = Math.round(volume)const cellsToColored = allVolumeCells.slice(0, numberOfCells)for (const cell of allVolumeCells) {cell.style.backgroundColor = "#e3e3e5"}for (const cell of cellsToColored) {cell.style.backgroundColor = "#79c545"}}activeSound()
</script>
</body>
</html>

参考文档

Enter Audio Worklet

文章到此结束。如果对你有用的话,欢迎点赞,谢谢。

文章首发于 IICOOM-个人博客|技术博客 - 浏览器检测麦克风音量


http://chatgpt.dhexx.cn/article/WLSblj4X.shtml

相关文章

最大信息系数(MIC)

童鞋们觉得文章不错&#xff0c;就麻烦点一下下面人工智能的教程链接吧&#xff0c;然后随便翻阅一下 https://www.captainbed.net/qtlyx MIC&#xff08;Maximal information coefficient&#xff09;一个很神奇的东西&#xff0c;源自于2011年发在sicence上的一个论文。 学…

麦克风基础参数

麦克风由Microphone读音翻译的&#xff0c;正确的翻译为微音器&#xff0c;或者称拾音器&#xff0c;传声器。我们的咪芯其实就是一只麦克风&#xff0c;只不过属于半成品。给它加网罩&#xff0c;手柄&#xff0c;开关&#xff0c;导线就成了一支完整的麦克风了。 我们生产的…

mic的灵敏度和声音的大小

今天查看麦克风的一些资料&#xff0c;发现这个博客写得很好&#xff0c;不知道这样转载是否可以。 mic的灵敏度和声音的大小 我对于mic的灵敏度一直无法说出其准确定义的表述。我看过mic的sepc上面都是标注的负的多少dB&#xff0c;比如-42dB。所以我一直认为-30dB的mic的灵…

MIC(最大信息系数)

MIC 我在论文使用MIC来衡量两个基因之间的关联程度&#xff0c;线性或非线性关系&#xff0c;相较于Mutual Information&#xff08;MI&#xff09;互信息而言有更高的准确度巴拉巴拉的&#xff0c;按作者的话说总之比其他的方式好。 原文参照&#xff1a; Detecting Novel As…

MIC相关基础知识

一、mic及声音的指标&#xff1a; &#xff08;1&#xff09;声音的分贝&#xff08;dB&#xff09; 分贝 Decibel 分贝&#xff08;dB&#xff09;是一个对数单位&#xff08;logarithmic unit&#xff09;, 它和很多常见的单位如“米”&#xff0c;“秒”或者“千克”等物理单…

MIC - 最大信息系数

MIC 文章目录 MIC前言MIC介绍MIC库Python实例MIC缺陷参考文章 前言 皮尔逊相关系数即我们通常说的(线性)相关系数&#xff0c;是用来反映两个变量线性相关程度的统计量&#xff0c;变化范围为-1到1。 系数的值为1意味着X和Y可以很好的由直线方程来进行描述&#xff0c;所有的…

MIC:最大信息系数

目录 1. 概念 1.1 MIC 1.2 互信息 2. MIC的优点 3. 算法原理 3.1 MIC公式原理 3.2 MIC计算步骤 &#xff08;1&#xff09;计算最大互信息值 &#xff08;2&#xff09;对最大的互信息值进行归一化 &#xff08;3&#xff09;选择不同尺度下互信息的最大值作为MIC值…

MIC 的指标解读

MIC 的指标解读 1.Sensitivity 灵敏度 麦克风是支持以声压信号为输入&#xff0c;最后转换为电信号的传感器。sensitivity是microphone 能够capture的最小声压信号&#xff0c;声压信号的单位为dBSPL.sensitivity 是ratio &#xff0c;是模拟输出电压或者数字输出值对于输入的…

MIC一般参数指标

SNR>68dB&#xff0c; 灵敏度>-34dB&#xff0c;频响范围&#xff1a;/-3dB &#xff08;300Hz-3kHz&#xff09;&#xff1b;失真度&#xff1a;<3% 麦克风的灵敏度高好还是低&#xff0c;要根据你使用的条件来选择。如果声源离麦克风较远&#xff0c;需用灵敏度高的…

Maximal Information Coefficient (MIC)最大互信息系数

MIC 我在论文使用MIC来衡量两个基因之间的关联程度&#xff0c;线性或非线性关系&#xff0c;相较于Mutual Information&#xff08;MI&#xff09;互信息而言有更高的准确度巴拉巴拉的&#xff0c;按作者的话说总之比其他的方式好。 原文参照&#xff1a; Detecting Novel A…

R+树

考虑R树的性能&#xff0c;其中覆盖(coverage)和重叠(overlap)两个概念很重要&#xff0c;因为R树查询是根据给定区域与当前MBR是否有交叉来判断, 因此覆盖和重叠都应当尽量小 覆盖小即MBR要小&#xff0c;最好刚好包围其中的数据点 (对于叶节点)或子MBR (对于非叶节点) 重叠…

R树及其应用场景

地理围栏&#xff08;Geo-fencing&#xff09;是LBS的一种应用&#xff0c;就是用一个虚拟的栅栏围出一个虚拟地理边界&#xff0c;当手机进入、离开某个特定地理区域&#xff0c;或在该区域内活动时&#xff0c;手机可以接收自动通知和警告。如下图所示&#xff0c;假设地图上…

R树与空间索引

B树或者B树可以非常好的处理一维空间存储的问题。B树是一棵平衡树&#xff0c;它是把一维直线分为若干段线段&#xff0c;当我们查找满足某个要求的点的时候&#xff0c;只要去查找它所属的线段即可。依我看来&#xff0c;这种思想其实就是先找一个大的空间&#xff0c;再逐步缩…

R语言学习(三)——决策树分类

分类 分类&#xff08;Classification&#xff09;任务就是通过学习获得一个目标函数&#xff08;Target Function&#xff09;f, 将每个属性集x映射到一个预先定义好的类标号y。 分类任务的输入数据是记录的集合&#xff0c;每条记录也称为实例或者样例。用元组(X,y)表示&am…

空间数据索引RTree(R树)完全解析及Java实现

本文是在https://www.cnblogs.com/cmi-sh-love/p/kong-jian-shud-ju-suo-yinRTree-wan-quan-jie-xi-jiJa.html?share_tokene5b096d7-6dbf-4839-9992-b29913335ba9基础上进行修改和补充的。 第一部分 空间数据的背景介绍 空间数据的建模 基于实体的模型&#xff08;基于对象…

最小生成树:kruskal算法的R语言实现

以如下图为例 library(hash)#需要用到hash包 Nodes<-c("A","B","C","D","E","F","G") #创建存放顶点的向量 edges<- data.frame(startcharacter(),endcharacter(),lengthnumeric(),stringsAsFa…

【转】R树

R树在数据库等领域做出的功绩是非常显著的。它很好的解决了在高维空间搜索等问题。举个R树在现实领域中能够解决的例子吧&#xff1a;查找20英里以内所有的餐厅。如果没有R树你会怎么解决&#xff1f;一般情况下我们会把餐厅的坐标(x,y)分为两个字段存放在数据库中&#xff0c;…

R语言实现决策树

R语言实现决策树 提示&#xff1a;本文使用R语言实现决策树&#xff0c;并对决策树结构图进行美化 文章目录 R语言实现决策树数据介绍一、相关R包的下载二、实现过程1.数据读取2.训练集与验证集划分3.构建决策树并绘制图形4.测试模型 总结 数据介绍 group就是分类结果&#x…

决策树与R语言(RPART)

关于决策树理论方面的介绍&#xff0c;李航的《统计机器学习》第五章有很好的讲解。 传统的ID3和C4.5一般用于分类问题&#xff0c;其中ID3使用信息增益进行特征选择&#xff0c;即递归的选择分类能力最强的特征对数据进行分割&#xff0c;C4.5唯一不同的是使用信息增益比进行…

经典查找算法 --- R树

R树&#xff1a;处理空间存储问题 -->是引用别人的文章 相信经过上面第一节的介绍&#xff0c;你已经对B树或者B树有所了解。这种树可以非常好的处理一维空间存储的问题。B树是一棵平衡树&#xff0c;它是把一维直线分为若干段线段&#xff0c;当我们查找满足某个要求的点的…