mirror of
https://github.com/rtlsdrblog/rtl-sdr-blog.git
synced 2025-02-05 14:54:58 +01:00
rtl_fm: add experimental options
Signed-off-by: Steve Markgraf <steve@steve-m.de>
This commit is contained in:
parent
d2f8faa204
commit
374abaebb8
133
src/rtl_fm.c
133
src/rtl_fm.c
@ -29,6 +29,9 @@
|
|||||||
* in-place array operations
|
* in-place array operations
|
||||||
* wide band support
|
* wide band support
|
||||||
* sanity checks
|
* sanity checks
|
||||||
|
* nicer FIR than square
|
||||||
|
* (tried this, was twice as slow and did not sound much better)
|
||||||
|
* scale squelch to other input parameters
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@ -69,7 +72,8 @@ struct fm_state
|
|||||||
int pre_r;
|
int pre_r;
|
||||||
int pre_j;
|
int pre_j;
|
||||||
int prev_index;
|
int prev_index;
|
||||||
int downsample; /* min 4, max 256 */
|
int downsample; /* min 1, max 256 */
|
||||||
|
int post_downsample;
|
||||||
int output_scale;
|
int output_scale;
|
||||||
int squelch_level;
|
int squelch_level;
|
||||||
int squelch_hits;
|
int squelch_hits;
|
||||||
@ -84,6 +88,10 @@ struct fm_state
|
|||||||
int freq_len;
|
int freq_len;
|
||||||
int freq_now;
|
int freq_now;
|
||||||
uint32_t sample_rate;
|
uint32_t sample_rate;
|
||||||
|
int fir_enable;
|
||||||
|
int fir[256]; /* fir_len == downsample */
|
||||||
|
int fir_sum;
|
||||||
|
int custom_atan;
|
||||||
};
|
};
|
||||||
|
|
||||||
void usage(void)
|
void usage(void)
|
||||||
@ -96,13 +104,17 @@ 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"
|
||||||
"Usage:\t -f frequency_to_tune_to [Hz]\n"
|
"Usage:\t -f frequency_to_tune_to [Hz]\n"
|
||||||
"\t (use multiple -f for scanning !!BROKEN!!)\n"
|
"\t (use multiple -f for scanning)\n"
|
||||||
"\t[-s samplerate (default: 24000 Hz)]\n"
|
"\t[-s samplerate (default: 24000 Hz)]\n"
|
||||||
"\t[-d device_index (default: 0)]\n"
|
"\t[-d device_index (default: 0)]\n"
|
||||||
"\t[-g tuner_gain (default: -1dB)]\n"
|
"\t[-g tuner_gain (default: -1dB)]\n"
|
||||||
"\t[-l squelch_level (default: 150)]\n"
|
"\t[-l squelch_level (default: 150)]\n"
|
||||||
"\t[-E freq sets lower edge (default: center)]\n"
|
"\t[-E freq sets lower edge (default: center)]\n"
|
||||||
"\tfilename (a '-' dumps samples to stdout)\n\n"
|
"\tfilename (a '-' dumps samples to stdout)\n\n"
|
||||||
|
"Experimental quality/cpu options:\n"
|
||||||
|
"\t[-o oversampling (default: 1) !!BROKEN!!]\n"
|
||||||
|
"\t[-F enables high quality FIR (default: off/square)]\n"
|
||||||
|
"\t[-A enables high speed arctan (default: off)]\n\n"
|
||||||
"Produces signed 16 bit ints, use Sox to hear them.\n"
|
"Produces signed 16 bit ints, use Sox to hear them.\n"
|
||||||
"\trtl_fm ... | play -t raw -r 24k -e signed-integer -b 16 -c 1 -V1 -\n\n");
|
"\trtl_fm ... | play -t raw -r 24k -e signed-integer -b 16 -c 1 -V1 -\n\n");
|
||||||
#endif
|
#endif
|
||||||
@ -152,8 +164,8 @@ void rotate_90(unsigned char *buf, uint32_t len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void low_pass(struct fm_state *fm, unsigned char *buf, uint32_t len)
|
void low_pass(struct fm_state *fm, unsigned char *buf, uint32_t len)
|
||||||
|
/* simple square window FIR */
|
||||||
{
|
{
|
||||||
/* simple square window FIR */
|
|
||||||
int i=0, i2=0;
|
int i=0, i2=0;
|
||||||
while (i < (int)len) {
|
while (i < (int)len) {
|
||||||
fm->now_r += ((int)buf[i] - 128);
|
fm->now_r += ((int)buf[i] - 128);
|
||||||
@ -173,6 +185,63 @@ void low_pass(struct fm_state *fm, unsigned char *buf, uint32_t len)
|
|||||||
fm->signal_len = i2;
|
fm->signal_len = i2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void build_fir(struct fm_state *fm)
|
||||||
|
/* for now, a simple triangle
|
||||||
|
* fancy FIRs are equally expensive, so use one */
|
||||||
|
/* point = sum(sample[i] * fir[i] * fir_len / fir_sum) */
|
||||||
|
{
|
||||||
|
int i, len;
|
||||||
|
len = fm->downsample;
|
||||||
|
for(i = 0; i < len; i++) {
|
||||||
|
fm->fir[i] = i;
|
||||||
|
}
|
||||||
|
for(i = len-1; i <= 0; i--) {
|
||||||
|
fm->fir[i] = len - i;
|
||||||
|
}
|
||||||
|
fm->fir_sum = 0;
|
||||||
|
for(i = 0; i < len; i++) {
|
||||||
|
fm->fir_sum += fm->fir[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void low_pass_fir(struct fm_state *fm, unsigned char *buf, uint32_t len)
|
||||||
|
/* perform an arbitrary FIR, doubles CPU use */
|
||||||
|
// possibly bugged, or overflowing
|
||||||
|
{
|
||||||
|
int i=0, i2=0, i3=0;
|
||||||
|
while (i < (int)len) {
|
||||||
|
fm->prev_index++;
|
||||||
|
i3 = fm->prev_index;
|
||||||
|
fm->now_r += ((int)buf[i] - 128) * fm->fir[i3] * fm->downsample / fm->fir_sum;
|
||||||
|
fm->now_j += ((int)buf[i+1] - 128) * fm->fir[i3] * fm->downsample / fm->fir_sum;
|
||||||
|
i += 2;
|
||||||
|
if (fm->prev_index < (fm->downsample)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fm->signal[i2] = fm->now_r * fm->output_scale;
|
||||||
|
fm->signal[i2+1] = fm->now_j * fm->output_scale;
|
||||||
|
fm->prev_index = -1;
|
||||||
|
fm->now_r = 0;
|
||||||
|
fm->now_j = 0;
|
||||||
|
i2 += 2;
|
||||||
|
}
|
||||||
|
fm->signal_len = i2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int low_pass_simple(int16_t *signal2, int len, int step)
|
||||||
|
// no wrap around, length must be multiple of step
|
||||||
|
{
|
||||||
|
int i, i2, sum;
|
||||||
|
for(i=0; i < len; i+=step) {
|
||||||
|
sum = 0;
|
||||||
|
for(i2=0; i2<step; i2++) {
|
||||||
|
sum += (int)signal2[i + i2];
|
||||||
|
}
|
||||||
|
signal2[i] = (int16_t)(sum / step);
|
||||||
|
}
|
||||||
|
return len / step;
|
||||||
|
}
|
||||||
|
|
||||||
/* define our own complex math ops
|
/* define our own complex math ops
|
||||||
because ARMv5 has no hardware float */
|
because ARMv5 has no hardware float */
|
||||||
|
|
||||||
@ -182,10 +251,22 @@ void multiply(int ar, int aj, int br, int bj, int *cr, int *cj)
|
|||||||
*cj = aj*br + ar*bj;
|
*cj = aj*br + ar*bj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int polar_discriminant(int ar, int aj, int br, int bj)
|
||||||
|
{
|
||||||
|
int cr, cj;
|
||||||
|
double angle;
|
||||||
|
multiply(ar, aj, br, -bj, &cr, &cj);
|
||||||
|
angle = atan2((double)cj, (double)cr);
|
||||||
|
//if (angle > (3.14159) || angle < (-3.14159))
|
||||||
|
// {fprintf(stderr, "overflow %f\n", angle);}
|
||||||
|
return (int)(angle / 3.14159 * (1<<14));
|
||||||
|
}
|
||||||
|
|
||||||
int fast_atan2(int y, int x)
|
int fast_atan2(int y, int x)
|
||||||
/* pre scaled for int16 */
|
/* pre scaled for int16 */
|
||||||
{
|
{
|
||||||
int yabs, angle, pi4=(1<<12); // note pi = 1<<14
|
int yabs, angle;
|
||||||
|
int pi4=(1<<12), pi34=3*(1<<12); // note pi = 1<<14
|
||||||
if (x==0 && y==0) {
|
if (x==0 && y==0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -196,7 +277,7 @@ int fast_atan2(int y, int x)
|
|||||||
if (x >= 0) {
|
if (x >= 0) {
|
||||||
angle = pi4 - pi4 * (x-yabs) / (x+yabs);
|
angle = pi4 - pi4 * (x-yabs) / (x+yabs);
|
||||||
} else {
|
} else {
|
||||||
angle = 3*pi4 - pi4 * (x+yabs) / (yabs-x);
|
angle = pi34 - pi4 * (x+yabs) / (yabs-x);
|
||||||
}
|
}
|
||||||
if (y < 0) {
|
if (y < 0) {
|
||||||
return -angle;
|
return -angle;
|
||||||
@ -204,14 +285,11 @@ int fast_atan2(int y, int x)
|
|||||||
return angle;
|
return angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
int polar_discriminant(int ar, int aj, int br, int bj)
|
int polar_disc_fast(int ar, int aj, int br, int bj)
|
||||||
{
|
{
|
||||||
int cr, cj;
|
int cr, cj;
|
||||||
double angle;
|
|
||||||
multiply(ar, aj, br, -bj, &cr, &cj);
|
multiply(ar, aj, br, -bj, &cr, &cj);
|
||||||
angle = atan2((double)cj, (double)cr);
|
return fast_atan2(cj, cr);
|
||||||
return (int)(angle / 3.14159 * (1<<14));
|
|
||||||
//return fast_atan2(cj, cr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void fm_demod(struct fm_state *fm)
|
void fm_demod(struct fm_state *fm)
|
||||||
@ -221,8 +299,13 @@ void fm_demod(struct fm_state *fm)
|
|||||||
fm->pre_r, fm->pre_j);
|
fm->pre_r, fm->pre_j);
|
||||||
fm->signal2[0] = (int16_t)pcm;
|
fm->signal2[0] = (int16_t)pcm;
|
||||||
for (i = 2; i < (fm->signal_len); i += 2) {
|
for (i = 2; i < (fm->signal_len); i += 2) {
|
||||||
|
if (fm->custom_atan) {
|
||||||
|
pcm = polar_disc_fast(fm->signal[i], fm->signal[i+1],
|
||||||
|
fm->signal[i-2], fm->signal[i-1]);
|
||||||
|
} else {
|
||||||
pcm = polar_discriminant(fm->signal[i], fm->signal[i+1],
|
pcm = polar_discriminant(fm->signal[i], fm->signal[i+1],
|
||||||
fm->signal[i-2], fm->signal[i-1]);
|
fm->signal[i-2], fm->signal[i-1]);
|
||||||
|
}
|
||||||
fm->signal2[i/2] = (int16_t)pcm;
|
fm->signal2[i/2] = (int16_t)pcm;
|
||||||
}
|
}
|
||||||
fm->pre_r = fm->signal[fm->signal_len - 2];
|
fm->pre_r = fm->signal[fm->signal_len - 2];
|
||||||
@ -281,13 +364,17 @@ static void optimal_settings(struct fm_state *fm, int freq, int hopping)
|
|||||||
r = rtlsdr_set_center_freq(dev, (uint32_t)capture_freq);
|
r = rtlsdr_set_center_freq(dev, (uint32_t)capture_freq);
|
||||||
if (hopping) {
|
if (hopping) {
|
||||||
return;}
|
return;}
|
||||||
fprintf(stderr, "Oversampling by: %ix.\n", fm->downsample);
|
fprintf(stderr, "Oversampling input by: %ix.\n", fm->downsample);
|
||||||
|
fprintf(stderr, "Oversampling output by: %ix.\n", fm->post_downsample);
|
||||||
|
fprintf(stderr, "Buffer size: %0.2fms\n",
|
||||||
|
1000 * 0.5 * (float)DEFAULT_BUF_LENGTH / (float)capture_rate);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
fprintf(stderr, "WARNING: Failed to set center freq.\n");}
|
fprintf(stderr, "WARNING: Failed to set center freq.\n");}
|
||||||
else {
|
else {
|
||||||
fprintf(stderr, "Tuned to %u Hz.\n", capture_freq);}
|
fprintf(stderr, "Tuned to %u Hz.\n", capture_freq);}
|
||||||
|
|
||||||
/* Set the sample rate */
|
/* Set the sample rate */
|
||||||
|
fprintf(stderr, "Sampling at %u Hz.\n", capture_rate);
|
||||||
r = rtlsdr_set_sample_rate(dev, (uint32_t)capture_rate);
|
r = rtlsdr_set_sample_rate(dev, (uint32_t)capture_rate);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
fprintf(stderr, "WARNING: Failed to set sample rate.\n");}
|
fprintf(stderr, "WARNING: Failed to set sample rate.\n");}
|
||||||
@ -298,9 +385,15 @@ void full_demod(unsigned char *buf, uint32_t len, struct fm_state *fm)
|
|||||||
{
|
{
|
||||||
int sr, freq_next;
|
int sr, freq_next;
|
||||||
rotate_90(buf, len);
|
rotate_90(buf, len);
|
||||||
|
if (fm->fir_enable) {
|
||||||
|
low_pass_fir(fm, buf, len);
|
||||||
|
} else {
|
||||||
low_pass(fm, buf, len);
|
low_pass(fm, buf, len);
|
||||||
|
}
|
||||||
fm_demod(fm);
|
fm_demod(fm);
|
||||||
sr = post_squelch(fm);
|
sr = post_squelch(fm);
|
||||||
|
if (fm->post_downsample > 1) {
|
||||||
|
fm->signal_len = low_pass_simple(fm->signal2, fm->signal_len, fm->post_downsample);}
|
||||||
/* ignore under runs for now */
|
/* ignore under runs for now */
|
||||||
fwrite(fm->signal2, 2, fm->signal_len/2, fm->file);
|
fwrite(fm->signal2, 2, fm->signal_len/2, fm->file);
|
||||||
if (fm->freq_len > 1 && !sr && fm->squelch_hits > CONSEQ_SQUELCH) {
|
if (fm->freq_len > 1 && !sr && fm->squelch_hits > CONSEQ_SQUELCH) {
|
||||||
@ -362,9 +455,13 @@ int main(int argc, char **argv)
|
|||||||
fm.squelch_level = 150;
|
fm.squelch_level = 150;
|
||||||
fm.freq_len = 0;
|
fm.freq_len = 0;
|
||||||
fm.edge = 0;
|
fm.edge = 0;
|
||||||
|
fm.fir_enable = 0;
|
||||||
|
fm.prev_index = -1;
|
||||||
|
fm.post_downsample = 1;
|
||||||
|
fm.custom_atan = 0;
|
||||||
sem_init(&data_ready, 0, 0);
|
sem_init(&data_ready, 0, 0);
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
while ((opt = getopt(argc, argv, "d:f:g:s:b:l:E::")) != -1) {
|
while ((opt = getopt(argc, argv, "d:f:g:s:b:l:o:EFA")) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'd':
|
case 'd':
|
||||||
dev_index = atoi(optarg);
|
dev_index = atoi(optarg);
|
||||||
@ -382,14 +479,25 @@ int main(int argc, char **argv)
|
|||||||
case 's':
|
case 's':
|
||||||
fm.sample_rate = (uint32_t)atof(optarg);
|
fm.sample_rate = (uint32_t)atof(optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'o':
|
||||||
|
fm.post_downsample = (int)atof(optarg);
|
||||||
|
break;
|
||||||
case 'E':
|
case 'E':
|
||||||
fm.edge = 1;
|
fm.edge = 1;
|
||||||
break;
|
break;
|
||||||
|
case 'F':
|
||||||
|
fm.fir_enable = 1;
|
||||||
|
break;
|
||||||
|
case 'A':
|
||||||
|
fm.custom_atan = 1;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
usage();
|
usage();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* double sample_rate to limit to Δθ to ±π */
|
||||||
|
fm.sample_rate *= fm.post_downsample;
|
||||||
|
|
||||||
if (argc <= optind) {
|
if (argc <= optind) {
|
||||||
usage();
|
usage();
|
||||||
@ -443,6 +551,7 @@ int main(int argc, char **argv)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
optimal_settings(&fm, 0, 0);
|
optimal_settings(&fm, 0, 0);
|
||||||
|
build_fir(&fm);
|
||||||
|
|
||||||
/* Set the tuner gain */
|
/* Set the tuner gain */
|
||||||
r = rtlsdr_set_tuner_gain(dev, gain);
|
r = rtlsdr_set_tuner_gain(dev, gain);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user