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
This commit is contained in:
hta@webrtc.org 2013-12-11 08:36:16 +00:00
parent 77507eff4f
commit c4038d795d
2 changed files with 104 additions and 55 deletions

View File

@ -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");

View File

@ -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);
}
</script>
<style>
button {
font: 14px sans-serif;
padding: 8px;
}
canvas {
border:1px solid #000000;
}
</style>
</head>
<body onload="onload()">
@ -111,16 +156,16 @@
using WebAudio.<br>
Press Start, select a microphone, listen to your own voice in loopback,
and see the numbers change as you speak.</p>
<style>
button {
font: 14px sans-serif;
padding: 8px;
}
</style>
The "instant" volume changes approximately every 50 ms; the "slow"
volume approximates the average volume over about a second.
<audio id="audio" autoplay="autoplay" controls="controls"></audio><br><br>
<button id="start" onclick="start()">Start</button>
<button id="stop" onclick="stop()">Stop</button><br><br>
Volume (instant): <span id="volume">Not set</span><br>
Volume (slow): <span id="decaying_volume">Not set</span>
Volume (slow): <span id="decaying_volume">Not set</span><br>
<canvas id="graphic_volume" width="400" height="20"></canvas> Volume<br>
<canvas id="graphic_slow" width="400" height="20"></canvas> Slow<br>
<canvas id="graphic_clip" width="400" height="20"></canvas> Clipping
</body>
</html>