OBECA Receive Process
LimeSdrReader.cpp
1 // OBECA - Open Broadcast Edge Cache Appliance
2 // Receive 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 "LimeSdrReader.h"
20 
21 #include <chrono>
22 #include <cmath>
23 
24 #include "spdlog/spdlog.h"
25 
27  if (_sdr != nullptr) {
28  LMS_StopStream(&_stream);
29  LMS_DestroyStream(_sdr, &_stream);
30  LMS_Close(_sdr);
31  }
32 
33  if (_reading_from_file) {
34  srslte_filesource_free(&file_source);
35  }
36 
37  if (_writing_to_file) {
38  srslte_filesink_free(&file_sink);
39  }
40 }
41 
42 auto LimeSdrReader::init(uint8_t device_index, const char* sample_file,
43  const char* write_sample_file) -> bool {
44  if (sample_file != nullptr) {
45  if (0 == srslte_filesource_init(&file_source,
46  const_cast<char*>(sample_file),
47  SRSLTE_COMPLEX_FLOAT_BIN)) {
48  _reading_from_file = true;
49  } else {
50  spdlog::error("Could not open file {}", sample_file);
51  return false;
52  }
53  } else {
54  int device_count = LMS_GetDeviceList(nullptr);
55  if (device_count < 0) {
56  spdlog::error("LMS_GetDeviceList() : {}", LMS_GetLastErrorMessage());
57  return false;
58  }
59 
60  lms_info_str_t device_list[device_count]; // NOLINT
61  if (LMS_GetDeviceList(device_list) < 0) {
62  spdlog::error("LMS_GetDeviceList() : {}", LMS_GetLastErrorMessage());
63  return false;
64  }
65 
66  if (LMS_Open(&_sdr, device_list[device_index], nullptr) != 0) {
67  spdlog::error("LMS_Open() : {}", LMS_GetLastErrorMessage());
68  return false;
69  }
70 
71  if (LMS_Init(_sdr) != 0) {
72  spdlog::error("LMS_Init() : {}", LMS_GetLastErrorMessage());
73  return false;
74  }
75  if (LMS_EnableChannel(_sdr, LMS_CH_RX, 0, true) != 0) {
76  spdlog::error("LMS_EnableChannel() : {}", LMS_GetLastErrorMessage());
77  return false;
78  }
79 
80  if (LMS_EnableChannel(_sdr, LMS_CH_TX, 0, true) != 0) {
81  spdlog::error("LMS_EnableChannel() : {}", LMS_GetLastErrorMessage());
82  return false;
83  }
84 
85  _stream.channel = 0;
86  _stream.fifoSize = 1024*1024;
87  _stream.throughputVsLatency = 1;
88  _stream.isTx = LMS_CH_RX;
89  _stream.dataFmt = lms_stream_t::LMS_FMT_F32;
90  if (LMS_SetupStream(_sdr, &_stream) != 0) {
91  spdlog::error("LMS_SetupStream() : {}", LMS_GetLastErrorMessage());
92  return false;
93  }
94 
95  if (write_sample_file != nullptr) {
96  if (0 == srslte_filesink_init(&file_sink,
97  const_cast<char*>(write_sample_file),
98  SRSLTE_COMPLEX_FLOAT_BIN)) {
99  _writing_to_file = true;
100  } else {
101  spdlog::error("Could not open file {}", write_sample_file);
102  return false;
103  }
104  }
105  }
106 
107  _cfg.lookupValue("sdr.ringbuffer_size_ms", _buffer_ms);
108  unsigned int buffer_size = 16384/*15360*/ * _buffer_ms;
109  // at 10MHz BW / 15.36Mhz sample rate
110  int error = 0;
111  error = _buffer.initialize(sizeof(cf_t) * buffer_size);
112  if (error != 0) {
113  spdlog::error("Cannot allocate ringbuffer: {}", errno);
114  return false;
115  }
116  _buffer_ready = true;
117  return true;
118 }
119 
121  _buffer.clear();
122  _high_watermark_reached = false;
123 }
124 
125 auto LimeSdrReader::setSampleRate(unsigned /*sample_rate*/) -> bool {
126  return _sdr != nullptr;
127 }
128 
129 auto LimeSdrReader::tune(uint32_t frequency, uint32_t sample_rate,
130  uint32_t bandwidth, double gain, const std::string& antenna) -> bool {
131  _antenna = antenna;
132  _gain = gain;
133  _frequency = frequency;
134  _filterBw = bandwidth;
135 
136  _buffer.clear();
137  _high_watermark_reached = false;
138 
139  if (_reading_from_file) {
140  _sampleRate = sample_rate;
141  return true;
142  }
143 
144  if (_sdr == nullptr) {
145  return false;
146  }
147 
148  spdlog::info("Tuning Lime SDR Mini to {} MHz, filter bandwidth {} MHz, sample rate {}, gain {}, antenna path {}",
149  frequency/1000000.0, bandwidth/1000000.0, sample_rate/1000000.0, gain, antenna);
150 
151  if (LMS_SetLOFrequency(_sdr, LMS_CH_RX, 0, frequency) != 0) {
152  spdlog::error("LMS_SetLOFrequency() : {}", LMS_GetLastErrorMessage());
153  return false;
154  }
155  double freq = NAN;
156  if ( LMS_GetLOFrequency(_sdr, LMS_CH_RX, 0, &freq) != 0 ) {
157  spdlog::error("LMS_GetLOFrequency() : {}", LMS_GetLastErrorMessage());
158  return false;
159  }
160  spdlog::info("LO frequency set to {} MHz", freq / 1e6);
161 
162  sleep(2);
163 
164  int nrAntennas = LMS_GetAntennaList(_sdr, false, 0, nullptr);
165  lms_name_t list[nrAntennas]; // NOLINT
166  LMS_GetAntennaList(_sdr, false, 0, list);
167  int antennaFound = 0;
168  for (int i = 0; i < nrAntennas; i++) {
169  if (strcmp(list[i], antenna.c_str()) == 0) {
170  antennaFound = 1;
171  if (LMS_SetAntenna(_sdr, false, 0, i) != 0) {
172  spdlog::error("LMS_SetAntenna() : {}", LMS_GetLastErrorMessage());
173  return false;
174  }
175  break;
176  }
177  }
178  if (antennaFound == 0) {
179  spdlog::error("Antenna not found: {}", LMS_GetLastErrorMessage());
180  return false;
181  }
182 
183  if (LMS_SetSampleRate(_sdr, sample_rate, 0) != 0) {
184  spdlog::error("LMS_SetSampleRate() : {}", LMS_GetLastErrorMessage());
185  return false;
186  }
187 
188  double rf_rate = NAN;
189  if (LMS_GetSampleRate(_sdr, LMS_CH_RX, 0, &_sampleRate, &rf_rate) != 0) {
190  spdlog::error("LMS_GetSampleRate() : {}", LMS_GetLastErrorMessage());
191  return false;
192  }
193  spdlog::info("Sample rate on host interface {} MHz, RF ADC {} MHz", _sampleRate / 1e6, rf_rate / 1e6);
194 
195 
196 
197 
198  if (gain > 0) {
199  if (LMS_SetNormalizedGain(_sdr, LMS_CH_RX, 0, gain) != 0) {
200  spdlog::error("LMS_SetNormalizedGain() : {}", LMS_GetLastErrorMessage());
201  return false;
202  }
203  }
204 
205  float_type setgain = NAN; // normalized gain
206  unsigned int gaindB = 0; // gain in dB
207  if (LMS_GetNormalizedGain(_sdr, LMS_CH_RX, 0, &setgain) != 0) {
208  spdlog::error("LMS_GetNr() : {}", LMS_GetLastErrorMessage());
209  return false;
210  }
211  if (LMS_GetGaindB(_sdr, LMS_CH_RX, 0, &gaindB) != 0) {
212  spdlog::error("LMS_GetGaindB(() : {}", LMS_GetLastErrorMessage());
213  return false;
214  }
215 
216  spdlog::info("RX gain set to {} / {} dB", setgain, gaindB);
217 
218  lms_range_t range;
219  if (LMS_GetLPFBWRange(_sdr, LMS_CH_RX, &range) != 0) {
220  spdlog::error("LMS_GetLPFBWRange() : {}", LMS_GetLastErrorMessage());
221  return false;
222  }
223  spdlog::debug("LPF BW range {} - {} MHz, step {} Hz", range.min / 1e6, range.max / 1e6, range.step);
224 
225  if (LMS_SetLPFBW(_sdr, LMS_CH_RX, 0, bandwidth*1.2) != 0) {
226  spdlog::error("LMS_SetLPFBW() : {}", LMS_GetLastErrorMessage());
227  return false;
228  }
229 
230  if (LMS_Calibrate(_sdr, LMS_CH_RX, 0, bandwidth, 0) != 0) {
231  spdlog::error("LMS_Calibrate() : {}", LMS_GetLastErrorMessage());
232  return false;
233  }
234 
235  return true;
236 }
237 
239  _running = true;
240  LMS_StartStream(&_stream);
241 
242  // Start the reader thread and elevate its priority to realtime
243  _readerThread = std::thread{&LimeSdrReader::read, this};
244  struct sched_param thread_param = {};
245  thread_param.sched_priority = 50;
246  _cfg.lookupValue("sdr.reader_thread_priority_rt", thread_param.sched_priority);
247 
248  spdlog::debug("Launching sample reader thread with realtime scheduling priority {}", thread_param.sched_priority);
249 
250  int error = pthread_setschedparam(_readerThread.native_handle(), SCHED_RR, &thread_param);
251  if (error != 0) {
252  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));
253  }
254 }
255 
257  _running = false;
258  LMS_StopStream(&_stream);
259 
260  _readerThread.join();
261  _buffer.clear();
262 }
263 
264 void LimeSdrReader::read() {
265  std::array<void*, SRSLTE_MAX_CHANNELS> radio_buffers = { nullptr };
266  while (_running) {
267  int toRead = ceil(_sampleRate / 1000.0);
268  if (_buffer.free_size() < toRead * sizeof(cf_t)) {
269  spdlog::debug("ringbuffer overflow");
270  std::this_thread::sleep_for(std::chrono::microseconds(1000));
271  } else {
272  int read = 0;
273  if (_reading_from_file) {
274  std::chrono::steady_clock::time_point entered = {};
275  entered = std::chrono::steady_clock::now();
276  int64_t required_time_us = (1000000.0/_sampleRate) * toRead;
277 
278  radio_buffers[0] = _buffer.write_head();
279  read = srslte_filesource_read_multi(&file_source, radio_buffers.data(), toRead, 1);
280  if ( read == 0 ) {
281  srslte_filesource_seek(&file_source, 0);
282  }
283 
284  if (read> 0) {
285  _buffer.commit( read * sizeof(cf_t) );
286  }
287 
288  std::chrono::microseconds sleep = (std::chrono::microseconds(required_time_us) -
289  std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - entered));
290  std::this_thread::sleep_for(sleep);
291  } else {
292  read = LMS_RecvStream(&_stream, _buffer.write_head(), toRead, nullptr,
293  1000);
294  if (read> 0) {
295  if (_writing_to_file) {
296  srslte_filesink_write(&file_sink, _buffer.write_head(), read);
297  }
298  _buffer.commit( read * sizeof(cf_t) );
299  }
300  }
301  }
302  }
303  spdlog::debug("Sample reader thread exited");
304 }
305 
306 auto LimeSdrReader::getSamples(cf_t* data, uint32_t nsamples,
307  srslte_timestamp_t *
308  /*rx_time*/) -> int {
309  std::chrono::steady_clock::time_point entered = {};
310  entered = std::chrono::steady_clock::now();
311 
312  int64_t required_time_us = (1000000.0/_sampleRate) * nsamples;
313  size_t cnt = nsamples * sizeof(cf_t);
314 
315  if (_high_watermark_reached && _buffer.size() < (_sampleRate / 1000.0) * 10 * sizeof(cf_t)) {
316  _high_watermark_reached = false;
317  }
318 
319  if (!_high_watermark_reached) {
320  while (_buffer.size() < (_sampleRate / 1000.0) * (_buffer_ms / 2.0) * sizeof(cf_t)) {
321  std::this_thread::sleep_for(std::chrono::microseconds(500));
322  }
323  spdlog::debug("Filled ringbuffer to half capacity");
324  _high_watermark_reached = true;
325  }
326 
327  memcpy(data, _buffer.read_head(), cnt);
328  _buffer.consume(cnt);
329 
330  if (_buffer.size() < (_sampleRate / 1000.0) * (_buffer_ms / 2.0) * sizeof(cf_t)) {
331  required_time_us += 500;
332  } else {
333  required_time_us -= 500;
334  }
335 
336  spdlog::debug("read {} samples, adjusted required {} us, delta {} us, sleep adj {}, sleeping for {} us",
337  nsamples,
338  std::chrono::microseconds(required_time_us).count(),
339  std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - _last_read).count(),
340  _sleep_adjustment,
341  (std::chrono::microseconds(_sleep_adjustment + required_time_us) - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - _last_read)).count());
342 
343  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));
344 
345  if (sleep.count() > 0) {
346  std::this_thread::sleep_for(sleep);
347  _sleep_adjustment = 0;
348  } else if (sleep.count() > -100000) {
349  _sleep_adjustment = sleep.count();
350  }
351 
352  _last_read = std::chrono::steady_clock::now();
353  return 0;
354 }
LimeSdrReader::tune
bool tune(uint32_t frequency, uint32_t sample_rate, uint32_t bandwidth, double gain, const std::string &antenna)
Tune the SDR to the desired frequency, and set gain, filter and antenna parameters.
Definition: LimeSdrReader.cpp:129
LimeSdrReader::getSamples
int getSamples(cf_t *data, uint32_t nsamples, srslte_timestamp_t *rx_time)
Store nsamples count samples into the buffer at data.
Definition: LimeSdrReader.cpp:306
LimeSdrReader::init
bool init(uint8_t device_index, 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: LimeSdrReader.cpp:42
LimeSdrReader::setSampleRate
bool setSampleRate(unsigned sample_rate)
Adjust the sample rate.
Definition: LimeSdrReader.cpp:125
LimeSdrReader::~LimeSdrReader
virtual ~LimeSdrReader()
Default destructor.
Definition: LimeSdrReader.cpp:26
LimeSdrReader::start
void start()
Start reading samples from the SDR.
Definition: LimeSdrReader.cpp:238
LimeSdrReader::clear_buffer
void clear_buffer()
Clear all samples from the rx buffers.
Definition: LimeSdrReader.cpp:120
LimeSdrReader::stop
void stop()
Stop reading samples from the SDR.
Definition: LimeSdrReader.cpp:256