Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Volume with wasm #1588

Open
wants to merge 4 commits into
base: gh-pages
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/content/getusermedia/wasm-volume/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
emsdk
*~
3 changes: 3 additions & 0 deletions src/content/getusermedia/wasm-volume/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
js/wasm-soundmeter.js: js/wasm-soundmeter.cc
# Produces soundmeter.js, soundmeter.wasm and soundmeter.html
cd js; emcc --bind wasm-soundmeter.cc -s WASM=1 -o wasm-soundmeter.html
19 changes: 19 additions & 0 deletions src/content/getusermedia/wasm-volume/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Since this demo uses WebAssembly, the structure is a bit odd.

The WebAssembly and emscripten-produced Javascript is checked in,
and there's a script to setup and compile the source, which will
download the emscripten toolchain and install its prerequisites
if they are not present.

This toolchain will only run on Debian-derived Linux distros, but
the resulting WASM should run independent of platform.

To setup: Run `setup.sh`, and then `source emsdk/emsdk_env.sh`

To compile: Run `make`

The resulting content has to be served from a http: URL with the root
pointing above the "content" directory, because WASM fetching uses CORS,
and there are links to "../../.." in the HTML files here.


78 changes: 78 additions & 0 deletions src/content/getusermedia/wasm-volume/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!DOCTYPE html>
<!--
* Copyright (c) 2015 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.
-->
<html>
<head>

<meta charset="utf-8">
<meta name="description" content="WebRTC code samples">
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
<meta itemprop="description" content="Client-side WebRTC code samples">
<meta itemprop="image" content="../../../images/webrtc-icon-192x192.png">
<meta itemprop="name" content="WebRTC code samples">
<meta name="mobile-web-app-capable" content="yes">
<meta id="theme-color" name="theme-color" content="#ffffff">

<base target="_blank">

<title>WebAssembly Audio stream volume</title>

<link rel="icon" sizes="192x192" href="../../../images/webrtc-icon-192x192.png">
<link href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="../../../css/main.css" />
<link rel="stylesheet" href="css/main.css" />

</head>

<body>

<div id="container">

<h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a> <span>WebAssembly Audio stream volume</span></h1>

<p>Measure the volume of a local media stream using WebAudio and WebAssembly.</p>

<div id="meters">
<div id="instant">
<div class="label">Instant: </div>
<meter high="0.25" max="1" value="0"></meter>
<div class="value"></div>
</div>
<div id="slow">
<div class="label">Slow: </div>
<meter high="0.25" max="1" value="0"></meter>
<div class="value"></div>
</div>
<div id="wasm">
<div class="label">Wasm: </div>
<meter max="1" value="0"></meter>
<div class="value"></div>
</div>
</div>

<p>The 'instant' volume changes approximately every 50ms; the 'slow' volume approximates the average volume over about a second. This value is computed in Javascript.</p>
<p>The 'wasm' meter computes its value using WebAssembly.</p>
<p>Note that you will not hear your own voice; use the <a href="../audio">local audio rendering demo</a> for that.</p>
<p>The <code>audioContext</code>, <code>stream</code> and <code>soundMeter</code> variables are in global scope, so you can inspect them from the console.</p>

<a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/volume" title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>

</div>


<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="../../../js/common.js"></script>
<script src="js/js-soundmeter.js"></script>
<script src="js/main.js"></script>
<script src="js/wasm-soundmeter.js"></script>
<script src="js/wasm-soundmeter-wrapper.js"></script>

<script src="../../../js/lib/ga.js"></script>

</body>
</html>
60 changes: 60 additions & 0 deletions src/content/getusermedia/wasm-volume/js/js-soundmeter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2015 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);
var 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.instant = Math.sqrt(sum / input.length);
that.slow = 0.95 * that.slow + 0.05 * that.instant;
that.clip = clipcount / input.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);
}
}
};
SoundMeter.prototype.stop = function() {
this.mic.disconnect();
this.script.disconnect();
};
79 changes: 79 additions & 0 deletions src/content/getusermedia/wasm-volume/js/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2015 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.
*/

/* global AudioContext, SoundMeter */

'use strict';

// For WASM
var Module = {
locateFile: function(name) {
return 'js/' + name;
},
onRuntimeInitialized: function() {
navigator.mediaDevices.getUserMedia(constraints).
then(handleSuccess).catch(handleError);

},
};

