在Javascript中检查麦克风音量

27

我正在尝试制作一个需要访问用户麦克风的小游戏。我需要能够检查是否连接了麦克风,然后如果连接了,就需要在游戏期间检查通过麦克风传来的声音的音量。我该如何做到这一点?


两个原始答案指的是一个样本,但该样本已经不再适用(对我而言),因此我添加了一个新答案https://dev59.com/lFwX5IYBdhLWcg3w2SZq#50279260,其中包括具有数字和可视输出的麦克风音量表。 - www-0av-Com
4个回答

74

在自己摸索一番之后,我觉得提供稍微详细一些的答案可能会帮助其他人。以下代码将根据麦克风音量记录大约从0到100的数字。

navigator.mediaDevices.getUserMedia({
  audio: true,
  video: true
})
  .then(function(stream) {
    const audioContext = new AudioContext();
    const analyser = audioContext.createAnalyser();
    const microphone = audioContext.createMediaStreamSource(stream);
    const scriptProcessor = audioContext.createScriptProcessor(2048, 1, 1);

    analyser.smoothingTimeConstant = 0.8;
    analyser.fftSize = 1024;

    microphone.connect(analyser);
    analyser.connect(scriptProcessor);
    scriptProcessor.connect(audioContext.destination);
    scriptProcessor.onaudioprocess = function() {
      const array = new Uint8Array(analyser.frequencyBinCount);
      analyser.getByteFrequencyData(array);
      const arraySum = array.reduce((a, value) => a + value, 0);
      const average = arraySum / array.length;
      console.log(Math.round(average));
      // colorPids(average);
    };
  })
  .catch(function(err) {
    /* handle the error */
    console.error(err);
  });

如果您拥有这个数字 jQuery 来为颜色块设置样式。以下是我提供的示例函数,但那只是简单的部分。只需取消注释 color pids 函数即可。

function colorPids(vol) {
  const allPids = [...document.querySelectorAll('.pid')];
  const numberOfPidsToColor = Math.round(vol / 10);
  const pidsToColor = allPids.slice(0, numberOfPidsToColor);
  for (const pid of allPids) {
    pid.style.backgroundColor = "#e6e7e8";
  }
  for (const pid of pidsToColor) {
    // console.log(pid[i]);
    pid.style.backgroundColor = "#69ce2b";
  }
}

为了尽可能详细地回答,我也在下面附上了我的html和css,这样您就可以复制js html和css,如果您希望获得一个可运行的示例。

html:

<div class="pids-wrapper">
  <div class="pid"></div>
  <div class="pid"></div>
  <div class="pid"></div>
  <div class="pid"></div>
  <div class="pid"></div>
  <div class="pid"></div>
  <div class="pid"></div>
  <div class="pid"></div>
  <div class="pid"></div>
  <div class="pid"></div>
</div>

CSS:

.pids-wrapper{
  width: 100%;
}
.pid{
  width: calc(10% - 10px);
  height: 10px;
  display: inline-block;
  margin: 5px;
}

经过所有步骤之后,你最终将得到类似以下内容的东西。 enter image description here


音量级别使用此代码有时会超过100,甚至高达200。有什么办法可以确保它在0-100之间? - Egor Egorov
如何连接特定的音频设备? - Aamir Nakhwa
如何让它适用于特定设备而不是默认的内置扬声器? - Subhash Chandra
3
请注意,自2014年以来,“createScriptProcessor”已被弃用,取而代之的是“AudioWorklet”,希望能看到一个更新的示例。 - mrossman
@mrossman 我已经更新了答案,使用setTimeout代替。 - Minding
@mrossman 这是关于 AudioWorklet 的答案 https://dev59.com/OFIG5IYBdhLWcg3w2VMZ - david

18

这里是一个使用setTimeout代替废弃的createScriptProcessor函数的简单答案:

以下是使用setTimeout的示例代码:

