Implement command-line option for LNA gain.
This commit is contained in:
parent
5744e0affd
commit
c2de34860a
55
NOTES.txt
55
NOTES.txt
|
@ -160,18 +160,6 @@ radio4 2 MS/s 34 dB default ON 80 kHz 0.36 CLIP -38.0 dB/Hz
|
||||||
Conclusion: AGC mode has little effect on quality.
|
Conclusion: AGC mode has little effect on quality.
|
||||||
|
|
||||||
|
|
||||||
Local radio stations
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
radio2 92600000 (good)
|
|
||||||
radio3 96800000 (good)
|
|
||||||
radio4 94300000 (bad)
|
|
||||||
qmusic 100700000 (medium)
|
|
||||||
radio538 102100000 (medium)
|
|
||||||
radio10 103800000 (bad)
|
|
||||||
radio west 89300000 (medium)
|
|
||||||
|
|
||||||
|
|
||||||
Stereo pilot frequency
|
Stereo pilot frequency
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
@ -183,4 +171,47 @@ radio538 19 kHz pilot = 18999.78 Hz +- 0.04 Hz (6 hours measurement)
|
||||||
|
|
||||||
Conclusion: stereo pilot is not a reliable time source.
|
Conclusion: stereo pilot is not a reliable time source.
|
||||||
|
|
||||||
|
|
||||||
|
Ferrites
|
||||||
|
--------
|
||||||
|
|
||||||
|
Claming a ferrite block on the USB cable, as close as possible to the DVB
|
||||||
|
received, reduces disturbance peaks in the spectrum by ~ 6 dB.
|
||||||
|
A second ferrite block at the PC side gives another small improvement.
|
||||||
|
|
||||||
|
The ferrite causes a clearly audible improvement of weak stations.
|
||||||
|
|
||||||
|
|
||||||
|
DIY antenna
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Constructed antenna from a vertical telescopic rod antenna (85 cm)
|
||||||
|
and two steel wire ground radials ~ 85 cm.
|
||||||
|
Antenna placed indoors close to window.
|
||||||
|
Antenna connected to DVB receiver via 1.5 meter 75 Ohm coax cable.
|
||||||
|
|
||||||
|
The ground radials are floppy and look ridiculous but they are essential.
|
||||||
|
No ground radials, no reception.
|
||||||
|
|
||||||
|
The DIY antenna has ~ 20 dB more signal than the basic DVB antenna.
|
||||||
|
|
||||||
|
The DIY antenna causes notable improvement of weak stations.
|
||||||
|
Radio4 reception improves from very bad to almost good.
|
||||||
|
|
||||||
|
However, strong stations (radio3) sound slightly worse with the DIY antenna
|
||||||
|
than with the basic DVB antenna.
|
||||||
|
May be caused by clipping due to too strong signal from antenna.
|
||||||
|
|
||||||
|
|
||||||
|
Local radio stations
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
radio2 92600000 (good)
|
||||||
|
radio3 96800000 (good)
|
||||||
|
radio4 94300000 (bad)
|
||||||
|
qmusic 100700000 (medium)
|
||||||
|
radio538 102100000 (medium)
|
||||||
|
radio10 103800000 (bad)
|
||||||
|
radio west 89300000 (medium)
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <rtl-sdr.h>
|
#include <rtl-sdr.h>
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ bool RtlSdrSource::configure(uint32_t sample_rate,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tuner_gain < 0) {
|
if (tuner_gain == INT_MIN) {
|
||||||
r = rtlsdr_set_tuner_gain_mode(m_dev, 0);
|
r = rtlsdr_set_tuner_gain_mode(m_dev, 0);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
m_error = "rtlsdr_set_tuner_gain_mode could not set automatic gain";
|
m_error = "rtlsdr_set_tuner_gain_mode could not set automatic gain";
|
||||||
|
@ -116,31 +117,25 @@ uint32_t RtlSdrSource::get_frequency()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Return current tuner gain in dB.
|
// Return current tuner gain in units of 0.1 dB.
|
||||||
double RtlSdrSource::get_tuner_gain()
|
int RtlSdrSource::get_tuner_gain()
|
||||||
{
|
{
|
||||||
return 0.1 * rtlsdr_get_tuner_gain(m_dev);
|
return rtlsdr_get_tuner_gain(m_dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Return a list of supported tuner gain settings in dB.
|
// Return a list of supported tuner gain settings in units of 0.1 dB.
|
||||||
vector<double> RtlSdrSource::get_tuner_gains()
|
vector<int> RtlSdrSource::get_tuner_gains()
|
||||||
{
|
{
|
||||||
vector<double> result;
|
|
||||||
|
|
||||||
int num_gains = rtlsdr_get_tuner_gains(m_dev, NULL);
|
int num_gains = rtlsdr_get_tuner_gains(m_dev, NULL);
|
||||||
if (num_gains <= 0)
|
if (num_gains <= 0)
|
||||||
return result;
|
return vector<int>();
|
||||||
|
|
||||||
int gains[num_gains];
|
vector<int> gains(num_gains);
|
||||||
if (rtlsdr_get_tuner_gains(m_dev, gains) != num_gains)
|
if (rtlsdr_get_tuner_gains(m_dev, gains.data()) != num_gains)
|
||||||
return result;
|
return vector<int>();
|
||||||
|
|
||||||
result.reserve(num_gains);
|
return gains;
|
||||||
for (int i = 0; i < num_gains; i++)
|
|
||||||
result.push_back(0.1 * gains[i]);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ public:
|
||||||
*
|
*
|
||||||
* sample_rate :: desired sample rate in Hz.
|
* sample_rate :: desired sample rate in Hz.
|
||||||
* frequency :: desired center frequency in Hz.
|
* frequency :: desired center frequency in Hz.
|
||||||
* tuner_gain :: desired tuner gain in 0.1 dB, or -1 for auto-gain.
|
* tuner_gain :: desired tuner gain in 0.1 dB, or INT_MIN for auto-gain.
|
||||||
* block_length :: preferred number of samples per block.
|
* block_length :: preferred number of samples per block.
|
||||||
*
|
*
|
||||||
* Return true for success, false if an error occurred.
|
* Return true for success, false if an error occurred.
|
||||||
|
@ -42,11 +42,11 @@ public:
|
||||||
/** Return current center frequency in Hz. */
|
/** Return current center frequency in Hz. */
|
||||||
std::uint32_t get_frequency();
|
std::uint32_t get_frequency();
|
||||||
|
|
||||||
/** Return current tuner gain in dB. */
|
/** Return current tuner gain in units of 0.1 dB. */
|
||||||
double get_tuner_gain();
|
int get_tuner_gain();
|
||||||
|
|
||||||
/** Return a list of supported tuner gain settings in dB. */
|
/** Return a list of supported tuner gain settings in units of 0.1 dB. */
|
||||||
std::vector<double> get_tuner_gains();
|
std::vector<int> get_tuner_gains();
|
||||||
|
|
||||||
/** Return name of opened RTL-SDR device. */
|
/** Return name of opened RTL-SDR device. */
|
||||||
std::string get_device_name() const
|
std::string get_device_name() const
|
||||||
|
|
4
TODO.txt
4
TODO.txt
|
@ -1,6 +1,8 @@
|
||||||
|
* (quality) solve issues with bad sound due to strong antenna signal
|
||||||
|
(experiment with adaptive LNA gain or adaptive IF gain)
|
||||||
* (speedup) maybe replace high-order FIR downsampling filter with 2nd order butterworth followed by lower order FIR filter
|
* (speedup) maybe replace high-order FIR downsampling filter with 2nd order butterworth followed by lower order FIR filter
|
||||||
* figure out why we sometimes lose stereo lock
|
|
||||||
* (feature) implement RDS decoding
|
* (feature) implement RDS decoding
|
||||||
* (quality) consider FM demodulation with PLL instead of phase discriminator
|
* (quality) consider FM demodulation with PLL instead of phase discriminator
|
||||||
|
* (quality) consider pulse-counting discriminator
|
||||||
* (speedup) consider downsampling of IF signal before FM detection
|
* (speedup) consider downsampling of IF signal before FM detection
|
||||||
(work in progress in branch "filterif")
|
(work in progress in branch "filterif")
|
||||||
|
|
69
main.cc
69
main.cc
|
@ -23,6 +23,7 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -216,6 +217,7 @@ void usage()
|
||||||
"Usage: softfm -f freq [options]\n"
|
"Usage: softfm -f freq [options]\n"
|
||||||
" -f freq Frequency of radio station in Hz\n"
|
" -f freq Frequency of radio station in Hz\n"
|
||||||
" -d devidx RTL-SDR device index, 'list' to show device list (default 0)\n"
|
" -d devidx RTL-SDR device index, 'list' to show device list (default 0)\n"
|
||||||
|
" -g gain Set LNA gain in dB, or 'auto' (default auto)\n"
|
||||||
" -s ifrate IF sample rate in Hz (default 1000000, min 900001)\n"
|
" -s ifrate IF sample rate in Hz (default 1000000, min 900001)\n"
|
||||||
" -r pcmrate Audio sample rate in Hz (default 48000 Hz)\n"
|
" -r pcmrate Audio sample rate in Hz (default 48000 Hz)\n"
|
||||||
" -a 0 Disable RTL AGC mode (default 1 = enabled)\n"
|
" -a 0 Disable RTL AGC mode (default 1 = enabled)\n"
|
||||||
|
@ -290,6 +292,7 @@ int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
double freq = -1;
|
double freq = -1;
|
||||||
int devidx = 0;
|
int devidx = 0;
|
||||||
|
int lnagain = INT_MIN;
|
||||||
double ifrate = 1.0e6;
|
double ifrate = 1.0e6;
|
||||||
int pcmrate = 48000;
|
int pcmrate = 48000;
|
||||||
bool stereo = true;
|
bool stereo = true;
|
||||||
|
@ -308,6 +311,7 @@ int main(int argc, char **argv)
|
||||||
const struct option longopts[] = {
|
const struct option longopts[] = {
|
||||||
{ "freq", 1, NULL, 'f' },
|
{ "freq", 1, NULL, 'f' },
|
||||||
{ "dev", 1, NULL, 'd' },
|
{ "dev", 1, NULL, 'd' },
|
||||||
|
{ "gain", 1, NULL, 'g' },
|
||||||
{ "ifrate", 1, NULL, 's' },
|
{ "ifrate", 1, NULL, 's' },
|
||||||
{ "pcmrate", 1, NULL, 'r' },
|
{ "pcmrate", 1, NULL, 'r' },
|
||||||
{ "agc", 1, NULL, 'a' },
|
{ "agc", 1, NULL, 'a' },
|
||||||
|
@ -321,7 +325,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
int c, longindex;
|
int c, longindex;
|
||||||
while ((c = getopt_long(argc, argv,
|
while ((c = getopt_long(argc, argv,
|
||||||
"f:d:s:r:MR:W:P::T:b:a:",
|
"f:d:g:s:r:MR:W:P::T:b:a:",
|
||||||
longopts, &longindex)) >= 0) {
|
longopts, &longindex)) >= 0) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'f':
|
case 'f':
|
||||||
|
@ -333,6 +337,23 @@ int main(int argc, char **argv)
|
||||||
if (!parse_int(optarg, devidx))
|
if (!parse_int(optarg, devidx))
|
||||||
devidx = -1;
|
devidx = -1;
|
||||||
break;
|
break;
|
||||||
|
case 'g':
|
||||||
|
if (strcasecmp(optarg, "auto") == 0) {
|
||||||
|
lnagain = INT_MIN;
|
||||||
|
} else if (strcasecmp(optarg, "list") == 0) {
|
||||||
|
lnagain = INT_MIN + 1;
|
||||||
|
} else {
|
||||||
|
double tmpgain;
|
||||||
|
if (!parse_dbl(optarg, tmpgain)) {
|
||||||
|
badarg("-g");
|
||||||
|
}
|
||||||
|
long int tmpgain2 = lrint(tmpgain * 10);
|
||||||
|
if (tmpgain2 <= INT_MIN || tmpgain2 >= INT_MAX) {
|
||||||
|
badarg("-g");
|
||||||
|
}
|
||||||
|
lnagain = tmpgain2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
// NOTE: RTL does not suppor sample rate 900 kS/s or lower
|
// NOTE: RTL does not suppor sample rate 900 kS/s or lower
|
||||||
if (!parse_dbl(optarg, ifrate) || ifrate <= 900000) {
|
if (!parse_dbl(optarg, ifrate) || ifrate <= 900000) {
|
||||||
|
@ -388,6 +409,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
vector<string> devnames = RtlSdrSource::get_device_names();
|
vector<string> devnames = RtlSdrSource::get_device_names();
|
||||||
if (devidx < 0 || (unsigned int)devidx >= devnames.size()) {
|
if (devidx < 0 || (unsigned int)devidx >= devnames.size()) {
|
||||||
|
if (devidx != -1)
|
||||||
fprintf(stderr, "ERROR: invalid device index %d\n", devidx);
|
fprintf(stderr, "ERROR: invalid device index %d\n", devidx);
|
||||||
fprintf(stderr, "Found %u devices:\n", (unsigned int)devnames.size());
|
fprintf(stderr, "Found %u devices:\n", (unsigned int)devnames.size());
|
||||||
for (unsigned int i = 0; i < devnames.size(); i++) {
|
for (unsigned int i = 0; i < devnames.size(); i++) {
|
||||||
|
@ -427,8 +449,22 @@ int main(int argc, char **argv)
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check LNA gain.
|
||||||
|
if (lnagain != INT_MIN) {
|
||||||
|
vector<int> gains = rtlsdr.get_tuner_gains();
|
||||||
|
if (find(gains.begin(), gains.end(), lnagain) == gains.end()) {
|
||||||
|
if (lnagain != INT_MIN + 1)
|
||||||
|
fprintf(stderr, "ERROR: LNA gain %.1f dB not supported by tuner\n", lnagain * 0.1);
|
||||||
|
fprintf(stderr, "Supported LNA gains: ");
|
||||||
|
for (int g: gains)
|
||||||
|
fprintf(stderr, " %.1f dB ", 0.1 * g);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Configure RTL-SDR device and start streaming.
|
// Configure RTL-SDR device and start streaming.
|
||||||
rtlsdr.configure(ifrate, tuner_freq, -1,
|
rtlsdr.configure(ifrate, tuner_freq, lnagain,
|
||||||
RtlSdrSource::default_block_length, agcmode);
|
RtlSdrSource::default_block_length, agcmode);
|
||||||
if (!rtlsdr) {
|
if (!rtlsdr) {
|
||||||
fprintf(stderr, "ERROR: RtlSdr: %s\n", rtlsdr.error().c_str());
|
fprintf(stderr, "ERROR: RtlSdr: %s\n", rtlsdr.error().c_str());
|
||||||
|
@ -436,12 +472,19 @@ int main(int argc, char **argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
tuner_freq = rtlsdr.get_frequency();
|
tuner_freq = rtlsdr.get_frequency();
|
||||||
fprintf(stderr, "device tuned for %.6f MHz\n", tuner_freq * 1.0e-6);
|
fprintf(stderr, "device tuned for: %.6f MHz\n", tuner_freq * 1.0e-6);
|
||||||
|
|
||||||
|
if (lnagain == INT_MIN)
|
||||||
|
fprintf(stderr, "LNA gain: auto\n");
|
||||||
|
else
|
||||||
|
fprintf(stderr, "LNA gain: %.1f dB\n",
|
||||||
|
0.1 * rtlsdr.get_tuner_gain());
|
||||||
|
|
||||||
ifrate = rtlsdr.get_sample_rate();
|
ifrate = rtlsdr.get_sample_rate();
|
||||||
fprintf(stderr, "IF sample rate %.0f Hz\n", ifrate);
|
fprintf(stderr, "IF sample rate: %.0f Hz\n", ifrate);
|
||||||
|
|
||||||
fprintf(stderr, "RTL AGC mode %s\n", agcmode ? "enabled" : "disabled");
|
fprintf(stderr, "RTL AGC mode: %s\n",
|
||||||
|
agcmode ? "enabled" : "disabled");
|
||||||
|
|
||||||
// Create source data queue.
|
// Create source data queue.
|
||||||
DataBuffer<IQSample> source_buffer;
|
DataBuffer<IQSample> source_buffer;
|
||||||
|
@ -458,8 +501,8 @@ int main(int argc, char **argv)
|
||||||
// Prevent aliasing at very low output sample rates.
|
// Prevent aliasing at very low output sample rates.
|
||||||
double bandwidth_pcm = min(FmDecoder::default_bandwidth_pcm,
|
double bandwidth_pcm = min(FmDecoder::default_bandwidth_pcm,
|
||||||
0.45 * pcmrate);
|
0.45 * pcmrate);
|
||||||
fprintf(stderr, "audio sample rate %u Hz\n", pcmrate);
|
fprintf(stderr, "audio sample rate: %u Hz\n", pcmrate);
|
||||||
fprintf(stderr, "audio bandwidth %.3f kHz\n", bandwidth_pcm * 1.0e-3);
|
fprintf(stderr, "audio bandwidth: %.3f kHz\n", bandwidth_pcm * 1.0e-3);
|
||||||
|
|
||||||
// Prepare decoder.
|
// Prepare decoder.
|
||||||
FmDecoder fm(ifrate, // sample_rate_if
|
FmDecoder fm(ifrate, // sample_rate_if
|
||||||
|
@ -483,17 +526,17 @@ int main(int argc, char **argv)
|
||||||
outputbuf_samples = (unsigned int)(bufsecs * pcmrate);
|
outputbuf_samples = (unsigned int)(bufsecs * pcmrate);
|
||||||
}
|
}
|
||||||
if (outputbuf_samples > 0) {
|
if (outputbuf_samples > 0) {
|
||||||
fprintf(stderr, "output buffer %.1f seconds\n",
|
fprintf(stderr, "output buffer: %.1f seconds\n",
|
||||||
outputbuf_samples / double(pcmrate));
|
outputbuf_samples / double(pcmrate));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open PPS file.
|
// Open PPS file.
|
||||||
if (!ppsfilename.empty()) {
|
if (!ppsfilename.empty()) {
|
||||||
if (ppsfilename == "-") {
|
if (ppsfilename == "-") {
|
||||||
fprintf(stderr, "Writing pulse-per-second markers to stdout\n");
|
fprintf(stderr, "writing pulse-per-second markers to stdout\n");
|
||||||
ppsfile = stdout;
|
ppsfile = stdout;
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Writing pulse-per-second markers to '%s'\n",
|
fprintf(stderr, "writing pulse-per-second markers to '%s'\n",
|
||||||
ppsfilename.c_str());
|
ppsfilename.c_str());
|
||||||
ppsfile = fopen(ppsfilename.c_str(), "w");
|
ppsfile = fopen(ppsfilename.c_str(), "w");
|
||||||
if (ppsfile == NULL) {
|
if (ppsfile == NULL) {
|
||||||
|
@ -510,17 +553,17 @@ int main(int argc, char **argv)
|
||||||
unique_ptr<AudioOutput> audio_output;
|
unique_ptr<AudioOutput> audio_output;
|
||||||
switch (outmode) {
|
switch (outmode) {
|
||||||
case MODE_RAW:
|
case MODE_RAW:
|
||||||
fprintf(stderr, "Writing raw 16-bit audio samples to '%s'\n",
|
fprintf(stderr, "writing raw 16-bit audio samples to '%s'\n",
|
||||||
filename.c_str());
|
filename.c_str());
|
||||||
audio_output.reset(new RawAudioOutput(filename));
|
audio_output.reset(new RawAudioOutput(filename));
|
||||||
break;
|
break;
|
||||||
case MODE_WAV:
|
case MODE_WAV:
|
||||||
fprintf(stderr, "Writing audio samples to '%s'\n",
|
fprintf(stderr, "writing audio samples to '%s'\n",
|
||||||
filename.c_str());
|
filename.c_str());
|
||||||
audio_output.reset(new WavAudioOutput(filename, pcmrate, stereo));
|
audio_output.reset(new WavAudioOutput(filename, pcmrate, stereo));
|
||||||
break;
|
break;
|
||||||
case MODE_ALSA:
|
case MODE_ALSA:
|
||||||
fprintf(stderr, "Playing audio to ALSA device '%s'\n",
|
fprintf(stderr, "playing audio to ALSA device '%s'\n",
|
||||||
alsadev.c_str());
|
alsadev.c_str());
|
||||||
audio_output.reset(new AlsaAudioOutput(alsadev, pcmrate, stereo));
|
audio_output.reset(new AlsaAudioOutput(alsadev, pcmrate, stereo));
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Reference in New Issue