This commit is contained in:
Hannes Verschore 2016-01-07 11:54:03 +01:00
Родитель dae7da33fa
Коммит 054b59358c
18 изменённых файлов: 5899 добавлений и 1 удалений

Просмотреть файл

@ -0,0 +1,20 @@
# webaudio-benchmark
## Run
Just open `index.html`. Time are in milliseconds, lower is better.
## Adding new benchmarks
Look into `benchmarks.js`, it's pretty straightforward.
## Grafana dashboard
When adding new benchmarks, run `node generate-grafana-dashboard.js`, this
should overwrite `webaudio.json` so that the new benchmarks are registered in
grafana.
## License
MPL 2.0

Просмотреть файл

@ -0,0 +1,323 @@
if (typeof(window) == "undefined") {
benchmarks = []
registerTestFile = function() {}
registerTestCase = function(o) { return benchmarks.push(o.name); }
}
registerTestFile("think-mono-48000.wav");
registerTestFile("think-mono-44100.wav");
registerTestFile("think-mono-38000.wav");
registerTestFile("think-stereo-48000.wav");
registerTestFile("think-stereo-44100.wav");
registerTestFile("think-stereo-38000.wav");
registerTestCase({
func: function () {
var oac = new OfflineAudioContext(1, 120 * samplerate, samplerate);
return oac;
},
name: "Empty testcase"
});
registerTestCase({
func: function () {
var oac = new OfflineAudioContext(1, 120 * samplerate, samplerate);
var source0 = oac.createBufferSource();
source0.buffer = getSpecificFile({rate: oac.samplerate, channels:1});
source0.loop = true;
source0.connect(oac.destination);
source0.start(0);
return oac;
},
name: "Simple gain test without resampling"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(2, 120 * samplerate, samplerate);
var source0 = oac.createBufferSource();
source0.buffer = getSpecificFile({rate: oac.samplerate, channels:2});
source0.loop = true;
source0.connect(oac.destination);
source0.start(0);
return oac;
},
name: "Simple gain test without resampling (Stereo)"
});
registerTestCase({
func: function () {
var oac = new OfflineAudioContext(2, 120 * samplerate, samplerate);
var source0 = oac.createBufferSource();
var panner = oac.createPanner();
source0.buffer = getSpecificFile({rate: oac.samplerate, channels:2});
source0.loop = true;
panner.setPosition(1, 2, 3);
panner.setOrientation(10, 10, 10);
source0.connect(panner);
panner.connect(oac.destination);
source0.start(0);
return oac;
},
name: "Simple gain test without resampling (Stereo and positional)"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(1, 120 * samplerate, samplerate);
var source0 = oac.createBufferSource();
source0.buffer = getSpecificFile({rate: 38000, channels:1});
source0.loop = true;
source0.connect(oac.destination);
source0.start(0);
return oac;
},
name: "Simple gain test"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(2, 120 * samplerate, samplerate);
var source0 = oac.createBufferSource();
source0.buffer = getSpecificFile({rate: 38000, channels:2});
source0.loop = true;
source0.connect(oac.destination);
source0.start(0);
return oac;
},
name: "Simple gain test (Stereo)"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(2, 120 * samplerate, samplerate);
var source0 = oac.createBufferSource();
var panner = oac.createPanner();
source0.buffer = getSpecificFile({rate: 38000, channels:2});
source0.loop = true;
panner.setPosition(1, 2, 3);
panner.setOrientation(10, 10, 10);
source0.connect(panner);
panner.connect(oac.destination);
source0.start(0);
return oac;
},
name: "Simple gain test (Stereo and positional)"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(2, 120 * samplerate, samplerate);
var source0 = oac.createBufferSource();
source0.buffer = getSpecificFile({rate: oac.samplerate, channels:1});
source0.loop = true;
source0.connect(oac.destination);
source0.start(0);
return oac;
},
name: "Upmix without resampling (Mono -> Stereo)"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(1, 120 * samplerate, samplerate);
var source0 = oac.createBufferSource();
source0.buffer = getSpecificFile({rate: oac.samplerate, channels:2});
source0.loop = true;
source0.connect(oac.destination);
source0.start(0);
return oac;
},
name: "Downmix without resampling (Mono -> Stereo)"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(2, 30 * samplerate, samplerate);
for (var i = 0; i < 100; i++) {
var source0 = oac.createBufferSource();
source0.buffer = getSpecificFile({rate: 38000, channels:1});
source0.loop = true;
source0.connect(oac.destination);
source0.start(0);
}
return oac;
},
name: "Simple mixing (same buffer)"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(2, 30 * samplerate, samplerate);
var reference = getSpecificFile({rate: 38000, channels:1}).getChannelData(0);
for (var i = 0; i < 100; i++) {
var source0 = oac.createBufferSource();
// copy the buffer into the a new one, so we know the implementation is not
// sharing them.
var b = oac.createBuffer(1, reference.length, 38000);
var data = b.getChannelData(0);
for (var j = 0; j < b.length; j++) {
data[i] = reference[i];
}
source0.buffer = b;
source0.loop = true;
source0.connect(oac.destination);
source0.start(0);
}
return oac;
},
name: "Simple mixing (different buffers)"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(2, 300 * samplerate, samplerate);
var gain = oac.createGain();
gain.gain.value = -1;
gain.connect(oac.destination);
var gainsi = [];
for (var i = 0; i < 4; i++) {
var gaini = oac.createGain();
gaini.gain.value = 0.25;
gaini.connect(gain);
gainsi[i] = gaini
}
for (var j = 0; j < 2; j++) {
var sourcej = oac.createBufferSource();
sourcej.buffer = getSpecificFile({rate: 38000, channels:1});
sourcej.loop = true;
sourcej.start(0);
for (var i = 0; i < 4; i++) {
var gainij = oac.createGain();
gainij.gain.value = 0.5;
gainij.connect(gainsi[i]);
sourcej.connect(gainij);
}
}
return oac;
},
name: "Simple mixing with gains"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(1, 30 * samplerate, samplerate);
var i,l;
var decay = 10;
var duration = 4;
var len = samplerate * duration;
var buffer = ac.createBuffer(2, len, oac.sampleRate)
var iL = buffer.getChannelData(0)
var iR = buffer.getChannelData(1)
// Simple exp decay loop
for(i=0,l=buffer.length;i<l;i++) {
iL[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / len, decay);
iR[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / len, decay);
}
var convolver = oac.createConvolver();
convolver.buffer = buffer;
convolver.connect(oac.destination);
var audiobuffer = getSpecificFile({rate: samplerate, channels:1});
var source0 = oac.createBufferSource();
source0.buffer = audiobuffer;
source0.loop = true;
source0.connect(convolver);
source0.start(0);
return oac;
},
name: "Convolution reverb"
});
registerTestCase({
func: function() {
var oac = new OfflineAudioContext(1, 30 * samplerate, samplerate);
var duration = 30 * samplerate;
var audiobuffer = getSpecificFile({rate: samplerate, channels:1});
var offset = 0;
while (offset < duration / samplerate) {
var grain = oac.createBufferSource();
var enveloppe = oac.createGain();
grain.connect(enveloppe);
enveloppe.connect(oac.destination);
grain.buffer = audiobuffer;
// get a random 100-ish ms with enveloppes
var start = offset * Math.random() * 0.5;
var end = start + 0.005 * (0.999 * Math.random());
grain.start(offset, start, end);
enveloppe.gain.setValueAtTime(offset, 0);
enveloppe.gain.linearRampToValueAtTime(.5, offset + 0.005);
var startRelease = Math.max(offset + (end - start), 0);
enveloppe.gain.setValueAtTime(0.5, startRelease);
enveloppe.gain.linearRampToValueAtTime(0.0, startRelease + 0.05);
// some overlap
offset += 0.005;
}
return oac;
},
name: "Granular synthesis"
});
registerTestCase({
func: function() {
var samplerate = 44100;
var duration = 30;
var oac = new OfflineAudioContext(1, duration * samplerate, 44100);
var offset = 0;
while (offset < duration) {
var note = oac.createOscillator();
var enveloppe = oac.createGain();
note.type = "sawtooth";
note.frequency.value = 110;
note.connect(enveloppe);
enveloppe.gain.setValueAtTime(0, 0);
enveloppe.gain.setValueAtTime(0.5, offset);
enveloppe.gain.setTargetAtTime(0, offset+0.01, 0.1);
enveloppe.connect(oac.destination);
note.start(offset);
note.stop(offset + 1.0);
offset += 140 / 60 / 4; // 140 bpm
}
return oac;
},
name: "Synth"
});
registerTestCase({
func: function() {
var samplerate = 44100;
var duration = 30;
var oac = new OfflineAudioContext(1, duration * samplerate, samplerate);
var offset = 0;
var osc = oac.createOscillator();
osc.type = "sawtooth";
var enveloppe = oac.createGain();
enveloppe.gain.setValueAtTime(0, 0);
var filter = oac.createBiquadFilter();
osc.connect(enveloppe);
enveloppe.connect(filter);
filter.connect(oac.destination);
filter.frequency.setValueAtTime(0.0, 0.0);
filter.Q.setValueAtTime(20, 0.0);
osc.start(0);
osc.frequency.setValueAtTime(110, 0);
while (offset < duration) {
enveloppe.gain.setValueAtTime(1.0, offset);
enveloppe.gain.setTargetAtTime(0.0, offset, 0.1);
filter.frequency.setValueAtTime(0, offset);
filter.frequency.setTargetAtTime(3500, offset, 0.03);
offset += 140 / 60 / 16;
}
return oac;
},
name: "Substractive synth"
});
if (typeof(window) == "undefined") {
exports.benchmarks = benchmarks;
}

