开发直播类的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.jsconst SMOOTHING_FACTOR = 0.8class VolumeMeter extends AudioWorkletProcessor {  static get parameterDescriptors() {    return []  }  constructor() {    super()    this.volume = 0    this.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 time      this.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 object    audioContext = new AudioContext()    // Creating a MediaStreamSource object and sending a MediaStream object granted by the user    let microphone = audioContext.createMediaStreamSource(stream)    await audioContext.audioWorklet.addModule('processor.js')    // Creating AudioWorkletNode sending    // context and name of processor registered    // in vumeter-processor.js    const node = new AudioWorkletNode(audioContext, 'vumeter')    // Listing any message from AudioWorkletProcessor in its    // process method here where you can know    // the volume level    node.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 audioContext    microphone.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 microphone        try {            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 object        audioContext = new AudioContext()        // Creating a MediaStreamSource object and sending a MediaStream object granted by the user        let microphone = audioContext.createMediaStreamSource(stream)        await audioContext.audioWorklet.addModule('processor.js')        // Creating AudioWorkletNode sending        // context and name of processor registered        // in vumeter-processor.js        const node = new AudioWorkletNode(audioContext, 'vumeter')        // Listing any message from AudioWorkletProcessor in its        // process method here where you can know        // the volume level        node.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 audioContext        microphone.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

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