From 4bc3599e0ac40f6e3b8762cc3ec5f1b13a0c1b19 Mon Sep 17 00:00:00 2001 From: Ian Bishop <1296987+porjo@users.noreply.github.com> Date: Thu, 6 Apr 2023 21:37:15 +1000 Subject: [PATCH] Update volume example to use web audio API --- src/content/getusermedia/volume/index.html | 3 +- src/content/getusermedia/volume/js/main.js | 31 +++---- .../getusermedia/volume/js/soundmeter.js | 85 +++++++++---------- 3 files changed, 55 insertions(+), 64 deletions(-) diff --git a/src/content/getusermedia/volume/index.html b/src/content/getusermedia/volume/index.html index 88873e958e..101a2d8aad 100644 --- a/src/content/getusermedia/volume/index.html +++ b/src/content/getusermedia/volume/index.html @@ -65,7 +65,7 @@

WebRTC about a second.

Note that you will not hear your own voice; use the local audio rendering demo for that.

-

The audioContext, stream and soundMeter variables are in global scope, so +

The audioContext and stream variables are in global scope, so you can inspect them from the console.

WebRTC - diff --git a/src/content/getusermedia/volume/js/main.js b/src/content/getusermedia/volume/js/main.js index c0e2f457c3..263838afd9 100644 --- a/src/content/getusermedia/volume/js/main.js +++ b/src/content/getusermedia/volume/js/main.js @@ -31,25 +31,23 @@ const constraints = window.constraints = { let meterRefresh = null; -function handleSuccess(stream) { +async function handleSuccess(stream) { // Put variables in global scope to make them available to the // browser console. window.stream = stream; - const soundMeter = window.soundMeter = new SoundMeter(window.audioContext); - soundMeter.connectToSource(stream, function(e) { - if (e) { - alert(e); - return; - } - meterRefresh = setInterval(() => { - instantMeter.value = instantValueDisplay.innerText = - soundMeter.instant.toFixed(2); - slowMeter.value = slowValueDisplay.innerText = - soundMeter.slow.toFixed(2); - clipMeter.value = clipValueDisplay.innerText = - soundMeter.clip; - }, 200); - }); + await audioContext.audioWorklet.addModule("js/soundmeter.js"); + const soundmeterNode = new AudioWorkletNode(audioContext,"soundmeter"); + const source = audioContext.createMediaStreamSource(stream); + source.connect(soundmeterNode).connect(audioContext.destination); + + soundmeterNode.port.onmessage = e => { + instantMeter.value = instantValueDisplay.innerText = + e.data.instant.toFixed(2); + slowMeter.value = slowValueDisplay.innerText = + e.data.slow.toFixed(2); + clipMeter.value = clipValueDisplay.innerText = + e.data.clip; + } } function handleError(error) { @@ -81,7 +79,6 @@ function stop() { stopButton.disabled = true; window.stream.getTracks().forEach(track => track.stop()); - window.soundMeter.stop(); window.audioContext.close(); clearInterval(meterRefresh); instantMeter.value = instantValueDisplay.innerText = ''; diff --git a/src/content/getusermedia/volume/js/soundmeter.js b/src/content/getusermedia/volume/js/soundmeter.js index 20c5ae9c46..c0a9375a62 100644 --- a/src/content/getusermedia/volume/js/soundmeter.js +++ b/src/content/getusermedia/volume/js/soundmeter.js @@ -1,62 +1,57 @@ /* - * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. + * Copyright (c) 2023 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ -'use strict'; - // Meter class that generates a number correlated to audio volume. // The meter class itself displays nothing, but it makes the // instantaneous and time-decaying volumes available for inspection. // It also reports on the fraction of samples that were at or near // the top of the measurement range. -function SoundMeter(context) { - this.context = context; - this.instant = 0.0; - this.slow = 0.0; - this.clip = 0.0; - this.script = context.createScriptProcessor(2048, 1, 1); - const that = this; - this.script.onaudioprocess = function(event) { - const input = event.inputBuffer.getChannelData(0); - let i; - let sum = 0.0; - let clipcount = 0; - for (i = 0; i < input.length; ++i) { - sum += input[i] * input[i]; - if (Math.abs(input[i]) > 0.99) { - clipcount += 1; +class SoundMeter extends AudioWorkletProcessor { + _instant + _slow + _clip + _updateIntervalInMS + _nextUpdateFrame + + constructor() { + super(); + + this._instant=0.0; + this._slow=0.0; + this._clip=0.0; + this._updateIntervalInMS = 50; + this._nextUpdateFrame = this._updateIntervalInMS; + } + + process(inputs, outputs, parameters) { + const input = inputs[0]; + if (input.length > 0) { + const samples = input[0]; + let sum = 0.0; + let clipcount = 0; + for (let i = 0; i < samples.length; ++i) { + sum += samples[i] * samples[i]; + if (Math.abs(samples[i]) > 0.99) { + clipcount += 1; + } } - } - that.instant = Math.sqrt(sum / input.length); - that.slow = 0.95 * that.slow + 0.05 * that.instant; - that.clip = clipcount / input.length; - }; -} + this._instant = Math.sqrt(sum / samples.length); + this._slow = 0.95 * this._slow + 0.05 * this._instant; + this._clip = clipcount / samples.length; -SoundMeter.prototype.connectToSource = function(stream, callback) { - console.log('SoundMeter connecting'); - try { - this.mic = this.context.createMediaStreamSource(stream); - this.mic.connect(this.script); - // necessary to make sample run, but should not be. - this.script.connect(this.context.destination); - if (typeof callback !== 'undefined') { - callback(null); - } - } catch (e) { - console.error(e); - if (typeof callback !== 'undefined') { - callback(e); + this._nextUpdateFrame -= samples.length; + if (this._nextUpdateFrame < 0) { + this._nextUpdateFrame += this._updateIntervalInMS / 1000 * sampleRate; + this.port.postMessage({instant: this._instant, slow: this._slow, clip: this._clip}); + } } + return true; } -}; +} -SoundMeter.prototype.stop = function() { - console.log('SoundMeter stopping'); - this.mic.disconnect(); - this.script.disconnect(); -}; +registerProcessor("soundmeter", SoundMeter);