mirror of
https://github.com/rtlsdrblog/rtl-sdr-blog.git
synced 2025-03-25 05:35:28 +01:00
rtl_fm: frequency ranges, flexible squelch
Signed-off-by: Steve Markgraf <steve@steve-m.de>
This commit is contained in:
parent
e4e8ef55fc
commit
d6107bb059
107
src/rtl_fm.c
107
src/rtl_fm.c
@ -25,16 +25,14 @@
|
|||||||
* based on rtl_sdr.c and rtl_tcp.c
|
* based on rtl_sdr.c and rtl_tcp.c
|
||||||
* todo: realtime ARMv5
|
* todo: realtime ARMv5
|
||||||
* remove float math (disqualifies complex.h)
|
* remove float math (disqualifies complex.h)
|
||||||
* replace atan2 with a fast approximation
|
|
||||||
* in-place array operations
|
* in-place array operations
|
||||||
* better wide band support
|
|
||||||
* sanity checks
|
* sanity checks
|
||||||
* nicer FIR than square
|
* nicer FIR than square
|
||||||
* scale squelch to other input parameters
|
* scale squelch to other input parameters
|
||||||
* test all the demodulations
|
* test all the demodulations
|
||||||
* sci notation parsing
|
|
||||||
* pad output on hop
|
* pad output on hop
|
||||||
* nearest gain approx
|
* nearest gain approx
|
||||||
|
* frequency ranges could be stored better
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@ -66,7 +64,6 @@
|
|||||||
#define DEFAULT_BUF_LENGTH (1 * 16384)
|
#define DEFAULT_BUF_LENGTH (1 * 16384)
|
||||||
#define MAXIMUM_OVERSAMPLE 16
|
#define MAXIMUM_OVERSAMPLE 16
|
||||||
#define MAXIMUM_BUF_LENGTH (MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH)
|
#define MAXIMUM_BUF_LENGTH (MAXIMUM_OVERSAMPLE * DEFAULT_BUF_LENGTH)
|
||||||
#define CONSEQ_SQUELCH 20
|
|
||||||
#define AUTO_GAIN -100
|
#define AUTO_GAIN -100
|
||||||
|
|
||||||
static pthread_t demod_thread;
|
static pthread_t demod_thread;
|
||||||
@ -86,8 +83,10 @@ struct fm_state
|
|||||||
int post_downsample;
|
int post_downsample;
|
||||||
int output_scale;
|
int output_scale;
|
||||||
int squelch_level;
|
int squelch_level;
|
||||||
|
int conseq_squelch;
|
||||||
int squelch_hits;
|
int squelch_hits;
|
||||||
int term_squelch_hits;
|
int terminate_on_squelch;
|
||||||
|
int exit_flag;
|
||||||
uint8_t buf[MAXIMUM_BUF_LENGTH];
|
uint8_t buf[MAXIMUM_BUF_LENGTH];
|
||||||
uint32_t buf_len;
|
uint32_t buf_len;
|
||||||
int signal[MAXIMUM_BUF_LENGTH]; /* 16 bit signed i/q pairs */
|
int signal[MAXIMUM_BUF_LENGTH]; /* 16 bit signed i/q pairs */
|
||||||
@ -96,7 +95,7 @@ struct fm_state
|
|||||||
int signal2_len;
|
int signal2_len;
|
||||||
FILE *file;
|
FILE *file;
|
||||||
int edge;
|
int edge;
|
||||||
uint32_t freqs[100];
|
uint32_t freqs[1000];
|
||||||
int freq_len;
|
int freq_len;
|
||||||
int freq_now;
|
int freq_now;
|
||||||
uint32_t sample_rate;
|
uint32_t sample_rate;
|
||||||
@ -116,24 +115,26 @@ void usage(void)
|
|||||||
{
|
{
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"rtl_fm, a simple narrow band FM demodulator for RTL2832 based DVB-T receivers\n\n"
|
"rtl_fm, a simple narrow band FM demodulator for RTL2832 based DVB-T receivers\n\n"
|
||||||
"Use:\trtl_fm -f freq [-options] filename\n"
|
"Use:\trtl_fm -f freq [-options] [filename]\n"
|
||||||
"\t-f frequency_to_tune_to [Hz]\n"
|
"\t-f frequency_to_tune_to [Hz]\n"
|
||||||
"\t (use multiple -f for scanning)\n"
|
"\t (use multiple -f for scanning, requires squelch)\n"
|
||||||
"\t[-s samplerate (default: 24k Hz)]\n"
|
"\t (ranges supported, -f 118M:137M:25k)\n"
|
||||||
|
"\t[-s sample_rate (default: 24k)]\n"
|
||||||
"\t[-d device_index (default: 0)]\n"
|
"\t[-d device_index (default: 0)]\n"
|
||||||
"\t[-g tuner_gain (default: automatic)]\n"
|
"\t[-g tuner_gain (default: automatic)]\n"
|
||||||
"\t[-l squelch_level (default: 0/off)]\n"
|
"\t[-l squelch_level (default: 0/off)]\n"
|
||||||
"\t[-o oversampling (default: 1)]\n"
|
"\t[-o oversampling (default: 1, 4 recommended)]\n"
|
||||||
"\t[-p ppm_error (default: 0)]\n"
|
"\t[-p ppm_error (default: 0)]\n"
|
||||||
"\t[-E sets lower edge tuning (default: center)]\n"
|
"\t[-E sets lower edge tuning (default: center)]\n"
|
||||||
"\t[-N enables NBFM mode (default: on)]\n"
|
"\t[-N enables NBFM mode (default: on)]\n"
|
||||||
"\t[-W enables WBFM mode (default: off)]\n"
|
"\t[-W enables WBFM mode (default: off)]\n"
|
||||||
"\t (-N -s 170k -o 4 -A -r 32k -l 0 -D)\n"
|
"\t (-N -s 170k -o 4 -A -r 32k -l 0 -D)\n"
|
||||||
"\tfilename (a '-' dumps samples to stdout)\n\n"
|
"\tfilename (a '-' dumps samples to stdout)\n"
|
||||||
|
"\t (omitting the filename also uses stdout)\n\n"
|
||||||
"Experimental options:\n"
|
"Experimental options:\n"
|
||||||
"\t[-r output rate (default: same as -s)]\n"
|
"\t[-r output_rate (default: same as -s)]\n"
|
||||||
"\t[-t terminate_hits (default: 0/off)]\n"
|
"\t[-t squelch_delay (default: 20)]\n"
|
||||||
"\t (requires squelch on and scanning off)\n"
|
"\t (+values will mute/scan, -values will exit)\n"
|
||||||
"\t[-M enables AM mode (default: off)]\n"
|
"\t[-M enables AM mode (default: off)]\n"
|
||||||
"\t[-L enables LSB mode (default: off)]\n"
|
"\t[-L enables LSB mode (default: off)]\n"
|
||||||
"\t[-U enables USB mode (default: off)]\n"
|
"\t[-U enables USB mode (default: off)]\n"
|
||||||
@ -369,7 +370,7 @@ void am_demod(struct fm_state *fm)
|
|||||||
int i, pcm;
|
int i, pcm;
|
||||||
for (i = 0; i < (fm->signal_len); i += 2) {
|
for (i = 0; i < (fm->signal_len); i += 2) {
|
||||||
// hypot uses floats but won't overflow
|
// hypot uses floats but won't overflow
|
||||||
//pcm = (int)hypot(fm->signal[i], fm->signal[i+1]);
|
//fm->signal2[i/2] = (int16_t)hypot(fm->signal[i], fm->signal[i+1]);
|
||||||
pcm = fm->signal[i] * fm->signal[i];
|
pcm = fm->signal[i] * fm->signal[i];
|
||||||
pcm += fm->signal[i+1] * fm->signal[i+1];
|
pcm += fm->signal[i+1] * fm->signal[i+1];
|
||||||
fm->signal2[i/2] = (int16_t)sqrt(pcm); // * fm->output_scale;
|
fm->signal2[i/2] = (int16_t)sqrt(pcm); // * fm->output_scale;
|
||||||
@ -445,7 +446,7 @@ int mad(int *samples, int len, int step)
|
|||||||
int post_squelch(struct fm_state *fm)
|
int post_squelch(struct fm_state *fm)
|
||||||
/* returns 1 for active signal, 0 for no signal */
|
/* returns 1 for active signal, 0 for no signal */
|
||||||
{
|
{
|
||||||
int i, i2, dev_r, dev_j, len, sq_l;
|
int dev_r, dev_j, len, sq_l;
|
||||||
/* only for small samples, big samples need chunk processing */
|
/* only for small samples, big samples need chunk processing */
|
||||||
len = fm->signal_len;
|
len = fm->signal_len;
|
||||||
sq_l = fm->squelch_level;
|
sq_l = fm->squelch_level;
|
||||||
@ -456,12 +457,6 @@ int post_squelch(struct fm_state *fm)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
fm->squelch_hits++;
|
fm->squelch_hits++;
|
||||||
if (fm->term_squelch_hits) {
|
|
||||||
return 0;}
|
|
||||||
/* weak signal, kill it entirely */
|
|
||||||
for (i=0; i<len; i++) {
|
|
||||||
fm->signal2[i] = 0;
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,7 +499,7 @@ static void optimal_settings(struct fm_state *fm, int freq, int hopping)
|
|||||||
|
|
||||||
void full_demod(struct fm_state *fm)
|
void full_demod(struct fm_state *fm)
|
||||||
{
|
{
|
||||||
int sr, freq_next;
|
int i, sr, freq_next, hop = 0;
|
||||||
rotate_90(fm->buf, fm->buf_len);
|
rotate_90(fm->buf, fm->buf_len);
|
||||||
if (fm->fir_enable) {
|
if (fm->fir_enable) {
|
||||||
low_pass_fir(fm, fm->buf, fm->buf_len);
|
low_pass_fir(fm, fm->buf, fm->buf_len);
|
||||||
@ -517,6 +512,16 @@ void full_demod(struct fm_state *fm)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sr = post_squelch(fm);
|
sr = post_squelch(fm);
|
||||||
|
if (!sr && fm->squelch_hits > fm->conseq_squelch) {
|
||||||
|
if (fm->terminate_on_squelch) {
|
||||||
|
fm->exit_flag = 1;}
|
||||||
|
if (fm->freq_len == 1) { /* mute */
|
||||||
|
for (i=0; i<fm->signal_len; i++) {
|
||||||
|
fm->signal2[i] = 0;}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
hop = 1;}
|
||||||
|
}
|
||||||
if (fm->post_downsample > 1) {
|
if (fm->post_downsample > 1) {
|
||||||
fm->signal2_len = low_pass_simple(fm->signal2, fm->signal2_len, fm->post_downsample);}
|
fm->signal2_len = low_pass_simple(fm->signal2, fm->signal2_len, fm->post_downsample);}
|
||||||
if (fm->output_rate > 0) {
|
if (fm->output_rate > 0) {
|
||||||
@ -526,11 +531,11 @@ void full_demod(struct fm_state *fm)
|
|||||||
deemph_filter(fm);}
|
deemph_filter(fm);}
|
||||||
/* ignore under runs for now */
|
/* ignore under runs for now */
|
||||||
fwrite(fm->signal2, 2, fm->signal2_len, fm->file);
|
fwrite(fm->signal2, 2, fm->signal2_len, fm->file);
|
||||||
if (fm->freq_len > 1 && !sr && fm->squelch_hits > CONSEQ_SQUELCH) {
|
if (hop) {
|
||||||
freq_next = (fm->freq_now + 1) % fm->freq_len;
|
freq_next = (fm->freq_now + 1) % fm->freq_len;
|
||||||
optimal_settings(fm, freq_next, 1);
|
optimal_settings(fm, freq_next, 1);
|
||||||
fm->squelch_hits = CONSEQ_SQUELCH + 1; /* hair trigger */
|
fm->squelch_hits = fm->conseq_squelch + 1; /* hair trigger */
|
||||||
/* wait for settling and dump buffer */
|
/* wait for settling and flush buffer */
|
||||||
usleep(5000);
|
usleep(5000);
|
||||||
rtlsdr_read_sync(dev, NULL, 4096, NULL);
|
rtlsdr_read_sync(dev, NULL, 4096, NULL);
|
||||||
}
|
}
|
||||||
@ -559,16 +564,14 @@ static void *demod_thread_fn(void *arg)
|
|||||||
while (!do_exit) {
|
while (!do_exit) {
|
||||||
sem_wait(&data_ready);
|
sem_wait(&data_ready);
|
||||||
full_demod(fm2);
|
full_demod(fm2);
|
||||||
if (!fm2->term_squelch_hits) {
|
if (fm2->exit_flag) {
|
||||||
continue;}
|
|
||||||
if (fm2->squelch_hits > fm2->term_squelch_hits) {
|
|
||||||
do_exit = 1;
|
do_exit = 1;
|
||||||
rtlsdr_cancel_async(dev);}
|
rtlsdr_cancel_async(dev);}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int atofs(char* f)
|
double atofs(char* f)
|
||||||
/* standard suffixes */
|
/* standard suffixes */
|
||||||
{
|
{
|
||||||
char* chop;
|
char* chop;
|
||||||
@ -584,8 +587,26 @@ int atofs(char* f)
|
|||||||
suff *= atof(chop);}
|
suff *= atof(chop);}
|
||||||
free(chop);
|
free(chop);
|
||||||
if (suff != 1.0) {
|
if (suff != 1.0) {
|
||||||
return (int)suff;}
|
return suff;}
|
||||||
return (int)atof(f);
|
return atof(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void frequency_range(struct fm_state *fm, char *arg)
|
||||||
|
{
|
||||||
|
char *start, *stop, *step;
|
||||||
|
int i;
|
||||||
|
start = arg;
|
||||||
|
stop = strchr(start, ':') + 1;
|
||||||
|
stop[-1] = '\0';
|
||||||
|
step = strchr(stop, ':') + 1;
|
||||||
|
step[-1] = '\0';
|
||||||
|
for(i=(int)atofs(start); i<=(int)atofs(stop); i+=(int)atofs(step))
|
||||||
|
{
|
||||||
|
fm->freqs[fm->freq_len] = (uint32_t)i;
|
||||||
|
fm->freq_len++;
|
||||||
|
}
|
||||||
|
stop[-1] = ':';
|
||||||
|
step[-1] = ':';
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
@ -605,7 +626,8 @@ int main(int argc, char **argv)
|
|||||||
fm.freqs[0] = 100000000;
|
fm.freqs[0] = 100000000;
|
||||||
fm.sample_rate = DEFAULT_SAMPLE_RATE;
|
fm.sample_rate = DEFAULT_SAMPLE_RATE;
|
||||||
fm.squelch_level = 0;
|
fm.squelch_level = 0;
|
||||||
fm.term_squelch_hits = 0;
|
fm.conseq_squelch = 20;
|
||||||
|
fm.terminate_on_squelch = 0;
|
||||||
fm.freq_len = 0;
|
fm.freq_len = 0;
|
||||||
fm.edge = 0;
|
fm.edge = 0;
|
||||||
fm.fir_enable = 0;
|
fm.fir_enable = 0;
|
||||||
@ -623,8 +645,13 @@ int main(int argc, char **argv)
|
|||||||
dev_index = atoi(optarg);
|
dev_index = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
|
if (strchr(optarg, ':'))
|
||||||
|
{frequency_range(&fm, optarg);}
|
||||||
|
else
|
||||||
|
{
|
||||||
fm.freqs[fm.freq_len] = (uint32_t)atofs(optarg);
|
fm.freqs[fm.freq_len] = (uint32_t)atofs(optarg);
|
||||||
fm.freq_len++;
|
fm.freq_len++;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'g':
|
case 'g':
|
||||||
gain = (int)(atof(optarg) * 10);
|
gain = (int)(atof(optarg) * 10);
|
||||||
@ -644,10 +671,15 @@ int main(int argc, char **argv)
|
|||||||
fprintf(stderr, "Oversample must be between 1 and %i\n", MAXIMUM_OVERSAMPLE);}
|
fprintf(stderr, "Oversample must be between 1 and %i\n", MAXIMUM_OVERSAMPLE);}
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
fm.term_squelch_hits = (int)atof(optarg);
|
fm.conseq_squelch = (int)atof(optarg);
|
||||||
|
if (fm.conseq_squelch < 0) {
|
||||||
|
fm.conseq_squelch = -fm.conseq_squelch;
|
||||||
|
fm.terminate_on_squelch = 1;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
ppm_error = atoi(optarg);
|
ppm_error = atoi(optarg);
|
||||||
|
break;
|
||||||
case 'E':
|
case 'E':
|
||||||
fm.edge = 1;
|
fm.edge = 1;
|
||||||
break;
|
break;
|
||||||
@ -693,8 +725,13 @@ int main(int argc, char **argv)
|
|||||||
/* quadruple sample_rate to limit to Δθ to ±π/2 */
|
/* quadruple sample_rate to limit to Δθ to ±π/2 */
|
||||||
fm.sample_rate *= fm.post_downsample;
|
fm.sample_rate *= fm.post_downsample;
|
||||||
|
|
||||||
|
if (fm.freq_len > 1) {
|
||||||
|
fm.terminate_on_squelch = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (argc <= optind) {
|
if (argc <= optind) {
|
||||||
usage();
|
//usage();
|
||||||
|
filename = "-";
|
||||||
} else {
|
} else {
|
||||||
filename = argv[optind];
|
filename = argv[optind];
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user