many fixes and new features like IMA ADPCM compression

This commit is contained in:
ha7ilm
2015-08-17 20:32:58 +02:00
parent f388e80624
commit 0713f57bb4
32 changed files with 12695 additions and 272 deletions

0
htdocs/gfx/openwebrx-avatar-background.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 459 B

After

Width:  |  Height:  |  Size: 459 B

0
htdocs/gfx/openwebrx-avatar.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 742 B

After

Width:  |  Height:  |  Size: 742 B

0
htdocs/gfx/openwebrx-background-cool-blue.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

0
htdocs/gfx/openwebrx-background-lingrad.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 679 B

After

Width:  |  Height:  |  Size: 679 B

0
htdocs/gfx/openwebrx-ha5kfu-top-logo.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

0
htdocs/gfx/openwebrx-logo-big.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

0
htdocs/gfx/openwebrx-rx-details-arrow-up.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 518 B

0
htdocs/gfx/openwebrx-rx-details-arrow.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 505 B

After

Width:  |  Height:  |  Size: 505 B

0
htdocs/gfx/openwebrx-scale-background.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

0
htdocs/gfx/openwebrx-top-logo.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

0
htdocs/gfx/openwebrx-top-photo.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

51
htdocs/index.wrx Executable file → Normal file
View File

@ -1,31 +1,35 @@
<!DOCTYPE HTML>
<!--
OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu>
This file is part of OpenWebRX.
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
OpenWebRX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
-->
<html>
<head>
<title>OpenWebRX | Open Source Web-based SDR for everyone!</title>
<title>OpenWebRX | Open Source SDR Web App for Everyone!</title>
<script type="text/javascript">
//Local variables
client_id="%[CLIENT_ID]";
ws_url="%[WS_URL]";
rx_photo_height=%[RX_PHOTO_HEIGHT];
var audio_buffering_fill_to=%[AUDIO_BUFSIZE];
</script>
<script src="sdr.js"></script>
<script src="openwebrx.js"></script>
<link rel="stylesheet" type="text/css" href="openwebrx.css" />
<meta charset="utf-8">
@ -50,6 +54,13 @@ This file is part of OpenWebRX.
<a id="openwebrx-rx-details-arrow-up" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow-up.png" /></a>
<a id="openwebrx-rx-details-arrow-down" onclick="toggle_rx_photo();"><img src="gfx/openwebrx-rx-details-arrow.png" /></a>
</div>
<section id="openwebrx-main-buttons">
<ul>
<li onmouseup="toggle_panel('openwebrx-panel-status');"><img src="gfx/openwebrx-panel-status.png" /><br/>Status</li>
<li onmouseup="toggle_panel('openwebrx-panel-log');"><img src="gfx/openwebrx-panel-log.png" /><br/>Log</li>
<li onmouseup="toggle_panel('openwebrx-panel-receiver');"><img src="gfx/openwebrx-panel-receiver.png" /><br/>Receiver</li>
</ul>
</section>
</div>
</div>
<div id="webrx-main-container">
@ -61,7 +72,7 @@ This file is part of OpenWebRX.
<!-- add canvas here by javascript -->
</div>
<div id="openwebrx-panels-container">
<div class="openwebrx-panel" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="215,70">
<div class="openwebrx-panel" id="openwebrx-panel-receiver" data-panel-name="client-params" data-panel-pos="right" data-panel-order="0" data-panel-size="215,70">
<div id="webrx-actual-freq">---.--- MHz</div>
<div id="webrx-mouse-freq">---.--- MHz</div>
<!--<div class="openwebrx-button" onclick="ws.send('SET mod=wfm');" >WFM</div>-->
@ -71,17 +82,23 @@ This file is part of OpenWebRX.
<div class="openwebrx-button" onclick="demodulator_analog_replace('usb');">USB</div>
<div class="openwebrx-button" onclick="demodulator_analog_replace('cw');">CW</div>
</div>
<div class="openwebrx-panel" id="webrx-config" data-panel-name="debug" data-panel-pos="left" data-panel-order="0" data-panel-size="585,130">
<div class="openwebrx-panel-inner">
<div id="openwebrx-client-log-title">openwebrx.js (beta) client log </strong><span id="openwebrx-problems"></span></div>
<div class="openwebrx-panel" id="openwebrx-panel-log" data-panel-name="debug" data-panel-pos="left" data-panel-order="1" data-panel-size="619,142">
<div class="openwebrx-panel-inner" id="openwebrx-log-scroll">
<div id="openwebrx-client-log-title">OpenWebRX (beta) client log<span style="color: #ff5900;"></span> </strong><span id="openwebrx-problems"></span></div>
Author: <a href="javascript:sendmail2('pi7qtu=alz$pc');">HA7ILM</a>. Please send me bug reports and suggestions.<br/>
Client status: <span id="openwebrx-client-status">
<span id="openwebrx-audio-sps"></span><br/>
<!--Server status: <span id="openwebrx-server-status">no information</span><br/>-->
Your client ID is: <em>%[CLIENT_ID]</em><br />
<div id="openwebrx-debugdiv"></div>
</div>
</div>
<div class="openwebrx-panel" id="openwebrx-panel-status" data-panel-name="status" data-panel-pos="left" data-panel-order="0" data-panel-size="615,50" data-panel-transparent="true">
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-buffer"> <span class="openwebrx-progressbar-text">Audio buffer [0 ms]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-output"> <span class="openwebrx-progressbar-text">Audio output [0 sps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-audio-speed"> <span class="openwebrx-progressbar-text">Audio stream [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-network-speed"> <span class="openwebrx-progressbar-text">Network speed [0 kbps]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-server-cpu"> <span class="openwebrx-progressbar-text">Server CPU [0%]</span><div class="openwebrx-progressbar-bar"></div></div>
<div class="openwebrx-progressbar" id="openwebrx-bar-clients"> <span class="openwebrx-progressbar-text">Clients [1]</span><div class="openwebrx-progressbar-bar"></div></div>
</div>
<div class="openwebrx-panel" data-panel-name="client-under-devel" data-panel-pos="none" data-panel-order="0" data-panel-size="245,55" style="background-color: Red;">
<span style="font-size: 15pt; font-weight: bold;">Under construction</span>
<br />We're working on the code right now, so the application might fail.

132
htdocs/openwebrx.css Executable file → Normal file
View File

@ -1,20 +1,22 @@
/*
OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu>
This file is part of OpenWebRX.
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
OpenWebRX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
*/
html, body
@ -52,9 +54,14 @@ html, body
{
margin:0;
padding:0;
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
#webrx-top-logo
{
position: absolute;
@ -65,7 +72,7 @@ html, body
#webrx-ha5kfu-top-logo
{
position: absolute;
top: 19px;
top: 15px;
right: 15px;
}
@ -177,8 +184,10 @@ html, body
#webrx-rx-photo-desc a
{
/*color: #007df1;*/
color: #5ca8ff;
text-shadow: none;
/*text-shadow: 0px 0px 7px #fff;*/
}
#webrx-rx-title
@ -269,6 +278,8 @@ html, body
{
position: absolute;
border-style: none;
image-rendering: crisp-edges;
image-rendering: -webkit-optimize-contrast;
}
#openwebrx-phantom-canvas
@ -410,6 +421,12 @@ html, body
cursor: pointer;
background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0 , #373737), color-stop(1, #4F4F4F) );
background:-moz-linear-gradient( center top, #373737 0%, #4F4F4F 100% );
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.openwebrx-button:hover
@ -431,3 +448,94 @@ html, body
margin-bottom: 5px;
font-weight: bold;
}
.openwebrx-progressbar
{
position: relative;
border-radius: 5px;
background-color: #003850; /*#006235;*/
display: inline-block;
text-align: center;
font-size: 8pt;
font-weight: bold;
text-shadow: 0px 0px 4px #000000;
cursor: default;
user-select: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.openwebrx-progressbar-bar
{
border-radius: 5px;
height: 100%;
width: 100%;
}
.openwebrx-progressbar-text
{
position: absolute;
left:0px;
top:4px;
width: inherit;
}
#openwebrx-panel-status
{
margin: 0px;
padding: 0px;
background-color:rgba(0, 0, 0, 0);
}
#openwebrx-panel-status div.openwebrx-progressbar
{
width: 200px;
height: 20px;
}
#openwebrx-main-buttons img
{
}
#openwebrx-main-buttons ul
{
display: table;
margin:0;
}
#openwebrx-main-buttons ul li
{
display: table-cell;
padding-left: 5px;
padding-right: 5px;
cursor:pointer;
}
#openwebrx-main-buttons li:hover
{
background-color: rgba(255, 255, 255, 0.3);
}
#openwebrx-main-buttons li:active
{
background-color: rgba(255, 255, 255, 0.55);
}
#openwebrx-main-buttons
{
position: absolute;
right: 133px;
top: 3px;
margin:0;
color: white;
text-shadow: 0px 0px 4px #000000;
text-align: center;
font-size: 9pt;
font-weight: bold;
}

379
htdocs/openwebrx.js Executable file → Normal file
View File

@ -1,21 +1,23 @@
/*
OpenWebRX (c) Copyright 2013-2014 Andras Retzler <randras@sdr.hu>
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This file is part of OpenWebRX.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
OpenWebRX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
*/
@ -43,6 +45,9 @@ var audio_buffer_current_count_debug=0;
var audio_buffer_current_size=0;
var fft_size;
var fft_fps;
var fft_compression="none";
var fft_codec=new sdrjs.ImaAdpcm();
var audio_compression="none";
var waterfall_setup_done=0;
var waterfall_queue = [];
var waterfall_timer;
@ -270,7 +275,7 @@ demodulator.draggable_ranges={none: 0, beginning:1 /*from*/, ending: 2 /*to*/, a
// This can be used as a base for basic audio demodulators.
// It already supports most basic modulations used for ham radio and commercial services: AM/FM/LSB/USB
demodulator_response_time=100;
demodulator_response_time=50;
//in ms; if we don't limit the number of SETs sent to the server, audio will underrun (possibly output buffer is cleared on SETs in GNU Radio
function demodulator_default_analog(offset_frequency,subtype)
@ -703,27 +708,21 @@ function mkscale()
var text_measured=scale_ctx.measureText(text_to_draw);
scale_ctx.textAlign = "center";
//advanced text drawing begins
if(zoom_level==0&&range.start+spacing.smallbw*spacing.ratio>marker_hz)
{ //if this is the first overall marker when zoomed out
if(x<text_measured.width/2)
{ //and if it would be clipped off the screen
if(scale_px_from_freq(marker_hz+spacing.smallbw*spacing.ratio,range)-text_measured.width>=scale_min_space_bw_texts)
{ //and if we have enough space to draw it correctly without clipping
scale_ctx.textAlign = "left";
scale_ctx.fillText(text_to_draw, 0, text_h_pos);
}
if( zoom_level==0 && (range.start+spacing.smallbw*spacing.ratio>marker_hz) && (x<text_measured.width/2) )
{ //if this is the first overall marker when zoomed out... and if it would be clipped off the screen...
if(scale_px_from_freq(marker_hz+spacing.smallbw*spacing.ratio,range)-text_measured.width>=scale_min_space_bw_texts)
{ //and if we have enough space to draw it correctly without clipping
scale_ctx.textAlign = "left";
scale_ctx.fillText(text_to_draw, 0, text_h_pos);
}
}
else if(zoom_level==0&&range.end-spacing.smallbw*spacing.ratio<marker_hz)
{ //if this is the last overall marker when zoomed out
if(x>window.innerWidth-text_measured.width/2)
{ //and if it would be clipped off the screen
if(window.innerWidth-text_measured.width-scale_px_from_freq(marker_hz-spacing.smallbw*spacing.ratio,range)>=scale_min_space_bw_texts)
{ //and if we have enough space to draw it correctly without clipping
scale_ctx.textAlign = "right";
scale_ctx.fillText(text_to_draw, window.innerWidth, text_h_pos);
}
}
else if( zoom_level==0 && (range.end-spacing.smallbw*spacing.ratio<marker_hz) && (x>window.innerWidth-text_measured.width/2) )
{ // if this is the last overall marker when zoomed out... and if it would be clipped off the screen...
if(window.innerWidth-text_measured.width-scale_px_from_freq(marker_hz-spacing.smallbw*spacing.ratio,range)>=scale_min_space_bw_texts)
{ //and if we have enough space to draw it correctly without clipping
scale_ctx.textAlign = "right";
scale_ctx.fillText(text_to_draw, window.innerWidth, text_h_pos);
}
}
else scale_ctx.fillText(text_to_draw, x, text_h_pos); //draw text normally
}
@ -960,11 +959,16 @@ function resize_waterfall_container(check_init)
canvas_container.style.height=(window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px";
}
debug_ws_data_received=0;
max_clients_num=0;
var COMPRESS_FFT_PAD_N=10; //should be the same as in csdr.c
function on_ws_recv(evt)
{
if(!(evt.data instanceof ArrayBuffer)) { divlog("on_ws_recv(): Not ArrayBuffer received...",1); return; }
//
debug_ws_data_received+=evt.data.byteLength/1000;
firstChars=getFirstChars(evt.data,3);
if(firstChars=="CLI")
{
@ -973,7 +977,9 @@ function on_ws_recv(evt)
}
if(firstChars=="AUD")
{
var audio_data=new Int16Array(evt.data,4);
var audio_data;
if(audio_compression=="adpcm") audio_data=new Uint8Array(evt.data,4)
else audio_data=new Int16Array(evt.data,4);
audio_prepare(audio_data);
audio_buffer_current_size_debug+=audio_data.length;
audio_buffer_all_size_debug+=audio_data.length;
@ -982,8 +988,15 @@ function on_ws_recv(evt)
else if(firstChars=="FFT")
{
//alert("Yupee! Doing FFT");
var floatArray = new Float32Array(evt.data,4);
waterfall_add_queue(floatArray);
if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4));
else if(fft_compression="adpcm")
{
fft_codec.reset();
var waterfall_i16=fft_codec.decode(new Uint8Array(evt.data,4));
var waterfall_f32=new Float32Array(waterfall_i16.length-COMPRESS_FFT_PAD_N);
for(var i=0;i<waterfall_i16.length;i++) waterfall_f32[i]=waterfall_i16[i+COMPRESS_FFT_PAD_N]/100;
waterfall_add_queue(waterfall_f32);
}
} else if(firstChars=="MSG")
{
/*try
@ -999,18 +1012,36 @@ function on_ws_recv(evt)
waterfall_init();
break;
case "bandwidth":
bandwidth=parseInt(param[1])
bandwidth=parseInt(param[1]);
break;
case "center_freq":
center_freq=parseInt(param[1])
center_freq=parseInt(param[1]); //there was no ; and it was no problem... why?
break;
case "fft_size":
fft_size=parseInt(param[1])
fft_size=parseInt(param[1]);
break;
case "fft_fps":
fft_fps=parseInt(param[1])
fft_fps=parseInt(param[1]);
break;
case "audio_compression":
audio_compression=param[1];
divlog( "Audio stream is "+ ((audio_compression=="adpcm")?"compressed":"uncompressed")+"." )
break;
case "fft_compression":
fft_compression=param[1];
divlog( "FFT stream is "+ ((fft_compression=="adpcm")?"compressed":"uncompressed")+"." )
break;
case "cpu_usage":
var server_cpu_usage=parseInt(param[1]);
progressbar_set(e("openwebrx-bar-server-cpu"),server_cpu_usage/100,"Server CPU ["+param[1]+"%]",server_cpu_usage>85);
break;
case "clients":
var clients_num=parseInt(param[1]);
progressbar_set(e("openwebrx-bar-clients"),clients_num/max_clients_num,"Clients ["+param[1]+"]",clients_num>max_clients_num*0.85);
break;
case "max_clients":
max_clients_num=parseInt(param[1]);
break;
}
}
/*}
@ -1032,17 +1063,33 @@ function add_problem(what)
window.setTimeout(function(ps,ns) { ps.removeChild(ns); }, 1000,problems_span,new_span);
}
waterfall_measure_minmax=false;
waterfall_measure_minmax_min=1e100;
waterfall_measure_minmax_max=-1e100;
function waterfall_measure_minmax_do(what)
{
waterfall_measure_minmax_min=Math.min(waterfall_measure_minmax_min,Math.min.apply(Math,what));
waterfall_measure_minmax_max=Math.max(waterfall_measure_minmax_max,Math.max.apply(Math,what));
}
function waterfall_measure_minmax_print()
{
console.log("Waterfall | min = "+waterfall_measure_minmax_min.toString()+" dB | max = "+waterfall_measure_minmax_max.toString()+" dB");
}
function waterfall_add_queue(what)
{
if(waterfall_measure_minmax) waterfall_measure_minmax_do(what);
waterfall_queue.push(what);
}
function waterfall_dequeue()
{
if(waterfall_queue.length) waterfall_add(waterfall_queue.shift());
if(waterfall_queue.length>Math.max(fft_fps/2,8)) //in case of emergency
if(waterfall_queue.length>Math.max(fft_fps/2,20)) //in case of emergency
{
console.log(waterfall_queue.length);
console.log("waterfall queue length:", waterfall_queue.length);
add_problem("fft overflow");
while(waterfall_queue.length) waterfall_add(waterfall_queue.shift());
}
@ -1054,10 +1101,20 @@ function on_ws_opened()
divlog("WebSocket opened to "+ws_url);
}
var was_error=0;
function divlog(what, is_error)
{
if(typeof is_error !== undefined && is_error == 1) what="<span class=\"webrx-error\">"+what+"</span>";
is_error=!!is_error;
was_error |= is_error;
if(is_error)
{
what="<span class=\"webrx-error\">"+what+"</span>";
if(e("openwebrx-panel-log").openwebrxHidden) toggle_panel("openwebrx-panel-log"); //show panel if any error is present
}
e("openwebrx-debugdiv").innerHTML+=what+"<br />";
var wls=e("openwebrx-log-scroll");
wls.scrollTop=wls.scrollHeight; //scroll to bottom
}
var audio_context;
@ -1065,54 +1122,105 @@ var audio_initialized=0;
var audio_received = Array();
var audio_buffer_index = 0;
var audio_resampler;
var audio_resampler=new sdrjs.RationalResamplerFF(4,1);
var audio_codec=new sdrjs.ImaAdpcm();
var audio_compression="unknown";
var audio_node;
//var audio_received_sample_rate = 48000;
var audio_input_buffer_size;
// Optimalise these if audio lags or is choppy:
var audio_buffer_size = 8192;//2048 was choppy
var audio_buffer_maximal_length_sec=1.7; //actual number of samples are calculated from sample rate
var audio_flush_interval_ms=250; //the interval in which audio_flush() is called
var audio_buffer_size = 4096;//2048 was choppy
var audio_buffer_maximal_length_sec=3; //actual number of samples are calculated from sample rate
var audio_buffer_decrease_to_on_overrun_sec=2.2;
var audio_flush_interval_ms=500; //the interval in which audio_flush() is called
var audio_prepared_buffers = Array();
var audio_rebuffer = new sdrjs.Rebuffer(audio_buffer_size,sdrjs.REBUFFER_FIXED);
var audio_last_output_buffer = new Float32Array(audio_buffer_size);
var audio_last_output_offset = 0;
var audio_buffering = false;
var audio_buffering_fill_to=10; //on audio underrun we wait until this n*audio_buffer_size samples are present
//var audio_buffering_fill_to=4; //on audio underrun we wait until this n*audio_buffer_size samples are present
//tnx to the hint from HA3FLT, now we have about half the response time! (original value: 10)
function gain_ff(gain_value,data) //great! solved clicking! will have to move to sdr.js
{
for(var i=0;i<data.length;i++)
data[i]*=gain_value;
return data;
}
function audio_prepare(data)
{
//audio_rebuffer.push(sdrjs.ConvertI16_F(data));//no resampling
//audio_rebuffer.push(audio_resampler.process(sdrjs.ConvertI16_F(data)));//resampling without ADPCM
if(audio_compression=="none")
audio_rebuffer.push(audio_resampler.process(gain_ff(0.9,sdrjs.ConvertI16_F(data))));//resampling without ADPCM
else if(audio_compression=="adpcm")
audio_rebuffer.push(audio_resampler.process(gain_ff(0.9,sdrjs.ConvertI16_F(audio_codec.decode(data))))); //resampling & ADPCM
else return;
//console.log("prepare",data.length,audio_rebuffer.remaining());
while(audio_rebuffer.remaining())
{
audio_prepared_buffers.push(audio_rebuffer.take());
audio_buffer_current_count_debug++;
}
if(audio_buffering && audio_prepared_buffers.length>audio_buffering_fill_to) audio_buffering=false;
}
function audio_prepare_without_resampler(data)
{
audio_rebuffer.push(sdrjs.ConvertI16_F(data));
console.log("prepare",data.length,audio_rebuffer.remaining());
while(audio_rebuffer.remaining())
{
audio_prepared_buffers.push(audio_rebuffer.take());
audio_buffer_current_count_debug++;
}
if(audio_buffering && audio_prepared_buffers.length>audio_buffering_fill_to) audio_buffering=false;
}
function audio_prepare_old(data)
{
//console.log("audio_prepare :: "+data.length.toString());
//console.log("data.len = "+data.length.toString());
var dopush=function()
{
console.log(audio_last_output_buffer);
audio_prepared_buffers.push(audio_last_output_buffer);
audio_last_output_offset=0;
audio_last_output_buffer=new Float32Array(audio_buffer_size);
audio_buffer_current_count_debug++;
};
var original_data_length=data.length;
var f32data=new Float32Array(data.length);
for(var i=0;i<data.length;i++) f32data[i]=data[i]/32768; //convert_i16_f
data=audio_resampler.process(f32data);
console.log(data,data.length,original_data_length);
if(data.length==0) return;
if(audio_last_output_offset+data.length<=audio_buffer_size)
{ //array fits into output buffer
for(var i=0;i<data.length;i++) audio_last_output_buffer[i+audio_last_output_offset]=data[i]/32768;
for(var i=0;i<data.length;i++) audio_last_output_buffer[i+audio_last_output_offset]=data[i];
audio_last_output_offset+=data.length;
//console.log("fits into; offset="+audio_last_output_offset.toString());
console.log("fits into; offset="+audio_last_output_offset.toString());
if(audio_last_output_offset==audio_buffer_size) dopush();
}
else
{ //array is larger than the remaining space in the output buffer
{ //array is larger than the remaining space in the output buffer
var copied=audio_buffer_size-audio_last_output_offset;
var remain=data.length-copied;
for(var i=0;i<audio_buffer_size-audio_last_output_offset;i++) //fill the remaining space in the output buffer
audio_last_output_buffer[i+audio_last_output_offset]=data[i]/32768;
audio_last_output_buffer[i+audio_last_output_offset]=data[i];///32768;
dopush();//push the output buffer and create a new one
//console.log("larger than; copied half: "+copied.toString()+", now at: "+audio_last_output_offset.toString());
console.log("larger than; copied half: "+copied.toString()+", now at: "+audio_last_output_offset.toString());
for(var i=0;i<remain;i++) //copy the remaining input samples to the new output buffer
audio_last_output_buffer[i]=data[i+copied]/32768;
audio_last_output_buffer[i]=data[i+copied];///32768;
audio_last_output_offset+=remain;
//console.log("larger than; remained: "+remain.toString()+", now at: "+audio_last_output_offset.toString());
console.log("larger than; remained: "+remain.toString()+", now at: "+audio_last_output_offset.toString());
}
if(audio_buffering && audio_prepared_buffers.length>audio_buffering_fill_to) audio_buffering=false;
}
@ -1127,24 +1235,49 @@ if (!AudioBuffer.prototype.copyToChannel)
}
function audio_onprocess(e)
{
{
//console.log("audio onprocess");
if(audio_buffering) return;
if(audio_prepared_buffers.length==0) { add_problem("audio underrun"); audio_buffering=true; }
else e.outputBuffer.copyToChannel(audio_prepared_buffers.shift(),0);
if(audio_prepared_buffers.length==0) { audio_buffer_progressbar_update(); /*add_problem("audio underrun");*/ audio_buffering=true; }
else { e.outputBuffer.copyToChannel(audio_prepared_buffers.shift(),0); }
}
var audio_buffer_progressbar_update_disabled=false;
var audio_buffer_total_average_level=0;
var audio_buffer_total_average_level_length=0;
function audio_buffer_progressbar_update()
{
if(audio_buffer_progressbar_update_disabled) return;
var audio_buffer_value=(audio_prepared_buffers.length*audio_buffer_size)/44100;
audio_buffer_total_average_level_length++; audio_buffer_total_average_level=(audio_buffer_total_average_level*((audio_buffer_total_average_level_length-1)/audio_buffer_total_average_level_length))+(audio_buffer_value/audio_buffer_total_average_level_length);
var overrun=audio_buffer_value>audio_buffer_maximal_length_sec;
var underrun=audio_prepared_buffers.length==0;
var text="buffer";
if(overrun) text="overrun";
if(underrun) text="underrun";
if(overrun||underrun)
{
audio_buffer_progressbar_update_disabled=true;
window.setTimeout(function(){audio_buffer_progressbar_update_disabled=false; audio_buffer_progressbar_update();},1000);
}
progressbar_set(e("openwebrx-bar-audio-buffer"),(underrun)?1:audio_buffer_value/1.5,"Audio "+text+" ["+(audio_buffer_value).toFixed(1)+" s]",overrun||underrun||audio_buffer_value<0.25);
}
function audio_flush()
{
flushed=false;
while(audio_buffer_maximal_length_sec*audio_context.sampleRate<audio_prepared_buffers.length*audio_buffer_size)
we_have_more_than=function(sec){ return sec*audio_context.sampleRate<audio_prepared_buffers.length*audio_buffer_size; }
if(we_have_more_than(audio_buffer_maximal_length_sec)) while(we_have_more_than(audio_buffer_decrease_to_on_overrun_sec))
{
if(!flushed) audio_buffer_progressbar_update();
flushed=true;
audio_prepared_buffers.shift();
}
if(flushed) add_problem("audio overrun");
//if(flushed) add_problem("audio overrun");
}
@ -1238,7 +1371,8 @@ function audio_init()
//https://github.com/grantgalitz/XAudioJS/blob/master/XAudioServer.js
//audio_resampler = new Resampler(audio_received_sample_rate, audio_context.sampleRate, 1, audio_buffer_size, true);
//audio_input_buffer_size = audio_buffer_size*(audio_received_sample_rate/audio_context.sampleRate);
webrx_set_param("audio_rate",audio_context.sampleRate); //Don't try to resample
webrx_set_param("audio_rate",audio_context.sampleRate); //Don't try to resample //TODO remove this
window.setInterval(audio_flush,audio_flush_interval_ms);
divlog('Web Audio API succesfully initialized, sample rate: '+audio_context.sampleRate.toString()+ " sps");
/*audio_source=audio_context.createBufferSource();
@ -1246,6 +1380,14 @@ function audio_init()
audio_source.buffer = buffer;
audio_source.noteOn(0);*/
demodulator_analog_replace('nfm'); //needs audio_context.sampleRate to exist
//hide log panel in a second (if user has not hidden it yet)
window.setTimeout(function(){
if(typeof e("openwebrx-panel-log").openwebrxHidden == "undefined" && !was_error)
{
animate(e("openwebrx-panel-log"),"opacity","",1,0,0.9,1000,60);
window.setTimeout(function(){toggle_panel("openwebrx-panel-log");e("openwebrx-panel-log").style.opacity="1";},1200)
}
},1000);
}
function on_ws_closed()
@ -1267,11 +1409,11 @@ String.prototype.startswith=function(str){ return this.indexOf(str) == 0; }; //h
function open_websocket()
{
if(ws_url.startswith("ws://localhost:")&&window.location.hostname!="127.0.0.1"&&window.location.hostname!="localhost")
{
divlog("Server administrator should set <em>server_hostname</em> correctly, because it is left as <em>\"localhost\"</em>. Now guessing hostname from page URL.",1);
ws_url="ws://"+(window.location.origin.split("://")[1])+"/ws/"; //guess automatically
}
//if(ws_url.startswith("ws://localhost:")&&window.location.hostname!="127.0.0.1"&&window.location.hostname!="localhost")
//{
//divlog("Server administrator should set <em>server_hostname</em> correctly, because it is left as <em>\"localhost\"</em>. Now guessing hostname from page URL.",1);
ws_url="ws://"+(window.location.origin.split("://")[1])+"/ws/"; //guess automatically -> now default behaviour
//}
if (!("WebSocket" in window))
divlog("Your browser does not support WebSocket, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser.");
ws = new WebSocket(ws_url+client_id);
@ -1293,14 +1435,16 @@ function open_websocket()
//var color_scale=[ 0x000000FF, 0xff5656ff, 0xffffffff];
//2014-04-22
var color_scale=[0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff];
var color_scale=[0x000000ff,0x2e6893ff, 0x69a5d0ff, 0x214b69ff, 0x9dc4e0ff, 0xfff775ff, 0xff8a8aff, 0xb20000ff]
//2015-04-10
//var color_scale=[0x112634ff,0x4991c6ff,0x18364cff,0x9dc4e0ff,0xfff775ff,0xff9f60,0xff4d4dff,0x8d0000ff];
function waterfall_mkcolor(db_value)
{
min_value=-100; //in dB
max_value=10
if(db_value<min_value) db_value=min_value
if(db_value>max_value) db_value=max_value
min_value=-115; //in dB
max_value=0;
if(db_value<min_value) db_value=min_value;
if(db_value>max_value) db_value=max_value;
full_scale=max_value-min_value;
relative_value=db_value-min_value;
value_percent=relative_value/full_scale;
@ -1503,11 +1647,12 @@ function waterfall_add(data)
oneline_image.data[x*4+i] = ((color>>>0)>>((3-i)*8))&0xff;
}
//Draw image
canvas_context.putImageData(oneline_image, 0, canvas_actual_line--);
shift_canvases();
if(canvas_actual_line<0) add_canvas();
//divlog("Drawn FFT");
}
@ -1525,10 +1670,17 @@ function waterfall_shift()
function check_top_bar_congestion()
{
var wt=e("webrx-rx-title");
var tl=e("webrx-ha5kfu-top-logo");
if(wt.offsetLeft+wt.offsetWidth>tl.offsetLeft-20) tl.style.display="none";
else tl.style.display="block";
var rmf=function(x){ return x.offsetLeft+x.offsetWidth; };
var wet=e("webrx-rx-title");
var wed=e("webrx-rx-desc");
var rightmost=Math.max(rmf(wet),rmf(wed));
var tl=e("openwebrx-main-buttons");
[wet, wed].map(function(what) {
if(rmf(what)>tl.offsetLeft-20) what.style.opacity=what.style.opacity="0";
else wet.style.opacity=wed.style.opacity="1";
});
}
function openwebrx_resize()
@ -1546,6 +1698,7 @@ function openwebrx_init()
place_panels();
window.setTimeout(function(){window.setInterval(debug_audio,1000);},1000);
window.addEventListener("resize",openwebrx_resize);
check_top_bar_congestion();
}
/*
@ -1577,18 +1730,33 @@ function debug_audio()
audio_debug_time_since_last_call=(time_now-audio_debug_time_last_start)/1000;
audio_debug_time_last_start=time_now; //now
audio_debug_time_taken=(time_now-audio_debug_time_start)/1000;
e("openwebrx-audio-sps").innerHTML=
"audio recv. at "+(audio_buffer_current_size_debug/audio_debug_time_since_last_call).toFixed(0)+" sps ("+
(audio_buffer_all_size_debug/audio_debug_time_taken).toFixed(1)+" sps avg.), feed at "+
((audio_buffer_current_count_debug*audio_buffer_size)/audio_debug_time_taken).toFixed(1)+" sps output";
kbps_mult=(audio_compression=="adpcm")?8:16;
//e("openwebrx-audio-sps").innerHTML=
// ((audio_compression=="adpcm")?"ADPCM compressed":"uncompressed")+" audio downlink:<br/> "+(audio_buffer_current_size_debug*kbps_mult/audio_debug_time_since_last_call).toFixed(0)+" kbps ("+
// (audio_buffer_all_size_debug*kbps_mult/audio_debug_time_taken).toFixed(1)+" kbps avg.), feed at "+
// ((audio_buffer_current_count_debug*audio_buffer_size)/audio_debug_time_taken).toFixed(1)+" sps output";
var audio_speed_value=audio_buffer_current_size_debug*kbps_mult/audio_debug_time_since_last_call;
progressbar_set(e("openwebrx-bar-audio-speed"),audio_speed_value/500000,"Audio stream ["+(audio_speed_value/1000).toFixed(0)+" kbps]",false);
var audio_output_value=(audio_buffer_current_count_debug*audio_buffer_size)/audio_debug_time_taken;
progressbar_set(e("openwebrx-bar-audio-output"),audio_output_value/55000,"Audio output ["+(audio_output_value/1000).toFixed(1)+" ksps]",audio_output_value>55000||audio_output_value<10000);
audio_buffer_progressbar_update();
var network_speed_value=debug_ws_data_received/audio_debug_time_taken;
progressbar_set(e("openwebrx-bar-network-speed"),network_speed_value*8/2000,"Network usage ["+(network_speed_value*8).toFixed(1)+" kbps]",false);
audio_buffer_current_size_debug=0;
if(waterfall_measure_minmax) waterfall_measure_minmax_print();
}
// ========================================================
// ======================= PANELS =======================
// ========================================================
panel_margin=10;
panel_margin=5.9;
function pop_bottommost_panel(from)
{
@ -1608,8 +1776,19 @@ function pop_bottommost_panel(from)
return to_return;
}
function toggle_panel(what)
{
var item=e(what);
if(item.openwebrxDisableClick) return;
item.openwebrxHidden=!item.openwebrxHidden;
place_panels();
item.openwebrxDisableClick=true;
window.setTimeout(function(){item.openwebrxDisableClick=false;},100);
}
function place_panels()
{
var hoffset=0; //added this because the first panel should not have such great gap below
var left_col=[];
var right_col=[];
var plist=e("openwebrx-panels-container").children;
@ -1618,33 +1797,61 @@ function place_panels()
c=plist[i];
if(c.className=="openwebrx-panel")
{
if(c.openwebrxHidden)
{
c.style.display="none";
continue;
}
c.style.display="block";
c.openwebrxPanelTransparent=(!!c.dataset.panelTransparent);
newSize=c.dataset.panelSize.split(",");
if (c.dataset.panelPos=="left") { left_col.push(c); }
else if(c.dataset.panelPos=="right") { right_col.push(c); }
c.style.width=newSize[0]+"px";
c.style.height=newSize[1]+"px";
c.style.margin=panel_margin.toString()+"px";
if(!c.openwebrxPanelTransparent) c.style.margin=panel_margin.toString()+"px";
else c.style.marginLeft=panel_margin.toString()+"px";
c.openwebrxPanelWidth=parseInt(newSize[0]);
c.openwebrxPanelHeight=parseInt(newSize[1]);
}
}
y=0;
y=hoffset; //was y=0 before hoffset
while(left_col.length>0)
{
p=pop_bottommost_panel(left_col);
p.style.left="0px";
p.style.bottom=y.toString()+"px";
p.style.visibility="visible";
y+=p.openwebrxPanelHeight+3*panel_margin;
y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
}
y=0;
y=hoffset;
while(right_col.length>0)
{
p=pop_bottommost_panel(right_col);
p.style.right="10px";
p.style.right=(e("webrx-canvas-container").offsetWidth-e("webrx-canvas-container").clientWidth).toString()+"px"; //get scrollbar width
p.style.bottom=y.toString()+"px";
p.style.visibility="visible";
y+=p.openwebrxPanelHeight+3*panel_margin;
y+=p.openwebrxPanelHeight+((p.openwebrxPanelTransparent)?0:3)*panel_margin;
}
}
function progressbar_set(obj,val,text,over)
{
if (val<0.05) val=0;
if (val>1) val=1;
var innerBar=null;
var innerText=null;
for(var i=0;i<obj.children.length;i++)
{
if(obj.children[i].className=="openwebrx-progressbar-text") innerText=obj.children[i];
else if(obj.children[i].className=="openwebrx-progressbar-bar") innerBar=obj.children[i];
}
if(innerBar==null) return;
//.h: function animate(object,style_name,unit,from,to,accel,time_ms,fps,to_exec)
animate(innerBar,"width","px",innerBar.clientWidth,val*obj.clientWidth,0.7,700,60);
//innerBar.style.width=(val*100).toFixed(0)+"%";
innerBar.style.backgroundColor=(over)?"#ff6262":"#00aba6";
if(innerText==null) return;
innerText.innerHTML=text;
}

94
htdocs/retry.html Normal file
View File

@ -0,0 +1,94 @@
<html>
<!--
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<head><title>OpenWebRX</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
html, body
{
font-family: "DejaVu Sans", Verdana, Geneva, sans-serif;
width: 100%;
text-align: center;
margin: 0;
padding: 0;
}
img.logo
{
margin-top: 120px;
}
div.frame
{
text-align: left;
margin:0px auto;
width: 800px;
}
div.panel
{
text-align: center;
background-color:#777777;
border-radius: 15px;
padding: 12px;
font-weight: bold;
color: White;
font-size: 13pt;
/*text-shadow: 1px 1px 4px #444;*/
font-family: sans;
}
div.alt
{
font-size: 10pt;
padding-top: 10px;
}
body div a
{
color: #5ca8ff;
text-shadow: none;
}
span.browser
{
}
</style>
<script>
var irt = function (s,n) {return s.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c>="a"?97:65)<=(c=c.charCodeAt(0)-n)?c:c+26);});}
var sendmail2 = function (s) { window.location.href="mailto:"+irt(s.replace("=",String.fromCharCode(0100)).replace("$","."),8); }
window.addEventListener("load",function(){rs=document.getElementById("reconnect-secs"); rt=document.getElementById("reconnect-text"); cnt=29;window.setInterval(function(){if(cnt<=-1) window.location.href=window.location.href.split("retry.")[0]; else if(cnt==0) {rt.innerHTML="Reconnecting..."; cnt--;} else rs.innerHTML=(cnt--).toString();},1000);},false);
</script>
</head>
<body>
<div class="frame">
<img class="logo" src="gfx/openwebrx-logo-big.png" style="height: 60px;"/>
<div class="panel">
There are no client slots left on this server.
<div class="alt">
Please wait until a client disconnects.<br /><span id="reconnect-text">We will try to reconnect in <span id="reconnect-secs">30</span> seconds...</span>
</div>
</div>
</div>
</body>
</html>

11683
htdocs/sdr.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,21 +1,23 @@
<html>
<!--
OpenWebRX (c) Copyright 2013 Andras Retzler <ha7ilm@sdr.hu>
This file is part of OpenWebRX.
This file is part of OpenWebRX,
an open-source SDR receiver software with a web UI.
Copyright (c) 2013-2015 by Andras Retzler <randras@sdr.hu>
OpenWebRX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
OpenWebRX is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with OpenWebRX. If not, see <http://www.gnu.org/licenses/>.
-->
<head><title>OpenWebRX</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>