From a6f2b6b31ab4539957021656571ff984154c4108 Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 18 Jun 2022 20:23:50 +0200 Subject: [PATCH] add opus server-side integration --- csdr/chain/clientaudio.py | 16 +++++++-- htdocs/lib/AudioEngine.js | 54 ++++++++++++++++++---------- htdocs/openwebrx.js | 2 +- owrx/controllers/settings/general.py | 1 + 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/csdr/chain/clientaudio.py b/csdr/chain/clientaudio.py index febfd15..50c25f1 100644 --- a/csdr/chain/clientaudio.py +++ b/csdr/chain/clientaudio.py @@ -1,5 +1,5 @@ from csdr.chain import Chain -from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, Limit +from pycsdr.modules import AudioResampler, Convert, AdpcmEncoder, OpusEncoder, Limit from pycsdr.types import Format @@ -27,6 +27,8 @@ class ClientAudioChain(Chain): workers += [converter] if compression == "adpcm": workers += [AdpcmEncoder(sync=True)] + elif compression == "opus": + workers += [OpusEncoder()] super().__init__(workers) def _buildConverter(self): @@ -63,10 +65,18 @@ class ClientAudioChain(Chain): self._updateConverter() def setAudioCompression(self, compression: str) -> None: - index = self.indexOf(lambda x: isinstance(x, AdpcmEncoder)) + index = self.indexOf(lambda x: isinstance(x, AdpcmEncoder) or isinstance(x, OpusEncoder)) + newEncoder = None if compression == "adpcm": + newEncoder = AdpcmEncoder(sync=True) + elif compression == "opus": + newEncoder = OpusEncoder() + + if newEncoder: if index < 0: - self.append(AdpcmEncoder(sync=True)) + self.append(newEncoder) + else: + self.replace(index, newEncoder) else: if index >= 0: self.remove(index) diff --git a/htdocs/lib/AudioEngine.js b/htdocs/lib/AudioEngine.js index 48efb26..4258c6e 100644 --- a/htdocs/lib/AudioEngine.js +++ b/htdocs/lib/AudioEngine.js @@ -21,7 +21,11 @@ function AudioEngine(maxBufferLength, audioReporter) { me._start(); } - this.audioCodec = new ImaAdpcmCodec(); + this.audioCodecs = { + "adpcm": new ImaAdpcmCodec(), + "opus": new OpusCodec(this.audioContext) + }; + this.compression = 'none'; this.setupResampling(); @@ -277,25 +281,31 @@ AudioEngine.prototype.getSampleRate = function() { }; AudioEngine.prototype.processAudio = function(data, resampler) { + var me = this; + if (!this.audioNode) return; this.audioBytes.add(data.byteLength); - var buffer; - if (this.compression === "adpcm") { - //resampling & ADPCM - buffer = this.audioCodec.decodeWithSync(new Uint8Array(data)); - } else { - buffer = new Int16Array(data); - } - buffer = resampler.process(buffer); - if (this.audioNode.port) { - // AudioWorklets supported - this.audioNode.port.postMessage(buffer); - } else { - // silently drop excess samples - if (this.getBuffersize() + buffer.length <= this.maxBufferSize) { - this.audioBuffers.push(buffer); + + var audioDataCallback = function(buffer) { + buffer = resampler.process(buffer); + if (me.audioNode.port) { + // AudioWorklets supported + me.audioNode.port.postMessage(buffer); + } else { + // silently drop excess samples + if (this.getBuffersize() + buffer.length <= this.maxBufferSize) { + this.audioBuffers.push(buffer); + } } } + + if (this.compression !== "none") { + //resampling & ADPCM + this.audioCodecs[this.compression].decodeAsync(new Uint8Array(data), audioDataCallback); + } else { + audioDataCallback(new Int16Array(data)) + } + } AudioEngine.prototype.pushAudio = function(data) { @@ -357,7 +367,7 @@ ImaAdpcmCodec.prototype.decode = function(data) { return output; }; -ImaAdpcmCodec.prototype.decodeWithSync = function(data) { +ImaAdpcmCodec.prototype.decodeAsync = function(data, callback) { var output = new Int16Array(data.length * 2); var index = this.skip; var oi = 0; @@ -391,7 +401,7 @@ ImaAdpcmCodec.prototype.decodeWithSync = function(data) { } } this.skip = index - data.length; - return output.slice(0, oi); + callback(output.slice(0, oi)); }; ImaAdpcmCodec.prototype.decodeNibble = function(nibble) { @@ -412,6 +422,14 @@ ImaAdpcmCodec.prototype.decodeNibble = function(nibble) { return this.predictor; }; +function OpusCodec(context) { + this.context = context; +} + +OpusCodec.prototype.decodeAsync = function(data, callback) { + this.context.decodeAudioData(new ArrayBuffer(data)).then(callback); +}; + function Interpolator(factor) { this.factor = factor; this.lowpass = new Lowpass(factor) diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index 5c43ea7..21e076e 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -754,7 +754,7 @@ function on_ws_recv(evt) { if ('audio_compression' in config) { var audio_compression = config['audio_compression']; audioEngine.setCompression(audio_compression); - divlog("Audio stream is " + ((audio_compression === "adpcm") ? "compressed" : "uncompressed") + "."); + divlog("Audio stream is " + ((audio_compression !== "none") ? "compressed" : "uncompressed") + "."); } if ('fft_compression' in config) { fft_compression = config['fft_compression']; diff --git a/owrx/controllers/settings/general.py b/owrx/controllers/settings/general.py index 232c543..e85e761 100644 --- a/owrx/controllers/settings/general.py +++ b/owrx/controllers/settings/general.py @@ -132,6 +132,7 @@ class GeneralSettingsController(SettingsFormController): "Audio compression", options=[ Option("adpcm", "ADPCM"), + Option("opus", "OPUS"), Option("none", "None"), ], ),