From 8b81276df5f1cf33044c64cd1d93501ca5b75387 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Thu, 31 Mar 2016 20:15:11 +0000 Subject: [PATCH 01/37] Added NFM support --- rtl_airband.cpp | 107 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 13 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 09eb387..9d2fd23 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -106,10 +106,10 @@ #define BUF_SIZE 2560000 #define SOURCE_RATE 2560000 -#define WAVE_RATE 8000 +#define WAVE_RATE 16000 #define WAVE_BATCH 1000 #define WAVE_LEN 2048 -#define MP3_RATE 8000 +#define MP3_RATE 16000 #define MAX_SHOUT_QUEUELEN 32768 #define AGC_EXTRA 48 #define FFT_SIZE 512 @@ -159,10 +159,17 @@ struct file_data { FILE *f; }; +enum modulations { MOD_AM, MOD_NFM }; struct channel_t { float wavein[WAVE_LEN]; // FFT output waveform float waveref[WAVE_LEN]; // for power level calculation float waveout[WAVE_LEN]; // waveform after squelch + AGC + float complex_samples[2*WAVE_LEN]; // raw samples for NFM demod + int wavecnt; // sample counter for timeref shift +// FIXME: get this from complex_samples? + float pr; // previous sample - real part + float pj; // previous sample - imaginary part + enum modulations modulation; int agcsq; // squelch status, 0 = signal, 1 = suppressed char agcindicate; // squelch status indicator float agcavgfast; // average power, for AGC @@ -190,6 +197,7 @@ struct device_t { int channel_count; int bins[8]; channel_t channels[8]; + float timeref_freq[8]; int waveend; int waveavail; THREAD rtl_thread; @@ -197,6 +205,7 @@ struct device_t { int row; int failed; enum rec_modes mode; + }; device_t* devices; @@ -544,6 +553,21 @@ void* icecast_check(void* params) { #endif } +void multiply(float ar, float aj, float br, float bj, float *cr, float *cj) +{ + *cr = ar*br - aj*bj; + *cj = aj*br + ar*bj; +} + +float polar_discriminant(float ar, float aj, float br, float bj) +{ + float cr, cj; + double angle; + multiply(ar, aj, br, -bj, &cr, &cj); + angle = atan2((double)cj, (double)cr); + return (float)(angle * M_1_PI); +} + void demodulate() { // initialize fft engine @@ -568,6 +592,7 @@ void demodulate() { sizes[1] = sizeof(channel_t); #endif + float rotated_r, rotated_j; ALIGN float ALIGN2 levels[256]; for (int i=0; i<256; i++) { levels[i] = i-127.5f; @@ -678,10 +703,24 @@ void demodulate() { #ifdef USE_BCM_VC fftwave(dev->channels[0].wavein + dev->waveend, fft->out, sizes, dev->bins); + for (int j = 0; j < dev->channel_count; j++) { + if(dev->channels[j].modulation == MOD_NFM) { + struct GPU_FFT_COMPLEX *ptr = fft->out; + for (int job = 0; job < FFT_BATCH; job++) { + dev->channels[j].complex_samples[2*(dev->waveend+job)] = ptr[dev->bins[j]].re; + dev->channels[j].complex_samples[2*(dev->waveend+job)+1] = ptr[dev->bins[j]].im; + ptr += fft->step; + } + } + } #else for (int j = 0; j < dev->channel_count; j++) { dev->channels[j].wavein[dev->waveend] = sqrtf(fftout[dev->bins[j]][0] * fftout[dev->bins[j]][0] + fftout[dev->bins[j]][1] * fftout[dev->bins[j]][1]); + if(channel->modulation == MOD_NFM) { + dev->channels[j].complex_samples[2*dev->waveend] = fftout[dev->bins[j]][0]; + dev->channels[j].complex_samples[2*dev->waveend+1] = fftout[dev->bins[j]][1]; + } } #endif @@ -733,16 +772,19 @@ void demodulate() { if (channel->agcsq == 1 && channel->agcavgslow > 3.0f * channel->agcmin) { channel->agcsq = -AGC_EXTRA * 2; channel->agcindicate = '*'; + if(channel->modulation == MOD_AM) { // fade in - for (int k = j - AGC_EXTRA; k < j; k++) { - if (channel->wavein[k] > channel->agcmin * 3.0f) { - channel->agcavgfast = channel->agcavgfast * 0.98f + channel->wavein[k] * 0.02f; + for (int k = j - AGC_EXTRA; k < j; k++) { + if (channel->wavein[k] > channel->agcmin * 3.0f) { + channel->agcavgfast = channel->agcavgfast * 0.98f + channel->wavein[k] * 0.02f; + } } } } } else { if (channel->wavein[j] > channel->agcmin * 3.0f) { - channel->agcavgfast = channel->agcavgfast * 0.995f + channel->wavein[j] * 0.005f; + if(channel->modulation == MOD_AM) + channel->agcavgfast = channel->agcavgfast * 0.995f + channel->wavein[j] * 0.005f; channel->agclow = 0; } else { channel->agclow++; @@ -751,23 +793,46 @@ void demodulate() { if ((channel->agcsq == -1 && channel->agcavgslow < 2.4f * channel->agcmin) || channel->agclow == AGC_EXTRA - 12) { channel->agcsq = AGC_EXTRA * 2; channel->agcindicate = ' '; - // fade out - for (int k = j - AGC_EXTRA + 1; k < j; k++) { - channel->waveout[k] = channel->waveout[k - 1] * 0.94f; + if(channel->modulation == MOD_AM) { + // fade out + for (int k = j - AGC_EXTRA + 1; k < j; k++) { + channel->waveout[k] = channel->waveout[k - 1] * 0.94f; + } } } } - channel->waveout[j] = (channel->agcsq != -1) ? 0 : (channel->wavein[j - AGC_EXTRA] - channel->agcavgfast) / (channel->agcavgfast * 2.5f); - if (abs(channel->waveout[j]) > 0.8f) { - channel->waveout[j] *= 0.85f; - channel->agcavgfast *= 1.15f; + if(channel->agcsq != -1) { + channel->waveout[j] = 0; + } else { + if(channel->modulation == MOD_AM) { + channel->waveout[j] = (channel->wavein[j - AGC_EXTRA] - channel->agcavgfast) / (channel->agcavgfast * 2.5f); + if (abs(channel->waveout[j]) > 0.8f) { + channel->waveout[j] *= 0.85f; + channel->agcavgfast *= 1.15f; + } + } else { // NFM + multiply(channel->complex_samples[2*(j - AGC_EXTRA)], channel->complex_samples[2*(j - AGC_EXTRA)+1], +// FIXME: use j instead of wavecnt? + cosf(dev->timeref_freq[i] * (float)channel->wavecnt), + -sinf(dev->timeref_freq[i] * (float)channel->wavecnt), + &rotated_r, + &rotated_j); +// FIXME: auto gain coefficient? + channel->waveout[j] = polar_discriminant(rotated_r, rotated_j, channel->pr, channel->pj) * 0.5f; + channel->pr = rotated_r; + channel->pj = rotated_j; + } } + if(channel->modulation == MOD_NFM) + channel->wavecnt = (channel->wavecnt + 1) % WAVE_RATE; } #ifdef _WIN32 process_outputs(channel); memcpy(channel->waveout, channel->waveout + WAVE_BATCH, AGC_EXTRA * 4); #endif memcpy(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4); + if(channel->modulation == MOD_NFM) + memcpy(channel->complex_samples, channel->complex_samples + 2 * WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4 * 2); if (foreground) { if(dev->mode == R_SCAN) printf("%4.0f/%3.0f%c %7.3f", channel->agcavgslow, channel->agcmin, channel->agcindicate, (dev->channels[0].frequency / 1000000.0)); @@ -945,6 +1010,17 @@ int main(int argc, char* argv[]) { channel->agcavgslow = 0.5f; channel->agcmin = 100.0f; channel->agclow = 0; + channel->modulation = MOD_AM; + if(devs[i]["channels"][j].exists("modulation")) { + if(!strncmp(devs[i]["channels"][j]["modulation"], "nfm", 3)) { + channel->modulation = MOD_NFM; + } else if(!strncmp(devs[i]["channels"][j]["modulation"], "am", 2)) { + channel->modulation = MOD_AM; + } else { + cerr<<"Configuration error: devices.["<mode == R_MULTICHANNEL) { channel->frequency = devs[i]["channels"][j]["freq"]; } else { /* R_SCAN */ @@ -1016,6 +1092,11 @@ int main(int argc, char* argv[]) { channel->outputs[o].active = false; } dev->bins[j] = (int)ceil((channel->frequency + SOURCE_RATE - dev->centerfreq) / (double)(SOURCE_RATE / FFT_SIZE) - 1.0f) % FFT_SIZE; +// Calculate mixing frequencies needed for NFM to remove linear phase shift caused by FFT sliding window +// This equals bin_width_Hz * (distance_from_DC_bin) + dev->timeref_freq[j] = 2.0 * M_PI * (float)(SOURCE_RATE / FFT_SIZE) * + (float)(dev->bins[j] < (FFT_SIZE >> 1) ? dev->bins[j] + 1 : dev->bins[j] - FFT_SIZE + 1) / (float)WAVE_RATE; + } } } catch(FileIOException e) { From 2e6f2306baa0214dfc7fbed00dedc7ca79d9d340 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Fri, 1 Apr 2016 20:45:41 +0200 Subject: [PATCH 02/37] Fixed compile error on x86 --- rtl_airband.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 9d2fd23..cb040c6 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -717,7 +717,7 @@ void demodulate() { for (int j = 0; j < dev->channel_count; j++) { dev->channels[j].wavein[dev->waveend] = sqrtf(fftout[dev->bins[j]][0] * fftout[dev->bins[j]][0] + fftout[dev->bins[j]][1] * fftout[dev->bins[j]][1]); - if(channel->modulation == MOD_NFM) { + if(dev->channels[j].modulation == MOD_NFM) { dev->channels[j].complex_samples[2*dev->waveend] = fftout[dev->bins[j]][0]; dev->channels[j].complex_samples[2*dev->waveend+1] = fftout[dev->bins[j]][1]; } From f5088d15cc24bb8eb1798f99d557d9f08a97d5d7 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Sat, 2 Apr 2016 14:23:02 +0200 Subject: [PATCH 03/37] Added FM de-emphasis IIR filtering --- rtl_airband.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index cb040c6..c1d0b88 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -216,6 +216,7 @@ int avx; #endif int foreground = 0, do_syslog = 1; static volatile int do_exit = 0; +float alpha = exp(-1.0f/(WAVE_RATE * 2e-4)); void error() { #ifdef _WIN32 @@ -817,10 +818,11 @@ void demodulate() { -sinf(dev->timeref_freq[i] * (float)channel->wavecnt), &rotated_r, &rotated_j); -// FIXME: auto gain coefficient? - channel->waveout[j] = polar_discriminant(rotated_r, rotated_j, channel->pr, channel->pj) * 0.5f; + channel->waveout[j] = polar_discriminant(rotated_r, rotated_j, channel->pr, channel->pj); channel->pr = rotated_r; channel->pj = rotated_j; +// de-emphasis IIR + channel->waveout[j] = channel->waveout[j] * (1.0f - alpha) + channel->waveout[j-1] * alpha; } } if(channel->modulation == MOD_NFM) @@ -927,6 +929,8 @@ int main(int argc, char* argv[]) { #ifndef _WIN32 if(root.exists("pidfile")) pidfile = strdup(root["pidfile"]); #endif + if(root.exists("tau")) + alpha = ((int)root["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)root["tau"]))); Setting &devs = config.lookup("devices"); device_count = devs.getLength(); if (device_count < 1) { From 1c1bce8700ebf5e2be7405dce43c06733a7b6bef Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Sun, 3 Apr 2016 18:37:14 +0000 Subject: [PATCH 04/37] DC blocking filter for NFM --- rtl_airband.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index c1d0b88..42bbbaf 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -821,8 +821,9 @@ void demodulate() { channel->waveout[j] = polar_discriminant(rotated_r, rotated_j, channel->pr, channel->pj); channel->pr = rotated_r; channel->pj = rotated_j; -// de-emphasis IIR - channel->waveout[j] = channel->waveout[j] * (1.0f - alpha) + channel->waveout[j-1] * alpha; +// de-emphasis IIR + DC blocking + channel->agcavgfast = channel->agcavgfast * 0.995f + channel->waveout[j] * 0.005f; + channel->waveout[j] = channel->waveout[j] * (1.0f - alpha) + channel->waveout[j-1] * alpha - channel->agcavgfast; } } if(channel->modulation == MOD_NFM) From c61cb0ba8284b8d049b649a0c1f7b4490671e899 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Sun, 3 Apr 2016 21:01:30 +0200 Subject: [PATCH 05/37] Updated README.md and rtl_airband.conf.example --- README.md | 8 ++++---- rtl_airband.conf.example | 40 ++++++++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 82cb09d..e7b1ee9 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ RTLSDR-Airband ===================== -RTLSDR Airband is intended for Airband reception and online streaming to services such as liveatc.net +RTLSDR Airband is intended for AM/NFM voice channels reception and online streaming to services such as liveatc.net Features --------------------- - * Multichannel mode - decode up to eight AM channels per dongle (within bandwidth frequency range) - * Scanner mode - decode unlimited number of AM channels with frequency hopping in a round-robin + * Multichannel mode - decode up to eight AM or NFM channels per dongle (within bandwidth frequency range) + * Scanner mode - decode unlimited number of AM and/or NFM channels with frequency hopping in a round-robin fashion (no frequency range limitations) * Decode multiple dongles simutaneously * Auto squelch and Automatic Gain Control @@ -278,7 +278,7 @@ any error messages, for example: `tail /var/log/messages`. Common problems: License -------------------- -Copyright (C) 2015 Tomasz Lemiech +Copyright (C) 2015-2016 Tomasz Lemiech Based on original work by Wong Man Hang diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index ae2280b..fafa4ca 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -47,6 +47,13 @@ # This setting has no effect on Windows. # pidfile = /run/rtl_airband.pid +# De-emphasis filter time constant +# The filter suppresses high-frequency noise in NFM channels. +# Larger values cause more noise suppression. +# Set it to 0 if you don't want filtering. +# Default value: 200 (microseconds). +# tau = 200; + # devices section contains all settings related to the RTLSDR receivers. # # It's a list of groups - each group contains settings for a single receiver, @@ -80,6 +87,8 @@ devices = ( # (ie. +- 1.25 MHz around center frequency) # This setting is valid in multichannel mode only. freq = 118925000; +# Modulation - "am" or "nfm". Default is "am". + modulation = "am"; # Multiple outputs per channel are supported. For example, a single channel # can be sent to multiple Shoutcast servers. outputs = ( @@ -105,6 +114,9 @@ devices = ( # Subsequent channels { freq = 119100000; +# You can use both AM and NFM modulations for different channels on the same dongle. +# Probably not very useful in multichannel mode, but it's possible. + modulation = "nfm"; # This channel is streamed to two destinations outputs = ( { @@ -127,6 +139,7 @@ devices = ( }, { freq = 119500000; + modulation = "am"; # This channel is streamed to Icecast and saved to local files outputs = ( { @@ -153,6 +166,7 @@ devices = ( }, { freq = 120600000; + modulation = "am"; # This channel is not streamed to Icecast. # It is saved to local files into two destinations - one of them records continuously, the other one # skips silence periods. @@ -173,6 +187,7 @@ devices = ( }, { freq = 121300000; + modulation = "am"; outputs = ( { type = "icecast"; @@ -186,6 +201,7 @@ devices = ( }, { freq = 121600000; + modulation = "am"; outputs = ( { type = "icecast"; @@ -198,7 +214,7 @@ devices = ( ); } ); }, -# Receiver 1 - multichannel mode +# Receiver 1 - multichannel mode, 4 NFM channels { index = 1; gain = 28; @@ -207,59 +223,63 @@ devices = ( correction = 80; channels = ( { - freq = 122000000; + freq = 152000000; + modulation = "nfm"; outputs = ( { type = "icecast"; server = "www.example.com"; port = 8000; - mountpoint = "122000.mp3"; + mountpoint = "152000.mp3"; username = "source"; password = "password"; } ); }, { - freq = 122950000; + freq = 152100000; + modulation = "nfm"; outputs = ( { type = "icecast"; server = "www.example.com"; port = 8000; - mountpoint = "122950.mp3"; + mountpoint = "152100.mp3"; username = "source"; password = "password"; } ); }, { - freq = 123800000; + freq = 152200000; + modulation = "nfm"; outputs = ( { type = "icecast"; server = "www.example.com"; port = 8000; - mountpoint = "123800.mp3"; + mountpoint = "152200.mp3"; username = "source"; password = "password"; } ); }, { - freq = 123950000; + freq = 152300000; + modulation = "nfm"; outputs = ( { type = "icecast"; server = "www.example.com"; port = 8000; - mountpoint = "123950.mp3"; + mountpoint = "152300.mp3"; username = "source"; password = "password"; } ); } ); }, -# Receiver 2 - scan mode +# Receiver 2 - scan mode, AM modulation (default) { index = 2; gain = 28; From 041ce745014a236d89a795ed9596543d33fe8110 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Thu, 7 Apr 2016 19:47:05 +0000 Subject: [PATCH 06/37] NFM: improve performance of phase shift correction process --- rtl_airband.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 42bbbaf..c94a909 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -165,6 +165,8 @@ struct channel_t { float waveref[WAVE_LEN]; // for power level calculation float waveout[WAVE_LEN]; // waveform after squelch + AGC float complex_samples[2*WAVE_LEN]; // raw samples for NFM demod + float timeref_nsin[WAVE_RATE]; + float timeref_cos[WAVE_RATE]; int wavecnt; // sample counter for timeref shift // FIXME: get this from complex_samples? float pr; // previous sample - real part @@ -197,7 +199,6 @@ struct device_t { int channel_count; int bins[8]; channel_t channels[8]; - float timeref_freq[8]; int waveend; int waveavail; THREAD rtl_thread; @@ -814,8 +815,8 @@ void demodulate() { } else { // NFM multiply(channel->complex_samples[2*(j - AGC_EXTRA)], channel->complex_samples[2*(j - AGC_EXTRA)+1], // FIXME: use j instead of wavecnt? - cosf(dev->timeref_freq[i] * (float)channel->wavecnt), - -sinf(dev->timeref_freq[i] * (float)channel->wavecnt), + channel->timeref_cos[channel->wavecnt], + channel->timeref_nsin[channel->wavecnt], &rotated_r, &rotated_j); channel->waveout[j] = polar_discriminant(rotated_r, rotated_j, channel->pr, channel->pj); @@ -1097,11 +1098,17 @@ int main(int argc, char* argv[]) { channel->outputs[o].active = false; } dev->bins[j] = (int)ceil((channel->frequency + SOURCE_RATE - dev->centerfreq) / (double)(SOURCE_RATE / FFT_SIZE) - 1.0f) % FFT_SIZE; -// Calculate mixing frequencies needed for NFM to remove linear phase shift caused by FFT sliding window + if(channel->modulation == MOD_NFM) { +// Calculate mixing frequency needed for NFM to remove linear phase shift caused by FFT sliding window // This equals bin_width_Hz * (distance_from_DC_bin) - dev->timeref_freq[j] = 2.0 * M_PI * (float)(SOURCE_RATE / FFT_SIZE) * - (float)(dev->bins[j] < (FFT_SIZE >> 1) ? dev->bins[j] + 1 : dev->bins[j] - FFT_SIZE + 1) / (float)WAVE_RATE; - + float timeref_freq = 2.0f * M_PI * (float)(SOURCE_RATE / FFT_SIZE) * + (float)(dev->bins[j] < (FFT_SIZE >> 1) ? dev->bins[j] + 1 : dev->bins[j] - FFT_SIZE + 1) / (float)WAVE_RATE; +// Pre-generate the waveform for better performance + for(int k = 0; k < WAVE_RATE; k++) { + channel->timeref_cos[k] = cosf(timeref_freq * k); + channel->timeref_nsin[k] = -sinf(timeref_freq * k); + } + } } } } catch(FileIOException e) { From 9b881dd167142f410204eb214ba537026fcfa542 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Wed, 13 Apr 2016 19:11:16 +0000 Subject: [PATCH 07/37] DC blocking should be done before de-emphasis --- rtl_airband.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index c94a909..a8f9686 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -824,7 +824,8 @@ void demodulate() { channel->pj = rotated_j; // de-emphasis IIR + DC blocking channel->agcavgfast = channel->agcavgfast * 0.995f + channel->waveout[j] * 0.005f; - channel->waveout[j] = channel->waveout[j] * (1.0f - alpha) + channel->waveout[j-1] * alpha - channel->agcavgfast; + channel->waveout[j] -= channel->agcavgfast; + channel->waveout[j] = channel->waveout[j] * (1.0f - alpha) + channel->waveout[j-1] * alpha; } } if(channel->modulation == MOD_NFM) From b2c281ff1d81f991d97d432af6aad077220d732f Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Wed, 13 Apr 2016 19:45:16 +0000 Subject: [PATCH 08/37] NFM: support for per-device and per-channel tau setting --- rtl_airband.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index a8f9686..8c11678 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -172,6 +172,7 @@ struct channel_t { float pr; // previous sample - real part float pj; // previous sample - imaginary part enum modulations modulation; + float alpha; int agcsq; // squelch status, 0 = signal, 1 = suppressed char agcindicate; // squelch status indicator float agcavgfast; // average power, for AGC @@ -196,6 +197,7 @@ struct device_t { int centerfreq; int correction; int gain; + float alpha; int channel_count; int bins[8]; channel_t channels[8]; @@ -825,7 +827,7 @@ void demodulate() { // de-emphasis IIR + DC blocking channel->agcavgfast = channel->agcavgfast * 0.995f + channel->waveout[j] * 0.005f; channel->waveout[j] -= channel->agcavgfast; - channel->waveout[j] = channel->waveout[j] * (1.0f - alpha) + channel->waveout[j-1] * alpha; + channel->waveout[j] = channel->waveout[j] * (1.0f - channel->alpha) + channel->waveout[j-1] * channel->alpha; } } if(channel->modulation == MOD_NFM) @@ -1002,6 +1004,11 @@ int main(int argc, char* argv[]) { cerr<<"Configuration error: devices.["<alpha = ((int)devs[i]["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)devs[i]["tau"]))); + } else { + dev->alpha = alpha; + } dev->correction = (int)devs[i]["correction"]; dev->bins[0] = dev->bins[1] = dev->bins[2] = dev->bins[3] = dev->bins[4] = dev->bins[5] = dev->bins[6] = dev->bins[7] = 0; dev->bufs = dev->bufe = dev->waveend = dev->waveavail = dev->row = 0; @@ -1049,6 +1056,11 @@ int main(int argc, char* argv[]) { channel->frequency = channel->freqlist[0]; dev->centerfreq = channel->freqlist[0] + 2 * (double)(SOURCE_RATE / FFT_SIZE); } + if(devs[i]["channels"][j].exists("tau")) { + channel->alpha = ((int)devs[i]["channels"][j]["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)devs[i]["channels"][j]["tau"]))); + } else { + channel->alpha = dev->alpha; + } channel->output_count = devs[i]["channels"][j]["outputs"].getLength(); if(channel->output_count < 1) { cerr<<"Configuration error: devices.["< Date: Mon, 18 Apr 2016 21:00:11 +0000 Subject: [PATCH 09/37] Make NFM support optional Users who only use AM, can skip NFM support and thus revert back to 8k sample rate, which gives better performance. --- makefile | 9 +++++++- rtl_airband.cpp | 61 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/makefile b/makefile index 3e5ee24..542377d 100644 --- a/makefile +++ b/makefile @@ -43,6 +43,9 @@ else ifeq ($(PLATFORM), x86) else DEPS = endif +ifeq ($(NFM), 1) + CFLAGS += -DNFM +endif $(BIN): $(DEPS) ifndef DEPS @@ -50,7 +53,11 @@ ifndef DEPS \tPLATFORM=rpiv1 make\t\tRaspberry Pi V1 (VFP FPU, use BCM VideoCore for FFT)\n \ \tPLATFORM=rpiv2 make\t\tRaspberry Pi V2 (NEON FPU, use BCM VideoCore for FFT)\n \ \tPLATFORM=armv7-generic make\tOther ARMv7 platforms, like Cubieboard (NEON FPU, use main CPU for FFT)\n \ - \tPLATFORM=x86 make\t\tbuild binary for x86\n\n" + \tPLATFORM=x86 make\t\tbuild binary for x86\n\n \ + Additional options:\n \ + \tNFM=1\t\t\t\tInclude support for Narrow FM demodulation\n \ + \t\t\t\t\tWarning: this incurs noticeable performance penalty both for AM and FM\n \ + \t\t\t\t\tDo not enable NFM, if you only use AM (especially on low-power platforms, like RPi)\n\n" @false endif diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 8c11678..658c8cc 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -106,12 +106,16 @@ #define BUF_SIZE 2560000 #define SOURCE_RATE 2560000 +#ifdef NFM #define WAVE_RATE 16000 -#define WAVE_BATCH 1000 -#define WAVE_LEN 2048 -#define MP3_RATE 16000 +#else +#define WAVE_RATE 8000 +#endif +#define WAVE_BATCH WAVE_RATE / 8 +#define AGC_EXTRA WAVE_RATE / 160 +#define WAVE_LEN 2 * WAVE_BATCH + AGC_EXTRA +#define MP3_RATE WAVE_RATE #define MAX_SHOUT_QUEUELEN 32768 -#define AGC_EXTRA 48 #define FFT_SIZE 512 #define CHANNELS 8 @@ -159,11 +163,17 @@ struct file_data { FILE *f; }; -enum modulations { MOD_AM, MOD_NFM }; +enum modulations { + MOD_AM +#ifdef NFM + , MOD_NFM +#endif +}; struct channel_t { float wavein[WAVE_LEN]; // FFT output waveform float waveref[WAVE_LEN]; // for power level calculation float waveout[WAVE_LEN]; // waveform after squelch + AGC +#ifdef NFM float complex_samples[2*WAVE_LEN]; // raw samples for NFM demod float timeref_nsin[WAVE_RATE]; float timeref_cos[WAVE_RATE]; @@ -171,8 +181,9 @@ struct channel_t { // FIXME: get this from complex_samples? float pr; // previous sample - real part float pj; // previous sample - imaginary part - enum modulations modulation; float alpha; +#endif + enum modulations modulation; int agcsq; // squelch status, 0 = signal, 1 = suppressed char agcindicate; // squelch status indicator float agcavgfast; // average power, for AGC @@ -197,7 +208,9 @@ struct device_t { int centerfreq; int correction; int gain; +#ifdef NFM float alpha; +#endif int channel_count; int bins[8]; channel_t channels[8]; @@ -219,7 +232,9 @@ int avx; #endif int foreground = 0, do_syslog = 1; static volatile int do_exit = 0; +#ifdef NFM float alpha = exp(-1.0f/(WAVE_RATE * 2e-4)); +#endif void error() { #ifdef _WIN32 @@ -557,6 +572,7 @@ void* icecast_check(void* params) { #endif } +#ifdef NFM void multiply(float ar, float aj, float br, float bj, float *cr, float *cj) { *cr = ar*br - aj*bj; @@ -571,6 +587,7 @@ float polar_discriminant(float ar, float aj, float br, float bj) angle = atan2((double)cj, (double)cr); return (float)(angle * M_1_PI); } +#endif void demodulate() { @@ -596,7 +613,9 @@ void demodulate() { sizes[1] = sizeof(channel_t); #endif +#ifdef NFM float rotated_r, rotated_j; +#endif ALIGN float ALIGN2 levels[256]; for (int i=0; i<256; i++) { levels[i] = i-127.5f; @@ -707,6 +726,7 @@ void demodulate() { #ifdef USE_BCM_VC fftwave(dev->channels[0].wavein + dev->waveend, fft->out, sizes, dev->bins); +#ifdef NFM for (int j = 0; j < dev->channel_count; j++) { if(dev->channels[j].modulation == MOD_NFM) { struct GPU_FFT_COMPLEX *ptr = fft->out; @@ -717,16 +737,19 @@ void demodulate() { } } } +#endif // NFM #else for (int j = 0; j < dev->channel_count; j++) { dev->channels[j].wavein[dev->waveend] = sqrtf(fftout[dev->bins[j]][0] * fftout[dev->bins[j]][0] + fftout[dev->bins[j]][1] * fftout[dev->bins[j]][1]); +#ifdef NFM if(dev->channels[j].modulation == MOD_NFM) { dev->channels[j].complex_samples[2*dev->waveend] = fftout[dev->bins[j]][0]; dev->channels[j].complex_samples[2*dev->waveend+1] = fftout[dev->bins[j]][1]; } +#endif // NFM } -#endif +#endif // USE_BCM_VC dev->waveend += FFT_BATCH; @@ -814,7 +837,9 @@ void demodulate() { channel->waveout[j] *= 0.85f; channel->agcavgfast *= 1.15f; } - } else { // NFM + } +#ifdef NFM + else { // NFM multiply(channel->complex_samples[2*(j - AGC_EXTRA)], channel->complex_samples[2*(j - AGC_EXTRA)+1], // FIXME: use j instead of wavecnt? channel->timeref_cos[channel->wavecnt], @@ -829,17 +854,22 @@ void demodulate() { channel->waveout[j] -= channel->agcavgfast; channel->waveout[j] = channel->waveout[j] * (1.0f - channel->alpha) + channel->waveout[j-1] * channel->alpha; } +#endif // NFM } +#ifdef NFM if(channel->modulation == MOD_NFM) channel->wavecnt = (channel->wavecnt + 1) % WAVE_RATE; +#endif // NFM } #ifdef _WIN32 process_outputs(channel); memcpy(channel->waveout, channel->waveout + WAVE_BATCH, AGC_EXTRA * 4); #endif memcpy(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4); +#ifdef NFM if(channel->modulation == MOD_NFM) memcpy(channel->complex_samples, channel->complex_samples + 2 * WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4 * 2); +#endif if (foreground) { if(dev->mode == R_SCAN) printf("%4.0f/%3.0f%c %7.3f", channel->agcavgslow, channel->agcmin, channel->agcindicate, (dev->channels[0].frequency / 1000000.0)); @@ -934,8 +964,10 @@ int main(int argc, char* argv[]) { #ifndef _WIN32 if(root.exists("pidfile")) pidfile = strdup(root["pidfile"]); #endif +#ifdef NFM if(root.exists("tau")) alpha = ((int)root["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)root["tau"]))); +#endif Setting &devs = config.lookup("devices"); device_count = devs.getLength(); if (device_count < 1) { @@ -1004,11 +1036,13 @@ int main(int argc, char* argv[]) { cerr<<"Configuration error: devices.["<alpha = ((int)devs[i]["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)devs[i]["tau"]))); } else { dev->alpha = alpha; } +#endif dev->correction = (int)devs[i]["correction"]; dev->bins[0] = dev->bins[1] = dev->bins[2] = dev->bins[3] = dev->bins[4] = dev->bins[5] = dev->bins[6] = dev->bins[7] = 0; dev->bufs = dev->bufe = dev->waveend = dev->waveavail = dev->row = 0; @@ -1026,12 +1060,15 @@ int main(int argc, char* argv[]) { channel->agclow = 0; channel->modulation = MOD_AM; if(devs[i]["channels"][j].exists("modulation")) { +#ifdef NFM if(!strncmp(devs[i]["channels"][j]["modulation"], "nfm", 3)) { channel->modulation = MOD_NFM; - } else if(!strncmp(devs[i]["channels"][j]["modulation"], "am", 2)) { + } else +#endif + if(!strncmp(devs[i]["channels"][j]["modulation"], "am", 2)) { channel->modulation = MOD_AM; } else { - cerr<<"Configuration error: devices.["<frequency = channel->freqlist[0]; dev->centerfreq = channel->freqlist[0] + 2 * (double)(SOURCE_RATE / FFT_SIZE); } +#ifdef NFM if(devs[i]["channels"][j].exists("tau")) { channel->alpha = ((int)devs[i]["channels"][j]["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)devs[i]["channels"][j]["tau"]))); } else { channel->alpha = dev->alpha; } +#endif channel->output_count = devs[i]["channels"][j]["outputs"].getLength(); if(channel->output_count < 1) { cerr<<"Configuration error: devices.["<outputs[o].active = false; } dev->bins[j] = (int)ceil((channel->frequency + SOURCE_RATE - dev->centerfreq) / (double)(SOURCE_RATE / FFT_SIZE) - 1.0f) % FFT_SIZE; +#ifdef NFM if(channel->modulation == MOD_NFM) { // Calculate mixing frequency needed for NFM to remove linear phase shift caused by FFT sliding window // This equals bin_width_Hz * (distance_from_DC_bin) @@ -1122,6 +1162,7 @@ int main(int argc, char* argv[]) { channel->timeref_nsin[k] = -sinf(timeref_freq * k); } } +#endif } } } catch(FileIOException e) { From 2ee04e86a56254ec2c17222e58dba135e514455e Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Mon, 18 Apr 2016 23:22:27 +0200 Subject: [PATCH 10/37] Updated example config file --- rtl_airband.conf.example | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index fafa4ca..fbd62b1 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -52,6 +52,7 @@ # Larger values cause more noise suppression. # Set it to 0 if you don't want filtering. # Default value: 200 (microseconds). +# You can also set this parameter per dongle and / or per channel. # tau = 200; # devices section contains all settings related to the RTLSDR receivers. @@ -221,6 +222,9 @@ devices = ( mode = "multichannel"; centerfreq = 123000000; correction = 80; +# De-emphasis filter time constant +# This setting overrides the global tau setting for all channels on this dongle + tau = 0; channels = ( { freq = 152000000; @@ -239,6 +243,9 @@ devices = ( { freq = 152100000; modulation = "nfm"; +# De-emphasis filter time constant +# Applies to this channel only + tau = 500; outputs = ( { type = "icecast"; From 831f55981b709d58c6da7eba61b335ba6a7a60a1 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Mon, 18 Apr 2016 23:37:03 +0200 Subject: [PATCH 11/37] Update README.md --- README.md | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e7b1ee9..e04fd45 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Building * Install RTLSDR library (http://sdr.osmocom.org/trac/wiki/rtl-sdr): sudo apt-get update - sudo apt-get install git gcc autoconf make libusb-1.0-0-dev + sudo apt-get install git gcc autoconf make libusb-1.0-0-dev libtool cd git clone git://git.osmocom.org/rtl-sdr.git cd rtl-sdr/ @@ -143,26 +143,39 @@ Building sudo apt-get install libmp3lame-dev libvorbis-dev libshout-dev libconfig++-dev - * Set the PLATFORM environment variable (to indicate your hardware platform) and run `make`. - For example, to build a binary for RPi version 1 (ARMv6 CPU, Broadcom VideoCore GPU) type - the following: + * Set the build options and run `make`. Available build options: + + `PLATFORM=platform_name` - selects the hardware platform. This option is mandatory. See below. + + `NFM=1` - enables NFM support. By default, NFM is disabled. + + **Warning:** Do not enable NFM, if you only use AM (especially on low-power platforms, like RPi). + This incurs performance penalty, both for AM and NFM. + + Examples: + + Build a binary for RPi version 1 (ARMv6 CPU, Broadcom VideoCore GPU): PLATFORM=rpiv1 make - Building for RPi V2 (ARMv7 CPU, Broadcom VideoCore GPU): + Same platform, but with NFM support enabled: + + PLATFORM=rpiv1 NFM=1 make + + Building for RPi V2 (ARMv7 CPU, Broadcom VideoCore GPU), with NFM support: - PLATFORM=rpiv2 make + PLATFORM=rpiv2 NFM=1 make Building for other ARMv7-based platforms without VideoCore GPU, eg. Cubieboard (FFTW3 - library is needed in this case): + library is needed in this case), NFM disabled: sudo apt-get install libfftw3-dev PLATFORM=armv7-generic make - Building for generic x86 CPU (FFTW3 library is needed in this case): + Building for generic x86 CPU (FFTW3 library is needed in this case), NFM enabled: sudo apt-get install libfftw3-dev - PLATFORM=x86 make + PLATFORM=x86 NFM=1 make * Install the software: From 9c3b3e16c3679034e650fbd6c64faf5e80ff07dd Mon Sep 17 00:00:00 2001 From: mister-r Date: Mon, 25 Apr 2016 23:19:09 +0300 Subject: [PATCH 12/37] - fixed potential race condition on initialization - use memmove instead of memcpy where neccessary - improved error handling/logging - implemented AFC (Automatic Frequency Control) and option to configure it --- rtl_airband.conf.example | 5 ++ rtl_airband.cpp | 182 +++++++++++++++++++++++++++++++++------ 2 files changed, 161 insertions(+), 26 deletions(-) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index ae2280b..8ff9b48 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -80,6 +80,11 @@ devices = ( # (ie. +- 1.25 MHz around center frequency) # This setting is valid in multichannel mode only. freq = 118925000; + +#Automatioc Frequency Control (AFC) level (optional value) +#0 - disabled (by default) +#value from range 1...255 - AGC enabled, larger value means more aggressive AGC + afc = 0; # Multiple outputs per channel are supported. For example, a single channel # can be sent to multiple Shoutcast servers. outputs = ( diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 09eb387..bc79955 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -112,16 +112,17 @@ #define MP3_RATE 8000 #define MAX_SHOUT_QUEUELEN 32768 #define AGC_EXTRA 48 -#define FFT_SIZE 512 #define CHANNELS 8 #if defined USE_BCM_VC extern "C" void samplefft(GPU_FFT_COMPLEX* dest, unsigned char* buffer, float* window, float* levels); extern "C" void fftwave(float* dest, GPU_FFT_COMPLEX* src, int* sizes, int* bins); -#define FFT_SIZE_LOG 9 -#define FFT_BATCH 250 +# define FFT_SIZE_LOG 9 +# define FFT_SIZE (2<<(FFT_SIZE_LOG - 1)) +# define FFT_BATCH 250 #else -#define FFT_BATCH 1 +# define FFT_SIZE 512 +# define FFT_BATCH 1 #endif #if defined _WIN32 #pragma comment (lib, "Ws2_32.lib") @@ -164,11 +165,12 @@ struct channel_t { float waveref[WAVE_LEN]; // for power level calculation float waveout[WAVE_LEN]; // waveform after squelch + AGC int agcsq; // squelch status, 0 = signal, 1 = suppressed - char agcindicate; // squelch status indicator float agcavgfast; // average power, for AGC float agcavgslow; // average power, for squelch level detection float agcmin; // noise level int agclow; // low level sample count + char agcindicate; // squelch status indicator + unsigned char afc; //0 - AFC disabled; 1 - minimal AFC; 2 - more aggressive AFC and so on to 255 int frequency; int freq_count; int *freqlist; @@ -188,6 +190,7 @@ struct device_t { int correction; int gain; int channel_count; + int base_bins[8]; int bins[8]; channel_t channels[8]; int waveend; @@ -201,7 +204,7 @@ struct device_t { device_t* devices; int device_count; -int device_opened = 0; +volatile int device_opened = 0; #ifdef _WIN32 int avx; #endif @@ -215,6 +218,34 @@ void error() { exit(1); } + +int atomic_inc(volatile int *pv) +{ +#ifdef _WIN32 + return InterlockedIncrement((volatile LONG *)pv); +#else + return __sync_fetch_and_add(pv, 1); +#endif +} + +int atomic_dec(volatile int *pv) +{ +#ifdef _WIN32 + return InterlockedDecrement((volatile LONG *)pv); +#else + return __sync_fetch_and_sub(pv, 1); +#endif +} + +int atomic_get(volatile int *pv) +{ +#ifdef _WIN32 + return InterlockedCompareExchange((volatile LONG *)pv, 0, 0); +#else + return __sync_fetch_and_add(pv, 0); +#endif +} + void log(int priority, const char *format, ...) { va_list args; va_start(args, format); @@ -262,33 +293,49 @@ void rtlsdr_exec(void* params) { #else void* rtlsdr_exec(void* params) { #endif + int r; device_t *dev = (device_t*)params; + + dev->rtlsdr = NULL; rtlsdr_open(&dev->rtlsdr, dev->device); - if (NULL == dev) { + + if (NULL == dev->rtlsdr) { log(LOG_ERR, "Failed to open rtlsdr device #%d.\n", dev->device); error(); return NULL; } - rtlsdr_set_sample_rate(dev->rtlsdr, SOURCE_RATE); - rtlsdr_set_center_freq(dev->rtlsdr, dev->centerfreq); - rtlsdr_set_freq_correction(dev->rtlsdr, dev->correction); + r = rtlsdr_set_sample_rate(dev->rtlsdr, SOURCE_RATE); + if (r < 0) log(LOG_ERR, "Failed to set sample rate for device #%d. Error %d.\n", dev->device, r); + r = rtlsdr_set_center_freq(dev->rtlsdr, dev->centerfreq); + if (r < 0) log(LOG_ERR, "Failed to set center freq for device #%d. Error %d.\n", dev->device, r); + r = rtlsdr_set_freq_correction(dev->rtlsdr, dev->correction); + if (r < 0 && r != -2 ) log(LOG_ERR, "Failed to set freq correction for device #%d. Error %d.\n", dev->device, r); + if(dev->gain == AUTO_GAIN) { - rtlsdr_set_tuner_gain_mode(dev->rtlsdr, 0); - log(LOG_INFO, "Device #%d: gain set to automatic\n", dev->device); + r = rtlsdr_set_tuner_gain_mode(dev->rtlsdr, 0); + if (r < 0) + log(LOG_ERR, "Failed to set automatic gain for device #%d. Error %d.\n", dev->device, r); + else + log(LOG_INFO, "Device #%d: gain set to automatic\n", dev->device); } else { - rtlsdr_set_tuner_gain_mode(dev->rtlsdr, 1); - rtlsdr_set_tuner_gain(dev->rtlsdr, dev->gain); - log(LOG_INFO, "Device #%d: gain set to %0.2f dB\n", dev->device, (float)rtlsdr_get_tuner_gain(dev->rtlsdr) / 10.0); + r = rtlsdr_set_tuner_gain_mode(dev->rtlsdr, 1); + r |= rtlsdr_set_tuner_gain(dev->rtlsdr, dev->gain); + if (r < 0) + log(LOG_ERR, "Failed to set gain to %0.2f for device #%d. Error %d.\n", (float)rtlsdr_get_tuner_gain(dev->rtlsdr) / 10.0, dev->device, r); + else + log(LOG_INFO, "Device #%d: gain set to %0.2f dB\n", dev->device, (float)rtlsdr_get_tuner_gain(dev->rtlsdr) / 10.0); } - rtlsdr_set_agc_mode(dev->rtlsdr, 0); + + r = rtlsdr_set_agc_mode(dev->rtlsdr, 0); + if (r < 0) log(LOG_ERR, "Failed to disable AGC for device #%d. Error %d.\n", dev->device, r); rtlsdr_reset_buffer(dev->rtlsdr); log(LOG_INFO, "Device %d started.\n", dev->device); - device_opened++; + atomic_inc(&device_opened); dev->failed = 0; if(rtlsdr_read_async(dev->rtlsdr, rtlsdr_callback, params, 15, 320000) < 0) { log(LOG_WARNING, "Device #%d: async read failed, disabling\n", dev->device); dev->failed = 1; - device_opened--; + atomic_dec(&device_opened); } return 0; } @@ -544,6 +591,80 @@ void* icecast_check(void* params) { #endif } +class AFC +{ + const char _prev_agcindicate; + +#ifdef USE_BCM_VC + float square(const GPU_FFT_COMPLEX *fft_results, int index) + { + return fft_results[index].re * fft_results[index].re + fft_results[index].im * fft_results[index].im; + } +#else + float square(const fftwf_complex *fft_results, int index) + { + return fft_results[index][0] * fft_results[index][0] + fft_results[index][1] * fft_results[index][1]; + } +#endif + template + int check(const FFT_RESULTS* fft_results, const int base, const float base_value, unsigned char afc) + { + float threshold = 0; + int bin; + for (bin = base;; bin+= STEP) { + if (STEP < 0) { + if (bin < -STEP) + break; + + } else if ( (bin + STEP) >= FFT_SIZE) + break; + + const float value = square(fft_results, bin + STEP); + if (value <= base_value) + break; + + if (base == bin) { + threshold = (value - base_value) / (float)afc; + } else { + if ((value - base_value) < threshold) + break; + + threshold+= threshold / 10.0; + } + } + return bin; + } + +public: + AFC(device_t* dev, int index) : _prev_agcindicate(dev->channels[index].agcindicate) + { + } + + template + void finalize(device_t* dev, int index, const FFT_RESULTS* fft_results) + { + channel_t *channel = &dev->channels[index]; + if (channel->afc==0) + return; + + const char agcindicate = channel->agcindicate; + if (agcindicate != ' ' && _prev_agcindicate == ' ') { + const int base = dev->base_bins[index]; + const float base_value = square(fft_results, base); + int bin = check(fft_results, base, base_value, channel->afc); + if (bin == base) + bin = check(fft_results, base, base_value, channel->afc); + + if (dev->bins[index] != bin) { + log(LOG_INFO, "AFC device=%d channel=%d: base=%d prev=%d now=%d\n", dev->device, index, base, dev->bins[index], bin); + dev->bins[index] = bin; + } + } + else if (agcindicate == ' ' && _prev_agcindicate != ' ') + dev->bins[index] = dev->base_bins[index]; + } +}; + void demodulate() { // initialize fft engine @@ -610,7 +731,7 @@ void demodulate() { available = (BUF_SIZE - dev->bufe) + dev->bufs; } - if(!device_opened) { + if(atomic_get(&device_opened)==0) { log(LOG_ERR, "All receivers failed, exiting\n"); do_exit = 1; continue; @@ -680,8 +801,9 @@ void demodulate() { fftwave(dev->channels[0].wavein + dev->waveend, fft->out, sizes, dev->bins); #else for (int j = 0; j < dev->channel_count; j++) { + int bin = dev->bins[j]; dev->channels[j].wavein[dev->waveend] = - sqrtf(fftout[dev->bins[j]][0] * fftout[dev->bins[j]][0] + fftout[dev->bins[j]][1] * fftout[dev->bins[j]][1]); + sqrtf(fftout[bin][0] * fftout[bin][0] + fftout[bin][1] * fftout[bin][1]); } #endif @@ -692,6 +814,7 @@ void demodulate() { GOTOXY(0, device_num * 17 + dev->row + 3); } for (int i = 0; i < dev->channel_count; i++) { + AFC afc(dev, i); channel_t* channel = dev->channels + i; #if defined __arm__ float agcmin2 = channel->agcmin * 4.5f; @@ -765,9 +888,9 @@ void demodulate() { } #ifdef _WIN32 process_outputs(channel); - memcpy(channel->waveout, channel->waveout + WAVE_BATCH, AGC_EXTRA * 4); + memmove(channel->waveout, channel->waveout + WAVE_BATCH, AGC_EXTRA * 4); #endif - memcpy(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4); + memmove(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4); if (foreground) { if(dev->mode == R_SCAN) printf("%4.0f/%3.0f%c %7.3f", channel->agcavgslow, channel->agcmin, channel->agcindicate, (dev->channels[0].frequency / 1000000.0)); @@ -775,6 +898,11 @@ void demodulate() { printf("%4.0f/%3.0f%c", channel->agcavgslow, channel->agcmin, channel->agcindicate); fflush(stdout); } +#ifdef USE_BCM_VC + afc.finalize(dev, i, fft->out); +#else + afc.finalize(dev, i, fftout); +#endif } dev->waveavail = 1; dev->waveend -= WAVE_BATCH; @@ -931,7 +1059,8 @@ int main(int argc, char* argv[]) { error(); } dev->correction = (int)devs[i]["correction"]; - dev->bins[0] = dev->bins[1] = dev->bins[2] = dev->bins[3] = dev->bins[4] = dev->bins[5] = dev->bins[6] = dev->bins[7] = 0; + memset(dev->bins, 0, sizeof(dev->bins)); + memset(dev->base_bins, 0, sizeof(dev->base_bins)); dev->bufs = dev->bufe = dev->waveend = dev->waveavail = dev->row = 0; for (int j = 0; j < dev->channel_count; j++) { channel_t* channel = dev->channels + j; @@ -945,6 +1074,7 @@ int main(int argc, char* argv[]) { channel->agcavgslow = 0.5f; channel->agcmin = 100.0f; channel->agclow = 0; + channel->afc = devs[i]["channels"][j].exists("afc") ? (unsigned char) (unsigned int)devs[i]["channels"][j]["afc"] : 0; if(dev->mode == R_MULTICHANNEL) { channel->frequency = devs[i]["channels"][j]["freq"]; } else { /* R_SCAN */ @@ -1015,7 +1145,7 @@ int main(int argc, char* argv[]) { channel->outputs[o].enabled = true; channel->outputs[o].active = false; } - dev->bins[j] = (int)ceil((channel->frequency + SOURCE_RATE - dev->centerfreq) / (double)(SOURCE_RATE / FFT_SIZE) - 1.0f) % FFT_SIZE; + dev->base_bins[j] = dev->bins[j] = (int)ceil((channel->frequency + SOURCE_RATE - dev->centerfreq) / (double)(SOURCE_RATE / FFT_SIZE) - 1.0f) % FFT_SIZE; } } } catch(FileIOException e) { @@ -1101,11 +1231,11 @@ int main(int argc, char* argv[]) { } int timeout = 50; // 5 seconds - while (device_opened != device_count && timeout > 0) { + while (atomic_get(&device_opened) != device_count && timeout > 0) { SLEEP(100); timeout--; } - if(device_opened != device_count) { + if(atomic_get(&device_opened) != device_count) { cerr<<"Some devices failed to initialize - aborting\n"; error(); } From b13a47f120179d5613c299cfef29dc51b92fa1d3 Mon Sep 17 00:00:00 2001 From: mister-r Date: Mon, 25 Apr 2016 23:24:33 +0300 Subject: [PATCH 13/37] fixed mistype in comment --- rtl_airband.conf.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index 8ff9b48..6dc8fd7 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -83,7 +83,7 @@ devices = ( #Automatioc Frequency Control (AFC) level (optional value) #0 - disabled (by default) -#value from range 1...255 - AGC enabled, larger value means more aggressive AGC +#value from range 1...255 - AFC enabled, larger value means more aggressive AFC afc = 0; # Multiple outputs per channel are supported. For example, a single channel # can be sent to multiple Shoutcast servers. From 6ffe37ed9276ec7a297dea3df49fb744ec8813e1 Mon Sep 17 00:00:00 2001 From: mister-r Date: Tue, 26 Apr 2016 13:23:05 +0300 Subject: [PATCH 14/37] agcindicate now indicates both AGC and AFC and renamed to axcindicate --- rtl_airband.cpp | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index bc79955..9280383 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -113,17 +113,17 @@ #define MAX_SHOUT_QUEUELEN 32768 #define AGC_EXTRA 48 #define CHANNELS 8 +#define FFT_SIZE_LOG 9 #if defined USE_BCM_VC extern "C" void samplefft(GPU_FFT_COMPLEX* dest, unsigned char* buffer, float* window, float* levels); extern "C" void fftwave(float* dest, GPU_FFT_COMPLEX* src, int* sizes, int* bins); -# define FFT_SIZE_LOG 9 -# define FFT_SIZE (2<<(FFT_SIZE_LOG - 1)) # define FFT_BATCH 250 #else -# define FFT_SIZE 512 # define FFT_BATCH 1 #endif +#define FFT_SIZE (2<<(FFT_SIZE_LOG - 1)) + #if defined _WIN32 #pragma comment (lib, "Ws2_32.lib") #pragma comment (lib, "Mswsock.lib") @@ -169,7 +169,7 @@ struct channel_t { float agcavgslow; // average power, for squelch level detection float agcmin; // noise level int agclow; // low level sample count - char agcindicate; // squelch status indicator + char axcindicate; // squelch/AFC status indicator: ' ' - no signal; '*' - has signal; '>', '<' - signal tuned by AFC unsigned char afc; //0 - AFC disabled; 1 - minimal AFC; 2 - more aggressive AFC and so on to 255 int frequency; int freq_count; @@ -457,7 +457,7 @@ void process_outputs(channel_t* channel) { } } else if(channel->outputs[k].type == O_FILE) { file_data *fdata = (file_data *)(channel->outputs[k].data); - if(fdata->continuous == false && channel->agcindicate != '*' && channel->outputs[k].active == false) continue; + if(fdata->continuous == false && channel->axcindicate == ' ' && channel->outputs[k].active == false) continue; time_t t = time(NULL); struct tm *tmp = gmtime(&t); char suffix[32]; @@ -503,7 +503,7 @@ void process_outputs(channel_t* channel) { fdata->f = NULL; channel->outputs[k].enabled = false; } - channel->outputs[k].active = channel->agcindicate == '*' ? true : false; + channel->outputs[k].active = (channel->axcindicate != ' '); } } } @@ -536,7 +536,7 @@ void* controller_thread(void* params) { if(dev->channels[0].freq_count < 2) return 0; while(!do_exit) { SLEEP(200); - if(dev->channels[0].agcindicate == ' ') { + if(dev->channels[0].axcindicate == ' ') { if(consecutive_squelch_off < 10) { consecutive_squelch_off++; } else { @@ -593,7 +593,7 @@ void* icecast_check(void* params) { class AFC { - const char _prev_agcindicate; + const char _prev_axcindicate; #ifdef USE_BCM_VC float square(const GPU_FFT_COMPLEX *fft_results, int index) @@ -636,7 +636,7 @@ class AFC } public: - AFC(device_t* dev, int index) : _prev_agcindicate(dev->channels[index].agcindicate) + AFC(device_t* dev, int index) : _prev_axcindicate(dev->channels[index].axcindicate) { } @@ -647,8 +647,8 @@ class AFC if (channel->afc==0) return; - const char agcindicate = channel->agcindicate; - if (agcindicate != ' ' && _prev_agcindicate == ' ') { + const char axcindicate = channel->axcindicate; + if (axcindicate != ' ' && _prev_axcindicate == ' ') { const int base = dev->base_bins[index]; const float base_value = square(fft_results, base); int bin = check(fft_results, base, base_value, channel->afc); @@ -658,9 +658,13 @@ class AFC if (dev->bins[index] != bin) { log(LOG_INFO, "AFC device=%d channel=%d: base=%d prev=%d now=%d\n", dev->device, index, base, dev->bins[index], bin); dev->bins[index] = bin; + if ( bin > base ) + channel->axcindicate = '>'; + else if ( bin < base ) + channel->axcindicate = '<'; } } - else if (agcindicate == ' ' && _prev_agcindicate != ' ') + else if (axcindicate == ' ' && _prev_axcindicate != ' ') dev->bins[index] = dev->base_bins[index]; } }; @@ -855,7 +859,7 @@ void demodulate() { channel->agcsq = max(channel->agcsq - 1, 1); if (channel->agcsq == 1 && channel->agcavgslow > 3.0f * channel->agcmin) { channel->agcsq = -AGC_EXTRA * 2; - channel->agcindicate = '*'; + channel->axcindicate = '*'; // fade in for (int k = j - AGC_EXTRA; k < j; k++) { if (channel->wavein[k] > channel->agcmin * 3.0f) { @@ -873,7 +877,7 @@ void demodulate() { channel->agcsq = min(channel->agcsq + 1, -1); if ((channel->agcsq == -1 && channel->agcavgslow < 2.4f * channel->agcmin) || channel->agclow == AGC_EXTRA - 12) { channel->agcsq = AGC_EXTRA * 2; - channel->agcindicate = ' '; + channel->axcindicate = ' '; // fade out for (int k = j - AGC_EXTRA + 1; k < j; k++) { channel->waveout[k] = channel->waveout[k - 1] * 0.94f; @@ -893,9 +897,9 @@ void demodulate() { memmove(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4); if (foreground) { if(dev->mode == R_SCAN) - printf("%4.0f/%3.0f%c %7.3f", channel->agcavgslow, channel->agcmin, channel->agcindicate, (dev->channels[0].frequency / 1000000.0)); + printf("%4.0f/%3.0f%c %7.3f", channel->agcavgslow, channel->agcmin, channel->axcindicate, (dev->channels[0].frequency / 1000000.0)); else - printf("%4.0f/%3.0f%c", channel->agcavgslow, channel->agcmin, channel->agcindicate); + printf("%4.0f/%3.0f%c", channel->agcavgslow, channel->agcmin, channel->axcindicate); fflush(stdout); } #ifdef USE_BCM_VC @@ -1069,7 +1073,7 @@ int main(int argc, char* argv[]) { channel->waveout[k] = 0.5; } channel->agcsq = 1; - channel->agcindicate = ' '; + channel->axcindicate = ' '; channel->agcavgfast = 0.5f; channel->agcavgslow = 0.5f; channel->agcmin = 100.0f; From 1ab4430d60f293ddee8372cf8ee321441f4bb26e Mon Sep 17 00:00:00 2001 From: mister-r Date: Wed, 27 Apr 2016 17:54:09 +0300 Subject: [PATCH 15/37] fixed compilation with different FFT_SIZE-es (different FFT_SIZE_LOG-s) enabled hardware float acceleration support in GCC flags, that allowed removing some assembler code without performance degradation (actually performance on RPI2 even improved a bit) --- makefile | 10 ++-- rtl_airband.cpp | 49 +++++++++++------ rtl_airband_neon.s | 130 +++------------------------------------------ rtl_airband_vfp.s | 125 +++---------------------------------------- 4 files changed, 50 insertions(+), 264 deletions(-) diff --git a/makefile b/makefile index 3e5ee24..de7fa9a 100644 --- a/makefile +++ b/makefile @@ -21,19 +21,19 @@ FFT = hello_fft/hello_fft.a ifeq ($(PLATFORM), rpiv1) CFLAGS += -DUSE_BCM_VC CFLAGS += -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux - CFLAGS += -mcpu=arm1176jzf-s -mtune=arm1176jzf-s -march=armv6zk -mfpu=vfp - LDLIBS += -lbcm_host + CFLAGS += -mcpu=arm1176jzf-s -mtune=arm1176jzf-s -march=armv6zk -mfpu=vfp -ffast-math + LDLIBS += -lbcm_host -ldl export LDFLAGS = -L/opt/vc/lib DEPS = $(OBJ) $(FFT) rtl_airband_vfp.o else ifeq ($(PLATFORM), rpiv2) CFLAGS += -DUSE_BCM_VC CFLAGS += -I/opt/vc/include -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux - CFLAGS += -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard - LDLIBS += -lbcm_host + CFLAGS += -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -ffast-math + LDLIBS += -lbcm_host -ldl export LDFLAGS = -L/opt/vc/lib DEPS = $(OBJ) $(FFT) rtl_airband_neon.o else ifeq ($(PLATFORM), armv7-generic) - CFLAGS += -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard + CFLAGS += -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -ffast-math LDLIBS += -lfftw3f DEPS = $(OBJ) else ifeq ($(PLATFORM), x86) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 9280383..5e70204 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -30,6 +30,7 @@ #define _USE_MATH_DEFINES #include #include +#include #include #include #include @@ -116,8 +117,15 @@ #define FFT_SIZE_LOG 9 #if defined USE_BCM_VC -extern "C" void samplefft(GPU_FFT_COMPLEX* dest, unsigned char* buffer, float* window, float* levels); -extern "C" void fftwave(float* dest, GPU_FFT_COMPLEX* src, int* sizes, int* bins); +struct sample_fft_arg +{ + int fft_size_by4; + GPU_FFT_COMPLEX* dest; +}; +extern "C" void samplefft(sample_fft_arg *a, unsigned char* buffer, float* window, float* levels); + +#include + # define FFT_BATCH 250 #else # define FFT_BATCH 1 @@ -688,9 +696,6 @@ void demodulate() { case -2: log(LOG_CRIT, "log2_N=%d not supported. Try between 8 and 17.\n", FFT_SIZE_LOG); error(); case -3: log(LOG_CRIT, "Out of memory. Try a smaller batch or increase GPU memory.\n"); error(); } - int sizes[2]; - sizes[0] = fft->step * sizeof(GPU_FFT_COMPLEX); - sizes[1] = sizeof(channel_t); #endif ALIGN float ALIGN2 levels[256]; @@ -719,9 +724,9 @@ void demodulate() { // speed2 = number of bytes per wave sample (x 2 for I and Q) int speed2 = (SOURCE_RATE * 2) / WAVE_RATE; - int device_num = 0; while (true) { + if(do_exit) { #ifdef USE_BCM_VC log(LOG_INFO, "Freeing GPU memory\n"); @@ -729,6 +734,7 @@ void demodulate() { #endif return; } + device_t* dev = devices + device_num; int available = dev->bufe - dev->bufs; if (dev->bufe < dev->bufs) { @@ -752,8 +758,10 @@ void demodulate() { } #if defined USE_BCM_VC + sample_fft_arg sfa = {FFT_SIZE / 4, fft->in}; for (int i = 0; i < FFT_BATCH; i++) { - samplefft(fft->in + i * fft->step, dev->buffer + dev->bufs + i * speed2, window, levels); + samplefft(&sfa, dev->buffer + dev->bufs + i * speed2, window, levels); + sfa.dest+= fft->step; } #elif defined __arm__ for (int i = 0; i < FFT_SIZE; i++) { @@ -802,11 +810,18 @@ void demodulate() { #endif #ifdef USE_BCM_VC - fftwave(dev->channels[0].wavein + dev->waveend, fft->out, sizes, dev->bins); + for (int i = 0; i < dev->channel_count; i++) { + float *wavein = dev->channels[i].wavein + dev->waveend; + __builtin_prefetch(wavein, 1); + const int bin = dev->bins[i]; + const GPU_FFT_COMPLEX *fftout = fft->out + bin; + for (int j = 0; j < FFT_BATCH; j++, ++wavein, fftout+= fft->step) + *wavein = sqrtf(fftout->im * fftout->im + fftout->re * fftout->re); + } #else - for (int j = 0; j < dev->channel_count; j++) { - int bin = dev->bins[j]; - dev->channels[j].wavein[dev->waveend] = + for (int i = 0; i < dev->channel_count; i++) { + int bin = dev->bins[i]; + dev->channels[i].wavein[dev->waveend] = sqrtf(fftout[bin][0] * fftout[bin][0] + fftout[bin][1] * fftout[bin][1]); } #endif @@ -895,6 +910,11 @@ void demodulate() { memmove(channel->waveout, channel->waveout + WAVE_BATCH, AGC_EXTRA * 4); #endif memmove(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4); +#ifdef USE_BCM_VC + afc.finalize(dev, i, fft->out); +#else + afc.finalize(dev, i, fftout); +#endif if (foreground) { if(dev->mode == R_SCAN) printf("%4.0f/%3.0f%c %7.3f", channel->agcavgslow, channel->agcmin, channel->axcindicate, (dev->channels[0].frequency / 1000000.0)); @@ -902,11 +922,6 @@ void demodulate() { printf("%4.0f/%3.0f%c", channel->agcavgslow, channel->agcmin, channel->axcindicate); fflush(stdout); } -#ifdef USE_BCM_VC - afc.finalize(dev, i, fft->out); -#else - afc.finalize(dev, i, fftout); -#endif } dev->waveavail = 1; dev->waveend -= WAVE_BATCH; @@ -926,7 +941,7 @@ void demodulate() { } void usage() { - cout<<"Usage: rtl_airband [-f] [-c ]\n\ + cout<<"Usage: rtl_airband [-f] [-p] [-c ]\n\ \t-h\t\t\tDisplay this help text\n\ \t-f\t\t\tRun in foreground, display textual waterfalls\n\ \t-c \tUse non-default configuration file\n\t\t\t\t(default: "< Date: Wed, 27 Apr 2016 18:07:04 +0300 Subject: [PATCH 16/37] cosmetic cleanup --- rtl_airband.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 5e70204..d2d7ae1 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -30,7 +30,6 @@ #define _USE_MATH_DEFINES #include #include -#include #include #include #include @@ -941,7 +940,7 @@ void demodulate() { } void usage() { - cout<<"Usage: rtl_airband [-f] [-p] [-c ]\n\ + cout<<"Usage: rtl_airband [-f] [-c ]\n\ \t-h\t\t\tDisplay this help text\n\ \t-f\t\t\tRun in foreground, display textual waterfalls\n\ \t-c \tUse non-default configuration file\n\t\t\t\t(default: "< Date: Wed, 27 Apr 2016 18:10:04 +0300 Subject: [PATCH 17/37] improved comments --- rtl_airband.conf.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index 6dc8fd7..d0128bb 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -83,7 +83,7 @@ devices = ( #Automatioc Frequency Control (AFC) level (optional value) #0 - disabled (by default) -#value from range 1...255 - AFC enabled, larger value means more aggressive AFC +#value from range 1...10 - AFC enabled, larger value means more aggressive AFC. afc = 0; # Multiple outputs per channel are supported. For example, a single channel # can be sent to multiple Shoutcast servers. From c6968aeee05a627e67b34f582d2506038870852a Mon Sep 17 00:00:00 2001 From: mister-r Date: Wed, 27 Apr 2016 22:42:52 +0300 Subject: [PATCH 18/37] fixed rpi1 compilation removed AFC logging by default --- rtl_airband.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index d2d7ae1..6371f35 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -123,14 +123,14 @@ struct sample_fft_arg }; extern "C" void samplefft(sample_fft_arg *a, unsigned char* buffer, float* window, float* levels); -#include - # define FFT_BATCH 250 #else # define FFT_BATCH 1 #endif #define FFT_SIZE (2<<(FFT_SIZE_LOG - 1)) +//#define AFC_LOGGING + #if defined _WIN32 #pragma comment (lib, "Ws2_32.lib") #pragma comment (lib, "Mswsock.lib") @@ -663,7 +663,9 @@ class AFC bin = check(fft_results, base, base_value, channel->afc); if (dev->bins[index] != bin) { +#ifdef AFC_LOGGING log(LOG_INFO, "AFC device=%d channel=%d: base=%d prev=%d now=%d\n", dev->device, index, base, dev->bins[index], bin); +#endif dev->bins[index] = bin; if ( bin > base ) channel->axcindicate = '>'; From d7b00a42708c2cd7183633463147115df4f925f2 Mon Sep 17 00:00:00 2001 From: mister-r Date: Fri, 29 Apr 2016 02:55:03 +0300 Subject: [PATCH 19/37] Added 'append' option that appends data to existing mp3 files instead of overwrite. Missing fragment length is detected from file modification timestamp and its bounds marked with special tones. --- rtl_airband.conf.example | 4 ++ rtl_airband.cpp | 94 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 5 deletions(-) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index d0128bb..290e422 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -153,6 +153,10 @@ devices = ( # Set this to false if you want to skip silence (record only when squelch is open). # Default is false. continuous = true; +# Set this to true if you want to append to file if its exists with demarkation of missing fragment. +# Set this to false if you want to overwrite existing files. +# Default is false. + append = false; } ); }, diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 6371f35..5a7ce43 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -164,6 +164,7 @@ struct file_data { const char *prefix; char *suffix; bool continuous; + bool append; FILE *f; }; @@ -438,6 +439,90 @@ void lame_setup(channel_t *channel) { } unsigned char lamebuf[22000]; + +class WaveTone +{ +float *_buf; +int _samples; +public: + WaveTone(int msec, unsigned int hz = 0) { + _samples = (msec * WAVE_RATE) / 1000; + _buf = (float *)malloc(_samples * sizeof(float)); + if (!_buf) { + log(LOG_WARNING, "WaveTone: can't allocate %u samples\n", _samples); + return; + } + + if (hz > 0) { + const float period = 1.0 / (float)hz; + const float sample_time = 1.0 / (float)WAVE_RATE; + float t = 0; + for (int i = 0; i < _samples; ++i, t+= sample_time) { + _buf[i] = sin(t * 2.0 * M_PI / period); + } + } else + memset(_buf, 0, _samples * sizeof(float)); + } + + ~WaveTone() { + if (_buf) + free(_buf); + } + + int Write(lame_t lame, FILE *f) { + if (!_buf) + return 1; + + int bytes = lame_encode_buffer_ieee_float(lame, _buf, NULL, _samples, lamebuf, 22000); + if (bytes < 0) { + log(LOG_WARNING, "WaveTone: encode error %d\n"); + return 1; + } + + + if(fwrite(lamebuf, 1, bytes, f) != (unsigned int)bytes) { + log(LOG_WARNING, "WaveTone: failed to write %d bytes\n", bytes); + return -1; + } + + return 0; + } +}; + +static int fdata_open(lame_t lame, file_data *fdata, const char *filename) { + fdata->f = fopen(filename, fdata->append ? "a+" : "w"); + if(fdata->f == NULL) + return -1; + + struct stat st = {0}; + if (!fdata->append || fstat(fileno(fdata->f), &st)!=0 || st.st_size == 0) { + log(LOG_INFO, "Writing to %s\n", filename); + return 0; + } + log(LOG_INFO, "Appending from pos %llu to %s\n", (unsigned long long)st.st_size, filename); + + //fill missing space with marker tones + WaveTone wt_a(250, 2222); + WaveTone wt_b(500, 1111); + WaveTone wt_c(1000, 555); + + int r = wt_a.Write(lame, fdata->f); + if (r==0) r = wt_b.Write(lame, fdata->f); + if (r==0) r = wt_c.Write(lame, fdata->f); + time_t now = time(NULL); + if (now > st.st_mtime ) { + WaveTone wt_silence(1000); + for (time_t delta = now - st.st_mtime; (r==0 && delta > 4); --delta) + r = wt_silence.Write(lame, fdata->f); + } + if (r==0) r = wt_c.Write(lame, fdata->f); + if (r==0) r = wt_b.Write(lame, fdata->f); + if (r==0) r = wt_a.Write(lame, fdata->f); + + if (r<0) fseek(fdata->f, st.st_size, SEEK_SET); + return 0; +} + void process_outputs(channel_t* channel) { int bytes = lame_encode_buffer_ieee_float(channel->lame, channel->waveout, NULL, WAVE_BATCH, lamebuf, 22000); if (bytes < 0) { @@ -485,15 +570,13 @@ void process_outputs(channel_t* channel) { fclose(fdata->f); fdata->f = NULL; } - fdata->f = fopen(filename, "w"); - if(fdata->f == NULL) { + int r = fdata_open(channel->lame, fdata, filename); + free(filename); + if (r<0) { log(LOG_WARNING, "Cannot open output file %s (%s), output disabled\n", filename, strerror(errno)); channel->outputs[k].enabled = false; - free(filename); continue; } - log(LOG_INFO, "Writing to %s\n", filename); - free(filename); } // bytes is signed, but we've checked for negative values earlier // so it's save to ignore the warning here @@ -1158,6 +1241,7 @@ int main(int argc, char* argv[]) { fdata->prefix = strdup(devs[i]["channels"][j]["outputs"][o]["filename_template"]); fdata->continuous = devs[i]["channels"][j]["outputs"][o].exists("continuous") ? (bool)(devs[i]["channels"][j]["outputs"][o]["continuous"]) : false; + fdata->append = devs[i]["channels"][j]["outputs"][o].exists("append") && (bool)(devs[i]["channels"][j]["outputs"][o]["append"]); } else { cerr<<"Configuration error: devices.["< Date: Fri, 29 Apr 2016 15:25:53 +0300 Subject: [PATCH 20/37] appended silence period now generated much faster also don't eat one buffer --- rtl_airband.cpp | 142 +++++++++++++++++++++++++++++------------------- 1 file changed, 85 insertions(+), 57 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 5a7ce43..1b54e3e 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -114,7 +114,7 @@ #define AGC_EXTRA 48 #define CHANNELS 8 #define FFT_SIZE_LOG 9 - +#define LAMEBUF_SIZE 22000 //todo: calculate #if defined USE_BCM_VC struct sample_fft_arg { @@ -425,31 +425,42 @@ void shout_setup(icecast_data *icecast) { return; } } -void lame_setup(channel_t *channel) { - if(channel == NULL) return; - channel->lame = lame_init(); - lame_set_in_samplerate(channel->lame, WAVE_RATE); - lame_set_VBR(channel->lame, vbr_off); - lame_set_brate(channel->lame, 16); - lame_set_quality(channel->lame, 7); - lame_set_out_samplerate(channel->lame, MP3_RATE); - lame_set_num_channels(channel->lame, 1); - lame_set_mode(channel->lame, MONO); - lame_init_params(channel->lame); -} -unsigned char lamebuf[22000]; +lame_t airlame_init() { + lame_t lame = lame_init(); + if (!lame) { + log(LOG_WARNING, "lame_init failed\n"); + return NULL; + } -class WaveTone + lame_set_in_samplerate(lame, WAVE_RATE); + lame_set_VBR(lame, vbr_off); + lame_set_brate(lame, 16); + lame_set_quality(lame, 7); + lame_set_out_samplerate(lame, MP3_RATE); + lame_set_num_channels(lame, 1); + lame_set_mode(lame, MONO); + lame_init_params(lame); + return lame; +} + +class LameTone { -float *_buf; -int _samples; + unsigned char* _data; + int _bytes; + public: - WaveTone(int msec, unsigned int hz = 0) { - _samples = (msec * WAVE_RATE) / 1000; - _buf = (float *)malloc(_samples * sizeof(float)); - if (!_buf) { - log(LOG_WARNING, "WaveTone: can't allocate %u samples\n", _samples); + LameTone(int msec, unsigned int hz = 0) : _data(NULL), _bytes(0) { + _data = (unsigned char *)malloc(LAMEBUF_SIZE); + if (!_data) { + log(LOG_WARNING, "LameTone: can't alloc %u bytes\n", LAMEBUF_SIZE); + return; + } + + int samples = (msec * WAVE_RATE) / 1000; + float *buf = (float *)malloc(samples * sizeof(float)); + if (!buf) { + log(LOG_WARNING, "LameTone: can't alloc %u samples\n", samples); return; } @@ -457,39 +468,53 @@ int _samples; const float period = 1.0 / (float)hz; const float sample_time = 1.0 / (float)WAVE_RATE; float t = 0; - for (int i = 0; i < _samples; ++i, t+= sample_time) { - _buf[i] = sin(t * 2.0 * M_PI / period); + for (int i = 0; i < samples; ++i, t+= sample_time) { + buf[i] = sin(t * 2.0 * M_PI / period); + } + } else + memset(buf, 0, samples * sizeof(float)); + + lame_t lame = airlame_init(); + if (lame) { + _bytes = lame_encode_buffer_ieee_float(lame, buf, NULL, samples, _data, LAMEBUF_SIZE); + if (_bytes > 0) { + int flush_ofs = _bytes; + if (flush_ofs&0x1f) + flush_ofs+= 0x20 - (flush_ofs&0x1f); + if (flush_ofs < LAMEBUF_SIZE) { + int flush_bytes = lame_encode_flush(lame, _data + flush_ofs, LAMEBUF_SIZE - flush_ofs); + if (flush_bytes > 0) { + memmove(_data + _bytes, _data + flush_ofs, flush_bytes); + _bytes+= flush_bytes; + } + } } - } else - memset(_buf, 0, _samples * sizeof(float)); + else + log(LOG_WARNING, "lame_encode_buffer_ieee_float: %d\n", _bytes); + lame_close(lame); + } + free(buf); } - ~WaveTone() { - if (_buf) - free(_buf); + ~LameTone() { + if (_data) + free(_data); } - int Write(lame_t lame, FILE *f) { - if (!_buf) + int write(FILE *f) { + if (!_data || _bytes<=0) return 1; - int bytes = lame_encode_buffer_ieee_float(lame, _buf, NULL, _samples, lamebuf, 22000); - if (bytes < 0) { - log(LOG_WARNING, "WaveTone: encode error %d\n"); - return 1; - } - - - if(fwrite(lamebuf, 1, bytes, f) != (unsigned int)bytes) { - log(LOG_WARNING, "WaveTone: failed to write %d bytes\n", bytes); + if(fwrite(_data, 1, _bytes, f) != (unsigned int)_bytes) { + log(LOG_WARNING, "LameTone: failed to write %d bytes\n", _bytes); return -1; } - return 0; + return 0; } }; -static int fdata_open(lame_t lame, file_data *fdata, const char *filename) { +static int fdata_open(file_data *fdata, const char *filename) { fdata->f = fopen(filename, fdata->append ? "a+" : "w"); if(fdata->f == NULL) return -1; @@ -502,31 +527,33 @@ static int fdata_open(lame_t lame, file_data *fdata, const char *filename) { log(LOG_INFO, "Appending from pos %llu to %s\n", (unsigned long long)st.st_size, filename); //fill missing space with marker tones - WaveTone wt_a(250, 2222); - WaveTone wt_b(500, 1111); - WaveTone wt_c(1000, 555); + LameTone lt_a(250, 2222); + LameTone lt_b(500, 1111); + LameTone lt_c(1000, 555); - int r = wt_a.Write(lame, fdata->f); - if (r==0) r = wt_b.Write(lame, fdata->f); - if (r==0) r = wt_c.Write(lame, fdata->f); + int r = lt_a.write(fdata->f); + if (r==0) r = lt_b.write(fdata->f); + if (r==0) r = lt_c.write(fdata->f); time_t now = time(NULL); if (now > st.st_mtime ) { - WaveTone wt_silence(1000); + LameTone lt_silence(1000); for (time_t delta = now - st.st_mtime; (r==0 && delta > 4); --delta) - r = wt_silence.Write(lame, fdata->f); + r = lt_silence.write(fdata->f); } - if (r==0) r = wt_c.Write(lame, fdata->f); - if (r==0) r = wt_b.Write(lame, fdata->f); - if (r==0) r = wt_a.Write(lame, fdata->f); + if (r==0) r = lt_c.write(fdata->f); + if (r==0) r = lt_b.write(fdata->f); + if (r==0) r = lt_a.write(fdata->f); if (r<0) fseek(fdata->f, st.st_size, SEEK_SET); return 0; } +unsigned char lamebuf[LAMEBUF_SIZE]; + void process_outputs(channel_t* channel) { - int bytes = lame_encode_buffer_ieee_float(channel->lame, channel->waveout, NULL, WAVE_BATCH, lamebuf, 22000); + int bytes = lame_encode_buffer_ieee_float(channel->lame, channel->waveout, NULL, WAVE_BATCH, lamebuf, LAMEBUF_SIZE); if (bytes < 0) { - log(LOG_WARNING, "lame_encode_buffer_ieee_float: %d\n"); + log(LOG_WARNING, "lame_encode_buffer_ieee_float: %d\n", bytes); return; } else if (bytes == 0) return; @@ -567,10 +594,11 @@ void process_outputs(channel_t* channel) { } sprintf(filename, "%s/%s%s", fdata->dir, fdata->prefix, fdata->suffix); if(fdata->f != NULL) { + //todo: finalize file stream with lame_encode_flush_nogap fclose(fdata->f); fdata->f = NULL; } - int r = fdata_open(channel->lame, fdata, filename); + int r = fdata_open(fdata, filename); free(filename); if (r<0) { log(LOG_WARNING, "Cannot open output file %s (%s), output disabled\n", filename, strerror(errno)); @@ -1315,7 +1343,7 @@ int main(int argc, char* argv[]) { device_t* dev = devices + i; for (int j = 0; j < dev->channel_count; j++) { channel_t* channel = dev->channels + j; - lame_setup(channel); + channel->lame = airlame_init(); for (int k = 0; k < channel->output_count; k++) { output_t *output = channel->outputs + k; if(output->type == O_ICECAST) From 0d82ef98fa0f385f78ca62c090d0a2396d743b6f Mon Sep 17 00:00:00 2001 From: mister-r Date: Sat, 30 Apr 2016 01:50:28 +0300 Subject: [PATCH 21/37] use memmove on overlapping memory regions --- rtl_airband.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 131b524..999be17 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -1123,7 +1123,7 @@ void demodulate() { memmove(channel->wavein, channel->wavein + WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4); #ifdef NFM if(channel->modulation == MOD_NFM) - memcpy(channel->complex_samples, channel->complex_samples + 2 * WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4 * 2); + memmove(channel->complex_samples, channel->complex_samples + 2 * WAVE_BATCH, (dev->waveend - WAVE_BATCH) * 4 * 2); #endif #ifdef USE_BCM_VC From 1b230b38fe8621dcb9144f6df0a62124b46de4bc Mon Sep 17 00:00:00 2001 From: mister-r Date: Mon, 2 May 2016 01:09:56 +0300 Subject: [PATCH 22/37] do not insert silence period on append if continuous set to false --- rtl_airband.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 999be17..9b5218f 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -562,11 +562,13 @@ static int fdata_open(file_data *fdata, const char *filename) { int r = lt_a.write(fdata->f); if (r==0) r = lt_b.write(fdata->f); if (r==0) r = lt_c.write(fdata->f); - time_t now = time(NULL); - if (now > st.st_mtime ) { - LameTone lt_silence(1000); - for (time_t delta = now - st.st_mtime; (r==0 && delta > 4); --delta) - r = lt_silence.write(fdata->f); + if (fdata->continuous) { + time_t now = time(NULL); + if (now > st.st_mtime ) { + LameTone lt_silence(1000); + for (time_t delta = now - st.st_mtime; (r==0 && delta > 4); --delta) + r = lt_silence.write(fdata->f); + } } if (r==0) r = lt_c.write(fdata->f); if (r==0) r = lt_b.write(fdata->f); From eefd3b99567831ccefd6da7434ea831b5ba17747 Mon Sep 17 00:00:00 2001 From: mister-r Date: Mon, 2 May 2016 01:12:22 +0300 Subject: [PATCH 23/37] do not insert silence on append if continuous set to false --- rtl_airband.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 1b54e3e..c2270a9 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -534,11 +534,13 @@ static int fdata_open(file_data *fdata, const char *filename) { int r = lt_a.write(fdata->f); if (r==0) r = lt_b.write(fdata->f); if (r==0) r = lt_c.write(fdata->f); - time_t now = time(NULL); - if (now > st.st_mtime ) { - LameTone lt_silence(1000); - for (time_t delta = now - st.st_mtime; (r==0 && delta > 4); --delta) - r = lt_silence.write(fdata->f); + if (fdata->continuous) { + time_t now = time(NULL); + if (now > st.st_mtime ) { + LameTone lt_silence(1000); + for (time_t delta = now - st.st_mtime; (r==0 && delta > 4); --delta) + r = lt_silence.write(fdata->f); + } } if (r==0) r = lt_c.write(fdata->f); if (r==0) r = lt_b.write(fdata->f); From 6e311834864d2913b6f74d8bf5d6d6812f7a97c4 Mon Sep 17 00:00:00 2001 From: mister-r Date: Thu, 5 May 2016 11:25:50 +0300 Subject: [PATCH 24/37] reduced marker tones length and volume and append option is default now --- rtl_airband.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index c2270a9..bd530c6 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -469,7 +469,7 @@ class LameTone const float sample_time = 1.0 / (float)WAVE_RATE; float t = 0; for (int i = 0; i < samples; ++i, t+= sample_time) { - buf[i] = sin(t * 2.0 * M_PI / period); + buf[i] = 0.9 * sinf(t * 2.0 * M_PI / period); } } else memset(buf, 0, samples * sizeof(float)); @@ -527,9 +527,9 @@ static int fdata_open(file_data *fdata, const char *filename) { log(LOG_INFO, "Appending from pos %llu to %s\n", (unsigned long long)st.st_size, filename); //fill missing space with marker tones - LameTone lt_a(250, 2222); - LameTone lt_b(500, 1111); - LameTone lt_c(1000, 555); + LameTone lt_a(120, 2222); + LameTone lt_b(120, 1111); + LameTone lt_c(120, 555); int r = lt_a.write(fdata->f); if (r==0) r = lt_b.write(fdata->f); @@ -1271,7 +1271,7 @@ int main(int argc, char* argv[]) { fdata->prefix = strdup(devs[i]["channels"][j]["outputs"][o]["filename_template"]); fdata->continuous = devs[i]["channels"][j]["outputs"][o].exists("continuous") ? (bool)(devs[i]["channels"][j]["outputs"][o]["continuous"]) : false; - fdata->append = devs[i]["channels"][j]["outputs"][o].exists("append") && (bool)(devs[i]["channels"][j]["outputs"][o]["append"]); + fdata->append = (!devs[i]["channels"][j]["outputs"][o].exists("append")) || (bool)(devs[i]["channels"][j]["outputs"][o]["append"]); } else { cerr<<"Configuration error: devices.["< Date: Thu, 5 May 2016 11:29:42 +0300 Subject: [PATCH 25/37] corrected config comments according to changes --- rtl_airband.conf.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index 290e422..68140ed 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -155,7 +155,7 @@ devices = ( continuous = true; # Set this to true if you want to append to file if its exists with demarkation of missing fragment. # Set this to false if you want to overwrite existing files. -# Default is false. +# Default is true. append = false; } ); From 991662447fc442867ebc53e15c12578be4ed7451 Mon Sep 17 00:00:00 2001 From: mister-r Date: Thu, 5 May 2016 12:03:59 +0300 Subject: [PATCH 26/37] limit silence period to one hour --- rtl_airband.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index bd530c6..74ec600 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -537,8 +537,13 @@ static int fdata_open(file_data *fdata, const char *filename) { if (fdata->continuous) { time_t now = time(NULL); if (now > st.st_mtime ) { + time_t delta = now - st.st_mtime; + if (delta > 3600) { + log(LOG_WARNING, "Too big time difference: %llu sec, limiting to one hour\n", (unsigned long long)delta); + delta = 3600; + } LameTone lt_silence(1000); - for (time_t delta = now - st.st_mtime; (r==0 && delta > 4); --delta) + for (; (r==0 && delta > 1); --delta) r = lt_silence.write(fdata->f); } } From 11c8a6b91c756c0978f915f31cb3d3adab7e7a71 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Tue, 10 May 2016 20:33:29 +0000 Subject: [PATCH 27/37] NFM: fast_atan2 and quadri demodulators --- rtl_airband.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 1c717b6..d0b43e3 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -245,6 +245,11 @@ int foreground = 0, do_syslog = 1; static volatile int do_exit = 0; #ifdef NFM float alpha = exp(-1.0f/(WAVE_RATE * 2e-4)); +enum fm_demod_algo { + FM_FAST_ATAN2, + FM_QUADRI_DEMOD +}; +enum fm_demod_algo fm_demod = FM_FAST_ATAN2; #endif void error() { @@ -751,14 +756,39 @@ void multiply(float ar, float aj, float br, float bj, float *cr, float *cj) *cj = aj*br + ar*bj; } -float polar_discriminant(float ar, float aj, float br, float bj) +float fast_atan2(float y, float x) +{ + float yabs, angle; + float pi4=M_PI_4, pi34=3*M_PI_4; + if (x==0.0f && y==0.0f) { + return 0; + } + yabs = y; + if (yabs < 0.0f) { + yabs = -yabs; + } + if (x >= 0.0f) { + angle = pi4 - pi4 * (x-yabs) / (x+yabs); + } else { + angle = pi34 - pi4 * (x+yabs) / (yabs-x); + } + if (y < 0.0f) { + return -angle; + } + return angle; +} + +float polar_disc_fast(float ar, float aj, float br, float bj) { float cr, cj; - double angle; multiply(ar, aj, br, -bj, &cr, &cj); - angle = atan2((double)cj, (double)cr); - return (float)(angle * M_1_PI); + return (float)(fast_atan2(cj, cr) * M_1_PI); +} + +float fm_quadri_demod(float ar, float aj, float br, float bj) { + return (float)((br*aj - ar*bj)/(ar*ar + aj*aj + 1.0f) * M_1_PI); } + #endif class AFC @@ -1108,7 +1138,11 @@ void demodulate() { channel->timeref_nsin[channel->wavecnt], &rotated_r, &rotated_j); - channel->waveout[j] = polar_discriminant(rotated_r, rotated_j, channel->pr, channel->pj); + if(fm_demod == FM_FAST_ATAN2) { + channel->waveout[j] = polar_disc_fast(rotated_r, rotated_j, channel->pr, channel->pj); + } else if(fm_demod == FM_QUADRI_DEMOD) { + channel->waveout[j] = fm_quadri_demod(rotated_r, rotated_j, channel->pr, channel->pj); + } channel->pr = rotated_r; channel->pj = rotated_j; // de-emphasis IIR + DC blocking @@ -1182,8 +1216,17 @@ int main(int argc, char* argv[]) { #pragma GCC diagnostic warning "-Wwrite-strings" int opt; +#ifdef NFM + while((opt = getopt(argc, argv, "Qfhvc:")) != -1) { +#else while((opt = getopt(argc, argv, "fhvc:")) != -1) { +#endif switch(opt) { +#ifdef NFM + case 'Q': + fm_demod = FM_QUADRI_DEMOD; + break; +#endif case 'f': foreground = 1; break; From 934d6ff7699bf4cc90612ec4f30715175ddaf49f Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Sat, 14 May 2016 14:26:00 +0000 Subject: [PATCH 28/37] Added "disable" option for channels --- rtl_airband.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index d0b43e3..b7b7709 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -1322,11 +1322,7 @@ int main(int argc, char* argv[]) { } if(!devs[i].exists("correction")) devs[i].add("correction", Setting::TypeInt); dev->device = (int)devs[i]["index"]; - dev->channel_count = devs[i]["channels"].getLength(); - if(dev->channel_count < 1 || dev->channel_count > 8) { - cerr<<"Configuration error: devices.["<channel_count = 0; if(devs[i].exists("gain")) dev->gain = (int)devs[i]["gain"] * 10; else @@ -1344,10 +1340,6 @@ int main(int argc, char* argv[]) { dev->mode = R_MULTICHANNEL; } if(dev->mode == R_MULTICHANNEL) dev->centerfreq = (int)devs[i]["centerfreq"]; - if(dev->mode == R_SCAN && dev->channel_count > 1) { - cerr<<"Configuration error: devices.["<alpha = ((int)devs[i]["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)devs[i]["tau"]))); @@ -1359,8 +1351,12 @@ int main(int argc, char* argv[]) { memset(dev->bins, 0, sizeof(dev->bins)); memset(dev->base_bins, 0, sizeof(dev->base_bins)); dev->bufs = dev->bufe = dev->waveend = dev->waveavail = dev->row = 0; - for (int j = 0; j < dev->channel_count; j++) { - channel_t* channel = dev->channels + j; + int jj = 0; + for (int j = 0; j < devs[i]["channels"].getLength(); j++) { + if(devs[i]["channels"][j].exists("disable") && (bool)devs[i]["channels"][j]["disable"] == true) { + continue; + } + channel_t* channel = dev->channels + jj; for (int k = 0; k < AGC_EXTRA; k++) { channel->wavein[k] = 20; channel->waveout[k] = 0.5; @@ -1464,13 +1460,13 @@ int main(int argc, char* argv[]) { channel->outputs[o].enabled = true; channel->outputs[o].active = false; } - dev->base_bins[j] = dev->bins[j] = (int)ceil((channel->frequency + SOURCE_RATE - dev->centerfreq) / (double)(SOURCE_RATE / FFT_SIZE) - 1.0f) % FFT_SIZE; + dev->base_bins[jj] = dev->bins[jj] = (int)ceil((channel->frequency + SOURCE_RATE - dev->centerfreq) / (double)(SOURCE_RATE / FFT_SIZE) - 1.0f) % FFT_SIZE; #ifdef NFM if(channel->modulation == MOD_NFM) { // Calculate mixing frequency needed for NFM to remove linear phase shift caused by FFT sliding window // This equals bin_width_Hz * (distance_from_DC_bin) float timeref_freq = 2.0f * M_PI * (float)(SOURCE_RATE / FFT_SIZE) * - (float)(dev->bins[j] < (FFT_SIZE >> 1) ? dev->bins[j] + 1 : dev->bins[j] - FFT_SIZE + 1) / (float)WAVE_RATE; + (float)(dev->bins[jj] < (FFT_SIZE >> 1) ? dev->bins[jj] + 1 : dev->bins[jj] - FFT_SIZE + 1) / (float)WAVE_RATE; // Pre-generate the waveform for better performance for(int k = 0; k < WAVE_RATE; k++) { channel->timeref_cos[k] = cosf(timeref_freq * k); @@ -1478,7 +1474,17 @@ int main(int argc, char* argv[]) { } } #endif + jj++; + } + if(jj < 1 || jj > 8) { + cerr<<"Configuration error: devices.["<mode == R_SCAN && jj > 1) { + cerr<<"Configuration error: devices.["<channel_count = jj; } } catch(FileIOException e) { cerr<<"Cannot read configuration file "< Date: Sat, 14 May 2016 15:09:34 +0000 Subject: [PATCH 29/37] Added "disable" option for outputs --- rtl_airband.cpp | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index b7b7709..dcffe55 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -1420,16 +1420,20 @@ int main(int argc, char* argv[]) { cerr<<"Cannot allocate memory for outputs\n"; error(); } + int oo = 0; for(int o = 0; o < channel->output_count; o++) { + if(devs[i]["channels"][j]["outputs"][o].exists("disable") && (bool)devs[i]["channels"][j]["outputs"][o]["disable"] == true) { + continue; + } if(!strncmp(devs[i]["channels"][j]["outputs"][o]["type"], "icecast", 7)) { - channel->outputs[o].data = malloc(sizeof(struct icecast_data)); - if(channel->outputs[o].data == NULL) { + channel->outputs[oo].data = malloc(sizeof(struct icecast_data)); + if(channel->outputs[oo].data == NULL) { cerr<<"Cannot allocate memory for outputs\n"; error(); } - memset(channel->outputs[o].data, 0, sizeof(struct icecast_data)); - channel->outputs[o].type = O_ICECAST; - icecast_data *idata = (icecast_data *)(channel->outputs[o].data); + memset(channel->outputs[oo].data, 0, sizeof(struct icecast_data)); + channel->outputs[oo].type = O_ICECAST; + icecast_data *idata = (icecast_data *)(channel->outputs[oo].data); idata->hostname = strdup(devs[i]["channels"][j]["outputs"][o]["server"]); idata->port = devs[i]["channels"][j]["outputs"][o]["port"]; idata->mountpoint = strdup(devs[i]["channels"][j]["outputs"][o]["mountpoint"]); @@ -1440,14 +1444,14 @@ int main(int argc, char* argv[]) { if(devs[i]["channels"][j]["outputs"][o].exists("genre")) idata->genre = strdup(devs[i]["channels"][j]["outputs"][o]["genre"]); } else if(!strncmp(devs[i]["channels"][j]["outputs"][o]["type"], "file", 4)) { - channel->outputs[o].data = malloc(sizeof(struct file_data)); - if(channel->outputs[o].data == NULL) { + channel->outputs[oo].data = malloc(sizeof(struct file_data)); + if(channel->outputs[oo].data == NULL) { cerr<<"Cannot allocate memory for outputs\n"; error(); } - memset(channel->outputs[o].data, 0, sizeof(struct file_data)); - channel->outputs[o].type = O_FILE; - file_data *fdata = (file_data *)(channel->outputs[o].data); + memset(channel->outputs[oo].data, 0, sizeof(struct file_data)); + channel->outputs[oo].type = O_FILE; + file_data *fdata = (file_data *)(channel->outputs[oo].data); fdata->dir = strdup(devs[i]["channels"][j]["outputs"][o]["directory"]); fdata->prefix = strdup(devs[i]["channels"][j]["outputs"][o]["filename_template"]); fdata->continuous = devs[i]["channels"][j]["outputs"][o].exists("continuous") ? @@ -1457,9 +1461,21 @@ int main(int argc, char* argv[]) { cerr<<"Configuration error: devices.["<outputs[o].enabled = true; - channel->outputs[o].active = false; + channel->outputs[oo].enabled = true; + channel->outputs[oo].active = false; + oo++; + } + if(oo < 1) { + cerr<<"Configuration error: devices.["<outputs = (output_t *)realloc(channel->outputs, oo * sizeof(struct output_t)); + if(channel->outputs == NULL) { + cerr<<"Cannot allocate memory for outputs\n"; + error(); + } + channel->output_count = oo; + dev->base_bins[jj] = dev->bins[jj] = (int)ceil((channel->frequency + SOURCE_RATE - dev->centerfreq) / (double)(SOURCE_RATE / FFT_SIZE) - 1.0f) % FFT_SIZE; #ifdef NFM if(channel->modulation == MOD_NFM) { From dba31992bc1ff708f6410d0d6ef0061ea12ef212 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Sat, 14 May 2016 16:00:45 +0000 Subject: [PATCH 30/37] Added "disable" option for devices --- rtl_airband.cpp | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index dcffe55..ffc9ac2 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -1286,11 +1286,6 @@ int main(int argc, char* argv[]) { cerr<<"Configuration error: no devices defined\n"; error(); } - int device_count2 = rtlsdr_get_device_count(); - if (device_count2 < device_count) { - cerr<<"Not enough devices ("<= device_count2) { - cerr<<"Specified device id "<<(int)devs[i]["index"]<<" is >= number of devices "<device = (int)devs[i]["index"]; dev->channel_count = 0; @@ -1501,7 +1494,25 @@ int main(int argc, char* argv[]) { error(); } dev->channel_count = jj; + ii++; + } + if (ii < 1) { + cerr<<"Configuration error: no devices defined\n"; + error(); + } + int device_count2 = rtlsdr_get_device_count(); + if (device_count2 < ii) { + cerr<<"Not enough devices ("<device >= device_count2) { + cerr<<"Specified device id "<<(int)dev->device<<" is greater or equal than number of devices ("< Date: Sat, 14 May 2016 16:13:52 +0000 Subject: [PATCH 31/37] Added disable flags to example config file --- rtl_airband.conf.example | 46 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index bedd7a1..e743716 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -64,6 +64,8 @@ devices = ( { # Device index (as identified by librtlsdr library) index = 0; +# A quick way to ignore this device temporarily. Default: false + disable = false; # Gain (integer, in decibels, optional). Default is auto-gain. # gain = 28; # Dongle mode. Can be one of the following: @@ -88,6 +90,8 @@ devices = ( # (ie. +- 1.25 MHz around center frequency) # This setting is valid in multichannel mode only. freq = 118925000; +# A quick way to ignore this channel temporarily. Default: false + disable = false; # Modulation - "am" or "nfm". Default is "am". modulation = "am"; #Automatioc Frequency Control (AFC) level (optional value) @@ -98,6 +102,8 @@ devices = ( # can be sent to multiple Shoutcast servers. outputs = ( { +# A quick way to ignore this output temporarily. Default: false + disable = false; # Output type. Supported types are: icecast and file. type = "icecast"; # Host name or IP address of Shoutcast/Icecast server @@ -195,6 +201,10 @@ devices = ( ); }, { +# This channel will be ignored. +# This way you can quickly disable a channel without commenting out or deleting +# large sections of config file + disable = true; freq = 121300000; modulation = "am"; outputs = ( @@ -290,7 +300,15 @@ devices = ( mountpoint = "152300.mp3"; username = "source"; password = "password"; - } + }, + { +# This output will be ignored. +# This way you can quickly disable an output without deleting or commenting it out + disabled = true; + type = "file"; + directory = "/home/pi/streams"; + filename_template = "152300"; + } ); } ); }, @@ -317,4 +335,28 @@ devices = ( ); } ); - } ); + }, + { +# This device will be ignored. +# This way you can quickly disable a device without deleting or commenting it out + index = 3; + gain = 28; + mode = "scan"; + correction = 80; + channels = ( + { + freqs = ( 126300000, 121500000 ); + outputs = ( + { + type = "icecast"; + server = "www.example.com"; + port = 8000; + mountpoint = "test.mp3"; + username = "source"; + password = "password"; + } + ); + } + ); + } +); From d86073dc82c3ed706a01f365639a889b962ccdbc Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Sun, 15 May 2016 16:17:33 +0000 Subject: [PATCH 32/37] Reduce MP3_RATE to 8000 and enable VBR by default --- rtl_airband.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index ffc9ac2..895e1fa 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -114,7 +114,7 @@ #define WAVE_BATCH WAVE_RATE / 8 #define AGC_EXTRA WAVE_RATE / 160 #define WAVE_LEN 2 * WAVE_BATCH + AGC_EXTRA -#define MP3_RATE WAVE_RATE +#define MP3_RATE 8000 #define MAX_SHOUT_QUEUELEN 32768 #define CHANNELS 8 #define FFT_SIZE_LOG 9 @@ -467,7 +467,7 @@ lame_t airlame_init() { } lame_set_in_samplerate(lame, WAVE_RATE); - lame_set_VBR(lame, vbr_off); + lame_set_VBR(lame, vbr_mtrh); lame_set_brate(lame, 16); lame_set_quality(lame, 7); lame_set_out_samplerate(lame, MP3_RATE); From 2e0f08cb86f69691afcc0b7e94d5d2ac6fe943f9 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Tue, 14 Jun 2016 19:04:29 +0000 Subject: [PATCH 33/37] Make USB buffer count configurable The value of USB transfer buffers can be configured via a global parameter "rtlsdr_buffers". The new default is 10. Closes #22 --- rtl_airband.conf.example | 6 ++++++ rtl_airband.cpp | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index e743716..c3904a2 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -55,6 +55,12 @@ # You can also set this parameter per dongle and / or per channel. # tau = 200; +# Number of USB read buffers to be used per dongle +# The default value is 10. If you use four or more dongles simultaneously +# or you get error messages like "Failed to submit transfer 12!" +# you may need to lower this value +# rtlsdr_buffers = 10; + # devices section contains all settings related to the RTLSDR receivers. # # It's a list of groups - each group contains settings for a single receiver, diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 895e1fa..52a0a6f 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -238,6 +238,7 @@ struct device_t { device_t* devices; int device_count; volatile int device_opened = 0; +int rtlsdr_buffers = 10; #ifdef _WIN32 int avx; #endif @@ -373,7 +374,7 @@ void* rtlsdr_exec(void* params) { log(LOG_INFO, "Device %d started.\n", dev->device); atomic_inc(&device_opened); dev->failed = 0; - if(rtlsdr_read_async(dev->rtlsdr, rtlsdr_callback, params, 15, 320000) < 0) { + if(rtlsdr_read_async(dev->rtlsdr, rtlsdr_callback, params, rtlsdr_buffers, 320000) < 0) { log(LOG_WARNING, "Device #%d: async read failed, disabling\n", dev->device); dev->failed = 1; atomic_dec(&device_opened); @@ -1276,6 +1277,11 @@ int main(int argc, char* argv[]) { #ifndef _WIN32 if(root.exists("pidfile")) pidfile = strdup(root["pidfile"]); #endif + if(root.exists("rtlsdr_buffers")) rtlsdr_buffers = (int)(root["rtlsdr_buffers"]); + if(rtlsdr_buffers < 1) { + cerr<<"Configuration error: rtlsdr_buffers must be greater than 0\n"; + error(); + } #ifdef NFM if(root.exists("tau")) alpha = ((int)root["tau"] == 0 ? 0.0f : exp(-1.0f/(WAVE_RATE * 1e-6 * (int)root["tau"]))); From ca244c827c0fb2b4d2b4cbc0984ab255111f7b52 Mon Sep 17 00:00:00 2001 From: Jeff Lawson Date: Wed, 22 Jun 2016 01:22:23 -0500 Subject: [PATCH 34/37] pidfile syntax requires filename be quoted --- rtl_airband.conf.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index ae2280b..72ccb57 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -45,7 +45,7 @@ # PID file path (default: /run/rtl_airband.pid) # This setting has no effect on Windows. -# pidfile = /run/rtl_airband.pid +# pidfile = "/run/rtl_airband.pid" # devices section contains all settings related to the RTLSDR receivers. # From 3df2459d4dd9b010e16ae653d5e9d844e4f03c01 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Mon, 25 Jul 2016 20:25:37 +0000 Subject: [PATCH 35/37] Better power level calculation --- rtl_airband.cpp | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/rtl_airband.cpp b/rtl_airband.cpp index 52a0a6f..a374fda 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -180,7 +180,6 @@ enum modulations { }; struct channel_t { float wavein[WAVE_LEN]; // FFT output waveform - float waveref[WAVE_LEN]; // for power level calculation float waveout[WAVE_LEN]; // waveform after squelch + AGC #ifdef NFM float complex_samples[2*WAVE_LEN]; // raw samples for NFM demod @@ -1052,32 +1051,6 @@ void demodulate() { for (int i = 0; i < dev->channel_count; i++) { AFC afc(dev, i); channel_t* channel = dev->channels + i; -#if defined __arm__ - float agcmin2 = channel->agcmin * 4.5f; - for (int j = 0; j < WAVE_BATCH + AGC_EXTRA; j++) { - channel->waveref[j] = min(channel->wavein[j], agcmin2); - } -#elif defined _WIN32 - if (avx) { - __m256 agccap = _mm256_set1_ps(channel->agcmin * 4.5f); - for (int j = 0; j < WAVE_BATCH + AGC_EXTRA; j += 8) { - __m256 t = _mm256_loadu_ps(channel->wavein + j); - _mm256_storeu_ps(channel->waveref + j, _mm256_min_ps(t, agccap)); - } - } else { - __m128 agccap = _mm_set1_ps(channel->agcmin * 4.5f); - for (int j = 0; j < WAVE_BATCH + AGC_EXTRA; j += 4) { - __m128 t = _mm_loadu_ps(channel->wavein + j); - _mm_storeu_ps(channel->waveref + j, _mm_min_ps(t, agccap)); - } - } -#else - __m128 agccap = _mm_set1_ps(channel->agcmin * 4.5f); - for (int j = 0; j < WAVE_BATCH + AGC_EXTRA; j += 4) { - __m128 t = _mm_loadu_ps(channel->wavein + j); - _mm_storeu_ps(channel->waveref + j, _mm_min_ps(t, agccap)); - } -#endif for (int j = AGC_EXTRA; j < WAVE_BATCH + AGC_EXTRA; j++) { // auto noise floor if (j % 16 == 0) { @@ -1085,7 +1058,7 @@ void demodulate() { } // average power - channel->agcavgslow = channel->agcavgslow * 0.99f + channel->waveref[j] * 0.01f; + channel->agcavgslow = channel->agcavgslow * 0.99f + channel->wavein[j] * 0.01f; if (channel->agcsq > 0) { channel->agcsq = max(channel->agcsq - 1, 1); From b8c2686a49bd0de317a0e430e56eaf7f8c5f04d3 Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Mon, 25 Jul 2016 21:27:30 +0000 Subject: [PATCH 36/37] Allow per-channel manual squelch setting --- rtl_airband.conf.example | 7 +++++++ rtl_airband.cpp | 21 ++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/rtl_airband.conf.example b/rtl_airband.conf.example index c3904a2..aabe660 100644 --- a/rtl_airband.conf.example +++ b/rtl_airband.conf.example @@ -100,6 +100,11 @@ devices = ( disable = false; # Modulation - "am" or "nfm". Default is "am". modulation = "am"; +# Squelch level (integer, greater than 0). Default is auto. +# Set this parameter only when auto squelch does not work correctly +# (this is common with channels which transmit continuously, like ATIS/AWOS, +# when auto squelch is unable to figure out the correct noise floor level) +# squelch = 100; #Automatioc Frequency Control (AFC) level (optional value) #0 - disabled (by default) #value from range 1...10 - AFC enabled, larger value means more aggressive AFC. @@ -187,6 +192,8 @@ devices = ( }, { freq = 120600000; +# Manual squelch setting for this channel + squelch = 250; modulation = "am"; # This channel is not streamed to Icecast. # It is saved to local files into two destinations - one of them records continuously, the other one diff --git a/rtl_airband.cpp b/rtl_airband.cpp index a374fda..d41e9e2 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -196,6 +196,7 @@ struct channel_t { float agcavgfast; // average power, for AGC float agcavgslow; // average power, for squelch level detection float agcmin; // noise level + int sqlevel; // manually configured squelch level int agclow; // low level sample count char axcindicate; // squelch/AFC status indicator: ' ' - no signal; '*' - has signal; '>', '<' - signal tuned by AFC unsigned char afc; //0 - AFC disabled; 1 - minimal AFC; 2 - more aggressive AFC and so on to 255 @@ -1053,29 +1054,30 @@ void demodulate() { channel_t* channel = dev->channels + i; for (int j = AGC_EXTRA; j < WAVE_BATCH + AGC_EXTRA; j++) { // auto noise floor - if (j % 16 == 0) { + if (channel->sqlevel < 0 && j % 16 == 0) { channel->agcmin = channel->agcmin * 0.97f + min(channel->agcavgslow, channel->agcmin) * 0.03f + 0.0001f; } // average power channel->agcavgslow = channel->agcavgslow * 0.99f + channel->wavein[j] * 0.01f; + float sqlevel = channel->sqlevel > 0 ? (float)channel->sqlevel : 3.0f * channel->agcmin; if (channel->agcsq > 0) { channel->agcsq = max(channel->agcsq - 1, 1); - if (channel->agcsq == 1 && channel->agcavgslow > 3.0f * channel->agcmin) { + if (channel->agcsq == 1 && channel->agcavgslow > sqlevel) { channel->agcsq = -AGC_EXTRA * 2; channel->axcindicate = '*'; if(channel->modulation == MOD_AM) { // fade in for (int k = j - AGC_EXTRA; k < j; k++) { - if (channel->wavein[k] > channel->agcmin * 3.0f) { + if (channel->wavein[k] > sqlevel) { channel->agcavgfast = channel->agcavgfast * 0.98f + channel->wavein[k] * 0.02f; } } } } } else { - if (channel->wavein[j] > channel->agcmin * 3.0f) { + if (channel->wavein[j] > sqlevel) { if(channel->modulation == MOD_AM) channel->agcavgfast = channel->agcavgfast * 0.995f + channel->wavein[j] * 0.005f; channel->agclow = 0; @@ -1083,7 +1085,8 @@ void demodulate() { channel->agclow++; } channel->agcsq = min(channel->agcsq + 1, -1); - if ((channel->agcsq == -1 && channel->agcavgslow < 2.4f * channel->agcmin) || channel->agclow == AGC_EXTRA - 12) { + sqlevel = channel->sqlevel > 0 ? (float)channel->sqlevel : 2.4f * channel->agcmin; + if ((channel->agcsq == -1 && channel->agcavgslow < sqlevel) || channel->agclow == AGC_EXTRA - 12) { channel->agcsq = AGC_EXTRA * 2; channel->axcindicate = ' '; if(channel->modulation == MOD_AM) { @@ -1339,6 +1342,7 @@ int main(int argc, char* argv[]) { channel->agcavgslow = 0.5f; channel->agcmin = 100.0f; channel->agclow = 0; + channel->sqlevel = -1.0f; channel->modulation = MOD_AM; if(devs[i]["channels"][j].exists("modulation")) { #ifdef NFM @@ -1353,6 +1357,13 @@ int main(int argc, char* argv[]) { error(); } } + if(devs[i]["channels"][j].exists("squelch")) { + channel->sqlevel = (int)devs[i]["channels"][j]["squelch"]; + if(channel->sqlevel <= 0) { + cerr<<"Configuration error: devices.["<afc = devs[i]["channels"][j].exists("afc") ? (unsigned char) (unsigned int)devs[i]["channels"][j]["afc"] : 0; if(dev->mode == R_MULTICHANNEL) { channel->frequency = devs[i]["channels"][j]["freq"]; From dc3d1b466beea8e13753311e5f9ab2d46805a47f Mon Sep 17 00:00:00 2001 From: Tomasz Lemiech Date: Thu, 11 Aug 2016 18:04:58 +0000 Subject: [PATCH 37/37] Added -Q option to runtime help message and README.md --- README.md | 4 ++++ rtl_airband.cpp | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e04fd45..90092bd 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,10 @@ rtl_airband accepts the following command line options: -f Run in foreground, display textual waterfalls -c Use non-default configuration file +Additional options are avalable when rtl_airband is compiled with NFM support: + + -Q Use quadri correlator for FM demodulation (default is atan2) + Troubleshooting -------------------- diff --git a/rtl_airband.cpp b/rtl_airband.cpp index d41e9e2..b5cde50 100644 --- a/rtl_airband.cpp +++ b/rtl_airband.cpp @@ -1176,10 +1176,13 @@ void demodulate() { } void usage() { - cout<<"Usage: rtl_airband [-f] [-c ]\n\ + cout<<"Usage: rtl_airband [options] [-c ]\n\ \t-h\t\t\tDisplay this help text\n\ -\t-f\t\t\tRun in foreground, display textual waterfalls\n\ -\t-c \tUse non-default configuration file\n\t\t\t\t(default: "<\tUse non-default configuration file\n\t\t\t\t(default: "<