(async () => {
  let volumeCallback = null;
  let volumeInterval = null;
  const volumeVisualizer = document.getElementById('volume-visualizer');
  const startButton = document.getElementById('start');
  const stopButton = document.getElementById('stop');
  // Initialize
  try {
    const audioStream = await navigator.mediaDevices.getUserMedia({
      audio: {
        echoCancellation: true
      }
    });
    const audioContext = new AudioContext();
    const audioSource = audioContext.createMediaStreamSource(audioStream);
    const analyser = audioContext.createAnalyser();
    analyser.fftSize = 512;
    analyser.minDecibels = -127;
    analyser.maxDecibels = 0;
    analyser.smoothingTimeConstant = 0.4;
    audioSource.connect(analyser);
    const volumes = new Uint8Array(analyser.frequencyBinCount);
    volumeCallback = () => {
      analyser.getByteFrequencyData(volumes);
      let volumeSum = 0;
      for(const volume of volumes)
        volumeSum += volume;
      const averageVolume = volumeSum / volumes.length;
      // Value range: 127 = analyser.maxDecibels - analyser.minDecibels;
      volumeVisualizer.style.setProperty('--volume', (averageVolume * 100 / 127) + '%');
    };
  } catch(e) {
    console.error('Failed to initialize volume visualizer, simulating instead...', e);
    // Simulation
    //TODO remove in production!
    let lastVolume = 50;
    volumeCallback = () => {
      const volume = Math.min(Math.max(Math.random() * 100, 0.8 * lastVolume), 1.2 * lastVolume);
      lastVolume = volume;
      volumeVisualizer.style.setProperty('--volume', volume + '%');
    };
  }
  // Use
  startButton.addEventListener('click', () => {
    // Updating every 100ms (should be same as CSS transition speed)
    if(volumeCallback !== null && volumeInterval === null)
      volumeInterval = setInterval(volumeCallback, 100);
  });
  stopButton.addEventListener('click', () => {
    if(volumeInterval !== null) {
      clearInterval(volumeInterval);
      volumeInterval = null;
    }
  });
})();
div {
  --volume: 0%;
  position: relative;
  width: 200px;
  height: 20px;
  margin: 50px;
  background-color: #DDD;
}

div::before {
   content: '';
   position: absolute;
   top: 0;
   bottom: 0;
   left: 0;
   width: var(--volume);
   background-color: green;
   transition: width 100ms linear;
}

button {
  margin-left: 50px;
}

h3 {
  margin: 20px;
  font-family: sans-serif;
}
<h3><b>NOTE:</b> This is not accurate on stackoverflow, since microphone use is not permitted. It's a simulation instead.</h3>
<div id="volume-visualizer"></div>
<button id="start">Start</button>
<button id="stop">Stop</button>

这也意味着它可以很容易地按需启动和停止。


嗨!刚刚偶然看到了你的答案。我正在寻找一个不包括 createScriptProcessor 的答案。但是我还没有理解为什么 minDecibels 和 maxDecibels 以及 averageVolume 的计算,即 (averageVolume * 100 / 127)。这表示什么意思?任何理解这段代码的人请帮帮我。提前感谢你们。 - Shailesh B
1
@ShaileshB averageVolume 只是 volumes 的平均值。为了获取用于显示响度的百分比,需要将 averageVolume 除以最大可能值(偏移量)。要确定这一点,您需要从最大可能值(原始值)maxDecibels 中减去最小可能值(原始值)minDecibels。请注意,所有音量都是 minDecibels 的偏移量。 - Minding
为什么您选择将最大可能值设为127作为响度的特定原因吗? - Peter Toth
1
@PeterToth老实说不是,但你可以参考AnalyserNode文档中的getByteFrequencyData()minDecibelsmaxDecibels来选择适合你的数值。 - undefined

8
这是检测音频控件是否可用的代码片段(提取自:https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getUserMedia)。
navigator.getUserMedia(constraints, successCallback, errorCallback);

这是一个使用getUserMedia函数的示例,它将使您可以访问麦克风。

navigator.getUserMedia = navigator.getUserMedia ||
                     navigator.webkitGetUserMedia ||
                     navigator.mozGetUserMedia;

if (navigator.getUserMedia) {
   navigator.getUserMedia({ audio: true, video: { width: 1280, height: 720 } },
      function(stream) {
         console.log("Accessed the Microphone");
      },
      function(err) {
         console.log("The following error occured: " + err.name);
      }
    );
} else {
   console.log("getUserMedia not supported");
}

