├── .gitignore
├── TODO.txt
├── include
├── util.h
├── SoftFM.h
├── fastatan2.h
├── MovingAverage.h
├── parsekv.h
├── Source.h
├── DataBuffer.h
├── RtlSdrSource.h
├── HackRFSource.h
├── AirspySource.h
├── BladeRFSource.h
├── AudioOutput.h
├── Filter.h
└── FmDecode.h
├── CMakeLists.txt
├── NOTES.txt
├── sfmbase
├── AudioOutput.cpp
├── RtlSdrSource.cpp
├── BladeRFSource.cpp
├── FmDecode.cpp
├── Filter.cpp
├── HackRFSource.cpp
└── AirspySource.cpp
├── README.md
└── main.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | .cproject
2 | .project
3 | .settings/
4 | build/
5 |
--------------------------------------------------------------------------------
/TODO.txt:
--------------------------------------------------------------------------------
1 | * (speedup) maybe replace high-order FIR downsampling filter with 2nd order butterworth followed by lower order FIR filter
2 | * (feature) implement RDS decoding
3 |
--------------------------------------------------------------------------------
/include/util.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_UTIL_H_
20 | #define INCLUDE_UTIL_H_
21 |
22 | inline bool parse_dbl(const char *s, double& v)
23 | {
24 | char *endp;
25 |
26 | v = strtod(s, &endp);
27 |
28 | if (endp == s)
29 | {
30 | return false;
31 | }
32 |
33 | if (*endp == 'k')
34 | {
35 | v *= 1.0e3;
36 | endp++;
37 | }
38 | else if (*endp == 'M')
39 | {
40 | v *= 1.0e6;
41 | endp++;
42 | }
43 | else if (*endp == 'G')
44 | {
45 | v *= 1.0e9;
46 | endp++;
47 | }
48 |
49 | return (*endp == '\0');
50 | }
51 |
52 | #endif /* INCLUDE_UTIL_H_ */
53 |
--------------------------------------------------------------------------------
/include/SoftFM.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_H
20 | #define SOFTFM_H
21 |
22 | #include
23 | #include
24 |
25 | typedef std::complex IQSample;
26 | typedef std::vector IQSampleVector;
27 |
28 | typedef double Sample;
29 | typedef std::vector SampleVector;
30 |
31 |
32 | /** Compute mean and RMS over a sample vector. */
33 | inline void samples_mean_rms(const SampleVector& samples,
34 | double& mean, double& rms)
35 | {
36 | Sample vsum = 0;
37 | Sample vsumsq = 0;
38 |
39 | unsigned int n = samples.size();
40 | for (unsigned int i = 0; i < n; i++) {
41 | Sample v = samples[i];
42 | vsum += v;
43 | vsumsq += v * v;
44 | }
45 |
46 | mean = vsum / n;
47 | rms = sqrt(vsumsq / n);
48 | }
49 |
50 | #endif
51 |
--------------------------------------------------------------------------------
/include/fastatan2.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_FASTATAN2_H_
20 | #define INCLUDE_FASTATAN2_H_
21 |
22 | #include
23 |
24 | // Fast arctan2
25 |
26 | inline float fastatan2(float y, float x)
27 | {
28 | if ( x == 0.0f )
29 | {
30 | if ( y > 0.0f )
31 | {
32 | return M_PI/2.0f;
33 | }
34 |
35 | if ( y == 0.0f ) {
36 | return 0.0f;
37 | }
38 |
39 | return -M_PI/2.0f;
40 | }
41 |
42 | float atan;
43 | float z = y/x;
44 |
45 | if ( fabs( z ) < 1.0f )
46 | {
47 | atan = z/(1.0f + 0.277778f*z*z);
48 |
49 | if ( x < 0.0f )
50 | {
51 | if ( y < 0.0f )
52 | {
53 | return atan - M_PI;
54 | }
55 |
56 | return atan + M_PI;
57 | }
58 | }
59 | else
60 | {
61 | atan = (M_PI/2.0f) - z/(z*z + 0.277778f);
62 |
63 | if ( y < 0.0f )
64 | {
65 | return atan - M_PI;
66 | }
67 | }
68 |
69 | return atan;
70 | }
71 |
72 | #endif /* INCLUDE_FASTATAN2_H_ */
73 |
--------------------------------------------------------------------------------
/include/MovingAverage.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_MOVINGAVERAGE_H
20 | #define INCLUDE_MOVINGAVERAGE_H
21 |
22 | #include
23 | #include
24 |
25 | template class MovingAverage {
26 | public:
27 | MovingAverage() :
28 | m_history(),
29 | m_sum(0),
30 | m_ptr(0)
31 | {
32 | }
33 |
34 | MovingAverage(int historySize, Type initial) :
35 | m_history(historySize, initial),
36 | m_sum((float) historySize * initial),
37 | m_ptr(0)
38 | {
39 | }
40 |
41 | void resize(int historySize, Type initial)
42 | {
43 | m_history.resize(historySize);
44 | for(size_t i = 0; i < m_history.size(); i++)
45 | m_history[i] = initial;
46 | m_sum = (float) m_history.size() * initial;
47 | m_ptr = 0;
48 | }
49 |
50 | void feed(Type value)
51 | {
52 | m_sum -= m_history[m_ptr];
53 | m_history[m_ptr] = value;
54 | m_sum += value;
55 | m_ptr++;
56 | if(m_ptr >= m_history.size())
57 | m_ptr = 0;
58 | }
59 |
60 | void fill(Type value)
61 | {
62 | for(size_t i = 0; i < m_history.size(); i++)
63 | m_history[i] = value;
64 | m_sum = (float) m_history.size() * value;
65 | }
66 |
67 | Type average() const
68 | {
69 | return m_sum / (float) m_history.size();
70 | }
71 |
72 | Type sum() const
73 | {
74 | return m_sum;
75 | }
76 |
77 | protected:
78 | std::vector m_history;
79 | Type m_sum;
80 | uint m_ptr;
81 | };
82 |
83 | #endif // INCLUDE_MOVINGAVERAGE_H
84 |
--------------------------------------------------------------------------------
/include/parsekv.h:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | SoftFM - Software decoder for FM broadcast radio with stereo support
3 |
4 | Key-Value parser (http://www.boost.org/doc/libs/1_47_0/libs/spirit/example/qi/key_value_sequence.cpp)
5 |
6 | Usage:
7 |
8 | int main()
9 | {
10 | namespace qi = boost::spirit::qi;
11 |
12 | std::string input("key1=value1,key2,key3=value3");
13 | std::string::iterator begin = input.begin();
14 | std::string::iterator end = input.end();
15 |
16 | parsekv::key_value_sequence p;
17 | parsekv::pairs_type m;
18 |
19 | if (!qi::parse(begin, end, p, m))
20 | {
21 | std::cout << "Parsing failed\n";
22 | }
23 | else
24 | {
25 | std::cout << "Parsing succeeded, found entries:\n";
26 | parsekv::pairs_type::iterator end = m.end();
27 | for (parsekv::pairs_type::iterator it = m.begin(); it != end; ++it)
28 | {
29 | std::cout << (*it).first;
30 | if (!(*it).second.empty())
31 | std::cout << "=" << (*it).second;
32 | std::cout << std::endl;
33 | }
34 | }
35 | return 0;
36 | }
37 |
38 | ------------------------------------------------------------------------------
39 | Copyright (C) 2015 Edouard Griffiths, F4EXB
40 |
41 | This program is free software; you can redistribute it and/or modify
42 | it under the terms of the GNU General Public License as published by
43 | the Free Software Foundation as version 3 of the License, or
44 |
45 | This program is distributed in the hope that it will be useful,
46 | but WITHOUT ANY WARRANTY; without even the implied warranty of
47 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48 | GNU General Public License V3 for more details.
49 |
50 | You should have received a copy of the GNU General Public License
51 | along with this program. If not, see .
52 |
53 | ******************************************************************************/
54 |
55 | #ifndef INCLUDE_PARSEKV_H_
56 | #define INCLUDE_PARSEKV_H_
57 |
58 | #include
59 | #include
60 | #include
61 | #include
62 |
63 | namespace parsekv
64 | {
65 | namespace qi = boost::spirit::qi;
66 |
67 | typedef std::map pairs_type;
68 |
69 | template
70 | struct key_value_sequence
71 | : qi::grammar
72 | {
73 | key_value_sequence()
74 | : key_value_sequence::base_type(query)
75 | {
76 | query = pair >> *((qi::lit(',') | '&') >> pair);
77 | pair = key >> -('=' >> value);
78 | key = qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9");
79 | value = +qi::char_("a-zA-Z_0-9.");
80 | }
81 |
82 | qi::rule query;
83 | qi::rule()> pair;
84 | qi::rule key, value;
85 | };
86 | }
87 |
88 |
89 |
90 | #endif /* INCLUDE_PARSEKV_H_ */
91 |
--------------------------------------------------------------------------------
/include/Source.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_SOURCE_H_
20 | #define INCLUDE_SOURCE_H_
21 |
22 | #include
23 | #include
24 | #include
25 |
26 | #include "SoftFM.h"
27 | #include "DataBuffer.h"
28 |
29 | class Source
30 | {
31 | public:
32 | Source() : m_confFreq(0), m_buf(0) {}
33 | virtual ~Source() {}
34 |
35 | /**
36 | * Configure device and prepare for streaming.
37 | */
38 | virtual bool configure(std::string configuration) = 0;
39 |
40 | /** Return current sample frequency in Hz. */
41 | virtual std::uint32_t get_sample_rate() = 0;
42 |
43 | /** Return device current center frequency in Hz. */
44 | virtual std::uint32_t get_frequency() = 0;
45 |
46 | /** Return current configured center frequency in Hz. */
47 | std::uint32_t get_configured_frequency() const
48 | {
49 | return m_confFreq;
50 | }
51 |
52 | /** Print current parameters specific to device type */
53 | virtual void print_specific_parms() = 0;
54 |
55 | /** start device before sampling loop.
56 | * Give it a reference to the buffer of samples */
57 | virtual bool start(DataBuffer *buf, std::atomic_bool *stop_flag) = 0;
58 |
59 | /** stop device after sampling loop */
60 | virtual bool stop() = 0;
61 |
62 | /** Return true if the device is OK, return false if there is an error. */
63 | virtual operator bool() const = 0;
64 |
65 | /** Return name of opened RTL-SDR device. */
66 | std::string get_device_name() const
67 | {
68 | return m_devname;
69 | }
70 |
71 | /** Return the last error, or return an empty string if there is no error. */
72 | std::string error()
73 | {
74 | std::string ret(m_error);
75 | m_error.clear();
76 | return ret;
77 | }
78 |
79 | protected:
80 | std::string m_devname;
81 | std::string m_error;
82 | uint32_t m_confFreq;
83 | DataBuffer *m_buf;
84 | std::atomic_bool *m_stop_flag;
85 | };
86 |
87 | #endif /* INCLUDE_SOURCE_H_ */
88 |
--------------------------------------------------------------------------------
/include/DataBuffer.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef _INCLUDE_DATABUFFER_H_
20 | #define _INCLUDE_DATABUFFER_H_
21 |
22 | #include
23 | #include
24 | #include
25 |
26 |
27 | /** Buffer to move sample data between threads. */
28 | template
29 | class DataBuffer
30 | {
31 | public:
32 | /** Constructor. */
33 | DataBuffer()
34 | : m_qlen(0)
35 | , m_end_marked(false)
36 | { }
37 |
38 | /** Add samples to the queue. */
39 | void push(std::vector&& samples)
40 | {
41 | if (!samples.empty()) {
42 | std::unique_lock lock(m_mutex);
43 | m_qlen += samples.size();
44 | m_queue.push(move(samples));
45 | lock.unlock();
46 | m_cond.notify_all();
47 | }
48 | }
49 |
50 | /** Mark the end of the data stream. */
51 | void push_end()
52 | {
53 | std::unique_lock lock(m_mutex);
54 | m_end_marked = true;
55 | lock.unlock();
56 | m_cond.notify_all();
57 | }
58 |
59 | /** Return number of samples in queue. */
60 | std::size_t queued_samples()
61 | {
62 | std::unique_lock lock(m_mutex);
63 | return m_qlen;
64 | }
65 |
66 | /**
67 | * If the queue is non-empty, remove a block from the queue and
68 | * return the samples. If the end marker has been reached, return
69 | * an empty vector. If the queue is empty, wait until more data is pushed
70 | * or until the end marker is pushed.
71 | */
72 | std::vector pull()
73 | {
74 | std::vector ret;
75 | std::unique_lock lock(m_mutex);
76 | while (m_queue.empty() && !m_end_marked)
77 | m_cond.wait(lock);
78 | if (!m_queue.empty()) {
79 | m_qlen -= m_queue.front().size();
80 | swap(ret, m_queue.front());
81 | m_queue.pop();
82 | }
83 | return ret;
84 | }
85 |
86 | /** Return true if the end has been reached at the Pull side. */
87 | bool pull_end_reached()
88 | {
89 | std::unique_lock lock(m_mutex);
90 | return m_qlen == 0 && m_end_marked;
91 | }
92 |
93 | /** Wait until the buffer contains minfill samples or an end marker. */
94 | void wait_buffer_fill(std::size_t minfill)
95 | {
96 | std::unique_lock lock(m_mutex);
97 | while (m_qlen < minfill && !m_end_marked)
98 | m_cond.wait(lock);
99 | }
100 |
101 | private:
102 | std::size_t m_qlen;
103 | bool m_end_marked;
104 | std::queue> m_queue;
105 | std::mutex m_mutex;
106 | std::condition_variable m_cond;
107 | };
108 |
109 | #endif
110 |
111 |
--------------------------------------------------------------------------------
/include/RtlSdrSource.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_RTLSDRSOURCE_H
20 | #define SOFTFM_RTLSDRSOURCE_H
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "Source.h"
28 |
29 | class RtlSdrSource : public Source
30 | {
31 | public:
32 |
33 | static const int default_block_length = 65536;
34 |
35 | /** Open RTL-SDR device. */
36 | RtlSdrSource(int dev_index);
37 |
38 | /** Close RTL-SDR device. */
39 | virtual ~RtlSdrSource();
40 |
41 | virtual bool configure(std::string configuration);
42 |
43 | /** Return current sample frequency in Hz. */
44 | virtual std::uint32_t get_sample_rate();
45 |
46 | /** Return device current center frequency in Hz. */
47 | virtual std::uint32_t get_frequency();
48 |
49 | /** Print current parameters specific to device type */
50 | virtual void print_specific_parms();
51 |
52 | virtual bool start(DataBuffer* samples, std::atomic_bool *stop_flag);
53 | virtual bool stop();
54 |
55 | /** Return true if the device is OK, return false if there is an error. */
56 | virtual operator bool() const
57 | {
58 | return m_dev && m_error.empty();
59 | }
60 |
61 | /** Return a list of supported devices. */
62 | static void get_device_names(std::vector& devices);
63 |
64 | private:
65 | /**
66 | * Configure RTL-SDR tuner and prepare for streaming.
67 | *
68 | * sample_rate :: desired sample rate in Hz.
69 | * frequency :: desired center frequency in Hz.
70 | * tuner_gain :: desired tuner gain in 0.1 dB, or INT_MIN for auto-gain.
71 | * block_length :: preferred number of samples per block.
72 | *
73 | * Return true for success, false if an error occurred.
74 | */
75 | bool configure(std::uint32_t sample_rate,
76 | std::uint32_t frequency,
77 | int tuner_gain,
78 | int block_length=default_block_length,
79 | bool agcmode=false);
80 |
81 | /** Return a list of supported tuner gain settings in units of 0.1 dB. */
82 | std::vector get_tuner_gains();
83 |
84 | /** Return current tuner gain in units of 0.1 dB. */
85 | int get_tuner_gain();
86 |
87 | /**
88 | * Fetch a bunch of samples from the device.
89 | *
90 | * This function must be called regularly to maintain streaming.
91 | * Return true for success, false if an error occurred.
92 | */
93 | static bool get_samples(IQSampleVector *samples);
94 |
95 | static void run();
96 |
97 | struct rtlsdr_dev * m_dev;
98 | int m_block_length;
99 | std::vector m_gains;
100 | std::string m_gainsStr;
101 | bool m_confAgc;
102 | std::thread *m_thread;
103 | static RtlSdrSource *m_this;
104 | };
105 |
106 | #endif
107 |
--------------------------------------------------------------------------------
/include/HackRFSource.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_HACKRFSOURCE_H_
20 | #define INCLUDE_HACKRFSOURCE_H_
21 |
22 | #include
23 | #include
24 | #include
25 | #include "libhackrf/hackrf.h"
26 |
27 | #include "Source.h"
28 |
29 | class HackRFSource : public Source
30 | {
31 | public:
32 |
33 | //static const int default_block_length = 65536;
34 |
35 | /** Open HackRF device. */
36 | HackRFSource(int dev_index);
37 |
38 | /** Close HackRF device. */
39 | virtual ~HackRFSource();
40 |
41 | virtual bool configure(std::string configuration);
42 |
43 | /** Return current sample frequency in Hz. */
44 | virtual std::uint32_t get_sample_rate();
45 |
46 | /** Return device current center frequency in Hz. */
47 | virtual std::uint32_t get_frequency();
48 |
49 | /** Print current parameters specific to device type */
50 | virtual void print_specific_parms();
51 |
52 | virtual bool start(DataBuffer *buf, std::atomic_bool *stop_flag);
53 | virtual bool stop();
54 |
55 | /** Return true if the device is OK, return false if there is an error. */
56 | virtual operator bool() const
57 | {
58 | return m_dev && m_error.empty();
59 | }
60 |
61 | /** Return a list of supported devices. */
62 | static void get_device_names(std::vector& devices);
63 |
64 | private:
65 | /**
66 | * Configure HackRF tuner and prepare for streaming.
67 | *
68 | * sample_rate :: desired sample rate in Hz.
69 | * frequency :: desired center frequency in Hz.
70 | * ext_amp :: extra amplifier engaged
71 | * lna_gain :: desired LNA gain: 0, 3 or 6 dB.
72 | * vga1_gain :: desired VGA1 gain:
73 | * vga2_gain :: desired VGA1 gain:
74 | *
75 | * Return true for success, false if an error occurred.
76 | */
77 | bool configure(uint32_t sample_rate,
78 | uint32_t frequency,
79 | bool ext_amp,
80 | bool bias_ant,
81 | int lna_gain,
82 | int vga_gain,
83 | uint32_t bandwidth
84 | );
85 |
86 | void callback(const char* buf, int len);
87 | static int rx_callback(hackrf_transfer* transfer);
88 | static void run(hackrf_device* dev, std::atomic_bool *stop_flag);
89 |
90 | struct hackrf_device* m_dev;
91 | uint32_t m_sampleRate;
92 | uint64_t m_frequency;
93 | int m_lnaGain;
94 | int m_vgaGain;
95 | uint32_t m_bandwidth;
96 | bool m_extAmp;
97 | bool m_biasAnt;
98 | bool m_running;
99 | std::thread *m_thread;
100 | static HackRFSource *m_this;
101 | static const std::vector m_lgains;
102 | static const std::vector m_vgains;
103 | static const std::vector m_bwfilt;
104 | std::string m_lgainsStr;
105 | std::string m_vgainsStr;
106 | std::string m_bwfiltStr;
107 | };
108 |
109 | #endif /* INCLUDE_HACKRFSOURCE_H_ */
110 |
--------------------------------------------------------------------------------
/include/AirspySource.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef INCLUDE_AIRSPYSOURCE_H_
20 | #define INCLUDE_AIRSPYSOURCE_H_
21 |
22 | #include
23 | #include
24 | #include
25 | #include "libairspy/airspy.h"
26 |
27 | #include "Source.h"
28 |
29 | #define AIRSPY_MAX_DEVICE (32)
30 |
31 | class AirspySource : public Source
32 | {
33 | public:
34 |
35 | /** Open Airspy device. */
36 | AirspySource(int dev_index);
37 |
38 | /** Close Airspy device. */
39 | virtual ~AirspySource();
40 |
41 | virtual bool configure(std::string configuration);
42 |
43 | /** Return current sample frequency in Hz. */
44 | virtual std::uint32_t get_sample_rate();
45 |
46 | /** Return device current center frequency in Hz. */
47 | virtual std::uint32_t get_frequency();
48 |
49 | /** Print current parameters specific to device type */
50 | virtual void print_specific_parms();
51 |
52 | virtual bool start(DataBuffer *buf, std::atomic_bool *stop_flag);
53 | virtual bool stop();
54 |
55 | /** Return true if the device is OK, return false if there is an error. */
56 | virtual operator bool() const
57 | {
58 | return m_dev && m_error.empty();
59 | }
60 |
61 | /** Return a list of supported devices. */
62 | static void get_device_names(std::vector& devices);
63 |
64 | private:
65 | /**
66 | * Configure Airspy tuner and prepare for streaming.
67 | *
68 | * sampleRateIndex :: desired sample rate index in the sample rates enumeration list.
69 | * frequency :: desired center frequency in Hz.
70 | * bias_ant :: antenna bias
71 | * lna_gain :: desired LNA gain: 0 to 14 dB.
72 | * mix_gain :: desired mixer gain: 0 to 15 dB.
73 | * vga_gain :: desired VGA gain: 0 to 15 dB
74 | * lna_agc :: LNA AGC
75 | * mix_agc :: Mixer AGC
76 | *
77 | * Return true for success, false if an error occurred.
78 | */
79 | bool configure(int sampleRateIndex,
80 | uint32_t frequency,
81 | bool bias_ant,
82 | int lna_gain,
83 | int mix_gain,
84 | int vga_gain,
85 | bool lna_agc,
86 | bool mix_agc
87 | );
88 |
89 | void callback(const short* buf, int len);
90 | static int rx_callback(airspy_transfer_t* transfer);
91 | static void run(airspy_device* dev, std::atomic_bool *stop_flag);
92 |
93 | struct airspy_device* m_dev;
94 | uint32_t m_sampleRate;
95 | uint32_t m_frequency;
96 | int m_lnaGain;
97 | int m_mixGain;
98 | int m_vgaGain;
99 | bool m_biasAnt;
100 | bool m_lnaAGC;
101 | bool m_mixAGC;
102 | bool m_running;
103 | std::thread *m_thread;
104 | static AirspySource *m_this;
105 | static const std::vector m_lgains;
106 | static const std::vector m_mgains;
107 | static const std::vector m_vgains;
108 | std::vector m_srates;
109 | std::string m_lgainsStr;
110 | std::string m_mgainsStr;
111 | std::string m_vgainsStr;
112 | std::string m_sratesStr;
113 | };
114 |
115 | #endif /* INCLUDE_AIRSPYSOURCE_H_ */
116 |
--------------------------------------------------------------------------------
/include/BladeRFSource.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_BLADERFSOURCE_H
20 | #define SOFTFM_BLADERFSOURCE_H
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include "libbladeRF.h"
27 |
28 | #include "Source.h"
29 |
30 | class BladeRFSource : public Source
31 | {
32 | public:
33 |
34 | /** Open BladeRF device. */
35 | BladeRFSource(const char *serial);
36 |
37 | /** Close BladeRF device. */
38 | virtual ~BladeRFSource();
39 |
40 | virtual bool configure(std::string configuration);
41 |
42 | /** Return current sample frequency in Hz. */
43 | virtual std::uint32_t get_sample_rate();
44 |
45 | /** Return device current center frequency in Hz. */
46 | virtual std::uint32_t get_frequency();
47 |
48 | /** Print current parameters specific to device type */
49 | virtual void print_specific_parms();
50 |
51 | virtual bool start(DataBuffer* samples, std::atomic_bool *stop_flag);
52 | virtual bool stop();
53 |
54 | /** Return true if the device is OK, return false if there is an error. */
55 | virtual operator bool() const
56 | {
57 | return m_dev && m_error.empty();
58 | }
59 |
60 | /** Return a list of supported devices. */
61 | static void get_device_names(std::vector& devices);
62 |
63 | private:
64 | /**
65 | * Configure RTL-SDR tuner and prepare for streaming.
66 | *
67 | * sample_rate :: desired sample rate in Hz.
68 | * frequency :: desired center frequency in Hz.
69 | * bandwidth :: desired filter bandwidth in Hz.
70 | * lna_gain :: desired LNA gain index (1: 0dB, 2: 3dB, 3: 6dB).
71 | * vga1_gain :: desired VGA1 gain.
72 | * vga2_gain :: desired VGA2 gain.
73 | *
74 | * Return true for success, false if an error occurred.
75 | */
76 | bool configure(uint32_t sample_rate,
77 | uint32_t frequency,
78 | uint32_t bandwidth,
79 | int lna_gainIndex,
80 | int vga1_gain,
81 | int vga2_gain);
82 |
83 | /**
84 | * Fetch a bunch of samples from the device.
85 | *
86 | * This function must be called regularly to maintain streaming.
87 | * Return true for success, false if an error occurred.
88 | */
89 | static bool get_samples(IQSampleVector *samples);
90 |
91 | static void run();
92 |
93 | struct bladerf *m_dev;
94 | uint32_t m_sampleRate;
95 | uint32_t m_actualSampleRate;
96 | uint32_t m_frequency;
97 | uint32_t m_minFrequency;
98 | uint32_t m_bandwidth;
99 | uint32_t m_actualBandwidth;
100 | int m_lnaGain;
101 | int m_vga1Gain;
102 | int m_vga2Gain;
103 | std::thread *m_thread;
104 | static const int m_blockSize = 1<<14;
105 | static BladeRFSource *m_this;
106 | static const std::vector m_lnaGains;
107 | static const std::vector m_vga1Gains;
108 | static const std::vector m_vga2Gains;
109 | static const std::vector m_halfbw;
110 | std::string m_lnaGainsStr;
111 | std::string m_vga1GainsStr;
112 | std::string m_vga2GainsStr;
113 | std::string m_bwfiltStr;
114 | };
115 |
116 | #endif // SOFTFM_BLADERFSOURCE_H
117 |
--------------------------------------------------------------------------------
/include/AudioOutput.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_AUDIOOUTPUT_H
20 | #define SOFTFM_AUDIOOUTPUT_H
21 |
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "SoftFM.h"
28 |
29 |
30 | /** Base class for writing audio data to file or playback. */
31 | class AudioOutput
32 | {
33 | public:
34 |
35 | /** Destructor. */
36 | virtual ~AudioOutput() { }
37 |
38 | /**
39 | * Write audio data.
40 | *
41 | * Return true on success.
42 | * Return false if an error occurs.
43 | */
44 | virtual bool write(const SampleVector& samples) = 0;
45 |
46 | /** Return the last error, or return an empty string if there is no error. */
47 | std::string error()
48 | {
49 | std::string ret(m_error);
50 | m_error.clear();
51 | return ret;
52 | }
53 |
54 | /** Return true if the stream is OK, return false if there is an error. */
55 | operator bool() const
56 | {
57 | return (!m_zombie) && m_error.empty();
58 | }
59 |
60 | protected:
61 | /** Constructor. */
62 | AudioOutput() : m_zombie(false) { }
63 |
64 | /** Encode a list of samples as signed 16-bit little-endian integers. */
65 | static void samplesToInt16(const SampleVector& samples,
66 | std::vector& bytes);
67 |
68 | std::string m_error;
69 | bool m_zombie;
70 |
71 | private:
72 | AudioOutput(const AudioOutput&); // no copy constructor
73 | AudioOutput& operator=(const AudioOutput&); // no assignment operator
74 | };
75 |
76 |
77 | /** Write audio data as raw signed 16-bit little-endian data. */
78 | class RawAudioOutput : public AudioOutput
79 | {
80 | public:
81 |
82 | /**
83 | * Construct raw audio writer.
84 | *
85 | * filename :: file name (including path) or "-" to write to stdout
86 | */
87 | RawAudioOutput(const std::string& filename);
88 |
89 | ~RawAudioOutput();
90 | bool write(const SampleVector& samples);
91 |
92 | private:
93 | int m_fd;
94 | std::vector m_bytebuf;
95 | };
96 |
97 |
98 | /** Write audio data as .WAV file. */
99 | class WavAudioOutput : public AudioOutput
100 | {
101 | public:
102 |
103 | /**
104 | * Construct .WAV writer.
105 | *
106 | * filename :: file name (including path) or "-" to write to stdout
107 | * samplerate :: audio sample rate in Hz
108 | * stereo :: true if the output stream contains stereo data
109 | */
110 | WavAudioOutput(const std::string& filename,
111 | unsigned int samplerate,
112 | bool stereo);
113 |
114 | ~WavAudioOutput();
115 | bool write(const SampleVector& samples);
116 |
117 | private:
118 |
119 | /** (Re-)Write .WAV header. */
120 | bool write_header(unsigned int nsamples);
121 |
122 | static void encode_chunk_id(std::uint8_t * ptr, const char * chunkname);
123 |
124 | template
125 | static void set_value(std::uint8_t * ptr, T value);
126 |
127 | const unsigned numberOfChannels;
128 | const unsigned sampleRate;
129 | std::FILE *m_stream;
130 | std::vector m_bytebuf;
131 | };
132 |
133 |
134 | /** Write audio data to ALSA device. */
135 | class AlsaAudioOutput : public AudioOutput
136 | {
137 | public:
138 |
139 | /**
140 | * Construct ALSA output stream.
141 | *
142 | * dename :: ALSA PCM device
143 | * samplerate :: audio sample rate in Hz
144 | * stereo :: true if the output stream contains stereo data
145 | */
146 | AlsaAudioOutput(const std::string& devname,
147 | unsigned int samplerate,
148 | bool stereo);
149 |
150 | ~AlsaAudioOutput();
151 | bool write(const SampleVector& samples);
152 |
153 | private:
154 | unsigned int m_nchannels;
155 | struct _snd_pcm * m_pcm;
156 | std::vector m_bytebuf;
157 | };
158 |
159 | #endif
160 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # CMake definitions for SoftFM
2 |
3 | cmake_minimum_required(VERSION 3.0.2)
4 | project(SoftFM)
5 |
6 | find_package(Threads)
7 | find_package(PkgConfig)
8 | find_package(ALSA REQUIRED)
9 | find_package(Boost 1.47)
10 |
11 | # Find RTL-SDR library.
12 | pkg_check_modules(PKG_RTLSDR librtlsdr)
13 | find_path(RTLSDR_INCLUDE_DIR rtl-sdr.h
14 | HINT ${PKG_RTLSDR_INCLUDE_DIRS})
15 | find_library(RTLSDR_LIBRARY librtlsdr.a
16 | HINT ${PKG_RTLSDR_LIBRARY_DIRS})
17 |
18 | # Find HackRF library.
19 | pkg_check_modules(PKG_HACKRF libhackrf)
20 | find_path(HACKRF_INCLUDE_DIR hackrf.h
21 | HINT ${PKG_HACKRF_INCLUDE_DIRS})
22 | find_library(HACKRF_LIBRARY libhackrf.a
23 | HINT ${PKG_HACKRF_LIBRARY_DIRS})
24 |
25 | # Find Airspy library.
26 | pkg_check_modules(PKG_AIRSPY libairspy)
27 | find_path(AIRSPY_INCLUDE_DIR airspy.h
28 | HINT ${PKG_AIRSPY_INCLUDE_DIRS})
29 | find_library(AIRSPY_LIBRARY libairspy.a
30 | HINT ${PKG_AIRSPY_LIBRARY_DIRS})
31 |
32 | # Find BladeRF library.
33 | pkg_check_modules(PKG_BLADERF libbladerf)
34 | find_path(BLADERF_INCLUDE_DIR libbladeRF.h
35 | HINT ${PKG_BLADERF_INCLUDE_DIRS})
36 | find_library(BLADERF_LIBRARY libbladeRF.so
37 | HINT ${PKG_BLADERF_LIBRARY_DIRS})
38 |
39 | # Find libusb
40 | pkg_check_modules(PKG_LIBUSB libusb-1.0)
41 | find_path(LIBUSB_INCLUDE_DIR libusb.h
42 | HINT ${PKG_LIBUSB_INCLUDE_DIRS}
43 | PATH_SUFFIXES libusb-1.0)
44 | find_library(LIBUSB_LIBRARY usb-1.0
45 | HINT ${PKG_LIBUSB_LIBRARY_DIRS})
46 |
47 | if(RTLSDR_INCLUDE_DIR AND RTLSDR_LIBRARY)
48 | message(STATUS "Found librtlsdr: ${RTLSDR_INCLUDE_DIR}, ${RTLSDR_LIBRARY}")
49 | else()
50 | message(WARNING "Can not find Osmocom RTL-SDR library")
51 | message("Try again with environment variable PKG_CONFIG_PATH")
52 | message("or with -DRTLSDR_INCLUDE_DIR=/path/rtlsdr/include")
53 | message(" -DRTLSDR_LIBRARY=/path/rtlsdr/lib/librtlsdr.a")
54 | endif()
55 |
56 | set(RTLSDR_INCLUDE_DIRS ${RTLSDR_INCLUDE_DIR} ${LIBUSB_INCLUDE_DIR})
57 | set(RTLSDR_LIBRARIES ${RTLSDR_LIBRARY} ${LIBUSB_LIBRARY})
58 |
59 | set(HACKRF_INCLUDE_DIRS ${HACKRF_INCLUDE_DIR} ${LIBUSB_INCLUDE_DIR})
60 | set(HACKRF_LIBRARIES ${HACKRF_LIBRARY} ${LIBUSB_LIBRARY})
61 |
62 | set(AIRSPY_INCLUDE_DIRS ${AIRSPY_INCLUDE_DIR} ${LIBUSB_INCLUDE_DIR})
63 | set(AIRSPY_LIBRARIES ${AIRSPY_LIBRARY} ${LIBUSB_LIBRARY})
64 |
65 | set(BLADERF_INCLUDE_DIRS ${BLADERF_INCLUDE_DIR} ${LIBUSB_INCLUDE_DIR})
66 | set(BLADERF_LIBRARIES ${BLADERF_LIBRARY} ${LIBUSB_LIBRARY})
67 |
68 | # Compiler flags.
69 | set(CMAKE_CXX_FLAGS "-Wall -std=c++11 -O2 -ffast-math -ftree-vectorize ${EXTRA_FLAGS}")
70 |
71 | set(sfmbase_SOURCES
72 | sfmbase/Filter.cpp
73 | sfmbase/FmDecode.cpp
74 | sfmbase/AudioOutput.cpp
75 | )
76 |
77 | set(sfmbase_HEADERS
78 | include/AudioOutput.h
79 | include/Filter.h
80 | include/FmDecode.h
81 | include/MovingAverage.h
82 | include/Source.h
83 | include/SoftFM.h
84 | include/DataBuffer.h
85 | include/fastatan2.h
86 | include/parsekv.h
87 | include/util.h
88 | )
89 |
90 | # Base sources
91 |
92 | set(sfmbase_SOURCES
93 | ${sfmbase_SOURCES}
94 | ${sfmbase_HEADERS}
95 | )
96 |
97 | # RTL-SDR sources
98 |
99 | set(sfmrtlsdr_SOURCES
100 | sfmbase/RtlSdrSource.cpp
101 | )
102 |
103 | set(sfmrtlsdr_HEADERS
104 | include/RtlSdrSource.h
105 | )
106 |
107 | set(sfmrtlsdr_SOURCES
108 | ${sfmrtlsdr_SOURCES}
109 | ${sfmrtlsdr_HEADERS}
110 | )
111 |
112 | # HackRF sources
113 |
114 | set(sfmhackrf_SOURCES
115 | sfmbase/HackRFSource.cpp
116 | )
117 |
118 | set(sfmhackrf_HEADERS
119 | include/HackRFSource.h
120 | )
121 |
122 | set(sfmhackrf_SOURCES
123 | ${sfmhackrf_SOURCES}
124 | ${sfmhackrf_HEADERS}
125 | )
126 |
127 | # Airspy sources
128 |
129 | set(sfmairspy_SOURCES
130 | sfmbase/AirspySource.cpp
131 | )
132 |
133 | set(sfmairspy_HEADERS
134 | include/AirspySource.h
135 | )
136 |
137 | set(sfmairspy_SOURCES
138 | ${sfmairspy_SOURCES}
139 | ${sfmairspy_HEADERS}
140 | )
141 |
142 | # BLadeRF sources
143 |
144 | set(sfmbladerf_SOURCES
145 | sfmbase/BladeRFSource.cpp
146 | )
147 |
148 | set(sfmbladerf_HEADERS
149 | include/BladeRFSource.h
150 | )
151 |
152 | set(sfmbladerf_SOURCES
153 | ${sfmbladerf_SOURCES}
154 | ${sfmbladerf_HEADERS}
155 | )
156 |
157 | # Libraries
158 |
159 | add_library(sfmbase STATIC
160 | ${sfmbase_SOURCES}
161 | )
162 |
163 | add_library(sfmrtlsdr STATIC
164 | ${sfmrtlsdr_SOURCES}
165 | )
166 |
167 | add_library(sfmhackrf STATIC
168 | ${sfmhackrf_SOURCES}
169 | )
170 |
171 | add_library(sfmairspy STATIC
172 | ${sfmairspy_SOURCES}
173 | )
174 |
175 | add_library(sfmbladerf STATIC
176 | ${sfmbladerf_SOURCES}
177 | )
178 |
179 | add_executable(softfm
180 | main.cpp
181 | )
182 |
183 | include_directories(
184 | ${CMAKE_SOURCE_DIR}/include
185 | ${ALSA_INCLUDE_DIRS}
186 | ${EXTRA_INCLUDES}
187 | )
188 |
189 | target_link_libraries(softfm
190 | sfmbase
191 | sfmrtlsdr
192 | sfmhackrf
193 | sfmairspy
194 | sfmbladerf
195 | ${CMAKE_THREAD_LIBS_INIT}
196 | ${ALSA_LIBRARIES}
197 | ${EXTRA_LIBS}
198 | )
199 |
200 | target_include_directories(sfmrtlsdr PUBLIC
201 | ${RTLSDR_INCLUDE_DIRS}
202 | )
203 |
204 | target_link_libraries(sfmrtlsdr
205 | ${RTLSDR_LIBRARIES}
206 | )
207 |
208 | target_link_libraries(sfmhackrf
209 | ${HACKRF_LIBRARIES}
210 | )
211 |
212 | target_link_libraries(sfmairspy
213 | ${AIRSPY_LIBRARIES}
214 | )
215 |
216 | target_link_libraries(sfmbladerf
217 | ${BLADERF_LIBRARIES}
218 | )
219 |
220 | install(TARGETS softfm DESTINATION bin)
221 | install(TARGETS sfmbase sfmrtlsdr sfmhackrf sfmairspy sfmbladerf DESTINATION lib)
222 |
--------------------------------------------------------------------------------
/include/Filter.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_FILTER_H
20 | #define SOFTFM_FILTER_H
21 |
22 | #include
23 | #include "SoftFM.h"
24 |
25 |
26 | /** Fine tuner which shifts the frequency of an IQ signal by a fixed offset. */
27 | class FineTuner
28 | {
29 | public:
30 |
31 | /**
32 | * Construct fine tuner.
33 | *
34 | * table_size :: Size of internal sin/cos tables, determines the resolution
35 | * of the frequency shift.
36 | *
37 | * freq_shift :: Frequency shift. Signal frequency will be shifted by
38 | * (sample_rate * freq_shift / table_size).
39 | */
40 | FineTuner(unsigned int table_size, int freq_shift);
41 |
42 | /** Process samples. */
43 | void process(const IQSampleVector& samples_in, IQSampleVector& samples_out);
44 |
45 | private:
46 | unsigned int m_index;
47 | IQSampleVector m_table;
48 | };
49 |
50 |
51 | /** Low-pass filter for IQ samples, based on Lanczos FIR filter. */
52 | class LowPassFilterFirIQ
53 | {
54 | public:
55 |
56 | /**
57 | * Construct low-pass filter.
58 | *
59 | * filter_order :: FIR filter order.
60 | * cutoff :: Cutoff frequency relative to the full sample rate
61 | * (valid range 0.0 ... 0.5).
62 | */
63 | LowPassFilterFirIQ(unsigned int filter_order, double cutoff);
64 |
65 | /** Process samples. */
66 | void process(const IQSampleVector& samples_in, IQSampleVector& samples_out);
67 |
68 | private:
69 | std::vector m_coeff;
70 | IQSampleVector m_state;
71 | };
72 |
73 |
74 | /**
75 | * Downsampler with low-pass FIR filter for real-valued signals.
76 | *
77 | * Step 1: Low-pass filter based on Lanczos FIR filter
78 | * Step 2: (optional) Decimation by an arbitrary factor (integer or float)
79 | */
80 | class DownsampleFilter
81 | {
82 | public:
83 |
84 | /**
85 | * Construct low-pass filter with optional downsampling.
86 | *
87 | * filter_order :: FIR filter order
88 | * cutoff :: Cutoff frequency relative to the full input sample rate
89 | * (valid range 0.0 .. 0.5)
90 | * downsample :: Decimation factor (>= 1) or 1 to disable
91 | * integer_factor :: Enables a faster and more precise algorithm that
92 | * only works for integer downsample factors.
93 | *
94 | * The output sample rate is (input_sample_rate / downsample)
95 | */
96 | DownsampleFilter(unsigned int filter_order, double cutoff,
97 | double downsample=1, bool integer_factor=true);
98 |
99 | /** Process samples. */
100 | void process(const SampleVector& samples_in, SampleVector& samples_out);
101 |
102 | private:
103 | double m_downsample;
104 | unsigned int m_downsample_int;
105 | unsigned int m_pos_int;
106 | Sample m_pos_frac;
107 | SampleVector m_coeff;
108 | SampleVector m_state;
109 | };
110 |
111 |
112 | /** First order low-pass IIR filter for real-valued signals. */
113 | class LowPassFilterRC
114 | {
115 | public:
116 |
117 | /**
118 | * Construct 1st order low-pass IIR filter.
119 | *
120 | * timeconst :: RC time constant in seconds (1 / (2 * PI * cutoff_freq)
121 | */
122 | LowPassFilterRC(double timeconst);
123 |
124 | /** Process samples. */
125 | void process(const SampleVector& samples_in, SampleVector& samples_out);
126 |
127 | /** Process samples in-place. */
128 | void process_inplace(SampleVector& samples);
129 |
130 | /** Process interleaved samples. */
131 | void process_interleaved(const SampleVector& samples_in, SampleVector& samples_out);
132 |
133 | /** Process interleaved samples in-place. */
134 | void process_interleaved_inplace(SampleVector& samples);
135 |
136 | private:
137 | double m_timeconst;
138 | Sample m_a1;
139 | Sample m_b0;
140 | Sample m_y0_1;
141 | Sample m_y1_1;
142 | };
143 |
144 |
145 | /** Low-pass filter for real-valued signals based on Butterworth IIR filter. */
146 | class LowPassFilterIir
147 | {
148 | public:
149 |
150 | /**
151 | * Construct 4th order low-pass IIR filter.
152 | *
153 | * cutoff :: Low-pass cutoff relative to the sample frequency
154 | * (valid range 0.0 .. 0.5, 0.5 = Nyquist)
155 | */
156 | LowPassFilterIir(double cutoff);
157 |
158 | /** Process samples. */
159 | void process(const SampleVector& samples_in, SampleVector& samples_out);
160 |
161 | private:
162 | Sample b0, a1, a2, a3, a4;
163 | Sample y1, y2, y3, y4;
164 | };
165 |
166 |
167 | /** High-pass filter for real-valued signals based on Butterworth IIR filter. */
168 | class HighPassFilterIir
169 | {
170 | public:
171 |
172 | /**
173 | * Construct 2nd order high-pass IIR filter.
174 | *
175 | * cutoff :: High-pass cutoff relative to the sample frequency
176 | * (valid range 0.0 .. 0.5, 0.5 = Nyquist)
177 | */
178 | HighPassFilterIir(double cutoff);
179 |
180 | /** Process samples. */
181 | void process(const SampleVector& samples_in, SampleVector& samples_out);
182 |
183 | /** Process samples in-place. */
184 | void process_inplace(SampleVector& samples);
185 |
186 | private:
187 | Sample b0, b1, b2, a1, a2;
188 | Sample x1, x2, y1, y2;
189 | };
190 |
191 | #endif
192 |
--------------------------------------------------------------------------------
/include/FmDecode.h:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #ifndef SOFTFM_FMDECODE_H
20 | #define SOFTFM_FMDECODE_H
21 |
22 | #include
23 | #include
24 |
25 | #include "SoftFM.h"
26 | #include "Filter.h"
27 |
28 |
29 | /* Detect frequency by phase discrimination between successive samples. */
30 | class PhaseDiscriminator
31 | {
32 | public:
33 |
34 | /**
35 | * Construct phase discriminator.
36 | *
37 | * max_freq_dev :: Full scale frequency deviation relative to the
38 | * full sample frequency.
39 | */
40 | PhaseDiscriminator(double max_freq_dev);
41 |
42 | /**
43 | * Process samples.
44 | * Output is a sequence of frequency estimates, scaled such that
45 | * output value +/- 1.0 represents the maximum frequency deviation.
46 | */
47 | void process(const IQSampleVector& samples_in, SampleVector& samples_out);
48 |
49 | private:
50 | const Sample m_freq_scale_factor;
51 | IQSample m_last1_sample;
52 | IQSample m_last2_sample;
53 | };
54 |
55 |
56 | /** Phase-locked loop for stereo pilot. */
57 | class PilotPhaseLock
58 | {
59 | public:
60 |
61 | /** Expected pilot frequency (used for PPS events). */
62 | static constexpr int pilot_frequency = 19000;
63 |
64 | /** Timestamp event produced once every 19000 pilot periods. */
65 | struct PpsEvent
66 | {
67 | std::uint64_t pps_index;
68 | std::uint64_t sample_index;
69 | double block_position;
70 | };
71 |
72 | /**
73 | * Construct phase-locked loop.
74 | *
75 | * freq :: 19 kHz center frequency relative to sample freq
76 | * (0.5 is Nyquist)
77 | * bandwidth :: bandwidth relative to sample frequency
78 | * minsignal :: minimum pilot amplitude
79 | */
80 | PilotPhaseLock(double freq, double bandwidth, double minsignal);
81 |
82 | /**
83 | * Process samples and extract 19 kHz pilot tone.
84 | * Generate phase-locked 38 kHz tone with unit amplitude.
85 | */
86 | void process(const SampleVector& samples_in, SampleVector& samples_out);
87 |
88 | /** Return true if the phase-locked loop is locked. */
89 | bool locked() const
90 | {
91 | return m_lock_cnt >= m_lock_delay;
92 | }
93 |
94 | /** Return detected amplitude of pilot signal. */
95 | double get_pilot_level() const
96 | {
97 | return 2 * m_pilot_level;
98 | }
99 |
100 | /** Return PPS events from the most recently processed block. */
101 | std::vector get_pps_events() const
102 | {
103 | return m_pps_events;
104 | }
105 |
106 | private:
107 | Sample m_minfreq, m_maxfreq;
108 | Sample m_phasor_b0, m_phasor_a1, m_phasor_a2;
109 | Sample m_phasor_i1, m_phasor_i2, m_phasor_q1, m_phasor_q2;
110 | Sample m_loopfilter_b0, m_loopfilter_b1;
111 | Sample m_loopfilter_x1;
112 | Sample m_freq, m_phase;
113 | Sample m_minsignal;
114 | Sample m_pilot_level;
115 | int m_lock_delay;
116 | int m_lock_cnt;
117 | int m_pilot_periods;
118 | std::uint64_t m_pps_cnt;
119 | std::uint64_t m_sample_cnt;
120 | std::vector m_pps_events;
121 | };
122 |
123 |
124 | /** Complete decoder for FM broadcast signal. */
125 | class FmDecoder
126 | {
127 | public:
128 | static constexpr double default_deemphasis = 50;
129 | static constexpr double default_bandwidth_if = 100000;
130 | static constexpr double default_freq_dev = 75000;
131 | static constexpr double default_bandwidth_pcm = 15000;
132 | static constexpr double pilot_freq = 19000;
133 |
134 | /**
135 | * Construct FM decoder.
136 | *
137 | * sample_rate_if :: IQ sample rate in Hz.
138 | * tuning_offset :: Frequency offset in Hz of radio station with respect
139 | * to receiver LO frequency (positive value means
140 | * station is at higher frequency than LO).
141 | * sample_rate_pcm :: Audio sample rate.
142 | * stereo :: True to enable stereo decoding.
143 | * deemphasis :: Time constant of de-emphasis filter in microseconds
144 | * (50 us for broadcast FM, 0 to disable de-emphasis).
145 | * bandwidth_if :: Half bandwidth of IF signal in Hz
146 | * (~ 100 kHz for broadcast FM)
147 | * freq_dev :: Full scale carrier frequency deviation
148 | * (75 kHz for broadcast FM)
149 | * bandwidth_pcm :: Half bandwidth of audio signal in Hz
150 | * (15 kHz for broadcast FM)
151 | * downsample :: Downsampling factor to apply after FM demodulation.
152 | * Set to 1 to disable.
153 | */
154 | FmDecoder(double sample_rate_if,
155 | double tuning_offset,
156 | double sample_rate_pcm,
157 | bool stereo=true,
158 | double deemphasis=50,
159 | double bandwidth_if=default_bandwidth_if,
160 | double freq_dev=default_freq_dev,
161 | double bandwidth_pcm=default_bandwidth_pcm,
162 | unsigned int downsample=1);
163 |
164 | /**
165 | * Process IQ samples and return audio samples.
166 | *
167 | * If the decoder is set in stereo mode, samples for left and right
168 | * channels are interleaved in the output vector (even if no stereo
169 | * signal is detected). If the decoder is set in mono mode, the output
170 | * vector only contains samples for one channel.
171 | */
172 | void process(const IQSampleVector& samples_in,
173 | SampleVector& audio);
174 |
175 | /** Return true if a stereo signal is detected. */
176 | bool stereo_detected() const
177 | {
178 | return m_stereo_detected;
179 | }
180 |
181 | /** Return actual frequency offset in Hz with respect to receiver LO. */
182 | double get_tuning_offset() const
183 | {
184 | double tuned = - m_tuning_shift * m_sample_rate_if /
185 | double(m_tuning_table_size);
186 | return tuned + m_baseband_mean * m_freq_dev;
187 | }
188 |
189 | /** Return RMS IF level (where full scale IQ signal is 1.0). */
190 | double get_if_level() const
191 | {
192 | return m_if_level;
193 | }
194 |
195 | /** Return RMS baseband signal level (where nominal level is 0.707). */
196 | double get_baseband_level() const
197 | {
198 | return m_baseband_level;
199 | }
200 |
201 | /** Return amplitude of stereo pilot (nominal level is 0.1). */
202 | double get_pilot_level() const
203 | {
204 | return m_pilotpll.get_pilot_level();
205 | }
206 |
207 | /** Return PPS events from the most recently processed block. */
208 | std::vector get_pps_events() const
209 | {
210 | return m_pilotpll.get_pps_events();
211 | }
212 |
213 | private:
214 | /** Demodulate stereo L-R signal. */
215 | void demod_stereo(const SampleVector& samples_baseband,
216 | SampleVector& samples_stereo);
217 |
218 | /** Duplicate mono signal in left/right channels. */
219 | void mono_to_left_right(const SampleVector& samples_mono,
220 | SampleVector& audio);
221 |
222 | /** Extract left/right channels from mono/stereo signals. */
223 | void stereo_to_left_right(const SampleVector& samples_mono,
224 | const SampleVector& samples_stereo,
225 | SampleVector& audio);
226 |
227 | // Data members.
228 | const double m_sample_rate_if;
229 | const double m_sample_rate_baseband;
230 | const int m_tuning_table_size;
231 | const int m_tuning_shift;
232 | const double m_freq_dev;
233 | const unsigned int m_downsample;
234 | const bool m_stereo_enabled;
235 | bool m_stereo_detected;
236 | double m_if_level;
237 | double m_baseband_mean;
238 | double m_baseband_level;
239 |
240 | IQSampleVector m_buf_iftuned;
241 | IQSampleVector m_buf_iffiltered;
242 | SampleVector m_buf_baseband;
243 | SampleVector m_buf_mono;
244 | SampleVector m_buf_rawstereo;
245 | SampleVector m_buf_stereo;
246 |
247 | FineTuner m_finetuner;
248 | LowPassFilterFirIQ m_iffilter;
249 | PhaseDiscriminator m_phasedisc;
250 | DownsampleFilter m_resample_baseband;
251 | PilotPhaseLock m_pilotpll;
252 | DownsampleFilter m_resample_mono;
253 | DownsampleFilter m_resample_stereo;
254 | HighPassFilterIir m_dcblock_mono;
255 | HighPassFilterIir m_dcblock_stereo;
256 | LowPassFilterRC m_deemph_mono;
257 | LowPassFilterRC m_deemph_stereo;
258 | };
259 |
260 | #endif
261 |
--------------------------------------------------------------------------------
/NOTES.txt:
--------------------------------------------------------------------------------
1 |
2 | This file contains random notitions
3 | -----------------------------------
4 |
5 |
6 | Valid sample rates
7 | ------------------
8 |
9 | Sample rates between 300001 Hz and 900000 Hz (inclusive) are not supported.
10 | They cause an invalid configuration of the RTL chip.
11 |
12 | rsamp_ratio = 28.8 MHz * 2**22 / sample_rate
13 | If bit 27 and bit 28 of rsamp_ratio are different, the RTL chip malfunctions.
14 |
15 |
16 | Behaviour of RTL and Elonics tuner
17 | ----------------------------------
18 |
19 | The RTL chip has a configurable 32-tap FIR filter running at 28.8 MS/s.
20 | RTL-SDR currently configures it for cutoff at 1.2 MHz (2.4 MS/s).
21 |
22 | Casual test of ADC mismatch:
23 | * DC offset in order of 1 code step
24 | * I/Q gain mismatch in order of 4%
25 | * I/Q phase mismatch in order of 1% of sample interval
26 |
27 | With tuner in auto-gain mode, device autonomously switches between gain
28 | settings during a run. The LNA gain seems to switch between ~ 24 dB
29 | and ~ 34 dB without intermediate steps.
30 |
31 | With RTL in AGC mode, the level of the digital sample stream is normalized
32 | to -6 dB FS. Unknown whether this is an analog or digital gain stage.
33 |
34 | At first I suspected that AGC mode may be a cooperation between the RTL and
35 | the Elonics tuner. I thought that the RTL would monitor the level and send
36 | digital control signals to the Elonics to dynamically change the tuner IF gain.
37 | However that is in fact NOT what happens. (Manually changing IF gain in AGC
38 | mode causes a brief level spike, while manually rewriting the same IF gain in
39 | AGC mode does not have any effect).
40 | It seems more likely that AGC is a digital gain in the downsampling filter.
41 |
42 |
43 | Default settings in librtlsdr
44 | -----------------------------
45 |
46 | Elonics LNA gain: when auto tuner gain: autonomous control with slow update
47 | otherwise gain as configured via rtlsdr_set_tuner_gain
48 | Elonics mixer gain: autonomous control disabled,
49 | gain depending on rtlsdr_set_tuner_gain
50 | Elonics IF linearity: optimize sensitivity (default), auto switch disabled
51 | Elonics IF gain: +6, +0, +0, +0, +9, +9 (non-standard mode)
52 | Elonics IF filters: matched to sample rate (note this may not be optimal)
53 | RTL AGC mode off
54 |
55 |
56 | Effect of IF signal filtering
57 | -----------------------------
58 |
59 | Carson bandwidth rule:
60 | IF_half_bandwidth = peak_freq_devation + modulating_freq
61 |
62 | In case of broadcast FM, this is
63 | 75 kHz + 53 kHz = 128 kHz (worst case)
64 | 19 kHz + 53 kHz = 72 kHz (typical case)
65 |
66 | Simulations of IF filtering show:
67 | * narrow IF filter reduces noise in the baseband
68 | * narrow IF filter causes gain roll-off for high modulating frequencies
69 | * narrow IF filter causes harmonic distortion at high modulating deviation
70 |
71 | IF filter with 100 kHz half-bandwidth:
72 | * baseband gain >= -1 dB up to 75 kHz
73 | * less than 0.1% distortion of modulating signal at 19 kHz peak deviation
74 | * ~ 2% distortion of modulating signal at 75 kHz peak devation
75 |
76 | IF filter with 75 kHz half-bandwidth:
77 | * baseband gain ~ -3 dB at 60 kHz, ~ -8 dB at 75 kHz
78 | * ~ 1% distortion of modulating signal at 19 kHz peak deviation
79 |
80 | Optimal IF bandwidth is probably somewhere between 75 and 100 kHz, with
81 | roll-off not too steep.
82 | Weak stations benefit from a narrow IF filter to reduce noise.
83 | Strong stations benefit from a wider IF filter to reduce harmonics.
84 |
85 |
86 | Effect of settings on baseband SNR
87 | ----------------------------------
88 |
89 | Note: all measurements 10 second duration
90 | Note: all measurements have LO frequency set to station + 250 kHz
91 |
92 |
93 | STATION SRATE LNA IF GAIN AGC SOFT BW IF LEVEL GUARD/PILOT
94 |
95 | radio3 1 MS/s 24 dB default off 150 kHz 0.19 -62.6 dB/Hz
96 | radio3 1.5 MS 24 dB default off 150 kHz 0.19 -62.7 dB/Hz
97 | radio3 2 MS/s 24 dB default off 150 kHz 0.18 -62.7 dB/Hz
98 |
99 | radio3 2 MS/s 34 dB default off 150 kHz 0.46 -64.0 dB/Hz
100 | radio3 2 MS/s 34 dB default off 80 kHz -64.0 dB/Hz
101 | radio3 2 MS/s 34 dB default off 150 kHz adccal -64.0 dB/Hz
102 |
103 | radio4 2 MS/s 24 dB default off 150 kHz 0.04 -41.1 dB/Hz
104 |
105 | radio4 1 MS/s 34 dB default off 150 kHz 0.06 -43.3 dB/Hz
106 | radio4 1 MS/s 34 dB default off 80 kHz -51.2 dB/Hz
107 |
108 | radio4 2 MS/s 34 dB default off 150 kHz 0.10 -42.4 dB/Hz
109 | radio4 2 MS/s 34 dB default off 80 kHz -48.2 dB/Hz
110 | radio4 2 MS/s 34 dB default off 150 kHz adccal -42.4 dB/Hz
111 |
112 | Conclusion: Sample rate (1 MS/s to 2 MS/s) has little effect on quality.
113 | Conclusion: LNA gain has little effect on quality.
114 | Conclusion: Narrow IF filter improves quality of weak station.
115 | Conclusion: ADC gain/offset calibration has no effect on quality.
116 |
117 |
118 | STATION SRATE LNA IF GAIN AGC SOFT BW IF LEVEL GUARD/PILOT
119 |
120 | radio3 2 MS/s 34 dB 9,0,0,0,9,9 off 80 kHz 0.38 -63.2 dB/Hz
121 | radio3 2 MS/s 34 dB 0,0,0,0,3,3 off 80 kHz 0.03 -61.5 dB/Hz
122 | radio3 2 MS/s 34 dB 0,0,0,0,3,9 off 80 kHz 0.07 -61.5 dB/Hz
123 | radio3 2 MS/s 34 dB 0,0,0,0,3,15 off 80 kHz 0.11 -60.5 dB/Hz
124 | radio3 2 MS/s 34 dB 0,0,0,0,9,15 off 80 kHz 0.22 -61.0 dB/Hz
125 | radio3 2 MS/s 34 dB 0,0,0,0,15,15 off 80 kHz 0.36 -61.4 dB/Hz
126 | radio3 2 MS/s 34 dB 0,0,6,0,15,15 off 80 kHz 0.66 CLIP -63.5 dB/Hz
127 | radio3 2 MS/s 34 dB 0,6,0,0,3,3 off 80 kHz 0.07 -62.9 dB/Hz
128 | radio3 2 MS/s 34 dB 9,3,0,0,3,3 off 80 kHz 0.14 -63.9 dB/Hz
129 | radio3 2 MS/s 34 dB 9,9,0,0,3,3 off 80 kHz 0.26 -64.2 dB/Hz
130 | radio3 2 MS/s 34 dB 9,9,3,2,3,3 off 80 kHz 0.45 -64.1 dB/Hz
131 | radio3 2 MS/s 34 dB 9,9,6,0,3,3 off 80 kHz 0.49 -63.7 dB/Hz
132 | radio3 2 MS/s 34 dB 9,9,9,0,6,3 off 80 kHz 0.77 CLIP -61.2 dB/Hz
133 |
134 | radio4 2 MS/s 34 dB 9,0,0,0,9,9 off 80 kHz 0.09 -41.5 dB/Hz
135 | radio4 2 MS/s 34 dB 0,0,0,0,3,3 off 80 kHz 0.01 -36.7 dB/Hz
136 | radio4 2 MS/s 34 dB 0,0,0,0,3,9 off 80 kHz 0.02 -39.9 dB/Hz
137 | radio4 2 MS/s 34 dB 0,0,0,0,3,15 off 80 kHz 0.03 -40.4 dB/Hz
138 | radio4 2 MS/s 34 dB 0,0,0,0,9,15 off 80 kHz 0.06 -39.9 dB/Hz
139 | radio4 2 MS/s 34 dB 0,0,0,0,15,15 off 80 kHz 0.09 -40.9 dB/Hz
140 | radio4 2 MS/s 34 dB 0,0,6,0,15,15 off 80 kHz 0.17 -38.4 dB/Hz
141 | radio4 2 MS/s 34 dB 0,6,0,0,3,3 off 80 kHz 0.02 -36.9 dB/Hz
142 | radio4 2 MS/s 34 dB 9,3,0,0,3,3 off 80 kHz 0.03 -37.5 dB/Hz
143 | radio4 2 MS/s 34 dB 9,9,0,0,3,3 off 80 kHz 0.07 -39.4 dB/Hz
144 | radio4 2 MS/s 34 dB 9,9,3,2,3,3 off 80 kHz 0.11 -38.5 dB/Hz
145 | radio4 2 MS/s 34 dB 9,9,6,0,3,3 off 80 kHz 0.12 -38.0 dB/Hz
146 | radio4 2 MS/s 34 dB 9,9,9,0,6,3 off 80 kHz 0.22 -37.5 dB/Hz
147 |
148 | Conclusion: IF gain has little effect on quality, although very low gain
149 | has an adverse effect. Librtlsdr defaults look good.
150 |
151 |
152 | STATION SRATE LNA IF GAIN AGC SOFT BW IF LEVEL GUARD/PILOT
153 |
154 | radio3 2 MS/s 34 dB default off 80 kHz 0.36 -62.1 dB/Hz
155 | radio3 2 MS/s 34 dB default ON 80 kHz 0.36 -61.6 dB/Hz
156 |
157 | radio4 2 MS/s 34 dB default off 80 kHz 0.11 -38.5 dB/Hz
158 | radio4 2 MS/s 34 dB default ON 80 kHz 0.36 CLIP -38.0 dB/Hz
159 |
160 | Conclusion: AGC mode has little effect on quality.
161 |
162 |
163 | Stereo pilot frequency
164 | ----------------------
165 |
166 | Measuring 19 kHz pilot frequency vs Internet NTP (ADSL):
167 |
168 | radio2 19 kHz pilot = 18999.79 Hz +- 0.04 Hz (7 hours measurement)
169 | radio3 19 kHz pilot = 18999.8 Hz +- 0.05 Hz (5 hours measurement)
170 | radio538 19 kHz pilot = 18999.78 Hz +- 0.04 Hz (6 hours measurement)
171 |
172 | Conclusion: stereo pilot is not a reliable time source.
173 |
174 |
175 | Ferrites
176 | --------
177 |
178 | Claming a ferrite block on the USB cable, as close as possible to the DVB
179 | received, reduces disturbance peaks in the spectrum by ~ 6 dB.
180 | A second ferrite block at the PC side gives another small improvement.
181 |
182 | The ferrite causes a clearly audible improvement of weak stations.
183 |
184 |
185 | DIY antenna
186 | -----------
187 |
188 | Constructed antenna from a vertical telescopic rod antenna (85 cm)
189 | and two steel wire ground radials ~ 85 cm.
190 | Antenna placed indoors close to window.
191 | Antenna connected to DVB receiver via 1.5 meter 75 Ohm coax cable.
192 |
193 | The ground radials are floppy and look ridiculous but they are essential.
194 | No ground radials, no reception.
195 |
196 | The DIY antenna has ~ 20 dB more signal than the basic DVB antenna.
197 |
198 | The DIY antenna causes notable improvement of weak stations.
199 | Radio4 reception improves from very bad to almost good.
200 |
201 | However, strong stations (radio3) sound slightly worse with the DIY antenna
202 | than with the basic DVB antenna.
203 | Theory: Distortion caused by clipping I/Q samples due to strong antenna signal.
204 | No, that's not it. Reducing LNA gain or IF gain does not help much;
205 | small DVB antenna still sounds better than DIY antenna.
206 | Difference only clear in stereo mode.
207 | Don't know what's going on here, maybe the DIY antenna is just not good.
208 |
209 |
210 | IF processing
211 | -------------
212 |
213 | Idea: Filter I/Q samples with 3rd order Butterworth filter
214 | instead of 10th order FIR filter.
215 | Implemented in branch "iirfilter".
216 | Speed is unchanged.
217 | Sound quality is not much changed for strong stations.
218 | Sound quality is a bit worse for weak stations (at 1 MS/s).
219 | Conclusion: not worthwhile.
220 |
221 | Idea: Downsample I/Q samples to ~ 250 kS/s BEFORE quadrature detection
222 | instead of immediately after detection. Would reduce amount of work in
223 | I/Q filtering for same or higher FIR filter order.
224 | Implemented in branch "filterif".
225 | CPU time reduced by ~ 25%.
226 | Sound quality very slightly worse.
227 | Conclusion: not worthwhile.
228 |
229 |
230 | Local radio stations
231 | --------------------
232 |
233 | radio2 92600000 (good)
234 | radio3 96800000 (good)
235 | radio4 94300000 (bad)
236 | qmusic 100700000 (medium)
237 | radio538 102100000 (medium)
238 | radio10 103800000 (bad)
239 | radio west 89300000 (medium)
240 |
241 | --
242 |
--------------------------------------------------------------------------------
/sfmbase/AudioOutput.cpp:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #define _FILE_OFFSET_BITS 64
20 |
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | #include
29 |
30 | #include "SoftFM.h"
31 | #include "AudioOutput.h"
32 |
33 |
34 |
35 | /* **************** class AudioOutput **************** */
36 |
37 | // Encode a list of samples as signed 16-bit little-endian integers.
38 | void AudioOutput::samplesToInt16(const SampleVector& samples,
39 | std::vector& bytes)
40 | {
41 | bytes.resize(2 * samples.size());
42 |
43 | SampleVector::const_iterator i = samples.begin();
44 | SampleVector::const_iterator n = samples.end();
45 | std::vector::iterator k = bytes.begin();
46 |
47 | while (i != n) {
48 | Sample s = *(i++);
49 | s = std::max(Sample(-1.0), std::min(Sample(1.0), s));
50 | long v = lrint(s * 32767);
51 | unsigned long u = v;
52 | *(k++) = u & 0xff;
53 | *(k++) = (u >> 8) & 0xff;
54 | }
55 | }
56 |
57 |
58 | /* **************** class RawAudioOutput **************** */
59 |
60 | // Construct raw audio writer.
61 | RawAudioOutput::RawAudioOutput(const std::string& filename)
62 | {
63 | if (filename == "-") {
64 |
65 | m_fd = STDOUT_FILENO;
66 |
67 | } else {
68 |
69 | m_fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
70 | if (m_fd < 0) {
71 | m_error = "can not open '" + filename + "' (" +
72 | strerror(errno) + ")";
73 | m_zombie = true;
74 | return;
75 | }
76 |
77 | }
78 | }
79 |
80 |
81 | // Destructor.
82 | RawAudioOutput::~RawAudioOutput()
83 | {
84 | // Close file descriptor.
85 | if (m_fd >= 0 && m_fd != STDOUT_FILENO) {
86 | close(m_fd);
87 | }
88 | }
89 |
90 |
91 | // Write audio data.
92 | bool RawAudioOutput::write(const SampleVector& samples)
93 | {
94 | if (m_fd < 0)
95 | return false;
96 |
97 | // Convert samples to bytes.
98 | samplesToInt16(samples, m_bytebuf);
99 |
100 | // Write data.
101 | std::size_t p = 0;
102 | std::size_t n = m_bytebuf.size();
103 | while (p < n) {
104 |
105 | ssize_t k = ::write(m_fd, m_bytebuf.data() + p, n - p);
106 | if (k <= 0) {
107 | if (k == 0 || errno != EINTR) {
108 | m_error = "write failed (";
109 | m_error += strerror(errno);
110 | m_error += ")";
111 | return false;
112 | }
113 | } else {
114 | p += k;
115 | }
116 | }
117 |
118 | return true;
119 | }
120 |
121 |
122 | /* **************** class WavAudioOutput **************** */
123 |
124 | // Construct .WAV writer.
125 | WavAudioOutput::WavAudioOutput(const std::string& filename,
126 | unsigned int samplerate,
127 | bool stereo)
128 | : numberOfChannels(stereo ? 2 : 1)
129 | , sampleRate(samplerate)
130 | {
131 | m_stream = fopen(filename.c_str(), "wb");
132 | if (m_stream == NULL) {
133 | m_error = "can not open '" + filename + "' (" +
134 | strerror(errno) + ")";
135 | m_zombie = true;
136 | return;
137 | }
138 |
139 | // Write initial header with a dummy sample count.
140 | // This will be replaced with the actual header once the WavFile is closed.
141 | if (!write_header(0x7fff0000)) {
142 | m_error = "can not write to '" + filename + "' (" +
143 | strerror(errno) + ")";
144 | m_zombie = true;
145 | }
146 | }
147 |
148 |
149 | // Destructor.
150 | WavAudioOutput::~WavAudioOutput()
151 | {
152 | // We need to go back and fill in the header ...
153 |
154 | if (!m_zombie) {
155 |
156 | const unsigned bytesPerSample = 2;
157 |
158 | const long currentPosition = ftell(m_stream);
159 |
160 | assert((currentPosition - 44) % bytesPerSample == 0);
161 |
162 | const unsigned totalNumberOfSamples = (currentPosition - 44) / bytesPerSample;
163 |
164 | assert(totalNumberOfSamples % numberOfChannels == 0);
165 |
166 | // Put header in front
167 |
168 | if (fseek(m_stream, 0, SEEK_SET) == 0) {
169 | write_header(totalNumberOfSamples);
170 | }
171 | }
172 |
173 | // Done writing the file
174 |
175 | if (m_stream) {
176 | fclose(m_stream);
177 | }
178 | }
179 |
180 |
181 | // Write audio data.
182 | bool WavAudioOutput::write(const SampleVector& samples)
183 | {
184 | if (m_zombie)
185 | return false;
186 |
187 | // Convert samples to bytes.
188 | samplesToInt16(samples, m_bytebuf);
189 |
190 | // Write samples to file.
191 | std::size_t k = fwrite(m_bytebuf.data(), 1, m_bytebuf.size(), m_stream);
192 | if (k != m_bytebuf.size()) {
193 | m_error = "write failed (";
194 | m_error += strerror(errno);
195 | m_error += ")";
196 | return false;
197 | }
198 |
199 | return true;
200 | }
201 |
202 |
203 | // (Re)write .WAV header.
204 | bool WavAudioOutput::write_header(unsigned int nsamples)
205 | {
206 | const unsigned bytesPerSample = 2;
207 | const unsigned bitsPerSample = 16;
208 |
209 | enum wFormatTagId
210 | {
211 | WAVE_FORMAT_PCM = 0x0001,
212 | WAVE_FORMAT_IEEE_FLOAT = 0x0003
213 | };
214 |
215 | assert(nsamples % numberOfChannels == 0);
216 |
217 | // synthesize header
218 |
219 | uint8_t wavHeader[44];
220 |
221 | encode_chunk_id (wavHeader + 0, "RIFF");
222 | set_value(wavHeader + 4, 36 + nsamples * bytesPerSample);
223 | encode_chunk_id (wavHeader + 8, "WAVE");
224 | encode_chunk_id (wavHeader + 12, "fmt ");
225 | set_value(wavHeader + 16, 16);
226 | set_value(wavHeader + 20, WAVE_FORMAT_PCM);
227 | set_value(wavHeader + 22, numberOfChannels);
228 | set_value(wavHeader + 24, sampleRate ); // sample rate
229 | set_value(wavHeader + 28, sampleRate * numberOfChannels * bytesPerSample); // byte rate
230 | set_value(wavHeader + 32, numberOfChannels * bytesPerSample); // block size
231 | set_value(wavHeader + 34, bitsPerSample);
232 | encode_chunk_id (wavHeader + 36, "data");
233 | set_value(wavHeader + 40, nsamples * bytesPerSample);
234 |
235 | return fwrite(wavHeader, 1, 44, m_stream) == 44;
236 | }
237 |
238 |
239 | void WavAudioOutput::encode_chunk_id(uint8_t * ptr, const char * chunkname)
240 | {
241 | for (unsigned i = 0; i < 4; ++i)
242 | {
243 | assert(chunkname[i] != '\0');
244 | ptr[i] = chunkname[i];
245 | }
246 | assert(chunkname[4] == '\0');
247 | }
248 |
249 |
250 | template
251 | void WavAudioOutput::set_value(uint8_t * ptr, T value)
252 | {
253 | for (std::size_t i = 0; i < sizeof(T); ++i)
254 | {
255 | ptr[i] = value & 0xff;
256 | value >>= 8;
257 | }
258 | }
259 |
260 |
261 | /* **************** class AlsaAudioOutput **************** */
262 |
263 | // Construct ALSA output stream.
264 | AlsaAudioOutput::AlsaAudioOutput(const std::string& devname,
265 | unsigned int samplerate,
266 | bool stereo)
267 | {
268 | m_pcm = NULL;
269 | m_nchannels = stereo ? 2 : 1;
270 |
271 | int r = snd_pcm_open(&m_pcm, devname.c_str(),
272 | SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
273 |
274 | if (r < 0) {
275 | m_error = "can not open PCM device '" + devname + "' (" +
276 | strerror(-r) + ")";
277 | m_zombie = true;
278 | return;
279 | }
280 |
281 | snd_pcm_nonblock(m_pcm, 0);
282 |
283 | r = snd_pcm_set_params(m_pcm,
284 | SND_PCM_FORMAT_S16_LE,
285 | SND_PCM_ACCESS_RW_INTERLEAVED,
286 | m_nchannels,
287 | samplerate,
288 | 1, // allow soft resampling
289 | 500000); // latency in us
290 |
291 | if (r < 0) {
292 | m_error = "can not set PCM parameters (";
293 | m_error += strerror(-r);
294 | m_error += ")";
295 | m_zombie = true;
296 | }
297 | }
298 |
299 |
300 | // Destructor.
301 | AlsaAudioOutput::~AlsaAudioOutput()
302 | {
303 | // Close device.
304 | if (m_pcm != NULL) {
305 | snd_pcm_close(m_pcm);
306 | }
307 | }
308 |
309 |
310 | // Write audio data.
311 | bool AlsaAudioOutput::write(const SampleVector& samples)
312 | {
313 | if (m_zombie)
314 | return false;
315 |
316 | // Convert samples to bytes.
317 | samplesToInt16(samples, m_bytebuf);
318 |
319 | // Write data.
320 | unsigned int p = 0;
321 | unsigned int n = samples.size() / m_nchannels;
322 | unsigned int framesize = 2 * m_nchannels;
323 | while (p < n) {
324 |
325 | int k = snd_pcm_writei(m_pcm,
326 | m_bytebuf.data() + p * framesize, n - p);
327 | if (k < 0) {
328 | m_error = "write failed (";
329 | m_error += strerror(errno);
330 | m_error += ")";
331 | // After an underrun, ALSA keeps returning error codes until we
332 | // explicitly fix the stream.
333 | snd_pcm_recover(m_pcm, k, 0);
334 | return false;
335 | } else {
336 | p += k;
337 | }
338 | }
339 |
340 | return true;
341 | }
342 |
343 | /* end */
344 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | NGSoftFM
2 | ========
3 |
4 | **NGSoftFM** is a command line software decoder for FM broadcast radio with stereo support
5 |
6 | Introduction
7 |
8 | **NGSoftFM** is a software-defined radio receiver for FM broadcast radio. Stereo
9 | decoding is supported. It is written in C++. It is a derivative work of SoftFM (https://github.com/jorisvr/SoftFM) with a new application design and new features. It also corrects wrong de-emphasis scheme for stereo signals.
10 |
11 | Hardware supported:
12 |
13 | - **RTL-SDR** based (RTL2832-based) hardware is suppoeted and uses the _librtlsdr_ library to interface with the RTL-SDR hardware.
14 | - **HackRF** One and variants are supported with _libhackrf_ library.
15 | - **Airspy** is supported with _libairspy_ library.
16 | - **BladeRF** is supported with _libbladerf_ library.
17 |
18 | The purposes of NGSoftFM are:
19 |
20 | - experimenting with digital signal processing and software radio;
21 | - investigating the stability of the 19 kHz pilot;
22 | - doing the above while listening to my favorite radio station.
23 |
24 | NGSoftFM actually produces pretty good stereo sound
25 | when receiving a strong radio station. Weak stations are noisy,
26 | but NGSoftFM gets much better results than rtl_fm (bundled with RTL-SDR)
27 | and the few GNURadio-based FM receivers I have seen.
28 |
29 | NGSoftFM provides:
30 |
31 | - mono or stereo decoding of FM broadcasting stations
32 | - real-time playback to soundcard or dumping to file
33 | - command-line interface (no GUI, no visualization, nothing fancy)
34 |
35 | NGSoftFM requires:
36 |
37 | - Linux
38 | - C++11
39 | - RTL-SDR library (http://sdr.osmocom.org/trac/wiki/rtl-sdr)
40 | - HackRF library (https://github.com/mossmann/hackrf/tree/master/host/libhackrf)
41 | - Airspy library (https://github.com/airspy/host/tree/master/libairspy)
42 | - supported RTL-SDR DVB-T receiver or HackRF Rx/Tx
43 | - medium-fast computer (NGSoftFM takes 25% CPU time on a 1.6 GHz Core i3, ~12% of one core of a Core i7 5700HQ @ 2.7 GHz)
44 | - medium-strong FM radio signal. However the R820T2 based dongles give better results than the former R820T based dongles. HackRF, Airspy or BladeRF are usually even better but you have to spend the buck for the bang.
45 |
46 | For the latest version, see https://github.com/f4exb/ngsoftfm
47 |
48 | Branches:
49 |
50 | - _master_ is the "production" branch with the most stable release
51 | - _dev_ is the development branch that contains current developments that will be eventually released in the master branch
52 |
53 |
54 | Prerequisites
55 |
56 | Base requirements
57 |
58 | - `sudo apt-get install cmake pkg-config libusb-1.0-0-dev libasound2-dev libboost-all-dev`
59 |
60 | RTL-SDR support
61 |
62 | The Osmocom RTL-SDR library must be installed before you can build NGSoftFM.
63 | See http://sdr.osmocom.org/trac/wiki/rtl-sdr for more information.
64 | NGSoftFM has been tested successfully with RTL-SDR 0.5.3. Normally your distribution should provide the appropriate librtlsdr package.
65 | If you go with your own installation of librtlsdr you have to specify the include path and library path. For example if you installed it in `/opt/install/librtlsdr` you have to add `-DRTLSDR_INCLUDE_DIR=/opt/install/librtlsdr/include -DRTLSDR_LIBRARY=/opt/install/librtlsdr/lib/librtlsdr.a` to the cmake options
66 |
67 | To install the library from a Debian/Ubuntu installation just do:
68 |
69 | - `sudo apt-get install librtlsdr-dev`
70 |
71 | HackRF support
72 |
73 | For now HackRF support must be installed even if no HackRF device is connected.
74 |
75 | If you install from source (https://github.com/mossmann/hackrf/tree/master/host/libhackrf) in your own installation path you have to specify the include path and library path. For example if you installed it in `/opt/install/libhackrf` you have to add `-DHACKRF_INCLUDE_DIR=/opt/install/libhackrf/include -DHACKRF_LIBRARY=/opt/install/libhackrf/lib/libhackrf.a` to the cmake options.
76 |
77 | To install the library from a Debian/Ubuntu installation just do:
78 |
79 | - `sudo apt-get install libhackrf-dev`
80 |
81 | Airspy support
82 |
83 | For now Airspy support must be installed even if no Airspy device is connected.
84 |
85 | If you install from source (https://github.com/airspy/host/tree/master/libairspy) in your own installation path you have to specify the include path and library path. For example if you installed it in `/opt/install/libairspy` you have to add `-DAIRSPY_INCLUDE_DIR=/opt/install/libairspy/include -DHACKRF_LIBRARY=/opt/install/libairspy/lib/libairspy.a` to the cmake options.
86 |
87 | To install the library from a Debian/Ubuntu installation just do:
88 |
89 | - `sudo apt-get install libairspy-dev`
90 |
91 | BladeRF support
92 |
93 | For now BladeRF support must be installed even if no Airspy device is connected.
94 |
95 | If you install from source (https://github.com/Nuand/bladeRF) in your own installation path you have to specify the include path and library path. For example if you installed it in `/opt/install/libbladerf` you have to add `-DBLADERF_INCLUDE_DIR=/opt/install/libbladerf/include -DBLADERF_LIBRARY=/opt/install/libbladerf/lib/libbladeRF.so` to the cmake options.
96 |
97 | To install the library from a Debian/Ubuntu installation just do:
98 |
99 | - `sudo apt-get install libbladerf-dev`
100 |
101 | Note: for the BladeRF to work effectively on FM broadcast frequencies you have to fit it with the XB200 extension board.
102 |
103 | Installing
104 |
105 | To install NGSoftFM, download and unpack the source code and go to the
106 | top level directory. Then do like this:
107 |
108 | - `mkdir build`
109 | - `cd build`
110 | - `cmake ..`
111 |
112 | CMake tries to find librtlsdr. If this fails, you need to specify
113 | the location of the library in one the following ways:
114 |
115 | - `cmake .. -DRTLSDR_INCLUDE_DIR=/path/rtlsdr/include -DRTLSDR_LIBRARY_PATH=/path/rtlsdr/lib/librtlsdr.a`
116 | - `PKG_CONFIG_PATH=/path/to/rtlsdr/lib/pkgconfig cmake ..`
117 |
118 | Compile and install
119 |
120 | - `make -j8` (for machines with 8 CPUs)
121 | - `make install`
122 |
123 |
124 | Running
125 |
126 | Examples
127 |
128 | Basic usage:
129 |
130 | - `./softfm -t rtlsdr -c freq=94600000` Tunes to 94.6 MHz
131 |
132 | Specify gain:
133 |
134 | - `./softfm -t rtlsdr -c freq=94600000,gain=22.9` Tunes to 94.6 MHz and sets gain to 22.9 dB
135 |
136 | All options
137 |
138 | - `-t devtype` is mandatory and must be `rtlsdr` for RTL-SDR devices or `hackrf` for HackRF.
139 | - `-c config` Comma separated list of configuration options as key=value pairs or just key for switches. Depends on device type (see next paragraph).
140 | - `-d devidx` Device index, 'list' to show device list (default 0)
141 | - `-r pcmrate` Audio sample rate in Hz (default 48000 Hz)
142 | - `-M ` Disable stereo decoding
143 | - `-R filename` Write audio data as raw S16_LE samples. Uuse filename `-` to write to stdout
144 | - `-W filename` Write audio data to .WAV file
145 | - `-P [device]` Play audio via ALSA device (default `default`). Use `aplay -L` to get the list of devices for your system
146 | - `-T filename` Write pulse-per-second timestamps. Use filename '-' to write to stdout
147 | - `-b seconds` Set audio buffer size in seconds
148 |
149 | Device type specific configuration options
150 |
151 | RTL-SDR
152 |
153 | - `freq=` Desired tune frequency in Hz. Accepted range from 10M to 2.2G. (default 100M: `100000000`)
154 | - `gain=` (default `auto`)
155 | - `auto` Selects gain automatically
156 | - `list` Lists available gains and exit
157 | - `` gain in dB. Possible gains in dB are: `0.0, 0.9, 1.4, 2.7, 3.7, 7.7, 8.7, 12.5, 14.4, 15.7, 16.6, 19.7, 20.7, 22.9, 25.4, 28.0, 29.7, 32.8, 33.8, 36.4, 37.2, 38.6, 40.2, 42.1, 43.4, 43.9, 44.5, 48.0, 49.6`
158 | - `srate=` Device sample rate. valid values in the [225001, 300000], [900001, 3200000] ranges. (default `1000000`)
159 | - `blklen=` Device block length in bytes (default RTL-SDR default i.e. 64k)
160 | - `agc` Activates device AGC (default off)
161 |
162 | HackRF
163 |
164 | - `freq=` Desired tune frequency in Hz. Valid range from 1M to 6G. (default 100M: `100000000`)
165 | - `srate=` Device sample rate (default `5000000`). Valid values from 1M to 20M. In fact rates lower than 10M are not specified in the datasheets of the ADC chip however a rate of `1000000` (1M) still works well with NGSoftFM.
166 | - `lgain=` LNA gain in dB. Valid values are: `0, 8, 16, 24, 32, 40, list`. `list` lists valid values and exits. (default `16`)
167 | - `vgain=` VGA gain in dB. Valid values are: `0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, list`. `list` lists valid values and exits. (default `22`)
168 | - `bwfilter=` RF (IF) filter bandwith in MHz. Actual value is taken as the closest to the following values: `1.75, 2.5, 3.5, 5, 5.5, 6, 7, 8, 9, 10, 12, 14, 15, 20, 24, 28, list`. `list` lists valid values and exits. (default `2.5`)
169 | - `extamp` Turn on the extra amplifier (default off)
170 | - `antbias` Turn on the antenna bias for remote LNA (default off)
171 |
172 | Airspy
173 |
174 | - `freq=` Desired tune frequency in Hz. Valid range from 1M to 1.8G. (default 100M: `100000000`)
175 | - `srate=` Device sample rate. `list` lists valid values and exits. (default `10000000`). Valid values depend on the Airspy firmware. Airspy firmware and library must support dynamic sample rate query.
176 | - `lgain=` LNA gain in dB. Valid values are: `0, 1, 2, 3, 4, 5, 6, 7, 8 ,9 ,10, 11 12, 13, 14, list`. `list` lists valid values and exits. (default `8`)
177 | - `mgain=` Mixer gain in dB. Valid values are: `0, 1, 2, 3, 4, 5, 6, 7, 8 ,9 ,10, 11 12, 13, 14, 15, list`. `list` lists valid values and exits. (default `8`)
178 | - `vgain=` VGA gain in dB. Valid values are: `0, 1, 2, 3, 4, 5, 6, 7, 8 ,9 ,10, 11 12, 13, 14, 15, list`. `list` lists valid values and exits. (default `0`)
179 | - `antbias` Turn on the antenna bias for remote LNA (default off)
180 | - `lagc` Turn on the LNA AGC (default off)
181 | - `magc` Turn on the mixer AGC (default off)
182 |
183 | BladeRF
184 |
185 | - `freq=` Desired tune frequency in Hz. Valid range low boundary depends whether the XB200 extension board is fitted (default `300000000`).
186 | - XB200 fitted: 100kHz to 3,8 GHz
187 | - XB200 not fitted: 300 MHZ to 3.8 GHz.
188 | - `srate=` Device sample rate in Hz. Valid range is 48kHZ to 40MHz. (default `1000000`).
189 | - `bw=` IF filter bandwidth in Hz. `list` lists valid values and exits. (default `1500000`).
190 | - `lgain=` LNA gain in dB. Valid values are: `0, 3, 6, list`. `list` lists valid values and exits. (default `3`)
191 | - `v1gain=` VGA1 gain in dB. Valid values are: `5, 6, 7, 8 ,9 ,10, 11 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, list`. `list` lists valid values and exits. (default `20`)
192 | - `v2gain=` VGA2 gain in dB. Valid values are: `0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, list`. `list` lists valid values and exits. (default `9`)
193 |
194 |
195 | License
196 |
197 | **NGSoftFM**, copyright (C) 2015, Edouard Griffiths, F4EXB
198 |
199 | This program is free software; you can redistribute it and/or modify
200 | it under the terms of the GNU General Public License as published by
201 | the Free Software Foundation; either version 2 of the License, or
202 | (at your option) any later version.
203 |
204 | This program is distributed in the hope that it will be useful,
205 | but WITHOUT ANY WARRANTY; without even the implied warranty of
206 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
207 | GNU General Public License for more details.
208 |
209 | You should have received a copy of the GNU General Public License along
210 | with this program; if not, see http://www.gnu.org/licenses/gpl-2.0.html
211 |
--------------------------------------------------------------------------------
/sfmbase/RtlSdrSource.cpp:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "util.h"
28 | #include "parsekv.h"
29 | #include "RtlSdrSource.h"
30 |
31 | RtlSdrSource *RtlSdrSource::m_this = 0;
32 |
33 | // Open RTL-SDR device.
34 | RtlSdrSource::RtlSdrSource(int dev_index) :
35 | m_dev(0),
36 | m_block_length(default_block_length),
37 | m_thread(0)
38 | {
39 | int r;
40 |
41 | const char *devname = rtlsdr_get_device_name(dev_index);
42 | if (devname != NULL)
43 | m_devname = devname;
44 |
45 | r = rtlsdr_open(&m_dev, dev_index);
46 |
47 | if (r < 0)
48 | {
49 | m_error = "Failed to open RTL-SDR device (";
50 | m_error += strerror(-r);
51 | m_error += ")";
52 | }
53 | else
54 | {
55 | m_gains = get_tuner_gains();
56 | std::ostringstream gains_ostr;
57 |
58 | gains_ostr << std::fixed << std::setprecision(1);
59 |
60 | for (int g: m_gains)
61 | {
62 | gains_ostr << 0.1 * g << " ";
63 | }
64 |
65 | m_gainsStr = gains_ostr.str();
66 | }
67 |
68 | m_this = this;
69 | }
70 |
71 |
72 | // Close RTL-SDR device.
73 | RtlSdrSource::~RtlSdrSource()
74 | {
75 | if (m_dev)
76 | rtlsdr_close(m_dev);
77 |
78 | m_this = 0;
79 | }
80 |
81 | bool RtlSdrSource::configure(std::string configurationStr)
82 | {
83 | namespace qi = boost::spirit::qi;
84 | std::string::iterator begin = configurationStr.begin();
85 | std::string::iterator end = configurationStr.end();
86 |
87 | uint32_t sample_rate = 1000000;
88 | uint32_t frequency = 100000000;
89 | int tuner_gain = INT_MIN;
90 | int block_length = default_block_length;
91 | bool agcmode = false;
92 |
93 | parsekv::key_value_sequence p;
94 | parsekv::pairs_type m;
95 |
96 | if (!qi::parse(begin, end, p, m))
97 | {
98 | m_error = "Configuration parsing failed\n";
99 | return false;
100 | }
101 | else
102 | {
103 | if (m.find("srate") != m.end())
104 | {
105 | std::cerr << "RtlSdrSource::configure: srate: " << m["srate"] << std::endl;
106 | sample_rate = atoi(m["srate"].c_str());
107 |
108 | if ((sample_rate < 225001)
109 | || ((sample_rate > 300000) && (sample_rate < 900001))
110 | || (sample_rate > 3200000))
111 | {
112 | m_error = "Invalid sample rate";
113 | return false;
114 | }
115 | }
116 |
117 | if (m.find("freq") != m.end())
118 | {
119 | std::cerr << "RtlSdrSource::configure: freq: " << m["freq"] << std::endl;
120 | frequency = atoi(m["freq"].c_str());
121 |
122 | if ((frequency < 10000000) || (frequency > 2200000000))
123 | {
124 | m_error = "Invalid frequency";
125 | return false;
126 | }
127 | }
128 |
129 | if (m.find("gain") != m.end())
130 | {
131 | std::string gain_str = m["gain"];
132 | std::cerr << "RtlSdrSource::configure: gain: " << gain_str << std::endl;
133 |
134 | if (strcasecmp(gain_str.c_str(), "auto") == 0)
135 | {
136 | tuner_gain = INT_MIN;
137 | }
138 | else if (strcasecmp(gain_str.c_str(), "list") == 0)
139 | {
140 | m_error = "Available gains (dB): " + m_gainsStr;
141 | return false;
142 | }
143 | else
144 | {
145 | double tmpgain;
146 |
147 | if (!parse_dbl(gain_str.c_str(), tmpgain))
148 | {
149 | m_error = "Invalid gain";
150 | return false;
151 | }
152 | else
153 | {
154 | long int tmpgain2 = lrint(tmpgain * 10);
155 |
156 | if (tmpgain2 <= INT_MIN || tmpgain2 >= INT_MAX) {
157 | m_error = "Invalid gain";
158 | return false;
159 | }
160 | else
161 | {
162 | tuner_gain = tmpgain2;
163 |
164 | if (find(m_gains.begin(), m_gains.end(), tuner_gain) == m_gains.end())
165 | {
166 | m_error = "Gain not supported. Available gains (dB): " + m_gainsStr;
167 | return false;
168 | }
169 | }
170 | }
171 | }
172 | } // gain
173 |
174 | if (m.find("blklen") != m.end())
175 | {
176 | std::cerr << "RtlSdrSource::configure: blklen: " << m["blklen"] << std::endl;
177 | block_length = atoi(m["blklen"].c_str());
178 | }
179 |
180 | if (m.find("agc") != m.end())
181 | {
182 | std::cerr << "RtlSdrSource::configure: agc" << std::endl;
183 | agcmode = true;
184 | }
185 |
186 | // Intentionally tune at a higher frequency to avoid DC offset.
187 | m_confFreq = frequency;
188 | m_confAgc = agcmode;
189 | double tuner_freq = frequency + 0.25 * sample_rate;
190 |
191 | return configure(sample_rate, tuner_freq, tuner_gain, block_length, agcmode);
192 | }
193 | }
194 |
195 | // Configure RTL-SDR tuner and prepare for streaming.
196 | bool RtlSdrSource::configure(uint32_t sample_rate,
197 | uint32_t frequency,
198 | int tuner_gain,
199 | int block_length,
200 | bool agcmode)
201 | {
202 | int r;
203 |
204 | if (!m_dev)
205 | return false;
206 |
207 | r = rtlsdr_set_sample_rate(m_dev, sample_rate);
208 | if (r < 0) {
209 | m_error = "rtlsdr_set_sample_rate failed";
210 | return false;
211 | }
212 |
213 | r = rtlsdr_set_center_freq(m_dev, frequency);
214 | if (r < 0) {
215 | m_error = "rtlsdr_set_center_freq failed";
216 | return false;
217 | }
218 |
219 | if (tuner_gain == INT_MIN) {
220 | r = rtlsdr_set_tuner_gain_mode(m_dev, 0);
221 | if (r < 0) {
222 | m_error = "rtlsdr_set_tuner_gain_mode could not set automatic gain";
223 | return false;
224 | }
225 | } else {
226 | r = rtlsdr_set_tuner_gain_mode(m_dev, 1);
227 | if (r < 0) {
228 | m_error = "rtlsdr_set_tuner_gain_mode could not set manual gain";
229 | return false;
230 | }
231 |
232 | r = rtlsdr_set_tuner_gain(m_dev, tuner_gain);
233 | if (r < 0) {
234 | m_error = "rtlsdr_set_tuner_gain failed";
235 | return false;
236 | }
237 | }
238 |
239 | // set RTL AGC mode
240 | r = rtlsdr_set_agc_mode(m_dev, int(agcmode));
241 | if (r < 0) {
242 | m_error = "rtlsdr_set_agc_mode failed";
243 | return false;
244 | }
245 |
246 | // set block length
247 | m_block_length = (block_length < 4096) ? 4096 :
248 | (block_length > 1024 * 1024) ? 1024 * 1024 :
249 | block_length;
250 | m_block_length -= m_block_length % 4096;
251 |
252 | // reset buffer to start streaming
253 | if (rtlsdr_reset_buffer(m_dev) < 0) {
254 | m_error = "rtlsdr_reset_buffer failed";
255 | return false;
256 | }
257 |
258 | return true;
259 | }
260 |
261 |
262 | // Return current sample frequency in Hz.
263 | uint32_t RtlSdrSource::get_sample_rate()
264 | {
265 | return rtlsdr_get_sample_rate(m_dev);
266 | }
267 |
268 | // Return device current center frequency in Hz.
269 | uint32_t RtlSdrSource::get_frequency()
270 | {
271 | return rtlsdr_get_center_freq(m_dev);
272 | }
273 |
274 | void RtlSdrSource::print_specific_parms()
275 | {
276 | int lnagain = get_tuner_gain();
277 |
278 | if (lnagain == INT_MIN)
279 | fprintf(stderr, "LNA gain: auto\n");
280 | else
281 | fprintf(stderr, "LNA gain: %.1f dB\n",
282 | 0.1 * lnagain);
283 |
284 | fprintf(stderr, "RTL AGC mode: %s\n",
285 | m_confAgc ? "enabled" : "disabled");
286 | }
287 |
288 | // Return current tuner gain in units of 0.1 dB.
289 | int RtlSdrSource::get_tuner_gain()
290 | {
291 | return rtlsdr_get_tuner_gain(m_dev);
292 | }
293 |
294 |
295 | // Return a list of supported tuner gain settings in units of 0.1 dB.
296 | std::vector RtlSdrSource::get_tuner_gains()
297 | {
298 | int num_gains = rtlsdr_get_tuner_gains(m_dev, NULL);
299 | if (num_gains <= 0)
300 | return std::vector();
301 |
302 | std::vector gains(num_gains);
303 | if (rtlsdr_get_tuner_gains(m_dev, gains.data()) != num_gains)
304 | return std::vector();
305 |
306 | return gains;
307 | }
308 |
309 | bool RtlSdrSource::start(DataBuffer* buf, std::atomic_bool *stop_flag)
310 | {
311 | m_buf = buf;
312 | m_stop_flag = stop_flag;
313 |
314 | if (m_thread == 0)
315 | {
316 | m_thread = new std::thread(run);
317 | return true;
318 | }
319 | else
320 | {
321 | m_error = "Source thread already started";
322 | return false;
323 | }
324 | }
325 |
326 | bool RtlSdrSource::stop()
327 | {
328 | if (m_thread)
329 | {
330 | m_thread->join();
331 | delete m_thread;
332 | m_thread = 0;
333 | }
334 |
335 | return true;
336 | }
337 |
338 | void RtlSdrSource::run()
339 | {
340 | IQSampleVector iqsamples;
341 |
342 | while (!m_this->m_stop_flag->load() && get_samples(&iqsamples))
343 | {
344 | m_this->m_buf->push(move(iqsamples));
345 | }
346 | }
347 |
348 | // Fetch a bunch of samples from the device.
349 | bool RtlSdrSource::get_samples(IQSampleVector *samples)
350 | {
351 | int r, n_read;
352 |
353 | if (!m_this->m_dev) {
354 | return false;
355 | }
356 |
357 | if (!samples) {
358 | return false;
359 | }
360 |
361 | std::vector buf(2 * m_this->m_block_length);
362 |
363 | r = rtlsdr_read_sync(m_this->m_dev, buf.data(), 2 * m_this->m_block_length, &n_read);
364 |
365 | if (r < 0)
366 | {
367 | m_this->m_error = "rtlsdr_read_sync failed";
368 | return false;
369 | }
370 |
371 | if (n_read != 2 *m_this-> m_block_length)
372 | {
373 | m_this->m_error = "short read, samples lost";
374 | return false;
375 | }
376 |
377 | samples->resize(m_this->m_block_length);
378 |
379 | for (int i = 0; i < m_this->m_block_length; i++)
380 | {
381 | int32_t re = buf[2*i];
382 | int32_t im = buf[2*i+1];
383 | (*samples)[i] = IQSample( (re - 128) / IQSample::value_type(128),
384 | (im - 128) / IQSample::value_type(128) );
385 | }
386 |
387 | return true;
388 | }
389 |
390 |
391 | // Return a list of supported devices.
392 | void RtlSdrSource::get_device_names(std::vector& devices)
393 | {
394 | char manufacturer[256], product[256], serial[256];
395 | int device_count = rtlsdr_get_device_count();
396 |
397 | if (device_count > 0)
398 | {
399 | devices.resize(device_count);
400 | }
401 |
402 | devices.clear();
403 |
404 | for (int i = 0; i < device_count; i++)
405 | {
406 | if (!rtlsdr_get_device_usb_strings(i, manufacturer, product, serial))
407 | {
408 | std::ostringstream name_ostr;
409 | name_ostr << manufacturer << " " << product << " " << serial;
410 | devices.push_back(name_ostr.str());
411 | }
412 | }
413 | }
414 |
415 | /* end */
416 |
--------------------------------------------------------------------------------
/sfmbase/BladeRFSource.cpp:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "util.h"
28 | #include "parsekv.h"
29 | #include "BladeRFSource.h"
30 |
31 | BladeRFSource *BladeRFSource::m_this = 0;
32 | const std::vector BladeRFSource::m_lnaGains({0, 3, 6});
33 | const std::vector BladeRFSource::m_vga1Gains({5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30});
34 | const std::vector BladeRFSource::m_vga2Gains({0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30});
35 | const std::vector BladeRFSource::m_halfbw({750000, 875000, 1250000, 1375000, 1500000, 1920000, 2500000, 2750000, 3000000, 3500000, 4375000, 5000000, 6000000, 7000000, 10000000, 14000000});
36 |
37 | // Open BladeRF device.
38 | BladeRFSource::BladeRFSource(const char *serial) :
39 | m_dev(0),
40 | m_sampleRate(1000000),
41 | m_actualSampleRate(1000000),
42 | m_frequency(300000000),
43 | m_minFrequency(300000000),
44 | m_bandwidth(1500000),
45 | m_actualBandwidth(1500000),
46 | m_lnaGain(3),
47 | m_vga1Gain(6),
48 | m_vga2Gain(5),
49 | m_thread(0)
50 | {
51 | int status;
52 | struct bladerf_devinfo info;
53 |
54 | bladerf_init_devinfo(&info);
55 |
56 | if (serial != 0)
57 | {
58 | strncpy(info.serial, serial, BLADERF_SERIAL_LENGTH - 1);
59 | info.serial[BLADERF_SERIAL_LENGTH - 1] = '\0';
60 | }
61 |
62 | status = bladerf_open_with_devinfo(&m_dev, &info);
63 |
64 | if (status == BLADERF_ERR_NODEV)
65 | {
66 | std::ostringstream err_ostr;
67 | err_ostr << "No devices available with serial=" << serial;
68 | m_error = err_ostr.str();
69 | m_dev = 0;
70 | }
71 | else if (status != 0)
72 | {
73 | std::ostringstream err_ostr;
74 | err_ostr << "Failed to open device with serial=" << serial;
75 | m_error = err_ostr.str();
76 | m_dev = 0;
77 | }
78 | else
79 | {
80 | int fpga_loaded = bladerf_is_fpga_configured(m_dev);
81 |
82 | if (fpga_loaded < 0)
83 | {
84 | std::ostringstream err_ostr;
85 | err_ostr << "Failed to check FPGA state: " << bladerf_strerror(fpga_loaded);
86 | m_error = err_ostr.str();
87 | m_dev = 0;
88 | }
89 | else if (fpga_loaded == 0)
90 | {
91 | m_error = "The device's FPGA is not loaded.";
92 | m_dev = 0;
93 | }
94 | else
95 | {
96 | if ((status = bladerf_sync_config(m_dev, BLADERF_MODULE_RX, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000)) < 0)
97 | {
98 | std::ostringstream err_ostr;
99 | err_ostr << "bladerf_sync_config failed with return code " << status;
100 | m_error = err_ostr.str();
101 | m_dev = 0;
102 | }
103 | else
104 | {
105 | if ((status = bladerf_enable_module(m_dev, BLADERF_MODULE_RX, true)) < 0)
106 | {
107 | std::ostringstream err_ostr;
108 | err_ostr << "bladerf_enable_module failed with return code " << status;
109 | m_error = err_ostr.str();
110 | m_dev = 0;
111 | }
112 | else
113 | {
114 | if (bladerf_expansion_attach(m_dev, BLADERF_XB_200) == 0)
115 | {
116 | std::cerr << "BladeRFSource::BladeRFSource: Attached XB200 extension" << std::endl;
117 |
118 | if ((status = bladerf_xb200_set_path(m_dev, BLADERF_MODULE_RX, BLADERF_XB200_MIX)) != 0)
119 | {
120 | std::cerr << "BladeRFSource::BladeRFSource: bladerf_xb200_set_path failed with return code " << status << std::endl;
121 | }
122 | else
123 | {
124 | if ((status = bladerf_xb200_set_filterbank(m_dev, BLADERF_MODULE_RX, BLADERF_XB200_AUTO_1DB)) != 0)
125 | {
126 | std::cerr << "BladeRFSource::BladeRFSource: bladerf_xb200_set_filterbank failed with return code " << status << std::endl;
127 | }
128 | else
129 | {
130 | std::cerr << "BladeRFSource::BladeRFSource: XB200 configured. Min freq set to 100kHz" << std::endl;
131 | m_minFrequency = 100000;
132 | }
133 | }
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
140 | std::ostringstream lgains_ostr;
141 |
142 | for (int g: m_lnaGains) {
143 | lgains_ostr << g << " ";
144 | }
145 |
146 | m_lnaGainsStr = lgains_ostr.str();
147 |
148 | std::ostringstream v1gains_ostr;
149 |
150 | for (int g: m_vga1Gains) {
151 | v1gains_ostr << g << " ";
152 | }
153 |
154 | m_vga1GainsStr = v1gains_ostr.str();
155 |
156 | std::ostringstream v2gains_ostr;
157 |
158 | for (int g: m_vga2Gains) {
159 | v2gains_ostr << g << " ";
160 | }
161 |
162 | m_vga2GainsStr = v2gains_ostr.str();
163 |
164 | std::ostringstream bw_ostr;
165 |
166 | for (int b: m_halfbw) {
167 | bw_ostr << 2*b << " ";
168 | }
169 |
170 | m_bwfiltStr = bw_ostr.str();
171 |
172 | m_this = this;
173 | }
174 |
175 |
176 | // Close BladeRF device.
177 | BladeRFSource::~BladeRFSource()
178 | {
179 | if (m_dev) {
180 | bladerf_close(m_dev);
181 | }
182 |
183 | m_this = 0;
184 | }
185 |
186 | bool BladeRFSource::configure(std::string configurationStr)
187 | {
188 | namespace qi = boost::spirit::qi;
189 | std::string::iterator begin = configurationStr.begin();
190 | std::string::iterator end = configurationStr.end();
191 |
192 | uint32_t sample_rate = 1000000;
193 | uint32_t frequency = 300000000;
194 | uint32_t bandwidth = 1500000;
195 | int lnaGainIndex = 2; // 3 dB
196 | int vga1Gain = 20;
197 | int vga2Gain = 9;
198 |
199 | parsekv::key_value_sequence p;
200 | parsekv::pairs_type m;
201 |
202 | if (!qi::parse(begin, end, p, m))
203 | {
204 | m_error = "Configuration parsing failed\n";
205 | return false;
206 | }
207 | else
208 | {
209 | if (m.find("srate") != m.end())
210 | {
211 | std::cerr << "BladeRFSource::configure: srate: " << m["srate"] << std::endl;
212 | sample_rate = atoi(m["srate"].c_str());
213 |
214 | if ((sample_rate < 48000) || (sample_rate > 40000000))
215 | {
216 | m_error = "Invalid sample rate";
217 | return false;
218 | }
219 | }
220 |
221 | if (m.find("freq") != m.end())
222 | {
223 | std::cerr << "BladeRFSource::configure: freq: " << m["freq"] << std::endl;
224 | frequency = atoi(m["freq"].c_str());
225 |
226 | if ((frequency < m_minFrequency) || (frequency > 3800000000))
227 | {
228 | m_error = "Invalid frequency";
229 | return false;
230 | }
231 | }
232 |
233 | if (m.find("bw") != m.end())
234 | {
235 | std::cerr << "BladeRFSource::configure: bw: " << m["bw"] << std::endl;
236 |
237 | if (strcasecmp(m["bw"].c_str(), "list") == 0)
238 | {
239 | m_error = "Available bandwidths (Hz): " + m_bwfiltStr;
240 | return false;
241 | }
242 |
243 | bandwidth = atoi(m["bw"].c_str());
244 |
245 | if (bandwidth < 0)
246 | {
247 | m_error = "Invalid bandwidth";
248 | return false;
249 | }
250 | }
251 |
252 | if (m.find("v1gain") != m.end())
253 | {
254 | std::cerr << "BladeRFSource::configure: v1gain: " << m["v1gain"] << std::endl;
255 |
256 | if (strcasecmp(m["v1gain"].c_str(), "list") == 0)
257 | {
258 | m_error = "Available VGA1 gains (dB): " + m_vga1GainsStr;
259 | return false;
260 | }
261 |
262 | vga1Gain = atoi(m["v1gain"].c_str());
263 |
264 | if (find(m_vga1Gains.begin(), m_vga1Gains.end(), vga1Gain) == m_vga1Gains.end())
265 | {
266 | m_error = "VGA1 gain not supported. Available gains (dB): " + m_vga1GainsStr;
267 | return false;
268 | }
269 | }
270 |
271 | if (m.find("v2gain") != m.end())
272 | {
273 | std::cerr << "BladeRFSource::configure: v2gain: " << m["v2gain"] << std::endl;
274 |
275 | if (strcasecmp(m["v2gain"].c_str(), "list") == 0)
276 | {
277 | m_error = "Available VGA2 gains (dB): " + m_vga2GainsStr;
278 | return false;
279 | }
280 |
281 | vga1Gain = atoi(m["v2gain"].c_str());
282 |
283 | if (find(m_vga2Gains.begin(), m_vga2Gains.end(), vga2Gain) == m_vga2Gains.end())
284 | {
285 | m_error = "VGA2 gain not supported. Available gains (dB): " + m_vga2GainsStr;
286 | return false;
287 | }
288 | }
289 |
290 | if (m.find("lgain") != m.end())
291 | {
292 | std::cerr << "BladeRFSource::configure: lgain: " << m["lgain"] << std::endl;
293 |
294 | if (strcasecmp(m["lgain"].c_str(), "list") == 0)
295 | {
296 | m_error = "Available LNA gains (dB): " + m_lnaGainsStr;
297 | return false;
298 | }
299 |
300 | int lnaGain = atoi(m["lgain"].c_str());
301 | uint32_t i;
302 |
303 | for (i = 0; i < m_lnaGains.size(); i++)
304 | {
305 | if (m_lnaGains[i] == lnaGain)
306 | {
307 | lnaGainIndex = i+1;
308 | break;
309 | }
310 | }
311 |
312 | if (i == m_lnaGains.size())
313 | {
314 | m_error = "Invalid LNA gain";
315 | return false;
316 | }
317 | }
318 |
319 | // Intentionally tune at a higher frequency to avoid DC offset.
320 | m_confFreq = frequency;
321 | double tuner_freq = frequency + 0.25 * sample_rate;
322 |
323 | return configure(sample_rate, tuner_freq, bandwidth, lnaGainIndex, vga1Gain, vga2Gain);
324 | }
325 | }
326 |
327 | // Configure RTL-SDR tuner and prepare for streaming.
328 | bool BladeRFSource::configure(uint32_t sample_rate,
329 | uint32_t frequency,
330 | uint32_t bandwidth,
331 | int lna_gainIndex,
332 | int vga1_gain,
333 | int vga2_gain)
334 | {
335 | m_frequency = frequency;
336 | m_vga1Gain = vga1_gain;
337 | m_vga2Gain = vga2_gain;
338 | m_lnaGain = m_lnaGains[lna_gainIndex-1];
339 |
340 | if (bladerf_set_sample_rate(m_dev, BLADERF_MODULE_RX, sample_rate, &m_actualSampleRate) < 0)
341 | {
342 | m_error = "Cannot set sample rate";
343 | return false;
344 | }
345 |
346 | if (bladerf_set_frequency( m_dev, BLADERF_MODULE_RX, frequency ) != 0)
347 | {
348 | m_error = "Cannot set Rx frequency";
349 | return false;
350 | }
351 |
352 | if (bladerf_set_bandwidth(m_dev, BLADERF_MODULE_RX, bandwidth, &m_actualBandwidth) < 0)
353 | {
354 | m_error = "Cannot set Rx bandwidth";
355 | return false;
356 | }
357 |
358 | if (bladerf_set_lna_gain(m_dev, static_cast(lna_gainIndex)) != 0)
359 | {
360 | m_error = "Cannot set LNA gain";
361 | return false;
362 | }
363 |
364 | if (bladerf_set_rxvga1(m_dev, vga1_gain) != 0)
365 | {
366 | m_error = "Cannot set VGA1 gain";
367 | return false;
368 | }
369 |
370 | if (bladerf_set_rxvga2(m_dev, vga2_gain) != 0)
371 | {
372 | m_error = "Cannot set VGA2 gain";
373 | return false;
374 | }
375 |
376 | return true;
377 | }
378 |
379 |
380 | // Return current sample frequency in Hz.
381 | uint32_t BladeRFSource::get_sample_rate()
382 | {
383 | return m_actualSampleRate;
384 | }
385 |
386 | // Return device current center frequency in Hz.
387 | uint32_t BladeRFSource::get_frequency()
388 | {
389 | return static_cast(m_frequency);
390 | }
391 |
392 | void BladeRFSource::print_specific_parms()
393 | {
394 | fprintf(stderr, "Bandwidth: %d\n", m_actualBandwidth);
395 | fprintf(stderr, "LNA gain: %d\n", m_lnaGain);
396 | fprintf(stderr, "VGA1 gain: %d\n", m_vga1Gain);
397 | fprintf(stderr, "VGA2 gain: %d\n", m_vga2Gain);
398 | }
399 |
400 | bool BladeRFSource::start(DataBuffer* buf, std::atomic_bool *stop_flag)
401 | {
402 | m_buf = buf;
403 | m_stop_flag = stop_flag;
404 |
405 | if (m_thread == 0)
406 | {
407 | m_thread = new std::thread(run);
408 | return true;
409 | }
410 | else
411 | {
412 | m_error = "Source thread already started";
413 | return false;
414 | }
415 | }
416 |
417 | bool BladeRFSource::stop()
418 | {
419 | if (m_thread)
420 | {
421 | m_thread->join();
422 | delete m_thread;
423 | m_thread = 0;
424 | }
425 |
426 | return true;
427 | }
428 |
429 | void BladeRFSource::run()
430 | {
431 | IQSampleVector iqsamples;
432 |
433 | while (!m_this->m_stop_flag->load() && get_samples(&iqsamples))
434 | {
435 | m_this->m_buf->push(move(iqsamples));
436 | }
437 | }
438 |
439 | // Fetch a bunch of samples from the device.
440 | bool BladeRFSource::get_samples(IQSampleVector *samples)
441 | {
442 | int res;
443 | std::vector buf(2*m_blockSize);
444 |
445 | if ((res = bladerf_sync_rx(m_this->m_dev, buf.data(), m_blockSize, 0, 10000)) < 0)
446 | {
447 | m_this->m_error = "bladerf_sync_rx failed";
448 | return false;
449 | }
450 |
451 | samples->resize(m_blockSize);
452 |
453 | for (int i = 0; i < m_blockSize; i++)
454 | {
455 | int32_t re = buf[2*i];
456 | int32_t im = buf[2*i+1];
457 | (*samples)[i] = IQSample( re / IQSample::value_type(1<<11),
458 | im / IQSample::value_type(1<<11) );
459 | }
460 |
461 | return true;
462 | }
463 |
464 |
465 | // Return a list of supported devices.
466 | void BladeRFSource::get_device_names(std::vector& devices)
467 | {
468 | struct bladerf_devinfo *devinfo = 0;
469 |
470 | int count = bladerf_get_device_list(&devinfo);
471 |
472 | for (int i = 0; i < count; i++)
473 | {
474 | devices.push_back(std::string(devinfo[i].serial));
475 | }
476 |
477 | if (devinfo)
478 | {
479 | bladerf_free_device_list(devinfo);
480 | }
481 | }
482 |
483 | /* end */
484 |
--------------------------------------------------------------------------------
/sfmbase/FmDecode.cpp:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #include
20 | #include
21 |
22 | #include "fastatan2.h"
23 | #include "FmDecode.h"
24 |
25 |
26 |
27 | /** Fast approximation of atan function. */
28 | static inline Sample fast_atan(Sample x)
29 | {
30 | // http://stackoverflow.com/questions/7378187/approximating-inverse-trigonometric-funcions
31 |
32 | Sample y = 1;
33 | Sample p = 0;
34 |
35 | if (x < 0) {
36 | x = -x;
37 | y = -1;
38 | }
39 |
40 | if (x > 1) {
41 | p = y;
42 | y = -y;
43 | x = 1 / x;
44 | }
45 |
46 | const Sample b = 0.596227;
47 | y *= (b*x + x*x) / (1 + 2*b*x + x*x);
48 |
49 | return (y + p) * Sample(M_PI_2);
50 | }
51 |
52 |
53 | /** Compute RMS level over a small prefix of the specified sample vector. */
54 | static IQSample::value_type rms_level_approx(const IQSampleVector& samples)
55 | {
56 | unsigned int n = samples.size();
57 | n = (n + 63) / 64;
58 |
59 | IQSample::value_type level = 0;
60 | for (unsigned int i = 0; i < n; i++) {
61 | const IQSample& s = samples[i];
62 | IQSample::value_type re = s.real(), im = s.imag();
63 | level += re * re + im * im;
64 | }
65 |
66 | return sqrt(level / n);
67 | }
68 |
69 |
70 | /* **************** class PhaseDiscriminator **************** */
71 |
72 | // Construct phase discriminator.
73 | PhaseDiscriminator::PhaseDiscriminator(double max_freq_dev)
74 | : m_freq_scale_factor(1.0 / (max_freq_dev * 2.0 * M_PI))
75 | { }
76 |
77 |
78 | // Process samples.
79 | void PhaseDiscriminator::process(const IQSampleVector& samples_in,
80 | SampleVector& samples_out)
81 | {
82 | unsigned int n = samples_in.size();
83 | IQSample s0 = m_last1_sample;
84 |
85 | samples_out.resize(n);
86 |
87 | for (unsigned int i = 0; i < n; i++) {
88 | IQSample s1(samples_in[i]);
89 | IQSample d(conj(s0) * s1);
90 | //Sample w = atan2(d.imag(), d.real());
91 | Sample w = fastatan2(d.imag(), d.real()); // fast approximation of atan2
92 | samples_out[i] = w * m_freq_scale_factor;
93 | s0 = s1;
94 | }
95 |
96 | m_last2_sample = m_last1_sample;
97 | m_last1_sample = s0;
98 | }
99 |
100 |
101 | /* **************** class PilotPhaseLock **************** */
102 |
103 | // Construct phase-locked loop.
104 | PilotPhaseLock::PilotPhaseLock(double freq, double bandwidth, double minsignal)
105 | {
106 | /*
107 | * This is a type-2, 4th order phase-locked loop.
108 | *
109 | * Open-loop transfer function:
110 | * G(z) = K * (z - q1) / ((z - p1) * (z - p2) * (z - 1) * (z - 1))
111 | * K = 3.788 * (bandwidth * 2 * Pi)**3
112 | * q1 = exp(-0.1153 * bandwidth * 2*Pi)
113 | * p1 = exp(-1.146 * bandwidth * 2*Pi)
114 | * p2 = exp(-5.331 * bandwidth * 2*Pi)
115 | *
116 | * I don't understand what I'm doing; hopefully it will work.
117 | */
118 |
119 | // Set min/max locking frequencies.
120 | m_minfreq = (freq - bandwidth) * 2.0 * M_PI;
121 | m_maxfreq = (freq + bandwidth) * 2.0 * M_PI;
122 |
123 | // Set valid signal threshold.
124 | m_minsignal = minsignal;
125 | m_lock_delay = int(20.0 / bandwidth);
126 | m_lock_cnt = 0;
127 | m_pilot_level = 0;
128 |
129 | // Create 2nd order filter for I/Q representation of phase error.
130 | // Filter has two poles, unit DC gain.
131 | double p1 = exp(-1.146 * bandwidth * 2.0 * M_PI);
132 | double p2 = exp(-5.331 * bandwidth * 2.0 * M_PI);
133 | m_phasor_a1 = - p1 - p2;
134 | m_phasor_a2 = p1 * p2;
135 | m_phasor_b0 = 1 + m_phasor_a1 + m_phasor_a2;
136 |
137 | // Create loop filter to stabilize the loop.
138 | double q1 = exp(-0.1153 * bandwidth * 2.0 * M_PI);
139 | m_loopfilter_b0 = 0.62 * bandwidth * 2.0 * M_PI;
140 | m_loopfilter_b1 = - m_loopfilter_b0 * q1;
141 |
142 | // After the loop filter, the phase error is integrated to produce
143 | // the frequency. Then the frequency is integrated to produce the phase.
144 | // These integrators form the two remaining poles, both at z = 1.
145 |
146 | // Initialize frequency and phase.
147 | m_freq = freq * 2.0 * M_PI;
148 | m_phase = 0;
149 |
150 | m_phasor_i1 = 0;
151 | m_phasor_i2 = 0;
152 | m_phasor_q1 = 0;
153 | m_phasor_q2 = 0;
154 | m_loopfilter_x1 = 0;
155 |
156 | // Initialize PPS generator.
157 | m_pilot_periods = 0;
158 | m_pps_cnt = 0;
159 | m_sample_cnt = 0;
160 | }
161 |
162 |
163 | // Process samples.
164 | void PilotPhaseLock::process(const SampleVector& samples_in,
165 | SampleVector& samples_out)
166 | {
167 | unsigned int n = samples_in.size();
168 |
169 | samples_out.resize(n);
170 |
171 | bool was_locked = (m_lock_cnt >= m_lock_delay);
172 | m_pps_events.clear();
173 |
174 | if (n > 0)
175 | m_pilot_level = 1000.0;
176 |
177 | for (unsigned int i = 0; i < n; i++) {
178 |
179 | // Generate locked pilot tone.
180 | Sample psin = sin(m_phase);
181 | Sample pcos = cos(m_phase);
182 |
183 | // Generate double-frequency output.
184 | // sin(2*x) = 2 * sin(x) * cos(x)
185 | samples_out[i] = 2 * psin * pcos;
186 |
187 | // Multiply locked tone with input.
188 | Sample x = samples_in[i];
189 | Sample phasor_i = psin * x;
190 | Sample phasor_q = pcos * x;
191 |
192 | // Run IQ phase error through low-pass filter.
193 | phasor_i = m_phasor_b0 * phasor_i
194 | - m_phasor_a1 * m_phasor_i1
195 | - m_phasor_a2 * m_phasor_i2;
196 | phasor_q = m_phasor_b0 * phasor_q
197 | - m_phasor_a1 * m_phasor_q1
198 | - m_phasor_a2 * m_phasor_q2;
199 | m_phasor_i2 = m_phasor_i1;
200 | m_phasor_i1 = phasor_i;
201 | m_phasor_q2 = m_phasor_q1;
202 | m_phasor_q1 = phasor_q;
203 |
204 | // Convert I/Q ratio to estimate of phase error.
205 | Sample phase_err;
206 | if (phasor_i > abs(phasor_q)) {
207 | // We are within +/- 45 degrees from lock.
208 | // Use simple linear approximation of arctan.
209 | phase_err = phasor_q / phasor_i;
210 | } else if (phasor_q > 0) {
211 | // We are lagging more than 45 degrees behind the input.
212 | phase_err = 1;
213 | } else {
214 | // We are more than 45 degrees ahead of the input.
215 | phase_err = -1;
216 | }
217 |
218 | // Detect pilot level (conservative).
219 | m_pilot_level = std::min(m_pilot_level, phasor_i);
220 |
221 | // Run phase error through loop filter and update frequency estimate.
222 | m_freq += m_loopfilter_b0 * phase_err
223 | + m_loopfilter_b1 * m_loopfilter_x1;
224 | m_loopfilter_x1 = phase_err;
225 |
226 | // Limit frequency to allowable range.
227 | m_freq = std::max(m_minfreq, std::min(m_maxfreq, m_freq));
228 |
229 | // Update locked phase.
230 | m_phase += m_freq;
231 | if (m_phase > 2.0 * M_PI) {
232 | m_phase -= 2.0 * M_PI;
233 | m_pilot_periods++;
234 |
235 | // Generate pulse-per-second.
236 | if (m_pilot_periods == pilot_frequency) {
237 | m_pilot_periods = 0;
238 | if (was_locked) {
239 | struct PpsEvent ev;
240 | ev.pps_index = m_pps_cnt;
241 | ev.sample_index = m_sample_cnt + i;
242 | ev.block_position = double(i) / double(n);
243 | m_pps_events.push_back(ev);
244 | m_pps_cnt++;
245 | }
246 | }
247 | }
248 | }
249 |
250 | // Update lock status.
251 | if (2 * m_pilot_level > m_minsignal) {
252 | if (m_lock_cnt < m_lock_delay)
253 | m_lock_cnt += n;
254 | } else {
255 | m_lock_cnt = 0;
256 | }
257 |
258 | // Drop PPS events when pilot not locked.
259 | if (m_lock_cnt < m_lock_delay) {
260 | m_pilot_periods = 0;
261 | m_pps_cnt = 0;
262 | m_pps_events.clear();
263 | }
264 |
265 | // Update sample counter.
266 | m_sample_cnt += n;
267 | }
268 |
269 |
270 | /* **************** class FmDecoder **************** */
271 |
272 | FmDecoder::FmDecoder(double sample_rate_if,
273 | double tuning_offset,
274 | double sample_rate_pcm,
275 | bool stereo,
276 | double deemphasis,
277 | double bandwidth_if,
278 | double freq_dev,
279 | double bandwidth_pcm,
280 | unsigned int downsample)
281 |
282 | // Initialize member fields
283 | : m_sample_rate_if(sample_rate_if)
284 | , m_sample_rate_baseband(sample_rate_if / downsample)
285 | , m_tuning_table_size(64)
286 | , m_tuning_shift(lrint(-64.0 * tuning_offset / sample_rate_if))
287 | , m_freq_dev(freq_dev)
288 | , m_downsample(downsample)
289 | , m_stereo_enabled(stereo)
290 | , m_stereo_detected(false)
291 | , m_if_level(0)
292 | , m_baseband_mean(0)
293 | , m_baseband_level(0)
294 |
295 | // Construct FineTuner
296 | , m_finetuner(m_tuning_table_size, m_tuning_shift)
297 |
298 | // Construct LowPassFilterFirIQ
299 | , m_iffilter(10, bandwidth_if / sample_rate_if)
300 |
301 | // Construct PhaseDiscriminator
302 | , m_phasedisc(freq_dev / sample_rate_if)
303 |
304 | // Construct DownsampleFilter for baseband
305 | , m_resample_baseband(8 * downsample, 0.4 / downsample, downsample, true)
306 |
307 | // Construct PilotPhaseLock
308 | , m_pilotpll(pilot_freq / m_sample_rate_baseband, // freq
309 | 50 / m_sample_rate_baseband, // bandwidth
310 | 0.01) // minsignal (was 0.04)
311 |
312 | // Construct DownsampleFilter for mono channel
313 | , m_resample_mono(
314 | int(m_sample_rate_baseband / 1000.0), // filter_order
315 | bandwidth_pcm / m_sample_rate_baseband, // cutoff
316 | m_sample_rate_baseband / sample_rate_pcm, // downsample
317 | false) // integer_factor
318 |
319 | // Construct DownsampleFilter for stereo channel
320 | , m_resample_stereo(
321 | int(m_sample_rate_baseband / 1000.0), // filter_order
322 | bandwidth_pcm / m_sample_rate_baseband, // cutoff
323 | m_sample_rate_baseband / sample_rate_pcm, // downsample
324 | false) // integer_factor
325 |
326 | // Construct HighPassFilterIir
327 | , m_dcblock_mono(30.0 / sample_rate_pcm)
328 | , m_dcblock_stereo(30.0 / sample_rate_pcm)
329 |
330 | // Construct LowPassFilterRC
331 | , m_deemph_mono(
332 | (deemphasis == 0) ? 1.0 : (deemphasis * sample_rate_pcm * 1.0e-6))
333 | , m_deemph_stereo(
334 | (deemphasis == 0) ? 1.0 : (deemphasis * sample_rate_pcm * 1.0e-6))
335 |
336 | {
337 | // nothing more to do
338 | }
339 |
340 |
341 | void FmDecoder::process(const IQSampleVector& samples_in,
342 | SampleVector& audio)
343 | {
344 | // Fine tuning.
345 | m_finetuner.process(samples_in, m_buf_iftuned);
346 |
347 | // Low pass filter to isolate station.
348 | m_iffilter.process(m_buf_iftuned, m_buf_iffiltered);
349 |
350 | // Measure IF level.
351 | double if_rms = rms_level_approx(m_buf_iffiltered);
352 | m_if_level = 0.95 * m_if_level + 0.05 * if_rms;
353 |
354 | // Extract carrier frequency.
355 | m_phasedisc.process(m_buf_iffiltered, m_buf_baseband);
356 |
357 | // Downsample baseband signal to reduce processing.
358 | if (m_downsample > 1) {
359 | SampleVector tmp(move(m_buf_baseband));
360 | m_resample_baseband.process(tmp, m_buf_baseband);
361 | }
362 |
363 | // Measure baseband level.
364 | double baseband_mean, baseband_rms;
365 | samples_mean_rms(m_buf_baseband, baseband_mean, baseband_rms);
366 | m_baseband_mean = 0.95 * m_baseband_mean + 0.05 * baseband_mean;
367 | m_baseband_level = 0.95 * m_baseband_level + 0.05 * baseband_rms;
368 |
369 | // Extract mono audio signal.
370 | m_resample_mono.process(m_buf_baseband, m_buf_mono);
371 |
372 | // DC blocking
373 | m_dcblock_mono.process_inplace(m_buf_mono);
374 |
375 | if (m_stereo_enabled)
376 | {
377 | // Lock on stereo pilot.
378 | m_pilotpll.process(m_buf_baseband, m_buf_rawstereo);
379 | m_stereo_detected = m_pilotpll.locked();
380 |
381 | // Demodulate stereo signal.
382 | demod_stereo(m_buf_baseband, m_buf_rawstereo);
383 |
384 | // Extract audio and downsample.
385 | // NOTE: This MUST be done even if no stereo signal is detected yet,
386 | // because the downsamplers for mono and stereo signal must be
387 | // kept in sync.
388 | m_resample_stereo.process(m_buf_rawstereo, m_buf_stereo);
389 |
390 | // DC blocking
391 | m_dcblock_stereo.process_inplace(m_buf_stereo);
392 |
393 | if (m_stereo_detected)
394 | {
395 | // Extract left/right channels from (L+R) / (L-R) signals.
396 | stereo_to_left_right(m_buf_mono, m_buf_stereo, audio);
397 | m_deemph_stereo.process_interleaved_inplace(audio); // L and R de-emphasis.
398 | }
399 | else
400 | {
401 | m_deemph_mono.process_inplace(m_buf_mono); // De-emphasis.
402 | // Duplicate mono signal in left/right channels.
403 | mono_to_left_right(m_buf_mono, audio);
404 | }
405 | }
406 | else
407 | {
408 | m_deemph_mono.process_inplace(m_buf_mono); // De-emphasis.
409 | // Just return mono channel.
410 | audio = move(m_buf_mono);
411 | }
412 | }
413 |
414 |
415 | // Demodulate stereo L-R signal.
416 | void FmDecoder::demod_stereo(const SampleVector& samples_baseband,
417 | SampleVector& samples_rawstereo)
418 | {
419 | // Just multiply the baseband signal with the double-frequency pilot.
420 | // And multiply by 1.17 to get the full amplitude.
421 | // That's all.
422 |
423 | unsigned int n = samples_baseband.size();
424 | assert(n == samples_rawstereo.size());
425 |
426 | for (unsigned int i = 0; i < n; i++) {
427 | samples_rawstereo[i] *= 1.17 * samples_baseband[i];
428 | }
429 | }
430 |
431 |
432 | // Duplicate mono signal in left/right channels.
433 | void FmDecoder::mono_to_left_right(const SampleVector& samples_mono,
434 | SampleVector& audio)
435 | {
436 | unsigned int n = samples_mono.size();
437 |
438 | audio.resize(2*n);
439 | for (unsigned int i = 0; i < n; i++) {
440 | Sample m = samples_mono[i];
441 | audio[2*i] = m;
442 | audio[2*i+1] = m;
443 | }
444 | }
445 |
446 |
447 | // Extract left/right channels from (L+R) / (L-R) signals.
448 | void FmDecoder::stereo_to_left_right(const SampleVector& samples_mono,
449 | const SampleVector& samples_stereo,
450 | SampleVector& audio)
451 | {
452 | unsigned int n = samples_mono.size();
453 | assert(n == samples_stereo.size());
454 |
455 | audio.resize(2*n);
456 | for (unsigned int i = 0; i < n; i++) {
457 | Sample m = samples_mono[i];
458 | Sample s = samples_stereo[i];
459 | audio[2*i] = m + s;
460 | audio[2*i+1] = m - s;
461 | }
462 | }
463 |
464 | /* end */
465 |
--------------------------------------------------------------------------------
/sfmbase/Filter.cpp:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 |
25 | #include "Filter.h"
26 |
27 |
28 |
29 | /** Prepare Lanczos FIR filter coefficients. */
30 | template
31 | static void make_lanczos_coeff(unsigned int filter_order, double cutoff,
32 | std::vector& coeff)
33 | {
34 | coeff.resize(filter_order + 1);
35 |
36 | // Prepare Lanczos FIR filter.
37 | // t[i] = (i - order/2)
38 | // coeff[i] = Sinc(2 * cutoff * t[i]) * Sinc(t[i] / (order/2 + 1))
39 | // coeff /= sum(coeff)
40 |
41 | double ysum = 0.0;
42 |
43 | // Calculate filter kernel.
44 | for (int i = 0; i <= (int)filter_order; i++) {
45 | int t2 = 2 * i - filter_order;
46 |
47 | double y;
48 | if (t2 == 0) {
49 | y = 1.0;
50 | } else {
51 | double x1 = cutoff * t2;
52 | double x2 = t2 / double(filter_order + 2);
53 | y = ( sin(M_PI * x1) / M_PI / x1 ) *
54 | ( sin(M_PI * x2) / M_PI / x2 );
55 | }
56 |
57 | coeff[i] = y;
58 | ysum += y;
59 | }
60 |
61 | // Apply correction factor to ensure unit gain at DC.
62 | for (unsigned i = 0; i <= filter_order; i++) {
63 | coeff[i] /= ysum;
64 | }
65 | }
66 |
67 |
68 | /* **************** class FineTuner **************** */
69 |
70 | // Construct finetuner.
71 | FineTuner::FineTuner(unsigned int table_size, int freq_shift)
72 | : m_index(0)
73 | , m_table(table_size)
74 | {
75 | double phase_step = 2.0 * M_PI / double(table_size);
76 | for (unsigned int i = 0; i < table_size; i++) {
77 | double phi = (((int64_t)freq_shift * i) % table_size) * phase_step;
78 | double pcos = cos(phi);
79 | double psin = sin(phi);
80 | m_table[i] = IQSample(pcos, psin);
81 | }
82 | }
83 |
84 |
85 | // Process samples.
86 | void FineTuner::process(const IQSampleVector& samples_in,
87 | IQSampleVector& samples_out)
88 | {
89 | unsigned int tblidx = m_index;
90 | unsigned int tblsiz = m_table.size();
91 | unsigned int n = samples_in.size();
92 |
93 | samples_out.resize(n);
94 |
95 | for (unsigned int i = 0; i < n; i++) {
96 | samples_out[i] = samples_in[i] * m_table[tblidx];
97 | tblidx++;
98 | if (tblidx == tblsiz)
99 | tblidx = 0;
100 | }
101 |
102 | m_index = tblidx;
103 | }
104 |
105 |
106 | /* **************** class LowPassFilterFirIQ **************** */
107 |
108 | // Construct low-pass filter.
109 | LowPassFilterFirIQ::LowPassFilterFirIQ(unsigned int filter_order, double cutoff)
110 | : m_state(filter_order)
111 | {
112 | make_lanczos_coeff(filter_order, cutoff, m_coeff);
113 | }
114 |
115 |
116 | // Process samples.
117 | void LowPassFilterFirIQ::process(const IQSampleVector& samples_in,
118 | IQSampleVector& samples_out)
119 | {
120 | unsigned int order = m_state.size();
121 | unsigned int n = samples_in.size();
122 |
123 | samples_out.resize(n);
124 |
125 | if (n == 0)
126 | return;
127 |
128 | // NOTE: We use m_coeff the wrong way around because it is slightly
129 | // faster to scan forward through the array. The result is still correct
130 | // because the coefficients are symmetric.
131 |
132 | // The first few samples need data from m_state.
133 | unsigned int i = 0;
134 | for (; i < n && i < order; i++) {
135 | IQSample y = 0;
136 | for (unsigned int j = 0; j < order - i; j++)
137 | y += m_state[i+j] * m_coeff[j];
138 | for (unsigned int j = order - i; j <= order; j++)
139 | y += samples_in[i-order+j] * m_coeff[j];
140 | samples_out[i] = y;
141 | }
142 |
143 | // Remaining samples only need data from samples_in.
144 | for (; i < n; i++) {
145 | IQSample y = 0;
146 | IQSampleVector::const_iterator inp = samples_in.begin() + i - order;
147 | for (unsigned int j = 0; j <= order; j++)
148 | y += inp[j] * m_coeff[j];
149 | samples_out[i] = y;
150 | }
151 |
152 | // Update m_state.
153 | if (n < order) {
154 | copy(m_state.begin() + n, m_state.end(), m_state.begin());
155 | copy(samples_in.begin(), samples_in.end(), m_state.end() - n);
156 | } else {
157 | copy(samples_in.end() - order, samples_in.end(), m_state.begin());
158 | }
159 | }
160 |
161 |
162 | /* **************** class DownsampleFilter **************** */
163 |
164 | // Construct low-pass filter with optional downsampling.
165 | DownsampleFilter::DownsampleFilter(unsigned int filter_order, double cutoff,
166 | double downsample, bool integer_factor)
167 | : m_downsample(downsample)
168 | , m_downsample_int(integer_factor ? lrint(downsample) : 0)
169 | , m_pos_int(0)
170 | , m_pos_frac(0)
171 | , m_state(filter_order)
172 | {
173 | assert(downsample >= 1);
174 | assert(filter_order > 1);
175 |
176 | // Force the first coefficient to zero and append an extra zero at the
177 | // end of the array. This ensures we can always obtain (filter_order+1)
178 | // coefficients by linear interpolation between adjacent array elements.
179 | make_lanczos_coeff(filter_order - 1, cutoff, m_coeff);
180 | m_coeff.insert(m_coeff.begin(), 0);
181 | m_coeff.push_back(0);
182 | }
183 |
184 |
185 | // Process samples.
186 | void DownsampleFilter::process(const SampleVector& samples_in,
187 | SampleVector& samples_out)
188 | {
189 | unsigned int order = m_state.size();
190 | unsigned int n = samples_in.size();
191 |
192 | if (m_downsample_int != 0) {
193 |
194 | // Integer downsample factor, no linear interpolation.
195 | // This is relatively simple.
196 |
197 | unsigned int p = m_pos_int;
198 | unsigned int pstep = m_downsample_int;
199 |
200 | samples_out.resize((n - p + pstep - 1) / pstep);
201 |
202 | // The first few samples need data from m_state.
203 | unsigned int i = 0;
204 | for (; p < n && p < order; p += pstep, i++) {
205 | Sample y = 0;
206 | for (unsigned int j = 1; j <= p; j++)
207 | y += samples_in[p-j] * m_coeff[j];
208 | for (unsigned int j = p + 1; j <= order; j++)
209 | y += m_state[order+p-j] * m_coeff[j];
210 | samples_out[i] = y;
211 | }
212 |
213 | // Remaining samples only need data from samples_in.
214 | for (; p < n; p += pstep, i++) {
215 | Sample y = 0;
216 | for (unsigned int j = 1; j <= order; j++)
217 | y += samples_in[p-j] * m_coeff[j];
218 | samples_out[i] = y;
219 | }
220 |
221 | assert(i == samples_out.size());
222 |
223 | // Update index of start position in text sample block.
224 | m_pos_int = p - n;
225 |
226 | } else {
227 |
228 | // Fractional downsample factor via linear interpolation of
229 | // the FIR coefficient table. This is a bitch.
230 |
231 | // Estimate number of output samples we can produce in this run.
232 | Sample p = m_pos_frac;
233 | Sample pstep = m_downsample;
234 | unsigned int n_out = int(2 + n / pstep);
235 |
236 | samples_out.resize(n_out);
237 |
238 | // Produce output samples.
239 | unsigned int i = 0;
240 | Sample pf = p;
241 | unsigned int pi = int(pf);
242 | while (pi < n) {
243 | Sample k1 = pf - pi;
244 | Sample k0 = 1 - k1;
245 |
246 | Sample y = 0;
247 | for (unsigned int j = 0; j <= order; j++) {
248 | Sample k = m_coeff[j] * k0 + m_coeff[j+1] * k1;
249 | Sample s = (j <= pi) ? samples_in[pi-j] : m_state[order+pi-j];
250 | y += k * s;
251 | }
252 | samples_out[i] = y;
253 |
254 | i++;
255 | pf = p + i * pstep;
256 | pi = int(pf);
257 | }
258 |
259 | // We may overestimate the number of samples by 1 or 2.
260 | assert(i <= n_out && i + 2 >= n_out);
261 | samples_out.resize(i);
262 |
263 | // Update fractional index of start position in text sample block.
264 | // Limit to 0 to avoid catastrophic results of rounding errors.
265 | m_pos_frac = pf - n;
266 | if (m_pos_frac < 0)
267 | m_pos_frac = 0;
268 | }
269 |
270 | // Update m_state.
271 | if (n < order) {
272 | copy(m_state.begin() + n, m_state.end(), m_state.begin());
273 | copy(samples_in.begin(), samples_in.end(), m_state.end() - n);
274 | } else {
275 | copy(samples_in.end() - order, samples_in.end(), m_state.begin());
276 | }
277 |
278 | }
279 |
280 |
281 | /* **************** class LowPassFilterRC **************** */
282 |
283 | // Construct 1st order low-pass IIR filter.
284 | LowPassFilterRC::LowPassFilterRC(double timeconst) :
285 | m_timeconst(timeconst),
286 | m_y0_1(0),
287 | m_y1_1(0)
288 | {
289 | m_a1 = - exp(-1/m_timeconst);;
290 | m_b0 = 1 + m_a1;
291 | }
292 |
293 |
294 | // Process samples.
295 | void LowPassFilterRC::process(const SampleVector& samples_in, SampleVector& samples_out)
296 | {
297 | /*
298 | * Continuous domain:
299 | * H(s) = 1 / (1 - s * timeconst)
300 | *
301 | * Discrete domain:
302 | * H(z) = (1 - exp(-1/timeconst)) / (1 - exp(-1/timeconst) / z)
303 | */
304 | unsigned int n = samples_in.size();
305 | samples_out.resize(n);
306 |
307 | Sample y = m_y0_1;
308 |
309 | for (unsigned int i = 0; i < n; i++)
310 | {
311 | Sample x = samples_in[i];
312 | y = m_b0 * x - m_a1 * y;
313 | samples_out[i] = y;
314 | }
315 |
316 | m_y0_1 = y;
317 | }
318 |
319 | // Process interleaved samples.
320 | void LowPassFilterRC::process_interleaved(const SampleVector& samples_in, SampleVector& samples_out)
321 | {
322 | /*
323 | * Continuous domain:
324 | * H(s) = 1 / (1 - s * timeconst)
325 | *
326 | * Discrete domain:
327 | * H(z) = (1 - exp(-1/timeconst)) / (1 - exp(-1/timeconst) / z)
328 | */
329 | unsigned int n = samples_in.size();
330 | samples_out.resize(n);
331 |
332 | Sample y0 = m_y0_1;
333 | Sample y1 = m_y1_1;
334 |
335 | for (unsigned int i = 0; i < n-1; i+=2)
336 | {
337 | Sample x0 = samples_in[i];
338 | y0 = m_b0 * x0 - m_a1 * y0;
339 | samples_out[i] = y0;
340 |
341 | Sample x1 = samples_in[i+1];
342 | y1 = m_b0 * x1 - m_a1 * y1;
343 | samples_out[i+1] = y1;
344 | }
345 |
346 | m_y0_1 = y0;
347 | m_y1_1 = y1;
348 | }
349 |
350 |
351 | // Process samples in-place.
352 | void LowPassFilterRC::process_inplace(SampleVector& samples)
353 | {
354 | unsigned int n = samples.size();
355 |
356 | Sample y = m_y0_1;
357 |
358 | for (unsigned int i = 0; i < n; i++)
359 | {
360 | Sample x = samples[i];
361 | y = m_b0 * x - m_a1 * y;
362 | samples[i] = y;
363 | }
364 |
365 | m_y0_1 = y;
366 | }
367 |
368 | // Process interleaved samples in-place.
369 | void LowPassFilterRC::process_interleaved_inplace(SampleVector& samples)
370 | {
371 | unsigned int n = samples.size();
372 |
373 | Sample y0 = m_y0_1;
374 | Sample y1 = m_y1_1;
375 |
376 | for (unsigned int i = 0; i < n-1; i+=2)
377 | {
378 | Sample x0 = samples[i];
379 | y0 = m_b0 * x0 - m_a1 * y0;
380 | samples[i] = y0;
381 |
382 | Sample x1 = samples[i+1];
383 | y1 = m_b0 * x1 - m_a1 * y1;
384 | samples[i+1] = y1;
385 | }
386 |
387 | m_y0_1 = y0;
388 | m_y1_1 = y1;
389 | }
390 |
391 | /* **************** class LowPassFilterIir **************** */
392 |
393 | // Construct 4th order low-pass IIR filter.
394 | LowPassFilterIir::LowPassFilterIir(double cutoff)
395 | : y1(0), y2(0), y3(0), y4(0)
396 | {
397 | typedef std::complex CDbl;
398 |
399 | // Angular cutoff frequency.
400 | double w = 2 * M_PI * cutoff;
401 |
402 | // Poles 1 and 4 are a conjugate pair, and poles 2 and 3 are another pair.
403 | // Continuous domain:
404 | // p_k = w * exp( (2*k + n - 1) / (2*n) * pi * j)
405 | CDbl p1s = w * exp((2*1 + 4 - 1) / double(2 * 4) * CDbl(0, M_PI));
406 | CDbl p2s = w * exp((2*2 + 4 - 1) / double(2 * 4) * CDbl(0, M_PI));
407 |
408 | // Map poles to discrete-domain via matched Z transform.
409 | CDbl p1z = exp(p1s);
410 | CDbl p2z = exp(p2s);
411 |
412 | // Discrete-domain transfer function:
413 | // H(z) = b0 / ( (1 - p1/z) * (1 - p4/z) * (1 - p2/z) * (1 - p3/z) )
414 | // = b0 / ( (1 - (p1+p4)/z + p1*p4/z**2) *
415 | // (1 - (p2+p3)/z + p2*p3/z**2) )
416 | // = b0 / (1 - (p1 + p4 + p2 + p3)/z
417 | // + (p1*p4 + p2*p3 + (p1+p4)*(p2+p3))/z**2
418 | // - ((p1+p4)*p2*p3 + (p2+p3)*p1*p4)/z**3
419 | // + p1*p4*p2*p3/z**4
420 | //
421 | // Note that p3 = conj(p2), p4 = conj(p1)
422 | // Therefore p1+p4 == 2*real(p1), p1*p4 == abs(p1*p1)
423 | //
424 | a1 = - (2*real(p1z) + 2*real(p2z));
425 | a2 = (abs(p1z*p1z) + abs(p2z*p2z) + 2*real(p1z) * 2*real(p2z));
426 | a3 = - (2*real(p1z) * abs(p2z*p2z) + 2*real(p2z) * abs(p1z*p1z));
427 | a4 = abs(p1z*p1z) * abs(p2z*p2z);
428 |
429 | // Choose b0 to get unit DC gain.
430 | b0 = 1 + a1 + a2 + a3 + a4;
431 | }
432 |
433 |
434 | // Process samples.
435 | void LowPassFilterIir::process(const SampleVector& samples_in,
436 | SampleVector& samples_out)
437 | {
438 | unsigned int n = samples_in.size();
439 |
440 | samples_out.resize(n);
441 |
442 | for (unsigned int i = 0; i < n; i++) {
443 | Sample x = samples_in[i];
444 | Sample y = b0 * x - a1 * y1 - a2 * y2 - a3 * y3 - a4 * y4;
445 | y4 = y3; y3 = y2; y2 = y1; y1 = y;
446 | samples_out[i] = y;
447 | }
448 | }
449 |
450 |
451 | /* **************** class HighPassFilterIir **************** */
452 |
453 | // Construct 2nd order high-pass IIR filter.
454 | HighPassFilterIir::HighPassFilterIir(double cutoff)
455 | : x1(0), x2(0), y1(0), y2(0)
456 | {
457 | typedef std::complex CDbl;
458 |
459 | // Angular cutoff frequency.
460 | double w = 2 * M_PI * cutoff;
461 |
462 | // Poles 1 and 2 are a conjugate pair.
463 | // Continuous-domain:
464 | // p_k = w / exp( (2*k + n - 1) / (2*n) * pi * j)
465 | CDbl p1s = w / exp((2*1 + 2 - 1) / double(2 * 2) * CDbl(0, M_PI));
466 |
467 | // Map poles to discrete-domain via matched Z transform.
468 | CDbl p1z = exp(p1s);
469 |
470 | // Both zeros are located in s = 0, z = 1.
471 |
472 | // Discrete-domain transfer function:
473 | // H(z) = g * (1 - 1/z) * (1 - 1/z) / ( (1 - p1/z) * (1 - p2/z) )
474 | // = g * (1 - 2/z + 1/z**2) / (1 - (p1+p2)/z + (p1*p2)/z**2)
475 | //
476 | // Note that z2 = conj(z1).
477 | // Therefore p1+p2 == 2*real(p1), p1*2 == abs(p1*p1), z4 = conj(z1)
478 | //
479 | b0 = 1;
480 | b1 = -2;
481 | b2 = 1;
482 | a1 = -2 * real(p1z);
483 | a2 = abs(p1z*p1z);
484 |
485 | // Adjust b coefficients to get unit gain at Nyquist frequency (z=-1).
486 | double g = (b0 - b1 + b2) / (1 - a1 + a2);
487 | b0 /= g;
488 | b1 /= g;
489 | b2 /= g;
490 | }
491 |
492 |
493 | // Process samples.
494 | void HighPassFilterIir::process(const SampleVector& samples_in,
495 | SampleVector& samples_out)
496 | {
497 | unsigned int n = samples_in.size();
498 |
499 | samples_out.resize(n);
500 |
501 | for (unsigned int i = 0; i < n; i++) {
502 | Sample x = samples_in[i];
503 | Sample y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
504 | x2 = x1; x1 = x;
505 | y2 = y1; y1 = y;
506 | samples_out[i] = y;
507 | }
508 | }
509 |
510 |
511 | // Process samples in-place.
512 | void HighPassFilterIir::process_inplace(SampleVector& samples)
513 | {
514 | unsigned int n = samples.size();
515 |
516 | for (unsigned int i = 0; i < n; i++) {
517 | Sample x = samples[i];
518 | Sample y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
519 | x2 = x1; x1 = x;
520 | y2 = y1; y1 = y;
521 | samples[i] = y;
522 | }
523 | }
524 |
525 | /* end */
526 |
--------------------------------------------------------------------------------
/sfmbase/HackRFSource.cpp:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #include "util.h"
27 | #include "parsekv.h"
28 | #include "HackRFSource.h"
29 |
30 | HackRFSource *HackRFSource::m_this = 0;
31 | const std::vector HackRFSource::m_lgains({0, 8, 16, 24, 32, 40});
32 | const std::vector HackRFSource::m_vgains({0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62});
33 | const std::vector HackRFSource::m_bwfilt({1750000, 2500000, 3500000, 5000000, 5500000, 6000000, 7000000, 8000000, 9000000, 10000000, 12000000, 14000000, 15000000, 20000000, 24000000, 28000000});
34 |
35 | // Open HackRF device.
36 | HackRFSource::HackRFSource(int dev_index) :
37 | m_dev(0),
38 | m_sampleRate(5000000),
39 | m_frequency(100000000),
40 | m_lnaGain(16),
41 | m_vgaGain(22),
42 | m_bandwidth(2500000),
43 | m_extAmp(false),
44 | m_biasAnt(false),
45 | m_running(false),
46 | m_thread(0)
47 | {
48 | hackrf_error rc = (hackrf_error) hackrf_init();
49 |
50 | if (rc != HACKRF_SUCCESS)
51 | {
52 | std::ostringstream err_ostr;
53 | err_ostr << "Failed to open HackRF library (" << rc << ": " << hackrf_error_name(rc) << ")";
54 | m_error = err_ostr.str();
55 | m_dev = 0;
56 | }
57 | else
58 | {
59 | hackrf_device_list_t *hackrf_devices = hackrf_device_list();
60 |
61 | rc = (hackrf_error) hackrf_device_list_open(hackrf_devices, dev_index, &m_dev);
62 |
63 | if (rc != HACKRF_SUCCESS)
64 | {
65 | std::ostringstream err_ostr;
66 | err_ostr << "Failed to open HackRF device " << dev_index << " (" << rc << ": " << hackrf_error_name(rc) << ")";
67 | m_error = err_ostr.str();
68 | m_dev = 0;
69 | }
70 | }
71 |
72 | std::ostringstream lgains_ostr;
73 |
74 | for (int g: m_lgains) {
75 | lgains_ostr << g << " ";
76 | }
77 |
78 | m_lgainsStr = lgains_ostr.str();
79 |
80 | std::ostringstream vgains_ostr;
81 |
82 | for (int g: m_vgains) {
83 | vgains_ostr << g << " ";
84 | }
85 |
86 | m_vgainsStr = vgains_ostr.str();
87 |
88 | std::ostringstream bwfilt_ostr;
89 | bwfilt_ostr << std::fixed << std::setprecision(2);
90 |
91 | for (int b: m_bwfilt) {
92 | bwfilt_ostr << b * 1e-6 << " ";
93 | }
94 |
95 | m_bwfiltStr = bwfilt_ostr.str();
96 |
97 | m_this = this;
98 | }
99 |
100 | HackRFSource::~HackRFSource()
101 | {
102 | if (m_dev) {
103 | hackrf_close(m_dev);
104 | }
105 |
106 | hackrf_error rc = (hackrf_error) hackrf_exit();
107 | std::cerr << "HackRFSource::~HackRFSource: HackRF library exit: " << rc << ": " << hackrf_error_name(rc) << std::endl;
108 |
109 | m_this = 0;
110 | }
111 |
112 | void HackRFSource::get_device_names(std::vector& devices)
113 | {
114 | hackrf_device *hackrf_ptr;
115 | read_partid_serialno_t read_partid_serialno;
116 | hackrf_error rc;
117 | int i;
118 |
119 | rc = (hackrf_error) hackrf_init();
120 |
121 | if (rc != HACKRF_SUCCESS)
122 | {
123 | std::cerr << "HackRFSource::get_device_names: Failed to open HackRF library: " << rc << ": " << hackrf_error_name(rc) << std::endl;
124 | return;
125 | }
126 |
127 | hackrf_device_list_t *hackrf_devices = hackrf_device_list();
128 |
129 | devices.clear();
130 |
131 | for (i=0; i < hackrf_devices->devicecount; i++)
132 | {
133 | rc = (hackrf_error) hackrf_device_list_open(hackrf_devices, i, &hackrf_ptr);
134 |
135 | if (rc == HACKRF_SUCCESS)
136 | {
137 | std::cerr << "HackRFSource::get_device_names: try to get device " << i << " serial number" << std::endl;
138 | rc = (hackrf_error) hackrf_board_partid_serialno_read(hackrf_ptr, &read_partid_serialno);
139 |
140 | if (rc != HACKRF_SUCCESS)
141 | {
142 | std::cerr << "HackRFSource::get_device_names: failed to get device " << i << " serial number: " << rc << ": " << hackrf_error_name(rc) << std::endl;
143 | hackrf_close(hackrf_ptr);
144 | continue;
145 | }
146 | else
147 | {
148 | std::cerr << "HackRFSource::get_device_names: device " << i << " OK" << std::endl;
149 | hackrf_close(hackrf_ptr);
150 | }
151 |
152 | uint32_t serial_msb = read_partid_serialno.serial_no[2];
153 | uint32_t serial_lsb = read_partid_serialno.serial_no[3];
154 | std::ostringstream devname_ostr;
155 |
156 | devname_ostr << "Serial " << std::hex << std::setw(8) << std::setfill('0') << serial_msb << serial_lsb;
157 | devices.push_back(devname_ostr.str());
158 | }
159 | else
160 | {
161 | std::cerr << "HackRFSource::get_device_names: failed to open device " << i << std::endl;
162 | }
163 | }
164 |
165 | hackrf_device_list_free(hackrf_devices);
166 | rc = (hackrf_error) hackrf_exit();
167 | std::cerr << "HackRFSource::get_device_names: HackRF library exit: " << rc << ": " << hackrf_error_name(rc) << std::endl;
168 | }
169 |
170 | std::uint32_t HackRFSource::get_sample_rate()
171 | {
172 | return m_sampleRate;
173 | }
174 |
175 | std::uint32_t HackRFSource::get_frequency()
176 | {
177 | return m_frequency;
178 | }
179 |
180 | void HackRFSource::print_specific_parms()
181 | {
182 | fprintf(stderr, "LNA gain: %d\n", m_lnaGain);
183 | fprintf(stderr, "VGA gain: %d\n", m_vgaGain);
184 | fprintf(stderr, "Bandwidth %d\n", m_bandwidth);
185 | fprintf(stderr, "External Amp %s\n", m_extAmp ? "enabled" : "disabled");
186 | fprintf(stderr, "Bias ant %s\n", m_biasAnt ? "enabled" : "disabled");
187 | }
188 |
189 | bool HackRFSource::configure(uint32_t sample_rate,
190 | uint32_t frequency,
191 | bool ext_amp,
192 | bool bias_ant,
193 | int lna_gain,
194 | int vga_gain,
195 | uint32_t bandwidth
196 | )
197 | {
198 | m_sampleRate = sample_rate;
199 | m_frequency = frequency;
200 | m_extAmp = ext_amp;
201 | m_biasAnt = bias_ant;
202 | m_lnaGain = lna_gain;
203 | m_vgaGain = vga_gain;
204 | m_bandwidth = bandwidth;
205 | hackrf_error rc;
206 |
207 | if (!m_dev) {
208 | return false;
209 | }
210 |
211 | rc = (hackrf_error) hackrf_set_freq(m_dev, static_cast(m_frequency));
212 |
213 | if (rc != HACKRF_SUCCESS)
214 | {
215 | std::ostringstream err_ostr;
216 | err_ostr << "Could not set center frequency to " << m_frequency << " Hz";
217 | m_error = err_ostr.str();
218 | return false;
219 | }
220 |
221 | rc = (hackrf_error) hackrf_set_sample_rate_manual(m_dev, m_sampleRate, 1);
222 |
223 | if (rc != HACKRF_SUCCESS)
224 | {
225 | std::ostringstream err_ostr;
226 | err_ostr << "Could not set center sample rate to " << m_sampleRate << " Hz";
227 | m_error = err_ostr.str();
228 | return false;
229 | }
230 |
231 | rc = (hackrf_error) hackrf_set_lna_gain(m_dev, m_lnaGain);
232 |
233 | if (rc != HACKRF_SUCCESS)
234 | {
235 | std::ostringstream err_ostr;
236 | err_ostr << "Could not set LNA gain to " << m_lnaGain << " dB";
237 | m_error = err_ostr.str();
238 | return false;
239 | }
240 |
241 | rc = (hackrf_error) hackrf_set_vga_gain(m_dev, m_vgaGain);
242 |
243 | if (rc != HACKRF_SUCCESS)
244 | {
245 | std::ostringstream err_ostr;
246 | err_ostr << "Could not set VGA gain to " << m_vgaGain << " dB";
247 | m_error = err_ostr.str();
248 | return false;
249 | }
250 |
251 | rc = (hackrf_error) hackrf_set_antenna_enable(m_dev, (m_biasAnt ? 1 : 0));
252 |
253 | if (rc != HACKRF_SUCCESS)
254 | {
255 | std::ostringstream err_ostr;
256 | err_ostr << "Could not set bias antenna to " << m_biasAnt;
257 | m_error = err_ostr.str();
258 | return false;
259 | }
260 |
261 | rc = (hackrf_error) hackrf_set_amp_enable(m_dev, (m_extAmp ? 1 : 0));
262 |
263 | if (rc != HACKRF_SUCCESS)
264 | {
265 | std::ostringstream err_ostr;
266 | err_ostr << "Could not set extra amplifier to " << m_extAmp;
267 | m_error = err_ostr.str();
268 | return false;
269 | }
270 |
271 | uint32_t hackRFBandwidth = hackrf_compute_baseband_filter_bw_round_down_lt(m_bandwidth);
272 | rc = (hackrf_error) hackrf_set_baseband_filter_bandwidth(m_dev, hackRFBandwidth);
273 |
274 | if (rc != HACKRF_SUCCESS)
275 | {
276 | std::ostringstream err_ostr;
277 | err_ostr << "Could not set bandwidth to " << hackRFBandwidth << " Hz (" << m_bandwidth << " Hz requested)";
278 | m_error = err_ostr.str();
279 | return false;
280 | }
281 |
282 | return true;
283 | }
284 |
285 | bool HackRFSource::configure(std::string configurationStr)
286 | {
287 | namespace qi = boost::spirit::qi;
288 | std::string::iterator begin = configurationStr.begin();
289 | std::string::iterator end = configurationStr.end();
290 |
291 | uint32_t sampleRate = 5000000;
292 | uint32_t frequency = 100000000;
293 | int lnaGain = 16;
294 | int vgaGain = 22;
295 | uint32_t bandwidth = 2500000;
296 | bool extAmp = false;
297 | bool antBias = false;
298 |
299 | parsekv::key_value_sequence p;
300 | parsekv::pairs_type m;
301 |
302 | if (!qi::parse(begin, end, p, m))
303 | {
304 | m_error = "Configuration parsing failed\n";
305 | return false;
306 | }
307 | else
308 | {
309 | if (m.find("srate") != m.end())
310 | {
311 | std::cerr << "HackRFSource::configure: srate: " << m["srate"] << std::endl;
312 | sampleRate = atoi(m["srate"].c_str());
313 |
314 | if ((sampleRate < 1000000) || (sampleRate > 20000000))
315 | {
316 | m_error = "Invalid sample rate";
317 | return false;
318 | }
319 | }
320 |
321 | if (m.find("freq") != m.end())
322 | {
323 | std::cerr << "HackRFSource::configure: freq: " << m["freq"] << std::endl;
324 | frequency = strtoll(m["freq"].c_str(), 0, 10);
325 |
326 | if ((frequency < 1000000) || (frequency > 6000000000))
327 | {
328 | m_error = "Invalid frequency";
329 | return false;
330 | }
331 | }
332 |
333 | if (m.find("lgain") != m.end())
334 | {
335 | std::cerr << "HackRFSource::configure: lgain: " << m["lgain"] << std::endl;
336 |
337 | if (strcasecmp(m["lgain"].c_str(), "list") == 0)
338 | {
339 | m_error = "Available LNA gains (dB): " + m_lgainsStr;
340 | return false;
341 | }
342 |
343 | lnaGain = atoi(m["lgain"].c_str());
344 |
345 | if (find(m_lgains.begin(), m_lgains.end(), lnaGain) == m_lgains.end())
346 | {
347 | m_error = "LNA gain not supported. Available gains (dB): " + m_lgainsStr;
348 | return false;
349 | }
350 | }
351 |
352 | if (m.find("vgain") != m.end())
353 | {
354 | std::cerr << "HackRFSource::configure: vgain: " << m["vgain"] << std::endl;
355 | vgaGain = atoi(m["vgain"].c_str());
356 |
357 | if (strcasecmp(m["vgain"].c_str(), "list") == 0)
358 | {
359 | m_error = "Available VGA gains (dB): " + m_vgainsStr;
360 | return false;
361 | }
362 |
363 | if (find(m_vgains.begin(), m_vgains.end(), vgaGain) == m_vgains.end())
364 | {
365 | m_error = "VGA gain not supported. Available gains (dB): " + m_vgainsStr;
366 | return false;
367 | }
368 | }
369 |
370 | if (m.find("bwfilter") != m.end())
371 | {
372 | std::cerr << "HackRFSource::configure: bwfilter: " << m["bwfilter"] << std::endl;
373 | bandwidth = atoi(m["bwfilter"].c_str());
374 |
375 | if (strcasecmp(m["bwfilter"].c_str(), "list") == 0)
376 | {
377 | m_error = "Available filter bandwidths (MHz): " + m_bwfiltStr;
378 | return false;
379 | }
380 |
381 | double tmpbwd;
382 |
383 | if (!parse_dbl(m["bwfilter"].c_str(), tmpbwd))
384 | {
385 | m_error = "Invalid filter bandwidth";
386 | return false;
387 | }
388 | else
389 | {
390 | long int tmpbwi = lrint(tmpbwd * 1000000);
391 |
392 | if (tmpbwi <= INT_MIN || tmpbwi >= INT_MAX) {
393 | m_error = "Invalid filter bandwidth";
394 | return false;
395 | }
396 | else
397 | {
398 | bandwidth = tmpbwi;
399 |
400 | if (find(m_bwfilt.begin(), m_bwfilt.end(), bandwidth) == m_bwfilt.end())
401 | {
402 | m_error = "Filter bandwidth not supported. Available bandwidths (MHz): " + m_bwfiltStr;
403 | return false;
404 | }
405 | }
406 | }
407 | }
408 |
409 | if (m.find("extamp") != m.end())
410 | {
411 | std::cerr << "HackRFSource::configure: extamp" << std::endl;
412 | extAmp = true;
413 | }
414 |
415 | if (m.find("antbias") != m.end())
416 | {
417 | std::cerr << "HackRFSource::configure: antbias" << std::endl;
418 | antBias = true;
419 | }
420 | }
421 |
422 | m_confFreq = frequency;
423 | double tuner_freq = frequency + 0.25 * sampleRate;
424 | return configure(sampleRate, tuner_freq, extAmp, antBias, lnaGain, vgaGain, bandwidth);
425 | }
426 |
427 | bool HackRFSource::start(DataBuffer *buf, std::atomic_bool *stop_flag)
428 | {
429 | m_buf = buf;
430 | m_stop_flag = stop_flag;
431 |
432 | if (m_thread == 0)
433 | {
434 | std::cerr << "HackRFSource::start: starting" << std::endl;
435 | m_running = true;
436 | m_thread = new std::thread(run, m_dev, stop_flag);
437 | sleep(1);
438 | return *this;
439 | }
440 | else
441 | {
442 | std::cerr << "HackRFSource::start: error" << std::endl;
443 | m_error = "Source thread already started";
444 | return false;
445 | }
446 | }
447 |
448 | void HackRFSource::run(hackrf_device* dev, std::atomic_bool *stop_flag)
449 | {
450 | std::cerr << "HackRFSource::run" << std::endl;
451 |
452 | hackrf_error rc = (hackrf_error) hackrf_start_rx(dev, rx_callback, 0);
453 |
454 | if (rc == HACKRF_SUCCESS)
455 | {
456 | while (!stop_flag->load() && (hackrf_is_streaming(dev) == HACKRF_TRUE))
457 | {
458 | sleep(1);
459 | }
460 |
461 | rc = (hackrf_error) hackrf_stop_rx(dev);
462 |
463 | if (rc != HACKRF_SUCCESS)
464 | {
465 | std::cerr << "HackRFSource::run: Cannot stop HackRF Rx: " << rc << ": " << hackrf_error_name(rc) << std::endl;
466 | }
467 | }
468 | else
469 | {
470 | std::cerr << "HackRFSource::run: Cannot start HackRF Rx: " << rc << ": " << hackrf_error_name(rc) << std::endl;
471 | }
472 | }
473 |
474 | bool HackRFSource::stop()
475 | {
476 | std::cerr << "HackRFSource::stop" << std::endl;
477 |
478 | m_thread->join();
479 | delete m_thread;
480 | return true;
481 | }
482 |
483 | int HackRFSource::rx_callback(hackrf_transfer* transfer)
484 | {
485 | int bytes_to_write = transfer->valid_length;
486 |
487 | if (m_this)
488 | {
489 | m_this->callback((char *) transfer->buffer, bytes_to_write);
490 | }
491 |
492 | return 0;
493 | }
494 |
495 | void HackRFSource::callback(const char* buf, int len)
496 | {
497 | IQSampleVector iqsamples;
498 |
499 | iqsamples.resize(len/2);
500 |
501 | for (int i = 0; i < len/2; i++) {
502 | int32_t re = buf[2*i];
503 | int32_t im = buf[2*i+1];
504 | iqsamples[i] = IQSample( (re - 128) / IQSample::value_type(128),
505 | (im - 128) / IQSample::value_type(128) );
506 | }
507 |
508 | m_buf->push(move(iqsamples));
509 | }
510 |
--------------------------------------------------------------------------------
/sfmbase/AirspySource.cpp:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | #include "util.h"
27 | #include "parsekv.h"
28 | #include "AirspySource.h"
29 |
30 | AirspySource *AirspySource::m_this = 0;
31 | const std::vector AirspySource::m_lgains({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14});
32 | const std::vector AirspySource::m_mgains({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
33 | const std::vector AirspySource::m_vgains({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
34 |
35 | // Open Airspy device.
36 | AirspySource::AirspySource(int dev_index) :
37 | m_dev(0),
38 | m_sampleRate(10000000),
39 | m_frequency(100000000),
40 | m_lnaGain(8),
41 | m_mixGain(8),
42 | m_vgaGain(0),
43 | m_biasAnt(false),
44 | m_lnaAGC(false),
45 | m_mixAGC(false),
46 | m_running(false),
47 | m_thread(0)
48 | {
49 | airspy_error rc = (airspy_error) airspy_init();
50 |
51 | if (rc != AIRSPY_SUCCESS)
52 | {
53 | std::ostringstream err_ostr;
54 | err_ostr << "Failed to open Airspy library (" << rc << ": " << airspy_error_name(rc) << ")";
55 | m_error = err_ostr.str();
56 | m_dev = 0;
57 | }
58 | else
59 | {
60 | for (int i = 0; i < AIRSPY_MAX_DEVICE; i++)
61 | {
62 | rc = (airspy_error) airspy_open(&m_dev);
63 |
64 | if (rc == AIRSPY_SUCCESS)
65 | {
66 | if (i == dev_index)
67 | {
68 | break;
69 | }
70 | }
71 | else
72 | {
73 | std::ostringstream err_ostr;
74 | err_ostr << "Failed to open Airspy device at sequence " << i;
75 | m_error = err_ostr.str();
76 | m_dev = 0;
77 | }
78 | }
79 | }
80 |
81 | if (m_dev)
82 | {
83 | uint32_t nbSampleRates;
84 | uint32_t *sampleRates;
85 |
86 | airspy_get_samplerates(m_dev, &nbSampleRates, 0);
87 |
88 | sampleRates = new uint32_t[nbSampleRates];
89 |
90 | airspy_get_samplerates(m_dev, sampleRates, nbSampleRates);
91 |
92 | if (nbSampleRates == 0)
93 | {
94 | m_error = "Failed to get Airspy device sample rate list";
95 | airspy_close(m_dev);
96 | m_dev = 0;
97 | }
98 | else
99 | {
100 | for (uint32_t i=0; i& devices)
167 | {
168 | airspy_device *airspy_ptr;
169 | airspy_read_partid_serialno_t read_partid_serialno;
170 | uint32_t serial_msb = 0;
171 | uint32_t serial_lsb = 0;
172 | airspy_error rc;
173 | int i;
174 |
175 | rc = (airspy_error) airspy_init();
176 |
177 | if (rc != AIRSPY_SUCCESS)
178 | {
179 | std::cerr << "AirspySource::get_device_names: Failed to open Airspy library: " << rc << ": " << airspy_error_name(rc) << std::endl;
180 | return;
181 | }
182 |
183 | for (i=0; i < AIRSPY_MAX_DEVICE; i++)
184 | {
185 | rc = (airspy_error) airspy_open(&airspy_ptr);
186 | std::cerr << "AirspySource::get_device_names: try to get device " << i << " serial number" << std::endl;
187 |
188 | if (rc == AIRSPY_SUCCESS)
189 | {
190 | std::cerr << "AirspySource::get_device_names: device " << i << " open OK" << std::endl;
191 |
192 | rc = (airspy_error) airspy_board_partid_serialno_read(airspy_ptr, &read_partid_serialno);
193 |
194 | if (rc == AIRSPY_SUCCESS)
195 | {
196 | serial_msb = read_partid_serialno.serial_no[2];
197 | serial_lsb = read_partid_serialno.serial_no[3];
198 | std::ostringstream devname_ostr;
199 | devname_ostr << "Serial " << std::hex << std::setw(8) << std::setfill('0') << serial_msb << serial_lsb;
200 | devices.push_back(devname_ostr.str());
201 | }
202 | else
203 | {
204 | std::cerr << "AirspySource::get_device_names: failed to get device " << i << " serial number: " << rc << ": " << airspy_error_name(rc) << std::endl;
205 | }
206 |
207 | airspy_close(airspy_ptr);
208 | }
209 | else
210 | {
211 | std::cerr << "AirspySource::get_device_names: enumerated " << i << " Airspy devices: " << airspy_error_name(rc) << std::endl;
212 | break; // finished
213 | }
214 | }
215 |
216 | rc = (airspy_error) airspy_exit();
217 | std::cerr << "AirspySource::get_device_names: Airspy library exit: " << rc << ": " << airspy_error_name(rc) << std::endl;
218 | }
219 |
220 | std::uint32_t AirspySource::get_sample_rate()
221 | {
222 | return m_sampleRate;
223 | }
224 |
225 | std::uint32_t AirspySource::get_frequency()
226 | {
227 | return m_frequency;
228 | }
229 |
230 | void AirspySource::print_specific_parms()
231 | {
232 | fprintf(stderr, "LNA gain: %d\n", m_lnaGain);
233 | fprintf(stderr, "Mixer gain: %d\n", m_mixGain);
234 | fprintf(stderr, "VGA gain: %d\n", m_vgaGain);
235 | fprintf(stderr, "Antenna bias %s\n", m_biasAnt ? "enabled" : "disabled");
236 | fprintf(stderr, "LNA AGC %s\n", m_lnaAGC ? "enabled" : "disabled");
237 | fprintf(stderr, "Mixer AGC %s\n", m_mixAGC ? "enabled" : "disabled");
238 | }
239 |
240 | bool AirspySource::configure(int sampleRateIndex,
241 | uint32_t frequency,
242 | bool bias_ant,
243 | int lna_gain,
244 | int mix_gain,
245 | int vga_gain,
246 | bool lna_agc,
247 | bool mix_agc
248 | )
249 | {
250 | m_frequency = frequency;
251 | m_biasAnt = bias_ant;
252 | m_lnaGain = lna_gain;
253 | m_mixGain = mix_gain;
254 | m_vgaGain = vga_gain;
255 | m_lnaAGC = lna_agc;
256 | m_mixAGC = mix_agc;
257 |
258 | airspy_error rc;
259 |
260 | if (!m_dev) {
261 | return false;
262 | }
263 |
264 | rc = (airspy_error) airspy_set_freq(m_dev, static_cast(m_frequency));
265 |
266 | if (rc != AIRSPY_SUCCESS)
267 | {
268 | std::ostringstream err_ostr;
269 | err_ostr << "Could not set center frequency to " << m_frequency << " Hz";
270 | m_error = err_ostr.str();
271 | return false;
272 | }
273 |
274 | rc = (airspy_error) airspy_set_samplerate(m_dev, static_cast(sampleRateIndex));
275 |
276 | if (rc != AIRSPY_SUCCESS)
277 | {
278 | std::ostringstream err_ostr;
279 | err_ostr << "Could not set center sample rate to " << m_srates[sampleRateIndex] << " Hz";
280 | m_error = err_ostr.str();
281 | return false;
282 | }
283 | else
284 | {
285 | m_sampleRate = m_srates[sampleRateIndex];
286 | }
287 |
288 | rc = (airspy_error) airspy_set_lna_gain(m_dev, m_lnaGain);
289 |
290 | if (rc != AIRSPY_SUCCESS)
291 | {
292 | std::ostringstream err_ostr;
293 | err_ostr << "Could not set LNA gain to " << m_lnaGain << " dB";
294 | m_error = err_ostr.str();
295 | return false;
296 | }
297 |
298 | rc = (airspy_error) airspy_set_mixer_gain(m_dev, m_mixGain);
299 |
300 | if (rc != AIRSPY_SUCCESS)
301 | {
302 | std::ostringstream err_ostr;
303 | err_ostr << "Could not set mixer gain to " << m_mixGain << " dB";
304 | m_error = err_ostr.str();
305 | return false;
306 | }
307 |
308 | rc = (airspy_error) airspy_set_vga_gain(m_dev, m_vgaGain);
309 |
310 | if (rc != AIRSPY_SUCCESS)
311 | {
312 | std::ostringstream err_ostr;
313 | err_ostr << "Could not set VGA gain to " << m_vgaGain << " dB";
314 | m_error = err_ostr.str();
315 | return false;
316 | }
317 |
318 | rc = (airspy_error) airspy_set_rf_bias(m_dev, (m_biasAnt ? 1 : 0));
319 |
320 | if (rc != AIRSPY_SUCCESS)
321 | {
322 | std::ostringstream err_ostr;
323 | err_ostr << "Could not set bias antenna to " << m_biasAnt;
324 | m_error = err_ostr.str();
325 | return false;
326 | }
327 |
328 | rc = (airspy_error) airspy_set_lna_agc(m_dev, (m_lnaAGC ? 1 : 0));
329 |
330 | if (rc != AIRSPY_SUCCESS)
331 | {
332 | std::ostringstream err_ostr;
333 | err_ostr << "Could not set LNA AGC to " << m_lnaAGC;
334 | m_error = err_ostr.str();
335 | return false;
336 | }
337 |
338 | rc = (airspy_error) airspy_set_mixer_agc(m_dev, (m_mixAGC ? 1 : 0));
339 |
340 | if (rc != AIRSPY_SUCCESS)
341 | {
342 | std::ostringstream err_ostr;
343 | err_ostr << "Could not set mixer AGC to " << m_mixAGC;
344 | m_error = err_ostr.str();
345 | return false;
346 | }
347 |
348 | return true;
349 | }
350 |
351 | bool AirspySource::configure(std::string configurationStr)
352 | {
353 | namespace qi = boost::spirit::qi;
354 | std::string::iterator begin = configurationStr.begin();
355 | std::string::iterator end = configurationStr.end();
356 |
357 | int sampleRateIndex = 0;
358 | uint32_t frequency = 100000000;
359 | int lnaGain = 8;
360 | int mixGain = 8;
361 | int vgaGain = 0;
362 | bool antBias = false;
363 | bool lnaAGC = false;
364 | bool mixAGC = false;
365 |
366 | parsekv::key_value_sequence p;
367 | parsekv::pairs_type m;
368 |
369 | if (!qi::parse(begin, end, p, m))
370 | {
371 | m_error = "Configuration parsing failed\n";
372 | return false;
373 | }
374 | else
375 | {
376 | if (m.find("srate") != m.end())
377 | {
378 | std::cerr << "AirspySource::configure: srate: " << m["srate"] << std::endl;
379 |
380 | if (strcasecmp(m["srate"].c_str(), "list") == 0)
381 | {
382 | m_error = "Available sample rates (Hz): " + m_sratesStr;
383 | return false;
384 | }
385 |
386 | m_sampleRate = atoi(m["srate"].c_str());
387 | uint32_t i;
388 |
389 | for (i = 0; i < m_srates.size(); i++)
390 | {
391 | if (m_srates[i] == static_cast(m_sampleRate))
392 | {
393 | sampleRateIndex = i;
394 | break;
395 | }
396 | }
397 |
398 | if (i == m_srates.size())
399 | {
400 | m_error = "Invalid sample rate";
401 | m_sampleRate = 0;
402 | return false;
403 | }
404 | }
405 |
406 | if (m.find("freq") != m.end())
407 | {
408 | std::cerr << "AirspySource::configure: freq: " << m["freq"] << std::endl;
409 | frequency = atoi(m["freq"].c_str());
410 |
411 | if ((frequency < 24000000) || (frequency > 1800000000))
412 | {
413 | m_error = "Invalid frequency";
414 | return false;
415 | }
416 | }
417 |
418 | if (m.find("lgain") != m.end())
419 | {
420 | std::cerr << "AirspySource::configure: lgain: " << m["lgain"] << std::endl;
421 |
422 | if (strcasecmp(m["lgain"].c_str(), "list") == 0)
423 | {
424 | m_error = "Available LNA gains (dB): " + m_lgainsStr;
425 | return false;
426 | }
427 |
428 | lnaGain = atoi(m["lgain"].c_str());
429 |
430 | if (find(m_lgains.begin(), m_lgains.end(), lnaGain) == m_lgains.end())
431 | {
432 | m_error = "LNA gain not supported. Available gains (dB): " + m_lgainsStr;
433 | return false;
434 | }
435 | }
436 |
437 | if (m.find("mgain") != m.end())
438 | {
439 | std::cerr << "AirspySource::configure: mgain: " << m["mgain"] << std::endl;
440 |
441 | if (strcasecmp(m["mgain"].c_str(), "list") == 0)
442 | {
443 | m_error = "Available mixer gains (dB): " + m_mgainsStr;
444 | return false;
445 | }
446 |
447 | mixGain = atoi(m["mgain"].c_str());
448 |
449 | if (find(m_mgains.begin(), m_mgains.end(), mixGain) == m_mgains.end())
450 | {
451 | m_error = "Mixer gain not supported. Available gains (dB): " + m_mgainsStr;
452 | return false;
453 | }
454 | }
455 |
456 | if (m.find("vgain") != m.end())
457 | {
458 | std::cerr << "AirspySource::configure: vgain: " << m["vgain"] << std::endl;
459 | vgaGain = atoi(m["vgain"].c_str());
460 |
461 | if (strcasecmp(m["vgain"].c_str(), "list") == 0)
462 | {
463 | m_error = "Available VGA gains (dB): " + m_vgainsStr;
464 | return false;
465 | }
466 |
467 | if (find(m_vgains.begin(), m_vgains.end(), vgaGain) == m_vgains.end())
468 | {
469 | m_error = "VGA gain not supported. Available gains (dB): " + m_vgainsStr;
470 | return false;
471 | }
472 | }
473 |
474 | if (m.find("antbias") != m.end())
475 | {
476 | std::cerr << "AirspySource::configure: antbias" << std::endl;
477 | antBias = true;
478 | }
479 |
480 | if (m.find("lagc") != m.end())
481 | {
482 | std::cerr << "AirspySource::configure: lagc" << std::endl;
483 | lnaAGC = true;
484 | }
485 |
486 | if (m.find("magc") != m.end())
487 | {
488 | std::cerr << "AirspySource::configure: magc" << std::endl;
489 | mixAGC = true;
490 | }
491 | }
492 |
493 | m_confFreq = frequency;
494 | double tuner_freq = frequency + 0.25 * m_srates[sampleRateIndex];
495 | return configure(sampleRateIndex, tuner_freq, antBias, lnaGain, mixGain, vgaGain, lnaAGC, mixAGC);
496 | }
497 |
498 | bool AirspySource::start(DataBuffer *buf, std::atomic_bool *stop_flag)
499 | {
500 | m_buf = buf;
501 | m_stop_flag = stop_flag;
502 |
503 | if (m_thread == 0)
504 | {
505 | std::cerr << "AirspySource::start: starting" << std::endl;
506 | m_running = true;
507 | m_thread = new std::thread(run, m_dev, stop_flag);
508 | sleep(1);
509 | return *this;
510 | }
511 | else
512 | {
513 | std::cerr << "AirspySource::start: error" << std::endl;
514 | m_error = "Source thread already started";
515 | return false;
516 | }
517 | }
518 |
519 | void AirspySource::run(airspy_device* dev, std::atomic_bool *stop_flag)
520 | {
521 | std::cerr << "AirspySource::run" << std::endl;
522 |
523 | airspy_error rc = (airspy_error) airspy_start_rx(dev, rx_callback, 0);
524 |
525 | if (rc == AIRSPY_SUCCESS)
526 | {
527 | while (!stop_flag->load() && (airspy_is_streaming(dev) == AIRSPY_TRUE))
528 | {
529 | sleep(1);
530 | }
531 |
532 | rc = (airspy_error) airspy_stop_rx(dev);
533 |
534 | if (rc != AIRSPY_SUCCESS)
535 | {
536 | std::cerr << "AirspySource::run: Cannot stop Airspy Rx: " << rc << ": " << airspy_error_name(rc) << std::endl;
537 | }
538 | }
539 | else
540 | {
541 | std::cerr << "AirspySource::run: Cannot start Airspy Rx: " << rc << ": " << airspy_error_name(rc) << std::endl;
542 | }
543 | }
544 |
545 | bool AirspySource::stop()
546 | {
547 | std::cerr << "AirspySource::stop" << std::endl;
548 |
549 | m_thread->join();
550 | delete m_thread;
551 | return true;
552 | }
553 |
554 | int AirspySource::rx_callback(airspy_transfer_t* transfer)
555 | {
556 | int len = transfer->sample_count * 2; // interleaved I/Q samples
557 |
558 | if (m_this)
559 | {
560 | m_this->callback((short *) transfer->samples, len);
561 | }
562 |
563 | return 0;
564 | }
565 |
566 | void AirspySource::callback(const short* buf, int len)
567 | {
568 | IQSampleVector iqsamples;
569 |
570 | iqsamples.resize(len/2);
571 |
572 | for (int i = 0, j = 0; i < len; i+=2, j++)
573 | {
574 | int32_t re = buf[i];
575 | int32_t im = buf[i+1];
576 | iqsamples[j] = IQSample( re / IQSample::value_type(1<<11), // 12 bits samples
577 | im / IQSample::value_type(1<<11) );
578 | }
579 |
580 | m_buf->push(move(iqsamples));
581 | }
582 |
--------------------------------------------------------------------------------
/main.cpp:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////////
2 | // SoftFM - Software decoder for FM broadcast radio with stereo support //
3 | // //
4 | // Copyright (C) 2015 Edouard Griffiths, F4EXB //
5 | // //
6 | // This program is free software; you can redistribute it and/or modify //
7 | // it under the terms of the GNU General Public License as published by //
8 | // the Free Software Foundation as version 3 of the License, or //
9 | // //
10 | // This program is distributed in the hope that it will be useful, //
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of //
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
13 | // GNU General Public License V3 for more details. //
14 | // //
15 | // You should have received a copy of the GNU General Public License //
16 | // along with this program. If not, see . //
17 | ///////////////////////////////////////////////////////////////////////////////////
18 |
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 | #include
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 |
33 | #include "util.h"
34 | #include "SoftFM.h"
35 | #include "DataBuffer.h"
36 | #include "FmDecode.h"
37 | #include "AudioOutput.h"
38 | #include "MovingAverage.h"
39 |
40 | #include "RtlSdrSource.h"
41 | #include "HackRFSource.h"
42 | #include "AirspySource.h"
43 | #include "BladeRFSource.h"
44 |
45 | /** Flag is set on SIGINT / SIGTERM. */
46 | static std::atomic_bool stop_flag(false);
47 |
48 |
49 | /** Simple linear gain adjustment. */
50 | void adjust_gain(SampleVector& samples, double gain)
51 | {
52 | for (unsigned int i = 0, n = samples.size(); i < n; i++) {
53 | samples[i] *= gain;
54 | }
55 | }
56 |
57 | /**
58 | * Get data from output buffer and write to output stream.
59 | *
60 | * This code runs in a separate thread.
61 | */
62 | void write_output_data(AudioOutput *output, DataBuffer *buf,
63 | unsigned int buf_minfill)
64 | {
65 | while (!stop_flag.load()) {
66 |
67 | if (buf->queued_samples() == 0) {
68 | // The buffer is empty. Perhaps the output stream is consuming
69 | // samples faster than we can produce them. Wait until the buffer
70 | // is back at its nominal level to make sure this does not happen
71 | // too often.
72 | buf->wait_buffer_fill(buf_minfill);
73 | }
74 |
75 | if (buf->pull_end_reached()) {
76 | // Reached end of stream.
77 | break;
78 | }
79 |
80 | // Get samples from buffer and write to output.
81 | SampleVector samples = buf->pull();
82 | output->write(samples);
83 | if (!(*output)) {
84 | fprintf(stderr, "ERROR: AudioOutput: %s\n", output->error().c_str());
85 | }
86 | }
87 | }
88 |
89 |
90 | /** Handle Ctrl-C and SIGTERM. */
91 | static void handle_sigterm(int sig)
92 | {
93 | stop_flag.store(true);
94 |
95 | std::string msg = "\nGot signal ";
96 | msg += strsignal(sig);
97 | msg += ", stopping ...\n";
98 |
99 | const char *s = msg.c_str();
100 | write(STDERR_FILENO, s, strlen(s));
101 | }
102 |
103 |
104 | void usage()
105 | {
106 | fprintf(stderr,
107 | "Usage: softfm [options]\n"
108 | " -t devtype Device type:\n"
109 | " - rtlsdr: RTL-SDR devices\n"
110 | " - hackrf: HackRF One or Jawbreaker\n"
111 | " - airspy: Airspy\n"
112 | " - bladerf: BladeRF\n"
113 | " -c config Comma separated key=value configuration pairs or just key for switches\n"
114 | " See below for valid values per device type\n"
115 | " -d devidx Device index, 'list' to show device list (default 0)\n"
116 | " -r pcmrate Audio sample rate in Hz (default 48000 Hz)\n"
117 | " -M Disable stereo decoding\n"
118 | " -R filename Write audio data as raw S16_LE samples\n"
119 | " use filename '-' to write to stdout\n"
120 | " -W filename Write audio data to .WAV file\n"
121 | " -P [device] Play audio via ALSA device (default 'default')\n"
122 | " -T filename Write pulse-per-second timestamps\n"
123 | " use filename '-' to write to stdout\n"
124 | " -b seconds Set audio buffer size in seconds\n"
125 | "\n"
126 | "Configuration options for RTL-SDR devices\n"
127 | " freq= Frequency of radio station in Hz (default 100000000)\n"
128 | " valid values: 10M to 2.2G (working range depends on device)\n"
129 | " srate= IF sample rate in Hz (default 1000000)\n"
130 | " (valid ranges: [225001, 300000], [900001, 3200000]))\n"
131 | " gain= Set LNA gain in dB, or 'auto',\n"
132 | " or 'list' to just get a list of valid values (default auto)\n"
133 | " blklen= Set audio buffer size in seconds (default RTL-SDR default)\n"
134 | " agc Enable RTL AGC mode (default disabled)\n"
135 | "\n"
136 | "Configuration options for HackRF devices\n"
137 | " freq= Frequency of radio station in Hz (default 100000000)\n"
138 | " valid values: 1M to 6G\n"
139 | " srate= IF sample rate in Hz (default 5000000)\n"
140 | " (valid ranges: [2500000,20000000]))\n"
141 | " lgain= LNA gain in dB. 'list' to just get a list of valid values: (default 16)\n"
142 | " vgain= VGA gain in dB. 'list' to just get a list of valid values: (default 22)\n"
143 | " bwfilter= Filter bandwidth in MHz. 'list' to just get a list of valid values: (default 2.5)\n"
144 | " extamp Enable extra RF amplifier (default disabled)\n"
145 | " antbias Enable antemma bias (default disabled)\n"
146 | "\n"
147 | "Configuration options for Airspy devices\n"
148 | " freq= Frequency of radio station in Hz (default 100000000)\n"
149 | " valid values: 24M to 1.8G\n"
150 | " srate= IF sample rate in Hz. Depends on Airspy firmware and libairspy support\n"
151 | " Airspy firmware and library must support dynamic sample rate query. (default 10000000)\n"
152 | " lgain= LNA gain in dB. 'list' to just get a list of valid values: (default 8)\n"
153 | " mgain= Mixer gain in dB. 'list' to just get a list of valid values: (default 8)\n"
154 | " vgain= VGA gain in dB. 'list' to just get a list of valid values: (default 8)\n"
155 | " antbias Enable antemma bias (default disabled)\n"
156 | " lagc Enable LNA AGC (default disabled)\n"
157 | " magc Enable mixer AGC (default disabled)\n"
158 | "\n"
159 | "Configuration options for BladeRF devices\n"
160 | " freq= Frequency of radio station in Hz (default 300000000)\n"
161 | " valid values (with XB200): 100k to 3.8G\n"
162 | " valid values (without XB200): 300M to 3.8G\n"
163 | " srate= IF sample rate in Hz. Valid values: 48k to 40M (default 1000000)\n"
164 | " bw= Bandwidth in Hz. 'list' to just get a list of valid values: (default 1500000)\n"
165 | " lgain= LNA gain in dB. 'list' to just get a list of valid values: (default 3)\n"
166 | " v1gain= VGA1 gain in dB. 'list' to just get a list of valid values: (default 20)\n"
167 | " v2gain= VGA2 gain in dB. 'list' to just get a list of valid values: (default 9)\n"
168 | "\n");
169 | }
170 |
171 |
172 | void badarg(const char *label)
173 | {
174 | usage();
175 | fprintf(stderr, "ERROR: Invalid argument for %s\n", label);
176 | exit(1);
177 | }
178 |
179 |
180 | bool parse_int(const char *s, int& v, bool allow_unit=false)
181 | {
182 | char *endp;
183 | long t = strtol(s, &endp, 10);
184 | if (endp == s)
185 | return false;
186 | if ( allow_unit && *endp == 'k' &&
187 | t > INT_MIN / 1000 && t < INT_MAX / 1000 ) {
188 | t *= 1000;
189 | endp++;
190 | }
191 | if (*endp != '\0' || t < INT_MIN || t > INT_MAX)
192 | return false;
193 | v = t;
194 | return true;
195 | }
196 |
197 |
198 | /** Return Unix time stamp in seconds. */
199 | double get_time()
200 | {
201 | struct timeval tv;
202 | gettimeofday(&tv, NULL);
203 | return tv.tv_sec + 1.0e-6 * tv.tv_usec;
204 | }
205 |
206 | static bool get_device(std::vector &devnames, std::string& devtype, Source **srcsdr, int devidx)
207 | {
208 | if (strcasecmp(devtype.c_str(), "rtlsdr") == 0)
209 | {
210 | RtlSdrSource::get_device_names(devnames);
211 | }
212 | else if (strcasecmp(devtype.c_str(), "hackrf") == 0)
213 | {
214 | HackRFSource::get_device_names(devnames);
215 | }
216 | else if (strcasecmp(devtype.c_str(), "airspy") == 0)
217 | {
218 | AirspySource::get_device_names(devnames);
219 | }
220 | else if (strcasecmp(devtype.c_str(), "bladerf") == 0)
221 | {
222 | BladeRFSource::get_device_names(devnames);
223 | }
224 | else
225 | {
226 | fprintf(stderr, "ERROR: wrong device type (-t option) must be one of the following:\n");
227 | fprintf(stderr, " rtlsdr, hackrf, airspy, bladerf\n");
228 | return false;
229 | }
230 |
231 | if (devidx < 0 || (unsigned int)devidx >= devnames.size())
232 | {
233 | if (devidx != -1)
234 | {
235 | fprintf(stderr, "ERROR: invalid device index %d\n", devidx);
236 | }
237 |
238 | fprintf(stderr, "Found %u devices:\n", (unsigned int)devnames.size());
239 |
240 | for (unsigned int i = 0; i < devnames.size(); i++)
241 | {
242 | fprintf(stderr, "%2u: %s\n", i, devnames[i].c_str());
243 | }
244 |
245 | return false;
246 | }
247 |
248 | fprintf(stderr, "using device %d: %s\n", devidx, devnames[devidx].c_str());
249 |
250 | if (strcasecmp(devtype.c_str(), "rtlsdr") == 0)
251 | {
252 | // Open RTL-SDR device.
253 | *srcsdr = new RtlSdrSource(devidx);
254 | }
255 | else if (strcasecmp(devtype.c_str(), "hackrf") == 0)
256 | {
257 | // Open HackRF device.
258 | *srcsdr = new HackRFSource(devidx);
259 | }
260 | else if (strcasecmp(devtype.c_str(), "airspy") == 0)
261 | {
262 | // Open Airspy device.
263 | *srcsdr = new AirspySource(devidx);
264 | }
265 | else if (strcasecmp(devtype.c_str(), "bladerf") == 0)
266 | {
267 | // Open BladeRF device.
268 | *srcsdr = new BladeRFSource(devnames[devidx].c_str());
269 | }
270 |
271 | return true;
272 | }
273 |
274 | int main(int argc, char **argv)
275 | {
276 | int devidx = 0;
277 | int pcmrate = 48000;
278 | bool stereo = true;
279 | enum OutputMode { MODE_RAW, MODE_WAV, MODE_ALSA };
280 | OutputMode outmode = MODE_ALSA;
281 | std::string filename;
282 | std::string alsadev("default");
283 | std::string ppsfilename;
284 | FILE * ppsfile = NULL;
285 | double bufsecs = -1;
286 | std::string config_str;
287 | std::string devtype_str;
288 | std::vector devnames;
289 | Source *srcsdr = 0;
290 |
291 | fprintf(stderr,
292 | "SoftFM - Software decoder for FM broadcast radio\n");
293 |
294 | const struct option longopts[] = {
295 | { "devtype", 2, NULL, 't' },
296 | { "config", 2, NULL, 'c' },
297 | { "dev", 1, NULL, 'd' },
298 | { "pcmrate", 1, NULL, 'r' },
299 | { "mono", 0, NULL, 'M' },
300 | { "raw", 1, NULL, 'R' },
301 | { "wav", 1, NULL, 'W' },
302 | { "play", 2, NULL, 'P' },
303 | { "pps", 1, NULL, 'T' },
304 | { "buffer", 1, NULL, 'b' },
305 | { NULL, 0, NULL, 0 } };
306 |
307 | int c, longindex;
308 | while ((c = getopt_long(argc, argv,
309 | "t:c:d:r:MR:W:P::T:b:",
310 | longopts, &longindex)) >= 0) {
311 | switch (c) {
312 | case 't':
313 | devtype_str.assign(optarg);
314 | break;
315 | case 'c':
316 | config_str.assign(optarg);
317 | break;
318 | case 'd':
319 | if (!parse_int(optarg, devidx))
320 | devidx = -1;
321 | break;
322 | case 'r':
323 | if (!parse_int(optarg, pcmrate, true) || pcmrate < 1) {
324 | badarg("-r");
325 | }
326 | break;
327 | case 'M':
328 | stereo = false;
329 | break;
330 | case 'R':
331 | outmode = MODE_RAW;
332 | filename = optarg;
333 | break;
334 | case 'W':
335 | outmode = MODE_WAV;
336 | filename = optarg;
337 | break;
338 | case 'P':
339 | outmode = MODE_ALSA;
340 | if (optarg != NULL)
341 | alsadev = optarg;
342 | break;
343 | case 'T':
344 | ppsfilename = optarg;
345 | break;
346 | case 'b':
347 | if (!parse_dbl(optarg, bufsecs) || bufsecs < 0) {
348 | badarg("-b");
349 | }
350 | break;
351 | default:
352 | usage();
353 | fprintf(stderr, "ERROR: Invalid command line options\n");
354 | exit(1);
355 | }
356 | }
357 |
358 | if (optind < argc)
359 | {
360 | usage();
361 | fprintf(stderr, "ERROR: Unexpected command line options\n");
362 | exit(1);
363 | }
364 |
365 | // Catch Ctrl-C and SIGTERM
366 | struct sigaction sigact;
367 | sigact.sa_handler = handle_sigterm;
368 | sigemptyset(&sigact.sa_mask);
369 | sigact.sa_flags = SA_RESETHAND;
370 |
371 | if (sigaction(SIGINT, &sigact, NULL) < 0)
372 | {
373 | fprintf(stderr, "WARNING: can not install SIGINT handler (%s)\n", strerror(errno));
374 | }
375 |
376 | if (sigaction(SIGTERM, &sigact, NULL) < 0)
377 | {
378 | fprintf(stderr, "WARNING: can not install SIGTERM handler (%s)\n", strerror(errno));
379 | }
380 |
381 | // Open PPS file.
382 | if (!ppsfilename.empty())
383 | {
384 | if (ppsfilename == "-")
385 | {
386 | fprintf(stderr, "writing pulse-per-second markers to stdout\n");
387 | ppsfile = stdout;
388 | }
389 | else
390 | {
391 | fprintf(stderr, "writing pulse-per-second markers to '%s'\n", ppsfilename.c_str());
392 | ppsfile = fopen(ppsfilename.c_str(), "w");
393 |
394 | if (ppsfile == NULL)
395 | {
396 | fprintf(stderr, "ERROR: can not open '%s' (%s)\n", ppsfilename.c_str(), strerror(errno));
397 | exit(1);
398 | }
399 | }
400 |
401 | fprintf(ppsfile, "#pps_index sample_index unix_time\n");
402 | fflush(ppsfile);
403 | }
404 |
405 | // Calculate number of samples in audio buffer.
406 | unsigned int outputbuf_samples = 0;
407 |
408 | if (bufsecs < 0 && (outmode == MODE_ALSA || (outmode == MODE_RAW && filename == "-")))
409 | {
410 | // Set default buffer to 1 second for interactive output streams.
411 | outputbuf_samples = pcmrate;
412 | }
413 | else if (bufsecs > 0)
414 | {
415 | // Calculate nr of samples for configured buffer length.
416 | outputbuf_samples = (unsigned int)(bufsecs * pcmrate);
417 | }
418 |
419 | if (outputbuf_samples > 0)
420 | {
421 | fprintf(stderr, "output buffer: %.1f seconds\n", outputbuf_samples / double(pcmrate));
422 | }
423 |
424 | // Prepare output writer.
425 | std::unique_ptr audio_output;
426 |
427 | switch (outmode)
428 | {
429 | case MODE_RAW:
430 | fprintf(stderr, "writing raw 16-bit audio samples to '%s'\n", filename.c_str());
431 | audio_output.reset(new RawAudioOutput(filename));
432 | break;
433 | case MODE_WAV:
434 | fprintf(stderr, "writing audio samples to '%s'\n", filename.c_str());
435 | audio_output.reset(new WavAudioOutput(filename, pcmrate, stereo));
436 | break;
437 | case MODE_ALSA:
438 | fprintf(stderr, "playing audio to ALSA device '%s'\n", alsadev.c_str());
439 | audio_output.reset(new AlsaAudioOutput(alsadev, pcmrate, stereo));
440 | break;
441 | }
442 |
443 | if (!(*audio_output))
444 | {
445 | fprintf(stderr, "ERROR: AudioOutput: %s\n", audio_output->error().c_str());
446 | exit(1);
447 | }
448 |
449 | if (!get_device(devnames, devtype_str, &srcsdr, devidx))
450 | {
451 | exit(1);
452 | }
453 |
454 | if (!(*srcsdr))
455 | {
456 | fprintf(stderr, "ERROR source: %s\n", srcsdr->error().c_str());
457 | delete srcsdr;
458 | exit(1);
459 | }
460 |
461 |
462 | // Configure device and start streaming.
463 | if (!srcsdr->configure(config_str))
464 | {
465 | fprintf(stderr, "ERROR: configuration: %s\n", srcsdr->error().c_str());
466 | delete srcsdr;
467 | exit(1);
468 | }
469 |
470 | double freq = srcsdr->get_configured_frequency();
471 | fprintf(stderr, "tuned for: %.6f MHz\n", freq * 1.0e-6);
472 |
473 | double tuner_freq = srcsdr->get_frequency();
474 | fprintf(stderr, "device tuned for: %.6f MHz\n", tuner_freq * 1.0e-6);
475 |
476 | double ifrate = srcsdr->get_sample_rate();
477 | fprintf(stderr, "IF sample rate: %.0f Hz\n", ifrate);
478 |
479 | double delta_if = tuner_freq - freq;
480 | MovingAverage ppm_average(40, 0.0f);
481 |
482 | srcsdr->print_specific_parms();
483 |
484 | // Create source data queue.
485 | DataBuffer source_buffer;
486 |
487 | // ownership will be transferred to thread therefore the unique_ptr with move is convenient
488 | // if the pointer is to be shared with the main thread use shared_ptr (and no move) instead
489 | std::unique_ptr up_srcsdr(srcsdr);
490 |
491 | // Start reading from device in separate thread.
492 | //std::thread source_thread(read_source_data, std::move(up_srcsdr), &source_buffer);
493 | up_srcsdr->start(&source_buffer, &stop_flag);
494 |
495 | if (!up_srcsdr) {
496 | fprintf(stderr, "ERROR: source: %s\n", up_srcsdr->error().c_str());
497 | exit(1);
498 | }
499 |
500 | // The baseband signal is empty above 100 kHz, so we can
501 | // downsample to ~ 200 kS/s without loss of information.
502 | // This will speed up later processing stages.
503 | unsigned int downsample = std::max(1, int(ifrate / 215.0e3));
504 | fprintf(stderr, "baseband downsampling factor %u\n", downsample);
505 |
506 | // Prevent aliasing at very low output sample rates.
507 | double bandwidth_pcm = std::min(FmDecoder::default_bandwidth_pcm, 0.45 * pcmrate);
508 | fprintf(stderr, "audio sample rate: %u Hz\n", pcmrate);
509 | fprintf(stderr, "audio bandwidth: %.3f kHz\n", bandwidth_pcm * 1.0e-3);
510 |
511 | // Prepare decoder.
512 | FmDecoder fm(ifrate, // sample_rate_if
513 | freq - tuner_freq, // tuning_offset
514 | pcmrate, // sample_rate_pcm
515 | stereo, // stereo
516 | FmDecoder::default_deemphasis, // deemphasis,
517 | FmDecoder::default_bandwidth_if, // bandwidth_if
518 | FmDecoder::default_freq_dev, // freq_dev
519 | bandwidth_pcm, // bandwidth_pcm
520 | downsample); // downsample
521 |
522 | // If buffering enabled, start background output thread.
523 | DataBuffer output_buffer;
524 | std::thread output_thread;
525 |
526 | if (outputbuf_samples > 0)
527 | {
528 | unsigned int nchannel = stereo ? 2 : 1;
529 | output_thread = std::thread(write_output_data,
530 | audio_output.get(),
531 | &output_buffer,
532 | outputbuf_samples * nchannel);
533 | }
534 |
535 | SampleVector audiosamples;
536 | bool inbuf_length_warning = false;
537 | double audio_level = 0;
538 | bool got_stereo = false;
539 |
540 | double block_time = get_time();
541 |
542 | // Main loop.
543 | for (unsigned int block = 0; !stop_flag.load(); block++)
544 | {
545 |
546 | // Check for overflow of source buffer.
547 | if (!inbuf_length_warning && source_buffer.queued_samples() > 10 * ifrate)
548 | {
549 | fprintf(stderr, "\nWARNING: Input buffer is growing (system too slow)\n");
550 | inbuf_length_warning = true;
551 | }
552 |
553 | // Pull next block from source buffer.
554 | IQSampleVector iqsamples = source_buffer.pull();
555 |
556 | if (iqsamples.empty())
557 | {
558 | break;
559 | }
560 |
561 | double prev_block_time = block_time;
562 | block_time = get_time();
563 |
564 | // Decode FM signal.
565 | fm.process(iqsamples, audiosamples);
566 |
567 | // Measure audio level.
568 | double audio_mean, audio_rms;
569 | samples_mean_rms(audiosamples, audio_mean, audio_rms);
570 | audio_level = 0.95 * audio_level + 0.05 * audio_rms;
571 |
572 | // Set nominal audio volume.
573 | adjust_gain(audiosamples, 0.5);
574 |
575 | ppm_average.feed(((fm.get_tuning_offset() + delta_if) / tuner_freq) * -1.0e6); // the minus factor is to show the ppm correction to make and not the one made
576 |
577 | // Show statistics.
578 | fprintf(stderr,
579 | "\rblk=%6d freq=%10.6fMHz ppm=%+6.2f IF=%+5.1fdB BB=%+5.1fdB audio=%+5.1fdB ",
580 | block,
581 | (tuner_freq + fm.get_tuning_offset()) * 1.0e-6,
582 | ppm_average.average(),
583 | //((fm.get_tuning_offset() + delta_if) / tuner_freq) * 1.0e6,
584 | 20*log10(fm.get_if_level()),
585 | 20*log10(fm.get_baseband_level()) + 3.01,
586 | 20*log10(audio_level) + 3.01);
587 |
588 | if (outputbuf_samples > 0)
589 | {
590 | unsigned int nchannel = stereo ? 2 : 1;
591 | std::size_t buflen = output_buffer.queued_samples();
592 | fprintf(stderr, " buf=%.1fs ", buflen / nchannel / double(pcmrate));
593 | }
594 |
595 | fflush(stderr);
596 |
597 | // Show stereo status.
598 | if (fm.stereo_detected() != got_stereo)
599 | {
600 | got_stereo = fm.stereo_detected();
601 |
602 | if (got_stereo)
603 | {
604 | fprintf(stderr, "\ngot stereo signal (pilot level = %f)\n", fm.get_pilot_level());
605 | }
606 | else
607 | {
608 | fprintf(stderr, "\nlost stereo signal\n");
609 | }
610 | }
611 |
612 | // Write PPS markers.
613 | if (ppsfile != NULL)
614 | {
615 | for (const PilotPhaseLock::PpsEvent& ev : fm.get_pps_events())
616 | {
617 | double ts = prev_block_time;
618 | ts += ev.block_position * (block_time - prev_block_time);
619 | fprintf(ppsfile, "%8s %14s %18.6f\n",
620 | std::to_string(ev.pps_index).c_str(),
621 | std::to_string(ev.sample_index).c_str(),
622 | ts);
623 | fflush(ppsfile);
624 | }
625 | }
626 |
627 | // Throw away first block. It is noisy because IF filters
628 | // are still starting up.
629 | if (block > 0)
630 | {
631 | // Write samples to output.
632 | if (outputbuf_samples > 0)
633 | {
634 | // Buffered write.
635 | output_buffer.push(move(audiosamples));
636 | }
637 | else
638 | {
639 | // Direct write.
640 | audio_output->write(audiosamples);
641 | }
642 | }
643 | }
644 |
645 | fprintf(stderr, "\n");
646 |
647 | // Join background threads.
648 | //source_thread.join();
649 | up_srcsdr->stop();
650 |
651 | if (outputbuf_samples > 0)
652 | {
653 | output_buffer.push_end();
654 | output_thread.join();
655 | }
656 |
657 | // No cleanup needed; everything handled by destructors
658 |
659 | return 0;
660 | }
661 |
662 | /* end */
663 |
--------------------------------------------------------------------------------