Просмотреть файл

@ -0,0 +1,25 @@
var OUTPUT_PATH="webaudio.json";
var benchmarks = require("./benchmarks.js");
var template = require("./template-row.json");
var dashboard = require("./webaudio-dashboard.json");
var fs = require('fs');
var str = JSON.stringify(template);
var names = benchmarks.benchmarks;
for (var i in benchmarks.benchmarks) {
["win", "linux", "mac", "android"].forEach(function(platform) {
var out = str.replace(/{{benchmark}}/g, names[i]).replace(/{{platform}}/g, platform);
dashboard.rows.push(JSON.parse(out));
});
}
fs.writeFile(OUTPUT_PATH, JSON.stringify(dashboard, " ", 2), function(err) {
if(err) {
return console.log(err);
}
console.log("grafana dashboard saved as " + OUTPUT_PATH);
});

Просмотреть файл

@ -0,0 +1,44 @@
<html>
<head>
<script src=webaudio-bench.js></script>
<script src=benchmarks.js></script>
<meta charset="utf-8">
<style>
html {
font-family: helvetica, arial;
}
#in-progress {
display: none;
}
table {
margin: 1em;
border-collapse: collapse;
}
thead {
background-color: #aaaaaa;
}
tr:nth-child(even) {
background-color: #eeeeee;
}
td {
border: 1px solid black;
text-align: center;
}
#run-all {
display: none;
}
</style>
</head>
<body>
<progress id=loading></progress>
<div class=controls>
<button id=run-all>Run all</button>
<div id=in-progress>
<progress> </progress>
Benchmark in progress...
</div>
</div>
<div id=results>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,79 @@
{
"title": "{{benchmark}} -- {{platform}}",
"height": "250px",
"editable": true,
"collapse": true,
"panels": [
{
"title": "{{benchmark}} -- {{platform}}",
"error": false,
"span": 12,
"editable": true,
"type": "graph",
"id": 1,
"datasource": null,
"renderer": "flot",
"x-axis": true,
"y-axis": true,
"y_formats": [
"short",
"short"
],
"grid": {
"leftMax": null,
"rightMax": null,
"leftMin": null,
"rightMin": null,
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"lines": true,
"fill": 0,
"linewidth": 1,
"points": true,
"pointradius": 5,
"bars": false,
"stack": false,
"percentage": false,
"legend": {
"show": true,
"values": false,
"min": false,
"max": false,
"current": false,
"total": false,
"avg": false,
"alignAsTable": false
},
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative",
"shared": false
},
"targets": [
{
"function": "mean",
"column": "value",
"series": "benchmarks.webaudio-padenot.{{benchmark}}.{{platform}}.firefox.nightly",
"query": "select mean(value) from \"benchmarks.webaudio-padenot.{{benchmark}}.{{platform}}.firefox.nightly\" where $timeFilter group by time($interval) order asc",
"hide": false
},
{
"function": "mean",
"column": "value",
"series": "benchmarks.webaudio-padenot.{{benchmark}}.{{platform}}.chrome.canary",
"query": "select mean(value) from \"benchmarks.webaudio-padenot.{{benchmark}}.{{platform}}.chrome.canary\" where $timeFilter group by time($interval) order asc",
"hide": false,
"groupby_field": ""
}
],
"aliasColors": {},
"seriesOverrides": [],
"links": [],
"leftYAxisLabel": "Time in milliseconds, lower is better"
}
]
}

