diff --git a/htdocs/index.html b/htdocs/index.html index 1751cc7..985196b 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -30,6 +30,7 @@ + diff --git a/htdocs/lib/AudioEngine.js b/htdocs/lib/AudioEngine.js index 0445dd3..5860b2e 100644 --- a/htdocs/lib/AudioEngine.js +++ b/htdocs/lib/AudioEngine.js @@ -4,6 +4,7 @@ var useAudioWorklets = true; function AudioEngine(maxBufferLength, audioReporter) { this.audioReporter = audioReporter; + this.initStats(); this.resetStats(); var ctx = window.AudioContext || window.webkitAudioContext; if (!ctx) { @@ -55,7 +56,12 @@ AudioEngine.prototype.start = function(callback) { me.audioNode.port.addEventListener('message', function(m){ var json = JSON.parse(m.data); 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(); @@ -86,21 +92,23 @@ AudioEngine.prototype.start = function(callback) { var out = new Float32Array(bufferSize); while (me.audioBuffers.length) { 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 - if (newLength > bufferSize) { - var tokeep = b.subarray(0, bufferSize - total); + if (total + b.length > bufferSize) { + var spaceLeft = bufferSize - total; + var tokeep = b.subarray(0, spaceLeft); out.set(tokeep, total); - var tobuffer = b.subarray(bufferSize - total, b.length); + var tobuffer = b.subarray(spaceLeft, b.length); me.audioBuffers.unshift(tobuffer); + total += spaceLeft; break; } else { out.set(b, total); + total += b.length; } - total = newLength; } e.outputBuffer.copyToChannel(out, 0); + me.audioSamples.add(total); } @@ -124,27 +132,36 @@ AudioEngine.prototype.isAllowed = function() { }; AudioEngine.prototype.reportStats = function() { - var stats = {}; if (this.audioNode.port) { - this.audioNode.port.postMessage(JSON.stringify({cmd:'getBuffers'})); + this.audioNode.port.postMessage(JSON.stringify({cmd:'getStats'})); } 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 - this.stats.audioSamples = 0; +AudioEngine.prototype.initStats = function() { + 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() { - this.stats = { - startTime: new Date(), - audioBytes: 0, - audioSamples: 0 - }; + this.audioBytes.reset(); + this.audioSamples.reset(); }; AudioEngine.prototype.setupResampling = function() { //both at the server and the client @@ -178,7 +195,7 @@ AudioEngine.prototype.getSampleRate = function() { AudioEngine.prototype.pushAudio = function(data) { if (!this.audioNode) return; - this.stats.audioBytes += data.byteLength; + this.audioBytes.add(data.byteLength); var buffer; if (this.compression === "adpcm") { //resampling & ADPCM @@ -187,7 +204,6 @@ AudioEngine.prototype.pushAudio = function(data) { buffer = new Int16Array(data); } buffer = this.resampler.process(sdrjs.ConvertI16_F(buffer)); - this.stats.audioSamples += buffer.length; if (this.audioNode.port) { // AudioWorklets supported this.audioNode.port.postMessage(buffer); diff --git a/htdocs/lib/AudioProcessor.js b/htdocs/lib/AudioProcessor.js index 900e56f..7ac76c5 100644 --- a/htdocs/lib/AudioProcessor.js +++ b/htdocs/lib/AudioProcessor.js @@ -6,11 +6,12 @@ class OwrxAudioProcessor extends AudioWorkletProcessor { this.audioBuffer = new Float32Array(this.bufferSize); this.inPos = 0; this.outPos = 0; + this.samplesProcessed = 0; this.port.addEventListener('message', (m) => { if (typeof(m.data) === 'string') { const json = JSON.parse(m.data); - if (json.cmd && json.cmd === 'getBuffers') { - this.reportBuffers(); + if (json.cmd && json.cmd === 'getStats') { + this.reportStats(); } } else { // 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)); }); this.outPos = (this.outPos + 128) % this.bufferSize; + this.samplesProcessed += 128; return true; } remaining() { @@ -44,8 +46,12 @@ class OwrxAudioProcessor extends AudioWorkletProcessor { if (mod >= 0) return mod; return mod + this.bufferSize; } - reportBuffers() { - this.port.postMessage(JSON.stringify({buffersize: this.remaining()})); + reportStats() { + this.port.postMessage(JSON.stringify({ + buffersize: this.remaining(), + samplesProcessed: this.samplesProcessed + })); + this.samplesProcessed = 0; } } diff --git a/htdocs/lib/Measurement.js b/htdocs/lib/Measurement.js new file mode 100644 index 0000000..018739c --- /dev/null +++ b/htdocs/lib/Measurement.js @@ -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); +}; \ No newline at end of file