From f05afc4b0a874d03b40428dd079a5d3cf8ddcfdc Mon Sep 17 00:00:00 2001 From: Jakob Ketterl Date: Sat, 4 May 2019 23:11:13 +0200 Subject: [PATCH] get the audio going as well --- htdocs/openwebrx.js | 33 +++++++++++++++++++++----- owrx/controllers.py | 44 +++++++++++++++++++---------------- owrx/source.py | 56 ++++++++++++++++++++++++++++++++++++++++++++- owrx/websocket.py | 8 +++---- 4 files changed, 109 insertions(+), 32 deletions(-) diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index 8a7993a..980a6ae 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -161,7 +161,7 @@ function updateSquelch() { var sliderValue=parseInt(e("openwebrx-panel-squelch").value); var outputValue=(sliderValue==parseInt(e("openwebrx-panel-squelch").min))?0:getLinearSmeterValue(sliderValue); - ws.send("SET squelch_level="+outputValue.toString()); + ws.send(JSON.stringify({"type":"dspcontrol","params":{"squelch_level":outputValue}})); } function updateWaterfallColors(which) @@ -461,9 +461,13 @@ function demodulator_default_analog(offset_frequency,subtype) this.doset=function(first_time) { //this function sends demodulator parameters to the server - ws.send("SET"+((first_time)?" mod="+this.server_mod:"")+ - " low_cut="+this.low_cut.toString()+" high_cut="+this.high_cut.toString()+ - " offset_freq="+this.offset_frequency.toString()); + params = { + "low_cut": this.low_cut, + "high_cut": this.high_cut, + "offset_freq": this.offset_frequency + }; + if (first_time) params.mod = this.server_mod; + ws.send(JSON.stringify({"type":"dspcontrol","params":params})); } this.doset(true); //we set parameters on object creation @@ -1164,6 +1168,7 @@ function on_ws_recv(evt) window.starting_mod = config.start_mod window.starting_offset_frequency = config.start_offset_frequency; + window.audio_buffering_fill_to = config.client_audio_buffer_size; bandwidth = config.samp_rate; center_freq = config.shown_center_freq; fft_size = config.fft_size; @@ -1191,6 +1196,7 @@ function on_ws_recv(evt) switch (type) { case 1: + // FFT data if (fft_compression=="none") { waterfall_add_queue(new Float32Array(data)); } else if (fft_compression == "adpcm") { @@ -1202,6 +1208,19 @@ function on_ws_recv(evt) waterfall_add_queue(waterfall_f32); } break; + case 2: + // audio data + var audio_data; + if (audio_compression=="adpcm") { + audio_data = new Uint8Array(data); + } else { + audio_data = new Int16Array(data); + } + audio_prepare(audio_data); + audio_buffer_current_size_debug += audio_data.length; + audio_buffer_all_size_debug += audio_data.length; + if (!(ios||is_chrome) && (audio_initialized==0 && audio_prepared_buffers.length>audio_buffering_fill_to)) audio_init() + break; default: console.warn('unknown type of binary message: ' + type) } @@ -1619,7 +1638,9 @@ function audio_flush_notused() function webrx_set_param(what, value) { - ws.send("SET "+what+"="+value.toString()); + params = {}; + params[what] = value; + ws.send(JSON.stringify({"type":"dspcontrol","params":params})); } var starting_mute = false; @@ -1678,7 +1699,7 @@ function audio_preinit() audio_calculate_resampling(audio_context.sampleRate); audio_resampler = new sdrjs.RationalResamplerFF(audio_client_resampling_factor,1); - ws.send(JSON.stringify({"type":"start","params":{"output_rate":audio_server_output_rate}})) + ws.send(JSON.stringify({"type":"dspcontrol","action":"start","params":{"output_rate":audio_server_output_rate}})); } function audio_init() diff --git a/owrx/controllers.py b/owrx/controllers.py index 9ba87ea..9805cc4 100644 --- a/owrx/controllers.py +++ b/owrx/controllers.py @@ -1,8 +1,7 @@ import mimetypes from owrx.websocket import WebSocketConnection from owrx.config import PropertyManager -from owrx.source import SpectrumThread -import csdr +from owrx.source import SpectrumThread, DspThread import json class Controller(object): @@ -52,6 +51,8 @@ class SpectrumForwarder(object): self.conn = conn def write_spectrum_data(self, data): self.conn.send(bytes([0x01]) + data) + def write_dsp_data(self, data): + self.conn.send(bytes([0x02]) + data) class WebSocketMessageHandler(object): def __init__(self): @@ -64,7 +65,7 @@ class WebSocketMessageHandler(object): for key in ["waterfall_colors", "waterfall_min_level", "waterfall_max_level", "waterfall_auto_level_margin", "shown_center_freq", "samp_rate", "fft_size", "fft_fps", "audio_compression", "fft_compression", - "max_clients", "start_mod"]: + "max_clients", "start_mod", "client_audio_buffer_size"]: config[key] = pm.getPropertyValue(key) @@ -73,29 +74,30 @@ class WebSocketMessageHandler(object): conn.send({"type":"config","value":config}) print("client connection intitialized") - dsp = self.dsp = csdr.dsp() - dsp_initialized=False - dsp.set_audio_compression(pm.getPropertyValue("audio_compression")) - dsp.set_fft_compression(pm.getPropertyValue("fft_compression")) #used by secondary chains - dsp.set_format_conversion(pm.getPropertyValue("format_conversion")) - dsp.set_offset_freq(0) - dsp.set_bpf(-4000,4000) - dsp.set_secondary_fft_size(pm.getPropertyValue("digimodes_fft_size")) - dsp.nc_port=pm.getPropertyValue("iq_server_port") - dsp.csdr_dynamic_bufsize = pm.getPropertyValue("csdr_dynamic_bufsize") - dsp.csdr_print_bufsizes = pm.getPropertyValue("csdr_print_bufsizes") - dsp.csdr_through = pm.getPropertyValue("csdr_through") - do_secondary_demod=False - self.forwarder = SpectrumForwarder(conn) SpectrumThread.getSharedInstance().add_client(self.forwarder) + self.dsp = DspThread(self.forwarder) + else: try: message = json.loads(message) - if message["type"] == "start": - self.dsp.set_samp_rate(message["params"]["output_rate"]) - self.dsp.start() + if message["type"] == "dspcontrol": + if "params" in message: + params = message["params"] + for key in params: + methodname = "set_" + key + if hasattr(self.dsp, methodname): + method = getattr(self.dsp, methodname) + if callable(method): + method(params[key]) + else: + print("method {0} is not callable".format(methodname)) + else: + print("dsp has no method {0}".format(methodname)) + + if "action" in message and message["action"] == "start": + self.dsp.start() except json.JSONDecodeError: print("message is not json: {0}".format(message)) @@ -105,6 +107,8 @@ class WebSocketMessageHandler(object): def handleClose(self, conn): if self.forwarder: SpectrumThread.getSharedInstance().remove_client(self.forwarder) + if self.dsp: + self.dsp.stop() class WebSocketController(Controller): def handle_request(self): diff --git a/owrx/source.py b/owrx/source.py index 0f18014..dedaaa0 100644 --- a/owrx/source.py +++ b/owrx/source.py @@ -98,4 +98,58 @@ class SpectrumThread(threading.Thread): def shutdown(self): print("shutting down spectrum thread") SpectrumThread.sharedInstance = None - self.doRun = False \ No newline at end of file + self.doRun = False + +class DspThread(threading.Thread): + def __init__(self, handler): + self.doRun = True + self.handler = handler + + pm = PropertyManager.getSharedInstance() + + self.dsp = csdr.dsp() + #dsp_initialized=False + self.dsp.set_audio_compression(pm.getPropertyValue("audio_compression")) + self.dsp.set_fft_compression(pm.getPropertyValue("fft_compression")) #used by secondary chains + self.dsp.set_format_conversion(pm.getPropertyValue("format_conversion")) + self.dsp.set_offset_freq(0) + self.dsp.set_bpf(-4000,4000) + self.dsp.set_secondary_fft_size(pm.getPropertyValue("digimodes_fft_size")) + self.dsp.nc_port=pm.getPropertyValue("iq_server_port") + self.dsp.csdr_dynamic_bufsize = pm.getPropertyValue("csdr_dynamic_bufsize") + self.dsp.csdr_print_bufsizes = pm.getPropertyValue("csdr_print_bufsizes") + self.dsp.csdr_through = pm.getPropertyValue("csdr_through") + self.dsp.set_samp_rate(pm.getPropertyValue("samp_rate")) + #do_secondary_demod=False + super().__init__() + + def run(self): + self.dsp.start() + while (self.doRun): + data = self.dsp.read(256) + self.handler.write_dsp_data(data) + + def stop(self): + self.doRun = False + + def set_output_rate(self, samp_rate): + self.dsp.set_output_rate(samp_rate) + + def set_low_cut(self, cut): + bpf = self.dsp.get_bpf() + bpf[0] = cut + self.dsp.set_bpf(*bpf) + + def set_high_cut(self, cut): + bpf = self.dsp.get_bpf() + bpf[1] = cut + self.dsp.set_bpf(*bpf) + + def set_offset_freq(self, freq): + self.dsp.set_offset_freq(freq) + + def set_mod(self, mod): + if (self.dsp.get_demodulator() == mod): return + self.dsp.stop() + self.dsp.set_demodulator(mod) + self.dsp.start() diff --git a/owrx/websocket.py b/owrx/websocket.py index 7c96e34..044d3f0 100644 --- a/owrx/websocket.py +++ b/owrx/websocket.py @@ -20,7 +20,7 @@ class WebSocketConnection(object): def get_header(self, size, opcode): ws_first_byte = 0b10000000 | (opcode & 0x0F) - if(size>125): + if (size > 125): return bytes([ws_first_byte, 126, (size>>8) & 0xff, size & 0xff]) else: # 256 bytes binary message in a single unmasked frame @@ -34,14 +34,12 @@ class WebSocketConnection(object): # string-type messages are sent as text frames if (type(data) == str): header = self.get_header(len(data), 1) - self.handler.wfile.write(header) - self.handler.wfile.write(data.encode('utf-8')) + self.handler.wfile.write(header + data.encode('utf-8')) self.handler.wfile.flush() # anything else as binary else: header = self.get_header(len(data), 2) - self.handler.wfile.write(header) - self.handler.wfile.write(data) + self.handler.wfile.write(header + data) self.handler.wfile.flush() def read_loop(self):