implement new way of measuring stats that allows arbitrary timeranges

This commit is contained in:
Jakob Ketterl 2019-10-26 22:32:25 +02:00
parent fe08228204
commit 39120d9413
4 changed files with 111 additions and 26 deletions

View File

@ -30,6 +30,7 @@
<script src="static/lib/BookmarkBar.js"></script> <script src="static/lib/BookmarkBar.js"></script>
<script src="static/lib/AudioEngine.js"></script> <script src="static/lib/AudioEngine.js"></script>
<script src="static/lib/ProgressBar.js"></script> <script src="static/lib/ProgressBar.js"></script>
<script src="static/lib/Measurement.js"></script>
<link rel="stylesheet" type="text/css" href="static/lib/nanoscroller.css" /> <link rel="stylesheet" type="text/css" href="static/lib/nanoscroller.css" />
<link rel="stylesheet" type="text/css" href="static/css/openwebrx.css" /> <link rel="stylesheet" type="text/css" href="static/css/openwebrx.css" />
<meta charset="utf-8"> <meta charset="utf-8">

View File

@ -4,6 +4,7 @@ var useAudioWorklets = true;
function AudioEngine(maxBufferLength, audioReporter) { function AudioEngine(maxBufferLength, audioReporter) {
this.audioReporter = audioReporter; this.audioReporter = audioReporter;
this.initStats();
this.resetStats(); this.resetStats();
var ctx = window.AudioContext || window.webkitAudioContext; var ctx = window.AudioContext || window.webkitAudioContext;
if (!ctx) { if (!ctx) {
@ -55,7 +56,12 @@ AudioEngine.prototype.start = function(callback) {
me.audioNode.port.addEventListener('message', function(m){ me.audioNode.port.addEventListener('message', function(m){
var json = JSON.parse(m.data); var json = JSON.parse(m.data);
if (typeof(json.buffersize) !== 'undefined') { if (typeof(json.buffersize) !== 'undefined') {
me.audioReporter(json); me.audioReporter({
buffersize: json.buffersize
});
}
if (typeof(json.samplesProcessed) !== 'undefined') {
me.audioSamples.add(json.samplesProcessed);
} }
}); });
me.audioNode.port.start(); me.audioNode.port.start();
@ -86,21 +92,23 @@ AudioEngine.prototype.start = function(callback) {
var out = new Float32Array(bufferSize); var out = new Float32Array(bufferSize);
while (me.audioBuffers.length) { while (me.audioBuffers.length) {
var b = me.audioBuffers.shift(); var b = me.audioBuffers.shift();
var newLength = total + b.length;
// not enough space to fit all data, so splice and put back in the queue // not enough space to fit all data, so splice and put back in the queue
if (newLength > bufferSize) { if (total + b.length > bufferSize) {
var tokeep = b.subarray(0, bufferSize - total); var spaceLeft = bufferSize - total;
var tokeep = b.subarray(0, spaceLeft);
out.set(tokeep, total); out.set(tokeep, total);
var tobuffer = b.subarray(bufferSize - total, b.length); var tobuffer = b.subarray(spaceLeft, b.length);
me.audioBuffers.unshift(tobuffer); me.audioBuffers.unshift(tobuffer);
total += spaceLeft;
break; break;
} else { } else {
out.set(b, total); out.set(b, total);
total += b.length;
} }
total = newLength;
} }
e.outputBuffer.copyToChannel(out, 0); e.outputBuffer.copyToChannel(out, 0);
me.audioSamples.add(total);
} }
@ -124,27 +132,36 @@ AudioEngine.prototype.isAllowed = function() {
}; };
AudioEngine.prototype.reportStats = function() { AudioEngine.prototype.reportStats = function() {
var stats = {};
if (this.audioNode.port) { if (this.audioNode.port) {
this.audioNode.port.postMessage(JSON.stringify({cmd:'getBuffers'})); this.audioNode.port.postMessage(JSON.stringify({cmd:'getStats'}));
} else { } else {
stats.buffersize = this.getBuffersize(); this.audioReporter({
buffersize: this.getBuffersize()
});
} }
stats.audioRate = this.stats.audioSamples; };
var elapsed = new Date() - this.stats.startTime;
stats.audioByteRate = this.stats.audioBytes * 1000 / elapsed;
this.audioReporter(stats);
// sample rate is just measuring the last seconds AudioEngine.prototype.initStats = function() {
this.stats.audioSamples = 0; var me = this;
var buildReporter = function(key) {
return function(v){
var report = {};
report[key] = v;
me.audioReporter(report);
}
};
this.audioBytes = new Measurement();
this.audioBytes.report(10000, 1000, buildReporter('audioByteRate'));
this.audioSamples = new Measurement();
this.audioSamples.report(10000, 1000, buildReporter('audioRate'));
}; };
AudioEngine.prototype.resetStats = function() { AudioEngine.prototype.resetStats = function() {
this.stats = { this.audioBytes.reset();
startTime: new Date(), this.audioSamples.reset();
audioBytes: 0,
audioSamples: 0
};
}; };
AudioEngine.prototype.setupResampling = function() { //both at the server and the client AudioEngine.prototype.setupResampling = function() { //both at the server and the client
@ -178,7 +195,7 @@ AudioEngine.prototype.getSampleRate = function() {
AudioEngine.prototype.pushAudio = function(data) { AudioEngine.prototype.pushAudio = function(data) {
if (!this.audioNode) return; if (!this.audioNode) return;
this.stats.audioBytes += data.byteLength; this.audioBytes.add(data.byteLength);
var buffer; var buffer;
if (this.compression === "adpcm") { if (this.compression === "adpcm") {
//resampling & ADPCM //resampling & ADPCM
@ -187,7 +204,6 @@ AudioEngine.prototype.pushAudio = function(data) {
buffer = new Int16Array(data); buffer = new Int16Array(data);
} }
buffer = this.resampler.process(sdrjs.ConvertI16_F(buffer)); buffer = this.resampler.process(sdrjs.ConvertI16_F(buffer));
this.stats.audioSamples += buffer.length;
if (this.audioNode.port) { if (this.audioNode.port) {
// AudioWorklets supported // AudioWorklets supported
this.audioNode.port.postMessage(buffer); this.audioNode.port.postMessage(buffer);

View File

@ -6,11 +6,12 @@ class OwrxAudioProcessor extends AudioWorkletProcessor {
this.audioBuffer = new Float32Array(this.bufferSize); this.audioBuffer = new Float32Array(this.bufferSize);
this.inPos = 0; this.inPos = 0;
this.outPos = 0; this.outPos = 0;
this.samplesProcessed = 0;
this.port.addEventListener('message', (m) => { this.port.addEventListener('message', (m) => {
if (typeof(m.data) === 'string') { if (typeof(m.data) === 'string') {
const json = JSON.parse(m.data); const json = JSON.parse(m.data);
if (json.cmd && json.cmd === 'getBuffers') { if (json.cmd && json.cmd === 'getStats') {
this.reportBuffers(); this.reportStats();
} }
} else { } else {
// the ringbuffer size is aligned to the output buffer size, which means that the input buffers might // the ringbuffer size is aligned to the output buffer size, which means that the input buffers might
@ -37,6 +38,7 @@ class OwrxAudioProcessor extends AudioWorkletProcessor {
output.set(this.audioBuffer.subarray(this.outPos, this.outPos + 128)); output.set(this.audioBuffer.subarray(this.outPos, this.outPos + 128));
}); });
this.outPos = (this.outPos + 128) % this.bufferSize; this.outPos = (this.outPos + 128) % this.bufferSize;
this.samplesProcessed += 128;
return true; return true;
} }
remaining() { remaining() {
@ -44,8 +46,12 @@ class OwrxAudioProcessor extends AudioWorkletProcessor {
if (mod >= 0) return mod; if (mod >= 0) return mod;
return mod + this.bufferSize; return mod + this.bufferSize;
} }
reportBuffers() { reportStats() {
this.port.postMessage(JSON.stringify({buffersize: this.remaining()})); this.port.postMessage(JSON.stringify({
buffersize: this.remaining(),
samplesProcessed: this.samplesProcessed
}));
this.samplesProcessed = 0;
} }
} }

62
htdocs/lib/Measurement.js Normal file
View File

@ -0,0 +1,62 @@
function Measurement() {
this.reset();
};
Measurement.prototype.add = function(v) {
this.value += v;
};
Measurement.prototype.getValue = function() {
return this.value;
};
Measurement.prototype.getElapsed = function() {
return new Date() - this.start;
};
Measurement.prototype.getRate = function() {
return this.getValue() / this.getElapsed();
};
Measurement.prototype.reset = function() {
this.value = 0;
this.start = new Date();
};
Measurement.prototype.report = function(range, interval, callback) {
return new Reporter(this, range, interval, callback);
}
function Reporter(measurement, range, interval, callback) {
this.measurement = measurement;
this.range = range;
this.samples = [];
this.callback = callback;
this.interval = setInterval(this.report.bind(this), interval);
};
Reporter.prototype.sample = function(){
this.samples.push({
timestamp: new Date(),
value: this.measurement.getValue()
});
};
Reporter.prototype.report = function(){
this.sample();
var now = new Date();
var minDate = now.getTime() - this.range;
this.samples = this.samples.filter(function(s) {
return s.timestamp.getTime() > minDate;
});
this.samples.sort(function(a, b) {
return a.timestamp - b.timestamp;
});
var oldest = this.samples[0];
var newest = this.samples[this.samples.length -1];
var elapsed = newest.timestamp - oldest.timestamp;
if (elapsed <= 0) return;
var accumulated = newest.value - oldest.value;
// we want rate per second, but our time is in milliseconds... compensate by 1000
this.callback(accumulated * 1000 / elapsed);
};