var instantMeter = document.querySelector('#instant meter');
var slowMeter = document.querySelector('#slow meter');
var wasmMeter = document.querySelector('#wasm meter');

var instantValueDisplay = document.querySelector('#instant .value');
var slowValueDisplay = document.querySelector('#slow .value');
var wasmValueDisplay = document.querySelector('#wasm .value');

try {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
window.audioContext = new AudioContext();
} catch (e) {
alert('Web Audio API not supported.');
}

// Put variables in global scope to make them available to the browser console.
var constraints = window.constraints = {
audio: true,
video: false
};

function handleSuccess(stream) {
// Put variables in global scope to make them available to the
// browser console.
window.stream = stream;
var soundMeter = window.soundMeter = new SoundMeter(window.audioContext);
soundMeter.connectToSource(stream, function(e) {
if (e) {
alert(e);
return;
}
setInterval(function() {
instantMeter.value = instantValueDisplay.innerText =
soundMeter.instant.toFixed(2);
slowMeter.value = slowValueDisplay.innerText =
soundMeter.slow.toFixed(2);
}, 200);
});
var wasmSoundMeter = window.wasmSoundMeter = new WasmSoundMeter(window.audioContext);
wasmSoundMeter.connectToSource(stream, function(e) {
if (e) {
alert(e);
return;
}
setInterval(function() {
wasmMeter.value = wasmValueDisplay.innerText =
wasmSoundMeter.instant.toFixed(2);
}, 200);
});
}

function handleError(error) {
console.log('navigator.getUserMedia error: ', error);
}

57 changes: 57 additions & 0 deletions src/content/getusermedia/wasm-volume/js/wasm-soundmeter-wrapper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2015 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 WasmSoundMeter(context) {
this.context = context;
this.instant = 0.0;
this.slow = 0.0;
this.clip = 0.0;
this.script = context.createScriptProcessor(2048, 1, 1);
this.measurer = new Module.SoundMeter(2048);
var that = this;
this.script.onaudioprocess = function(event) {
var inputbuf = event.inputBuffer.getChannelData(0);
var asmbuf = that.measurer.data_buffer();
for (let i = 0; i < inputbuf.length; i++) {
asmbuf.set(i, inputbuf[i]);
}
that.measurer.process_data_buffer();
that.instant = that.measurer.get_fast_volume();
that.slow = that.measurer.get_slow_volume();
};
}

WasmSoundMeter.prototype.connectToSource = function(stream, callback) {
console.log('WASM 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);
}
}
};

WasmSoundMeter.prototype.stop = function() {
this.mic.disconnect();
this.script.disconnect();
};
49 changes: 49 additions & 0 deletions src/content/getusermedia/wasm-volume/js/wasm-soundmeter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Demonstration file for use of C++ objects across Emscripten
// into WebAssembly.
//
// It defines a "buffer" class with an 1-kbyte buffer, and a
// "sum" function that takes a buffer as argument.
// We'll then call this from Javascript.

// This uses the "Embind" method of defining the interface.
// http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/embind.html#embind

#include <math.h>
#include <emscripten/bind.h>

class SoundMeter {
public:
SoundMeter(size_t buffer_size)
: data_(buffer_size) {};
std::vector<float>* data_buffer() { return &data_; } // pass by reference
void process_data_buffer();
float get_fast_volume() { return decay_short_; }
float get_slow_volume() { return decay_long_; }
private:
float decay_long_;
float decay_short_;
std::vector<float> data_;
};

void SoundMeter::process_data_buffer() {
float sum = 0.0;
for (int i = 0; i < data_.size(); i++) {
sum += data_[i] * data_[i];
}
decay_short_ = sqrt(sum / data_.size());
decay_long_ = 0.95 * decay_long_ + 0.05 * decay_short_;
}

EMSCRIPTEN_BINDINGS(random_string) {
emscripten::class_<SoundMeter>("SoundMeter")
.constructor<size_t>()
.function("data_buffer", &SoundMeter::data_buffer, emscripten::allow_raw_pointers())
.function("process_data_buffer", &SoundMeter::process_data_buffer)
.function("get_fast_volume", &SoundMeter::get_fast_volume)
.function("get_slow_volume", &SoundMeter::get_slow_volume);
}

EMSCRIPTEN_BINDINGS(stl_wrappers) {
emscripten::register_vector<float>("VectorFloat");
}
Loading