forked from zf-lab/SoftFM
-
Notifications
You must be signed in to change notification settings - Fork 0
/
FmDecode.h
277 lines (236 loc) · 8.55 KB
/
FmDecode.h
1
2
3
4
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#ifndef SOFTFM_FMDECODE_H
#define SOFTFM_FMDECODE_H
#include <cstdint>
#include <vector>
#include "SoftFM.h"
#include "Filter.h"
class SampleBufferBlock;
/* Detect frequency by phase discrimination between successive samples. */
class PhaseDiscriminator
{
public:
/**
* Construct phase discriminator.
*
* max_freq_dev :: Full scale frequency deviation relative to the
* full sample frequency.
*/
PhaseDiscriminator(double max_freq_dev);
/**
* Process samples.
* Output is a sequence of frequency estimates, scaled such that
* output value +/- 1.0 represents the maximum frequency deviation.
*/
void process(const IQSampleVector& samples_in, SampleVector& samples_out);
private:
const Sample m_freq_scale_factor;
IQSample m_last_sample;
};
/** Phase-locked loop for stereo pilot. */
class PilotPhaseLock
{
public:
/** Expected pilot frequency (used for PPS events). */
static constexpr int pilot_frequency = 19000;
/** Timestamp event produced once every 19000 pilot periods. */
struct PpsEvent
{
std::uint64_t pps_index;
std::uint64_t sample_index;
double block_position;
};
/**
* Construct phase-locked loop.
*
* freq :: 19 kHz center frequency relative to sample freq
* (0.5 is Nyquist)
* bandwidth :: bandwidth relative to sample frequency
* minsignal :: minimum pilot amplitude
*/
PilotPhaseLock(double freq, double bandwidth, double minsignal);
/**
* Process samples and extract 19 kHz pilot tone.
* Generate phase-locked 38 kHz tone with unit amplitude.
*/
void process(const SampleVector& samples_in, SampleVector& samples_out);
/** Return true if the phase-locked loop is locked. */
bool locked() const
{
return m_lock_cnt >= m_lock_delay;
}
/** Return detected amplitude of pilot signal. */
double get_pilot_level() const
{
return 2 * m_pilot_level;
}
/** Return PPS events from the most recently processed block. */
std::vector<PpsEvent> get_pps_events() const
{
return m_pps_events;
}
private:
Sample m_minfreq, m_maxfreq;
Sample m_phasor_b0, m_phasor_a1, m_phasor_a2;
Sample m_phasor_i1, m_phasor_i2, m_phasor_q1, m_phasor_q2;
Sample m_loopfilter_b0, m_loopfilter_b1;
Sample m_loopfilter_x1;
Sample m_freq, m_phase;
Sample m_minsignal;
Sample m_pilot_level;
int m_lock_delay;
int m_lock_cnt;
int m_pilot_periods;
std::uint64_t m_pps_cnt;
std::uint64_t m_sample_cnt;
std::vector<PpsEvent> m_pps_events;
};
/** Complete decoder for FM broadcast signal. */
class FmDecoder
{
public:
static const double default_deemphasis;
static const double default_bandwidth_if;
static const double default_freq_dev;
static const double default_bandwidth_pcm;
static const double pilot_freq;
/**
* Construct FM decoder.
*
* sample_rate_if :: IQ sample rate in Hz.
* tuning_offset :: Frequency offset in Hz of radio station with respect
* to receiver LO frequency (positive value means
* station is at higher frequency than LO).
* sample_rate_pcm :: Audio sample rate.
* stereo :: True to enable stereo decoding.
* deemphasis :: Time constant of de-emphasis filter in microseconds
* (50 us for broadcast FM, 0 to disable de-emphasis).
* bandwidth_if :: Half bandwidth of IF signal in Hz
* (~ 100 kHz for broadcast FM)
* freq_dev :: Full scale carrier frequency deviation
* (75 kHz for broadcast FM)
* bandwidth_pcm :: Half bandwidth of audio signal in Hz
* (15 kHz for broadcast FM)
* downsample :: Downsampling factor to apply after FM demodulation.
* Set to 1 to disable.
*/
FmDecoder(double sample_rate_if,
double tuning_offset,
double sample_rate_pcm,
bool stereo=true,
double deemphasis=50,
double bandwidth_if=default_bandwidth_if,
double freq_dev=default_freq_dev,
double bandwidth_pcm=default_bandwidth_pcm,
unsigned int downsample=1);
/**
* Process IQ samples and return audio samples.
*
* If the decoder is set in stereo mode, samples for left and right
* channels are interleaved in the output vector (even if no stereo
* signal is detected). If the decoder is set in mono mode, the output
* vector only contains samples for one channel.
*/
void process(const IQSampleVector& samples_in, SampleVector& audio);
void Process(const SampleBufferBlock* samples_in, SampleVector& audio);
/** Return true if a stereo signal is detected. */
bool stereo_detected() const
{
return m_stereo_detected;
}
/** Return actual frequency offset in Hz with respect to receiver LO. */
double get_tuning_offset() const
{
double tuned = - m_tuning_shift * m_sample_rate_if /
double(m_tuning_table_size);
return tuned + m_baseband_mean * m_freq_dev;
}
/** Return RMS IF level (where full scale IQ signal is 1.0). */
double get_if_level() const
{
return m_if_level;
}
/** Return RMS baseband signal level (where nominal level is 0.707). */
double get_baseband_level() const
{
return m_baseband_level;
}
/** Return amplitude of stereo pilot (nominal level is 0.1). */
double get_pilot_level() const
{
return m_pilotpll.get_pilot_level();
}
/** Return PPS events from the most recently processed block. */
std::vector<PilotPhaseLock::PpsEvent> get_pps_events() const
{
return m_pilotpll.get_pps_events();
}
private:
/** Demodulate stereo L-R signal. */
void demod_stereo(const SampleVector& samples_baseband,
SampleVector& samples_stereo);
/** Duplicate mono signal in left/right channels. */
void mono_to_left_right(const SampleVector& samples_mono,
SampleVector& audio);
/** Extract left/right channels from mono/stereo signals. */
void stereo_to_left_right(const SampleVector& samples_mono,
const SampleVector& samples_stereo,
SampleVector& audio);
// Data members.
const double m_sample_rate_if;
const double m_sample_rate_baseband;
const int m_tuning_table_size;
const int m_tuning_shift;
const double m_freq_dev;
const unsigned int m_downsample;
const bool m_stereo_enabled;
bool m_stereo_detected;
double m_if_level;
double m_baseband_mean;
double m_baseband_level;
IQSampleVector m_buf_iftuned;
IQSampleVector m_buf_iffiltered;
SampleVector m_buf_baseband;
SampleVector m_buf_mono;
SampleVector m_buf_rawstereo;
SampleVector m_buf_stereo;
FineTuner m_finetuner;
LowPassFilterFirIQ m_iffilter;
PhaseDiscriminator m_phasedisc;
DownsampleFilter m_resample_baseband;
PilotPhaseLock m_pilotpll;
DownsampleFilter m_resample_mono;
DownsampleFilter m_resample_stereo;
HighPassFilterIir m_dcblock_mono;
HighPassFilterIir m_dcblock_stereo;
LowPassFilterRC m_deemph_mono;
LowPassFilterRC m_deemph_stereo;
};
#include "threads/iothread.h"
class RtlSdrSource;
class AudioOutput;
class FmDecoderThread
{
public:
FmDecoderThread(RtlSdrSource* src,
AudioOutput* output);
bool CreateDecoder(double sample_rate_if,
double tuning_offset,
double sample_rate_pcm,
bool stereo = true,
double deemphasis = 50,
double bandwidth_if = FmDecoder::default_bandwidth_if,
double freq_dev = FmDecoder::default_freq_dev,
double bandwidth_pcm = FmDecoder::default_bandwidth_pcm,
unsigned int downsample = 1);
~FmDecoderThread();
private:
void OnNewIQSamples(RtlSdrSource*);
void DecodeIQSamples();
LF::threads::IOThread mThread;
FmDecoder* mDecoder { nullptr };
RtlSdrSource* mSource { nullptr };
AudioOutput* mAudioOutput { nullptr };
bool mPrintStats { true };
uint32_t mBlocks { 0 };
};
#endif