5G-MAG Reference Tools - MBMS Modem
SdrReader.cpp
Go to the documentation of this file.
1 // 5G-MAG Reference Tools
2 // MBMS Modem Process
3 //
4 // Copyright (C) 2021 Klaus Kühnhammer (Österreichische Rundfunksender GmbH & Co KG)
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU Affero General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 
19 #include "SdrReader.h"
20 #include <SoapySDR/Device.hpp>
21 #include <SoapySDR/Types.hpp>
22 #include <SoapySDR/Formats.hpp>
23 
24 #include <boost/algorithm/string/join.hpp>
25 
26 #include <chrono>
27 #include <cmath>
28 
29 #include "spdlog/spdlog.h"
30 
32  if (_sdr != nullptr) {
33  auto sdr = (SoapySDR::Device*)_sdr;
34  sdr->deactivateStream((SoapySDR::Stream*)_stream, 0, 0);
35  sdr->closeStream((SoapySDR::Stream*)_stream);
36  SoapySDR::Device::unmake( sdr );
37  }
38 
39  if (_reading_from_file) {
40  srsran_filesource_free(&file_source);
41  }
42 
43  if (_writing_to_file) {
44  srsran_filesink_free(&file_sink);
45  }
46 }
47 
49 {
50  auto results = SoapySDR::Device::enumerate();
51  SoapySDR::Kwargs::iterator it;
52 
53  for( int i = 0; i < results.size(); ++i)
54  {
55  printf("Device #%d:\n", i);
56  for( it = results[i].begin(); it != results[i].end(); ++it)
57  {
58  printf("%s = %s\n", it->first.c_str(), it->second.c_str());
59  }
60  printf("\n\n");
61  }
62 
63 }
64 
65 auto SdrReader::init(const std::string& device_args, const char* sample_file,
66  const char* write_sample_file) -> bool {
67  if (sample_file != nullptr) {
68  if (0 == srsran_filesource_init(&file_source,
69  const_cast<char*>(sample_file),
70  SRSRAN_COMPLEX_FLOAT_BIN)) {
71  _reading_from_file = true;
72  } else {
73  spdlog::error("Could not open file {}", sample_file);
74  return false;
75  }
76  } else {
77  if (write_sample_file != nullptr) {
78  if (0 == srsran_filesink_init(&file_sink,
79  const_cast<char*>(write_sample_file),
80  SRSRAN_COMPLEX_FLOAT_BIN)) {
81  _writing_to_file = true;
82  } else {
83  spdlog::error("Could not open file {}", write_sample_file);
84  return false;
85  }
86  }
87 
88  _device_args = SoapySDR::KwargsFromString(device_args);
89  _sdr = SoapySDR::Device::make(_device_args);
90  if (_sdr == nullptr)
91  {
92  spdlog::error("SoapySDR: failed to open device with args {}", device_args);
93  return false;
94  }
95  }
96 
97  _cfg.lookupValue("modem.sdr.ringbuffer_size_ms", _buffer_ms);
98  return true;
99 }
100 
102  auto buffer_size = (unsigned int)ceil(_sampleRate/1000.0 * _buffer_ms);
103  _buffer = std::make_unique<MultichannelRingbuffer>(sizeof(cf_t) * buffer_size, _rx_channels);
104  _buffer_ready = true;
105 }
106 
108  _buffer->clear();
109  _high_watermark_reached = false;
110 }
111 
112 auto SdrReader::set_antenna(const std::string& antenna, uint8_t idx) -> bool {
113  auto sdr = (SoapySDR::Device*)_sdr;
114  auto antenna_list = sdr->listAntennas(SOAPY_SDR_RX, idx);
115  if (std::find(antenna_list.begin(), antenna_list.end(), antenna) != antenna_list.end()) {
116  sdr->setAntenna( SOAPY_SDR_RX, idx, antenna);
117  _antenna = sdr->getAntenna( SOAPY_SDR_RX, idx);
118  return true;
119  } else {
120  spdlog::error("Unknown antenna \"{}\". Available: {}.", antenna, boost::algorithm::join(antenna_list, ", ") );
121  return false;
122  }
123 }
124 
125 auto SdrReader::set_frequency(uint32_t frequency, uint8_t idx) -> bool {
126  auto sdr = (SoapySDR::Device*)_sdr;
127  sdr->setFrequency( SOAPY_SDR_RX, idx, frequency);
128  return true;
129 }
130 
131 auto SdrReader::set_filter_bw(uint32_t bandwidth, uint8_t idx) -> bool {
132  auto sdr = (SoapySDR::Device*)_sdr;
133  sdr->setBandwidth( SOAPY_SDR_RX, idx, bandwidth);
134  return true;
135 }
136 
137 auto SdrReader::set_sample_rate(uint32_t rate, uint8_t idx) -> bool {
138  auto sdr = (SoapySDR::Device*)_sdr;
139  sdr->setSampleRate( SOAPY_SDR_RX, idx, rate);
140  return true;
141 }
142 
143 auto SdrReader::set_gain(bool use_agc, double gain, uint8_t idx) -> bool {
144  auto sdr = (SoapySDR::Device*)_sdr;
145  if (sdr->hasGainMode(SOAPY_SDR_RX, idx)) {
146 // spdlog::info("{} AGC", use_agc ? "Enabling" : "Disabling");
147  sdr->setGainMode(SOAPY_SDR_RX, idx, use_agc);
148  } else if (use_agc) {
149 // spdlog::info("AGC is not supported by this device, please set gain manually");
150  }
151  auto gain_range = sdr->getGainRange(SOAPY_SDR_RX, idx);
152  _min_gain = gain_range.minimum();
153  _max_gain = gain_range.maximum();
154  if (gain >= gain_range.minimum() && gain <= gain_range.maximum()) {
155  sdr->setGain( SOAPY_SDR_RX, idx, gain);
156  if (idx == 0) {
157  _gain = sdr->getGain( SOAPY_SDR_RX, idx);
158  }
159  return true;
160  } else {
161  spdlog::error("Invalid gain setting {}. Allowed range is: {} - {}.", gain, gain_range.minimum(), gain_range.maximum());
162  return false;
163  }
164 }
165 
166 auto SdrReader::tune(uint32_t frequency, uint32_t sample_rate,
167  uint32_t bandwidth, double gain, const std::string& antenna, bool use_agc) -> bool {
168  _frequency = frequency;
169  _filterBw = bandwidth;
170  _sampleRate = sample_rate;
171  _use_agc = use_agc;
172 
173  init_buffer();
174 
175  if (_reading_from_file) {
176  return true;
177  }
178 
179  if (_sdr == nullptr) {
180  return false;
181  }
182 
183  spdlog::info("Tuning to {} MHz, filter bandwidth {} MHz, sample rate {}, gain {}, antenna path {} with AGC set to {}",
184  frequency/1000000.0, bandwidth/1000000.0, sample_rate/1000000.0, gain, antenna, use_agc);
185 
186  auto sdr = (SoapySDR::Device*)_sdr;
187 
188  for (auto ch = 0; ch < _rx_channels; ch++) {
189  set_antenna(antenna, ch);
190  set_gain(_use_agc, gain, ch);
191  set_frequency(frequency, ch);
192  set_filter_bw(bandwidth, ch);
193  set_sample_rate(sample_rate, ch);
194  }
195 
196  _frequency = sdr->getFrequency( SOAPY_SDR_RX, 0);
197  bandwidth = sdr->getBandwidth( SOAPY_SDR_RX, 0);
198  _sampleRate = sdr->getSampleRate( SOAPY_SDR_RX, 0);
199 
200  spdlog::info("SDR tuned to {} MHz, filter bandwidth {} MHz, sample rate {}, gain {}, antenna path {}",
201  _frequency/1000000.0, bandwidth/1000000.0, _sampleRate/1000000.0, _gain, _antenna);
202 
203 
204  auto sensors = sdr->listSensors();
205  if (std::find(sensors.begin(), sensors.end(), "lms7_temp") != sensors.end()) {
206  _temp_sensor_available = true;
207  _temp_sensor_key = "lms7_temp";
208  }
209 
210  return true;
211 }
212 
214  if (_sdr != nullptr) {
215  auto sdr = (SoapySDR::Device*)_sdr;
216  std::vector<size_t> channels(_rx_channels);
217  for (auto ch = 0; ch < _rx_channels; ch++) {
218  channels[ch] = ch;
219  }
220  _stream = sdr->setupStream( SOAPY_SDR_RX, SOAPY_SDR_CF32, channels, _device_args);
221  if( _stream == nullptr)
222  {
223  spdlog::error("Failed to set up RX stream");
224  SoapySDR::Device::unmake( sdr );
225  return ;
226  }
227  sdr->activateStream( (SoapySDR::Stream*)_stream, 0, 0, 0);
228  }
229  _running = true;
230 
231  // Start the reader thread and elevate its priority to realtime
232  _readerThread = std::thread{&SdrReader::read, this};
233  struct sched_param thread_param = {};
234  thread_param.sched_priority = 50;
235  _cfg.lookupValue("modem.sdr.reader_thread_priority_rt", thread_param.sched_priority);
236 
237  spdlog::debug("Launching sample reader thread with realtime scheduling priority {}", thread_param.sched_priority);
238 
239  int error = pthread_setschedparam(_readerThread.native_handle(), SCHED_RR, &thread_param);
240  if (error != 0) {
241  spdlog::warn("Cannot set reader thread priority to realtime: {}. Thread will run at default priority with a high probability of dropped samples and loss of synchronisation.", strerror(error));
242  }
243 }
244 
246  _running = false;
247 
248  if (_sdr != nullptr) {
249  auto sdr = (SoapySDR::Device*)_sdr;
250  sdr->deactivateStream((SoapySDR::Stream*)_stream, 0, 0);
251  sdr->closeStream((SoapySDR::Stream*)_stream);
252  }
253 
254  _readerThread.join();
255  clear_buffer();
256 }
257 
259  std::array<void*, SRSRAN_MAX_CHANNELS> radio_buffers = { nullptr };
260  while (_running) {
261  int toRead = ceil(_sampleRate / 1000.0);
262  //int toRead = 254;
263  if (_buffer->free_size() < toRead * sizeof(cf_t)) {
264  spdlog::debug("ringbuffer overflow");
265  std::this_thread::sleep_for(std::chrono::microseconds(1000));
266  } else {
267  int read = 0;
268  size_t writeable = 0;
269  auto buffers = _buffer->write_head(&writeable);
270  int writeable_samples = (int)floor(writeable / sizeof(cf_t));
271 
272  if (_reading_from_file) {
273  std::chrono::steady_clock::time_point entered = {};
274  entered = std::chrono::steady_clock::now();
275 
276  read = srsran_filesource_read_multi(&file_source, buffers.data(), std::min(writeable_samples, toRead), (int)_rx_channels);
277  if ( read == 0 ) {
278  srsran_filesource_seek(&file_source, 0);
279  }
280  read = read / _rx_channels;
281  int64_t required_time_us = (1000000.0/_sampleRate) * read;
282 
283  if (read > 0) {
284  _buffer->commit( read * sizeof(cf_t) );
285  }
286 
287  std::chrono::microseconds sleep = (std::chrono::microseconds(required_time_us) -
288  std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - entered));
289  std::this_thread::sleep_for(sleep);
290  } else {
291  auto sdr = (SoapySDR::Device*)_sdr;
292  int flags = 0;
293  long long time_ns = 0;
294 
295  read = sdr->readStream( (SoapySDR::Stream*)_stream, buffers.data(), std::min(writeable_samples, toRead), flags, time_ns);
296 
297 
298  if (read> 0) {
300  srsran_filesink_write_multi(&file_sink, buffers.data(), read, (int)_rx_channels);
301  }
302  _buffer->commit( read * sizeof(cf_t) );
303  spdlog::debug("buffer: commited {}, requested {}, writeable {}, flags {}", read, toRead, writeable_samples, flags);
304  }
305  else {
306  spdlog::error("readStream returned {}", read);
307  _buffer->commit(0);
308  }
309  }
310  }
311  }
312  spdlog::debug("Sample reader thread exited");
313 }
314 
315 auto SdrReader::get_samples(cf_t* data[SRSRAN_MAX_CHANNELS], uint32_t nsamples, //NOLINT
316  srsran_timestamp_t *
317  /*rx_time*/) -> int {
318  std::chrono::steady_clock::time_point entered = {};
319  entered = std::chrono::steady_clock::now();
320 
321  int64_t required_time_us = (1000000.0/_sampleRate) * nsamples;
322  size_t cnt = nsamples * sizeof(cf_t);
323 
324  if (_high_watermark_reached && _buffer->used_size() < (_sampleRate / 1000.0) * 10 * sizeof(cf_t)) {
325  _high_watermark_reached = false;
326  }
327 
328  if (!_high_watermark_reached) {
329  while (_buffer->used_size() < (_sampleRate / 1000.0) * (_buffer_ms / 2.0) * sizeof(cf_t)) {
330  std::this_thread::sleep_for(std::chrono::microseconds(500));
331  }
332  spdlog::debug("Filled ringbuffer to half capacity");
333  _high_watermark_reached = true;
334  }
335 
336  std::vector<char*> buffers(_rx_channels);
337  for (auto ch = 0; ch < _rx_channels; ch++) {
338  buffers[ch] = (char*)data[ch];
339  }
340  _buffer->read(buffers, cnt);
341 
342  if (_buffer->used_size() < (_sampleRate / 1000.0) * (_buffer_ms / 4.0) * sizeof(cf_t)) {
343  required_time_us += 500;
344  } else {
345  required_time_us -= 500;
346  }
347 
348  spdlog::debug("took {}, read {} samples, adjusted required {} us, delta {} us, sleep adj {}, sleeping for {} us",
349  std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - entered).count(),
350  nsamples,
351  std::chrono::microseconds(required_time_us).count(),
352  std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - _last_read).count(),
353  _sleep_adjustment,
354  (std::chrono::microseconds(_sleep_adjustment + required_time_us) - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - _last_read)).count());
355 
356  std::chrono::microseconds sleep = (std::chrono::microseconds(_sleep_adjustment + required_time_us) - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - _last_read));
357 
358  if (sleep.count() > 0) {
359  std::this_thread::sleep_for(sleep);
360  _sleep_adjustment = 0;
361  } else if (sleep.count() > -100000) {
362  _sleep_adjustment = sleep.count();
363  }
364 
365  _last_read = std::chrono::steady_clock::now();
366  return 0;
367 }
368 
370 {
371  if (!_buffer_ready) {
372  return 0;
373  }
374  return static_cast<double>(_buffer->used_size()) / static_cast<double>(_buffer->capacity());
375 }
bool _reading_from_file
Definition: SdrReader.h:190
void stop()
Stop reading samples from the SDR.
Definition: SdrReader.cpp:245
const libconfig::Config & _cfg
Definition: SdrReader.h:159
void start()
Start reading samples from the SDR.
Definition: SdrReader.cpp:213
double get_buffer_level()
Get current ringbuffer level (0 = empty .
Definition: SdrReader.cpp:369
bool _high_watermark_reached
Definition: SdrReader.h:185
bool init(const std::string &device_args, const char *sample_file, const char *write_sample_file)
Initializes the SDR interface and creates a ring buffer according to the params from Cfg.
Definition: SdrReader.cpp:65
void enumerateDevices()
Prints a list of all available SDR devices.
Definition: SdrReader.cpp:48
std::thread _readerThread
Definition: SdrReader.h:163
int get_samples(cf_t *data[SRSRAN_MAX_CHANNELS], uint32_t nsamples, srsran_timestamp_t *rx_time)
Store nsamples count samples into the buffer at data.
Definition: SdrReader.cpp:315
srsran_filesource_t file_source
Definition: SdrReader.h:180
bool set_gain(bool use_agc, double gain, uint8_t idx)
Definition: SdrReader.cpp:143
srsran_filesink_t file_sink
Definition: SdrReader.h:181
void * _stream
Definition: SdrReader.h:157
unsigned _rx_channels
Definition: SdrReader.h:166
bool _writing_to_file
Definition: SdrReader.h:191
bool _running
Definition: SdrReader.h:164
void clear_buffer()
Clear all samples from the rx buffers.
Definition: SdrReader.cpp:107
std::map< std::string, std::string > _device_args
Definition: SdrReader.h:199
bool _buffer_ready
Definition: SdrReader.h:189
bool set_sample_rate(uint32_t rate, uint8_t idx)
Definition: SdrReader.cpp:137
bool set_antenna(const std::string &antenna, uint8_t idx)
Definition: SdrReader.cpp:112
void init_buffer()
Definition: SdrReader.cpp:101
bool tune(uint32_t frequency, uint32_t sample_rate, uint32_t bandwidth, double gain, const std::string &antenna, bool use_agc)
Tune the SDR to the desired frequency, and set gain, filter and antenna parameters.
Definition: SdrReader.cpp:166
std::unique_ptr< MultichannelRingbuffer > _buffer
Definition: SdrReader.h:161
bool set_filter_bw(uint32_t bandwidth, uint8_t idx)
Definition: SdrReader.cpp:131
virtual ~SdrReader()
Default destructor.
Definition: SdrReader.cpp:31
void * _sdr
Definition: SdrReader.h:156
bool set_frequency(uint32_t frequency, uint8_t idx)
Definition: SdrReader.cpp:125
bool _write_samples
Definition: SdrReader.h:192
double _sampleRate
Definition: SdrReader.h:167
unsigned _buffer_ms
Definition: SdrReader.h:188
void read()
Definition: SdrReader.cpp:258
static std::string antenna
Antenna input to be used.
Definition: main.cpp:172
static bool use_agc
Definition: main.cpp:173
static unsigned sample_rate
Sample rate of the SDR.
Definition: main.cpp:167
static uint32_t bandwidth
Low pass filter bandwidth for the SDR.
Definition: main.cpp:170
static unsigned frequency
Center freqeuncy the SDR is tuned to.
Definition: main.cpp:169
static double gain
Overall system gain for the SDR.
Definition: main.cpp:171