From c4038d795d8935eab1fda87c03e48c785ed7f157 Mon Sep 17 00:00:00 2001 From: "hta@webrtc.org" Date: Wed, 11 Dec 2013 08:36:16 +0000 Subject: [PATCH] Rewriting the SoundMeter class to be RMS and be encapsulated differently This CL changes the SoundMeter to be root-mean-square. It also changes the interface between the meter and the display to be based on the display calling down to the meter rather than the meter calling up to the display. A graphic display of the results is also added. BUG= R=cwilso@google.com, dutton@google.com, henrika@webrtc.org, juberti@webrtc.org Review URL: https://webrtc-codereview.appspot.com/5439004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@5256 4adac7df-926f-26a2-2b94-8c16560cd09d --- samples/js/base/adapter.js | 16 +- samples/js/demos/html/local-audio-volume.html | 143 ++++++++++++------ 2 files changed, 104 insertions(+), 55 deletions(-) diff --git a/samples/js/base/adapter.js b/samples/js/base/adapter.js index 803ef150cb..e99a16ea6e 100644 --- a/samples/js/base/adapter.js +++ b/samples/js/base/adapter.js @@ -77,13 +77,17 @@ if (navigator.mozGetUserMedia) { }; // Fake get{Video,Audio}Tracks - MediaStream.prototype.getVideoTracks = function() { - return []; - }; + if (!MediaStream.prototype.getVideoTracks) { + MediaStream.prototype.getVideoTracks = function() { + return []; + }; + } - MediaStream.prototype.getAudioTracks = function() { - return []; - }; + if (!MediaStream.prototype.getAudioTracks) { + MediaStream.prototype.getAudioTracks = function() { + return []; + }; + } } else if (navigator.webkitGetUserMedia) { console.log("This appears to be Chrome"); diff --git a/samples/js/demos/html/local-audio-volume.html b/samples/js/demos/html/local-audio-volume.html index 57adf846d0..476825632c 100644 --- a/samples/js/demos/html/local-audio-volume.html +++ b/samples/js/demos/html/local-audio-volume.html @@ -9,7 +9,52 @@ var buttonStart; var buttonStop; var localStream; - var soundMeter; + var reporter; + var audioContext; + + // 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.volume = 0.0; + this.slow_volume = 0.0; + this.clip = 0.0; + this.script = context.createScriptProcessor(2048, 1, 1); + that = this; + this.script.onaudioprocess = function(event) { + var input = event.inputBuffer.getChannelData(0); + var i; + var sum = 0.0; + var clipcount = 0; + for (i = 0; i < input.length; ++i) { + sum += input[i] * input[i]; + if (Math.abs(input[i]) > 0.99) { + clipcount += 1 + } + } + that.volume = Math.sqrt(sum / input.length); + that.slow_volume = 0.95 * that.slow_volume + 0.05 * that.volume; + that.clip = clipcount / input.length; + } + } + + SoundMeter.prototype.connectToSource = function(stream) { + console.log('SoundMeter connecting'); + 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); + } + + SoundMeter.prototype.stop = function() { + this.mic.disconnect(); + this.script.disconnect(); + } + + // End of SoundMeter class. $ = function(id) { return document.getElementById(id); @@ -26,11 +71,13 @@ buttonStart.enabled = true; buttonStop.enabled = false; localStream.stop(); + clearInterval(reporter); + soundMeter.stop(); } function gotStream(stream) { - videoTracks = stream.getVideoTracks(); - audioTracks = stream.getAudioTracks(); + var videoTracks = stream.getVideoTracks(); + var audioTracks = stream.getAudioTracks(); if (audioTracks.length == 1 && videoTracks.length == 0) { console.log('gotStream({audio:true, video:false})'); console.log('Using audio device: ' + audioTracks[0].label); @@ -42,18 +89,25 @@ }; localStream = stream; - soundMeter = new SoundMeter() - meter = $('volume'); - decaying_meter = $('decaying_volume'); - decaying_volume = 0.0; - soundMeter.showVolume = function(volume) { - meter.innerHTML = volume.toFixed(2); - decaying_volume = volume * 0.05 + decaying_volume * 0.95; - decaying_meter.innerHTML = decaying_volume.toFixed(2); - } - soundMeter.connect(stream) + var soundMeter = new SoundMeter(audioContext); + soundMeter.connectToSource(stream); + + // Set up reporting of the volume every 0.2 seconds. + var meter = $('volume'); + var decaying_meter = $('decaying_volume'); + var meter_canvas = $('graphic_volume').getContext('2d'); + var meter_slow = $('graphic_slow').getContext('2d'); + var meter_clip = $('graphic_clip').getContext('2d'); + reporter = setInterval(function() { + meter.textContent = soundMeter.volume.toFixed(2); + decaying_meter.textContent = soundMeter.slow_volume.toFixed(2); + paintMeter(meter_canvas, soundMeter.volume); + paintMeter(meter_slow, soundMeter.slow_volume); + paintMeter(meter_clip, soundMeter.clip); + }, 200); } else { - alert('The media stream contains an invalid amount of audio tracks.'); + alert('The media stream contains an invalid amount of tracks:' + + audioTracks.length + ' audio ' + videoTracks.length + ' video'); stream.stop(); } } @@ -65,6 +119,12 @@ } function onload() { + try { + window.AudioContext = window.AudioContext || window.webkitAudioContext; + audioContext = new AudioContext(); + } catch(e) { + alert('Web Audio API not found'); + } audioElement = $('audio'); buttonStart = $('start'); buttonStop = $('stop'); @@ -72,37 +132,22 @@ buttonStop.disabled = true; } - // Meter class that generates a number. - function SoundMeter() { - this.context = new webkitAudioContext(); - this.volume = 0.0; - } - - SoundMeter.prototype.connect = function(stream) { - console.log('SoundMeter connecting'); - this.mic = this.context.createMediaStreamSource(stream); - this.script = this.context.createScriptProcessor(1024, 1, 1); - that = this; - this.script.onaudioprocess = function(event) { - var input = event.inputBuffer.getChannelData(0); - var i; - var sum = 0.0; - for (i = 0; i < input.length; ++i) { - sum += Math.abs(input[i]); - } - that.showVolume(sum / input.length); - } - console.log('Buffer size ' + this.script.bufferSize); - this.mic.connect(this.script); - // Necessary to make sample run, but should not be. - this.script.connect(this.context.destination) - } - - SoundMeter.prototype.showVolume = function(volume) { - alert('Dummy showVolume called') + function paintMeter(context, number) { + context.clearRect(0, 0, 400, 20); + context.fillStyle = 'red'; + context.fillRect(0, 0, number * 400, 20); } + @@ -111,16 +156,16 @@ using WebAudio.
Press Start, select a microphone, listen to your own voice in loopback, and see the numbers change as you speak.

- + The "instant" volume changes approximately every 50 ms; the "slow" + volume approximates the average volume over about a second.



Volume (instant): Not set
- Volume (slow): Not set + Volume (slow): Not set
+ Volume
+ Slow
+ Clipping +