这是一个演示所需“输入音量”的代码库。 https://github.com/cwilso/volume-meter/

非常感谢。这真的很有帮助! - Jacob Pickens
7
OP说:“检查通过麦克风传输的声音音量。” 我不明白这个答案如何有助于实现它。 - Maria Ines Parnisari
他最初的问题是想知道麦克风是如何连接的,然后如果已连接,就要检查通过麦克风传来的声音的音量,我提供了一个演示这一点的存储库(你也提供了)。 - ioneyed
看起来这在Firefox中可以工作,但在Chrome中不行(Chrome中没有绘制音量计)。 - Philipp Lenssen

5

简单的麦克风音量表,请参见https://codepen.io/www-0av-com/pen/jxzxEX

已经在2018年进行了检查和工作,包括由Chrome浏览器安全更新引起的错误修复。

HTML <h3>来自麦克风输入的VU表(getUserMedia API)</h3> <button onclick="startr();" title="点击开始,因为浏览器的安全性增加,只有在页面手势上启动语音麦克风才能启动">开始</button> <canvas id="canvas" width="150" height="300" style='background:blue'></canvas> <br> 点击开始 <div align=left>请查看JS以获取归属信息</div>

CSS

body {
  color: #888;
  background: #262626;
  margin: 0;
  padding: 40px;
  text-align: center;
  font-family: "helvetica Neue", Helvetica, Arial, sans-serif;
}

#canvas {
  width: 150px;
  height: 100px;
  position: absolute;
  top: 150px;
  left: 45%;
  text-align: center;
}

JS(需要 JQuery)

// Courtesy www/0AV.com, LGPL license or as set by forked host, Travis Holliday, https://codepen.io/travisholliday/pen/gyaJk 
function startr(){
 console.log ("starting...");
 navigator.getUserMedia = navigator.getUserMedia ||
   navigator.webkitGetUserMedia ||
   navigator.mozGetUserMedia;
 if (navigator.getUserMedia) {
  navigator.getUserMedia({
      audio: true
    },
    function(stream) {
      audioContext = new AudioContext();
      analyser = audioContext.createAnalyser();
      microphone = audioContext.createMediaStreamSource(stream);
      javascriptNode = audioContext.createScriptProcessor(2048, 1, 1);

      analyser.smoothingTimeConstant = 0.8;
      analyser.fftSize = 1024;

      microphone.connect(analyser);
      analyser.connect(javascriptNode);
      javascriptNode.connect(audioContext.destination);

      canvasContext = $("#canvas")[0].getContext("2d");

      javascriptNode.onaudioprocess = function() {
          var array = new Uint8Array(analyser.frequencyBinCount);
          analyser.getByteFrequencyData(array);
          var values = 0;

          var length = array.length;
          for (var i = 0; i < length; i++) {
            values += (array[i]);
          }

          var average = values / length;

//          console.log(Math.round(average - 40));

          canvasContext.clearRect(0, 0, 150, 300);
          canvasContext.fillStyle = '#BadA55';
          canvasContext.fillRect(0, 300 - average, 150, 300);
          canvasContext.fillStyle = '#262626';
          canvasContext.font = "48px impact";
          canvasContext.fillText(Math.round(average - 40), -2, 300);
          // console.log (average);
        } // end fn stream
    },
    function(err) {
      console.log("The following error occured: " + err.name)
    });
} else {
  console.log("getUserMedia not supported");
 }
}

有什么办法可以让它在Safari上运行吗?或者为什么它不能运行? - Ann
@Ann,我猜可能是因为在不同的操作系统上处理I/O时完全不同。例如:Linux通常在与MS兼容的硬件上运行,因此我想这在Chrome风格的浏览器下在MS或Linux下工作,但是苹果有完全不同的硬件。不幸的是,我没有时间去研究它(此外,您没有提到是iPhone / iPad还是Mac),但我会从谷歌搜索“audioContext.createMediaStreamSource在safari上无法工作”开始...这立即证实了你并不孤单。 - www-0av-Com
@Ann 对于Safari浏览器,使用audioContext = new (window.AudioContext || window.webkitAudioContext)();代替audioContext = new AudioContext(); - Jan Misker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接