开发直播类的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-个人博客|技术博客 - 浏览器检测麦克风音量

浏览器检测麦克风音量相关推荐

  1. java测量麦克风音量_Android-使用mediarecorder类获取当前麦克风音量值

    获取当前麦克风音量 2014年12月22日 0:21 首先是界面: 检测麦克风当前音量主要是用Android自己的类MediaRecorder来实现的,很多函数都是可以直接使用的,所以实现检测麦克风音 ...

  2. 实时监测麦克风音量及检测MIC

    调用的技术: Web Audio API(navigator.getUserMedia(),目前推荐使用最新的接口MediaDevices.getUserMedia()) RecordRTC(用来录音 ...

  3. JS 调用 麦克风 检测实时音量

    代码较多 看第二份调用代码 里面有注释 关键步骤 1.将成功调用麦克风后的 媒体轨道保存 (第二份代码) 2. 关闭的时候便利轨道数组 逐个关闭 (第三份代码) 封装部分 直接调用 封装代码 . /* ...

  4. 一些js代码,自己备用的。高手不要笑话我。。(跨浏览器基础事件,浏览器检测,判断浏览器的名称、版本号、操作系统)...

    跨浏览器基础事件 View Code 1 //跨浏览器添加事件 2 function addEvent(obj, type, fn) { 3 if (obj.addEventListener) { 4 ...

  5. 对象检测和浏览器检测

    对象检测是避免浏览器问题的最佳助手.不依赖浏览器种类和版本,依赖浏览器能力,一旦知道访问者的浏览器支持某些对象,就能安全启动脚本. function isCompatible(other) {     ...

  6. 一个前端博客(9)——浏览器检测和加载

    浏览器检测 浏览器检测是通过JavaScript BOM的navigator对象实现的. Navigator.userAgent W3C上:userAgent 属性是一个只读的字符串,声明了浏览器用于 ...

  7. 解决Realtek High Definition Audio麦克风音量过低的简易方法

    新装了Windows10,一切都很顺利. 直到有一天,想用耳麦跟人对话时,对方一直抱怨说我的声音太低了.检查了所有设备,运转都正常.打开声音设备管理,发现麦克风的输出音量果然很低,把麦克风的音量调至最 ...

  8. JavaScript做浏览器检测

    最近看了一些面试题,有些还比较有意思,所以拿出来分享一下 1.最短IE浏览器检测 if(!-[1,])//我试了多次,在windows10的ie不行啊console.log("这是ie浏览器 ...

  9. ios 调整麦克风音量_ios 调整麦克风音量_如何在ios 7中获得麦克风音量?

    有一个观点来获得ios 7中的麦克风音量? NSURL *url = [NSURL fileURLWithPath:@"/dev/null"]; NSDictionary *set ...

最新文章

  1. YOLO-v5训练自己的数据+TensorRT推理部署(2)
  2. 《数学之美》第23章 布隆过滤器
  3. 漫画:如何螺旋遍历二维数组?
  4. 201421123042 《Java程序设计》第8周学习总结
  5. 下划线间隔数字 排序_面试必备:经典算法动画解析之希尔排序
  6. (转)ArcObjects SDK(AE)10.1在vs2012安装的方法
  7. python爬虫-Python爬虫入门这一篇就够了
  8. 2021-08-30
  9. URPF - 单播逆向路径转发
  10. [技术讨论]关于前几天公布的京东bug上的问题分析
  11. 橙光游戏软件 怎么整体测试,橙光游戏怎么让编辑来审核?
  12. 在SVN安装目录的bin文件夹下没有找到svn.exe
  13. 百度搜索框的测试点:
  14. mysql查询第10到第20条记录_“取出数据表中第10条到第20条记录”的sql语句+selecttop用法...
  15. HYSBZ 1588 营业额统计 伸展树
  16. anaconda打不开怎么办
  17. Java毕设项目藏宝阁游戏交易系统(java+VUE+Mybatis+Maven+Mysql)
  18. 新AlphaGo首度揭秘:单机运行,4个TPU,算法更强(专访+演讲)
  19. 推荐最适合IT人自学的视频网站、社区网站
  20. Ubuntu配置静态IP地址

热门文章

  1. 如何实现 HTTP 断点续传多线程下载
  2. 【可解释性机器学习】详解Python的可解释机器学习库:SHAP
  3. linux内存调节之CMA
  4. 【ArcGIS】空间数据库的迁移
  5. 在windows10基础上安装ubuntu16.04——解决分区时找不到用于efi启动项的问题
  6. LC-1033. 移动石子直到连续(分类讨论)
  7. C++学习(六十八)超线程
  8. PTA 1014 福尔摩斯的约会(Python版)
  9. 分而治之策略-第m小的数(c++、java、python)
  10. 分享18个程序员最喜欢逛的国外论坛