Двоичные данные
benchmarks/webaudio-benchmark/think-mono-38000.wav Normal file

Двоичный файл не отображается.

Двоичные данные
benchmarks/webaudio-benchmark/think-mono-44100.wav Normal file

Двоичный файл не отображается.

Двоичные данные
benchmarks/webaudio-benchmark/think-mono-48000.wav Normal file

Двоичный файл не отображается.

Двоичные данные
benchmarks/webaudio-benchmark/think-mono.wav Normal file

Двоичный файл не отображается.

Двоичные данные
benchmarks/webaudio-benchmark/think-stereo-38000.wav Normal file

Двоичный файл не отображается.

Двоичные данные
benchmarks/webaudio-benchmark/think-stereo-44100.wav Normal file

Двоичный файл не отображается.

Двоичные данные
benchmarks/webaudio-benchmark/think-stereo-48000.wav Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,14 @@
#!/bin/sh
usage() {
echo "Usage:"
echo "\t$0" /path/to/webaudio-benchmarks
exit 1
}
if [ $# != "1" ]
then
usage $0
fi
cp $1/* .

Просмотреть файл

@ -0,0 +1,204 @@
if (window.AudioContext == undefined) {
window.AudioContext = window.webkitAudioContext;
window.OfflineAudioContext = window.webkitOfflineAudioContext;
}
// Global samplerate at which we run the context.
var samplerate = 48000;
// Array containing at first the url of the audio resources to fetch, and the
// the actual buffers audio buffer we have at our disposal to for tests.
var sources = [];
// Array containing the results, for each benchmark.
var results = [];
// Array containing the offline contexts used to run the testcases.
var testcases = [];
// Array containing the functions that can return a runnable testcase.
var testcases_registered = [];
// Array containing the audio buffers for each benchmark
var buffers = [];
var playingSource = null;
// audiocontext used to play back the result of the benchmarks
var ac = new AudioContext();
function getFile(url, callback) {
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "arraybuffer";
request.onload = function() {
var ctx = new AudioContext();
ctx.decodeAudioData(request.response, function(data) {
callback(data, undefined);
}, function() {
callback(undefined, "Error decoding the file " + url);
});
}
request.send();
}
function recordResult(result) {
results.push(result);
}
function benchmark(testcase, ended) {
var context = testcase.ctx;
var start;
context.oncomplete = function(e) {
var end = Date.now();
recordResult({
name: testcase.name,
duration: end - start,
buffer: e.renderedBuffer
});
ended();
};
start = Date.now();
context.startRendering();
}
function getMonoFile() {
return getSpecificFile({numberOfChannels: 1});
}
function getStereoFile() {
return getSpecificFile({numberOfChannels: 2});
}
function matchIfSpecified(a, b) {
if (b) {
return a == b;
}
return true;
}
function getSpecificFile(spec) {
for (var i = 0 ; i < sources.length; i++) {
if (matchIfSpecified(sources[i].numberOfChannels, spec.numberOfChannels) &&
matchIfSpecified(sources[i].samplerate, spec.samplerate)) {
return sources[i];
}
}
throw new Error("Could not find a file that matches the specs.");
}
function allDone() {
document.getElementById("in-progress").style.display = "none";
var result = document.getElementById("results");
var str = "<table><thead><tr><td>Test name</td><td>Time in ms</td><td>Speedup vs. realtime</td><td>Sound</td></tr></thead>";
var buffers_base = buffers.length;
var product_of_durations = 1.0;
for (var i = 0 ; i < results.length; i++) {
var r = results[i];
product_of_durations *= r.duration;
str += "<tr><td>" + r.name + "</td>" +
"<td>" + r.duration + "</td>"+
"<td>" + Math.round((r.buffer.duration * 1000) / r.duration) + "x</td>"+
"<td><button data-soundindex="+(buffers_base + i)+">Play</button> ("+r.buffer.duration+"s)</td>"
+"</tr>";
buffers[buffers_base + i] = r.buffer;
}
recordResult({
name: "Geometric Mean",
duration: Math.round(Math.pow(product_of_durations, 1.0/results.length)),
buffer: {}
});
str += "</table>";
result.innerHTML += str;
result.addEventListener("click", function(e) {
var t = e.target;
if (t.dataset.soundindex != undefined) {
if (playingSource != null) {
playingSource.button.innerHTML = "Play";
playingSource.onended = undefined;
playingSource.stop(0);
if (playingSource.button == t) {
playingSource = null;
return;
}
}
playingSource = ac.createBufferSource();
playingSource.connect(ac.destination);
playingSource.buffer = buffers[t.dataset.soundindex];
playingSource.start(0);
playingSource.button = t;
t.innerHTML = "Pause";
playingSource.onended = function () {
playingSource = null;
}
}
});
document.getElementById("run-all").disabled = false;
var xhr = new XMLHttpRequest();
xhr.open("POST", "/results", true);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send("results=" + JSON.stringify(results));
}
function runOne(i) {
benchmark(testcases[i], function() {
i++;
if (i < testcases.length) {
runOne(i);
} else {
allDone();
}
});
}
function runAll() {
initAll();
results = [];
runOne(0);
}
function initAll() {
for (var i = 0; i < testcases_registered.length; i++) {
testcases[i] = {};
testcases[i].ctx = testcases_registered[i].func();
testcases[i].name = testcases_registered[i].name;
}
}
function loadOne(i, endCallback) {
getFile(sources[i], function(b) {
sources[i] = b;
i++;
if (i == sources.length) {
loadOne(i);
} else {
endCallback();
}
})
}
function loadAllSources(endCallback) {
loadOne(0, endCallback);
}
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("run-all").addEventListener("click", function() {
document.getElementById("in-progress").style.display = "inline";
document.getElementById("run-all").disabled = true;
runAll();
});
loadAllSources(function() {
document.getElementById("loading").style.display = "none";
document.getElementById("run-all").style.display = "inline";
document.getElementById("in-progress").style.display = "inline";
setTimeout(runAll, 100);
});
});
/* Public API */
function registerTestCase(testCase) {
testcases_registered.push(testCase);
}
function registerTestFile(url) {
sources.push(url);
}

Просмотреть файл

@ -0,0 +1,57 @@
{
"id": null,
"title": "Web Audio API benchmark",
"originalTitle": "Web Audio API benchmark",
"tags": [],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [ ],
"nav": [
{
"type": "timepicker",
"collapse": false,
"enable": true,
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"now": true,
"notice": false
}
],
"time": {
"from": "now-7d",
"to": "now"
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"version": 6,
"hideAllLegends": false
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -80,11 +80,26 @@ class WebGLSamples(Benchmark):
def __init__(self): def __init__(self):
Benchmark.__init__(self, "webglsamples", "0.1", "benchmarks/webglsamples/test.html") Benchmark.__init__(self, "webglsamples", "0.1", "benchmarks/webglsamples/test.html")
class WebAudio(Benchmark):
def __init__(self):
Benchmark.__init__(self, "webaudio", "0.1", "benchmarks/webaudio-benchmark/index.html")
def processResults(self, results):
ret = []
total = 0
for item in results:
if item['name'] == "Geometric Mean":
item['name'] = "__total__"
ret.append({'name': item['name'], 'time': item['duration'] })
return ret
def getBenchmark(name): def getBenchmark(name):
if name == "webglsamples": if name == "webglsamples":
return WebGLSamples() return WebGLSamples()
if name == "assorteddom": if name == "assorteddom":
return AssortedDOM() return AssortedDOM()
if name == "webaudio":
return WebAudio()
raise Exception("Unknown benchmark") raise Exception("Unknown benchmark")
# Test if server is running and start server if needed. # Test if server is running and start server if needed.

Просмотреть файл

@ -51,7 +51,6 @@ class FakeHandler(SimpleHTTPRequestHandler):
if self.path.startswith("/submit"): if self.path.startswith("/submit"):
return self.captureResults(query) return self.captureResults(query)
else: else:
print self.path
return self.retrieveOffline(); return self.retrieveOffline();
def retrieveOffline(self): def retrieveOffline(self):
@ -246,6 +245,10 @@ class FakeHandler(SimpleHTTPRequestHandler):
if path == "/benchmarks/misc-desktop/hosted/assorted/driver.html": if path == "/benchmarks/misc-desktop/hosted/assorted/driver.html":
return data.replace('location = "results.html?" + encodeURI(outputString);', return data.replace('location = "results.html?" + encodeURI(outputString);',
'location.href = "http://localhost:8000/submit?results=" + encodeURI(outputString);'); 'location.href = "http://localhost:8000/submit?results=" + encodeURI(outputString);');
if host == "localhost":
if path == "/benchmarks/webaudio-benchmark/webaudio-bench.js":
return data.replace('xhr.open("POST", "/results", true);',
'xhr.open("POST", "/submit", true);');
if host.startswith("dromaeo."): if host.startswith("dromaeo."):
if path == "/webrunner.js": if path == "/webrunner.js":
data = data.replace('function init(){', data = data.replace('function init(){',