From 81ee21f8cf0ac86fd236344b2b4ee8e0ec567a8c Mon Sep 17 00:00:00 2001 From: Javier Arribas Date: Mon, 17 Jun 2019 18:13:06 +0200 Subject: [PATCH] Adding new a multichannel file source suitable for multifrequency captures stored in different files to avoid stream synchronization problems in post-processing --- .../signal_source/adapters/CMakeLists.txt | 2 + .../multichannel_file_signal_source.cc | 314 ++++++++++++++++++ .../multichannel_file_signal_source.h | 131 ++++++++ .../signal_source/libs/gnss_sdr_valve.cc | 16 +- src/core/receiver/gnss_block_factory.cc | 16 + src/core/receiver/gnss_flowgraph.cc | 13 +- 6 files changed, 482 insertions(+), 10 deletions(-) create mode 100644 src/algorithms/signal_source/adapters/multichannel_file_signal_source.cc create mode 100644 src/algorithms/signal_source/adapters/multichannel_file_signal_source.h diff --git a/src/algorithms/signal_source/adapters/CMakeLists.txt b/src/algorithms/signal_source/adapters/CMakeLists.txt index ba3cd23b0..e9cba3c93 100644 --- a/src/algorithms/signal_source/adapters/CMakeLists.txt +++ b/src/algorithms/signal_source/adapters/CMakeLists.txt @@ -129,6 +129,7 @@ endif() set(SIGNAL_SOURCE_ADAPTER_SOURCES file_signal_source.cc + multichannel_file_signal_source.cc gen_signal_source.cc nsr_file_signal_source.cc spir_file_signal_source.cc @@ -142,6 +143,7 @@ set(SIGNAL_SOURCE_ADAPTER_SOURCES set(SIGNAL_SOURCE_ADAPTER_HEADERS file_signal_source.h + multichannel_file_signal_source.h gen_signal_source.h nsr_file_signal_source.h spir_file_signal_source.h diff --git a/src/algorithms/signal_source/adapters/multichannel_file_signal_source.cc b/src/algorithms/signal_source/adapters/multichannel_file_signal_source.cc new file mode 100644 index 000000000..52081e2ad --- /dev/null +++ b/src/algorithms/signal_source/adapters/multichannel_file_signal_source.cc @@ -0,0 +1,314 @@ +/*! + * \file file_signal_source.cc + * \brief Implementation of a class that reads signals samples from a file + * and adapts it to a SignalSourceInterface + * \author Carlos Aviles, 2010. carlos.avilesr(at)googlemail.com + * Javier Arribas, 2011 jarribas(at)cttc.es + * + * ------------------------------------------------------------------------- + * + * Copyright (C) 2010-2018 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * GNSS-SDR is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNSS-SDR is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNSS-SDR. If not, see . + * + * ------------------------------------------------------------------------- + */ + +#include "multichannel_file_signal_source.h" +#include "configuration_interface.h" +#include "gnss_sdr_flags.h" +#include "gnss_sdr_valve.h" +#include +#include +#include +#include +#include // for std::cerr +#include + + +MultichannelFileSignalSource::MultichannelFileSignalSource(ConfigurationInterface* configuration, + const std::string& role, unsigned int in_streams, unsigned int out_streams, + boost::shared_ptr queue) : role_(role), in_streams_(in_streams), out_streams_(out_streams), queue_(std::move(queue)) +{ + std::string default_filename = "./example_capture.dat"; + std::string default_item_type = "short"; + std::string default_dump_filename = "./my_capture.dat"; + + double default_seconds_to_skip = 0.0; + size_t header_size = 0; + samples_ = configuration->property(role + ".samples", 0); + sampling_frequency_ = configuration->property(role + ".sampling_frequency", 0); + n_channels_ = configuration->property(role + ".total_channels", 1); + + for (unsigned int n = 0; n < n_channels_; n++) + { + filename_vec_.push_back(configuration->property(role + ".filename" + std::to_string(n), default_filename)); + } + + item_type_ = configuration->property(role + ".item_type", default_item_type); + repeat_ = configuration->property(role + ".repeat", false); + enable_throttle_control_ = configuration->property(role + ".enable_throttle_control", false); + + double seconds_to_skip = configuration->property(role + ".seconds_to_skip", default_seconds_to_skip); + header_size = configuration->property(role + ".header_size", 0); + int64_t samples_to_skip = 0; + + bool is_complex = false; + + if (item_type_ == "gr_complex") + { + item_size_ = sizeof(gr_complex); + } + else if (item_type_ == "float") + { + item_size_ = sizeof(float); + } + else if (item_type_ == "short") + { + item_size_ = sizeof(int16_t); + } + else if (item_type_ == "ishort") + { + item_size_ = sizeof(int16_t); + is_complex = true; + } + else if (item_type_ == "byte") + { + item_size_ = sizeof(int8_t); + } + else if (item_type_ == "ibyte") + { + item_size_ = sizeof(int8_t); + is_complex = true; + } + else + { + LOG(WARNING) << item_type_ + << " unrecognized item type. Using gr_complex."; + item_size_ = sizeof(gr_complex); + } + try + { + for (unsigned int n = 0; n < n_channels_; n++) + { + file_source_vec_.push_back(gr::blocks::file_source::make(item_size_, filename_vec_.at(n).c_str(), repeat_)); + + if (seconds_to_skip > 0) + { + samples_to_skip = static_cast(seconds_to_skip * sampling_frequency_); + + if (is_complex) + { + samples_to_skip *= 2; + } + } + if (header_size > 0) + { + samples_to_skip += header_size; + } + + if (samples_to_skip > 0) + { + LOG(INFO) << "Skipping " << samples_to_skip << " samples of the input file #" << n; + if (not file_source_vec_.back()->seek(samples_to_skip, SEEK_SET)) + { + LOG(INFO) << "Error skipping bytes!"; + } + } + } + } + catch (const std::exception& e) + { + if (filename_vec_.at(0) == default_filename) + { + std::cerr + << "The configuration file has not been found." + << std::endl + << "Please create a configuration file based on the examples at the 'conf/' folder " + << std::endl + << "and then generate your own GNSS Software Defined Receiver by doing:" + << std::endl + << "$ gnss-sdr --config_file=/path/to/my_GNSS_SDR_configuration.conf" + << std::endl; + } + else + { + std::cerr + << "The receiver was configured to work with a file signal source " + << std::endl + << "but the specified file is unreachable by GNSS-SDR." + << std::endl + << "Please modify your configuration file" + << std::endl + << "and point SignalSource.filename to a valid raw data file. Then:" + << std::endl + << "$ gnss-sdr --config_file=/path/to/my_GNSS_SDR_configuration.conf" + << std::endl + << "Examples of configuration files available at:" + << std::endl + << GNSSSDR_INSTALL_DIR "/share/gnss-sdr/conf/" + << std::endl; + } + + LOG(INFO) << "file_signal_source: Unable to open the samples file " + << filename_vec_.at(0).c_str() << ", exiting the program."; + throw(e); + } + + //todo from here.... add mux demux also + if (samples_ == 0) // read all file + { + /*! + * BUG workaround: The GNU Radio file source does not stop the receiver after reaching the End of File. + * A possible solution is to compute the file length in samples using file size, excluding the last 100 milliseconds, and enable always the + * valve block + */ + std::ifstream file(filename_vec_.at(0).c_str(), std::ios::in | std::ios::binary | std::ios::ate); + std::ifstream::pos_type size; + + if (file.is_open()) + { + size = file.tellg(); + DLOG(INFO) << "Total samples in the file= " << floor(static_cast(size) / static_cast(item_size())); + } + else + { + std::cout << "file_signal_source: Unable to open the samples file " << filename_vec_.at(0).c_str() << std::endl; + LOG(ERROR) << "file_signal_source: Unable to open the samples file " << filename_vec_.at(0).c_str(); + } + std::streamsize ss = std::cout.precision(); + std::cout << std::setprecision(16); + std::cout << "Processing file " << filename_vec_.at(0) << ", which contains " << static_cast(size) << " [bytes]" << std::endl; + std::cout.precision(ss); + + if (size > 0) + { + int64_t bytes_to_skip = samples_to_skip * item_size_; + int64_t bytes_to_process = static_cast(size) - bytes_to_skip; + samples_ = floor(static_cast(bytes_to_process) / static_cast(item_size()) - ceil(0.002 * static_cast(sampling_frequency_))); //process all the samples available in the file excluding at least the last 1 ms + } + } + + CHECK(samples_ > 0) << "File does not contain enough samples to process."; + double signal_duration_s; + signal_duration_s = static_cast(samples_) * (1 / static_cast(sampling_frequency_)); + + if (is_complex) + { + signal_duration_s /= 2.0; + } + + DLOG(INFO) << "Total number samples to be processed= " << samples_ << " GNSS signal duration= " << signal_duration_s << " [s]"; + std::cout << "GNSS signal recorded time to be processed: " << signal_duration_s << " [s]" << std::endl; + + valve_ = gnss_sdr_make_valve(item_size_, samples_ * n_channels_, queue_); + DLOG(INFO) << "valve(" << valve_->unique_id() << ")"; + + + if (enable_throttle_control_) + { + for (unsigned int n = 0; n < n_channels_; n++) + { + throttle_vec_.push_back(gr::blocks::throttle::make(item_size_, sampling_frequency_)); + } + } + + for (unsigned int n = 0; n < n_channels_; n++) + { + LOG(INFO) << "Multichanne File source filename #" << n << filename_vec_.at(n); + } + + DLOG(INFO) << "Samples " << samples_; + DLOG(INFO) << "Sampling frequency " << sampling_frequency_; + DLOG(INFO) << "Item type " << item_type_; + DLOG(INFO) << "Item size " << item_size_; + DLOG(INFO) << "Repeat " << repeat_; + + + if (in_streams_ > 0) + { + LOG(ERROR) << "A signal source does not have an input stream"; + } + if (out_streams_ > 1) + { + LOG(ERROR) << "This implementation only supports one output stream"; + } +} + + +MultichannelFileSignalSource::~MultichannelFileSignalSource() = default; + + +void MultichannelFileSignalSource::connect(gr::top_block_sptr top_block) +{ + if (enable_throttle_control_ == true) + { + for (unsigned int n = 0; n < n_channels_; n++) + { + top_block->connect(file_source_vec_.at(n), 0, throttle_vec_.at(n), 0); + DLOG(INFO) << "connected file_source #" << n << " to throttle"; + top_block->connect(throttle_vec_.at(n), 0, valve_, n); + DLOG(INFO) << "connected throttle #" << n << " to valve_"; + } + } + else + { + for (unsigned int n = 0; n < n_channels_; n++) + { + top_block->connect(file_source_vec_.at(n), 0, valve_, n); + DLOG(INFO) << "connected file_source #" << n << " to valve_"; + } + } +} + + +void MultichannelFileSignalSource::disconnect(gr::top_block_sptr top_block) +{ + if (enable_throttle_control_ == true) + { + for (unsigned int n = 0; n < n_channels_; n++) + { + top_block->disconnect(file_source_vec_.at(n), 0, throttle_vec_.at(n), 0); + DLOG(INFO) << "disconnected file_source #" << n << " to throttle"; + top_block->disconnect(throttle_vec_.at(n), 0, valve_, n); + DLOG(INFO) << "disconnected throttle #" << n << " to valve_"; + } + } + else + { + for (unsigned int n = 0; n < n_channels_; n++) + { + top_block->disconnect(file_source_vec_.at(n), 0, valve_, n); + DLOG(INFO) << "disconnected file_source #" << n << " to valve_"; + } + } +} + + +gr::basic_block_sptr MultichannelFileSignalSource::get_left_block() +{ + LOG(WARNING) << "Left block of a signal source should not be retrieved"; + return gr::blocks::file_source::sptr(); +} + + +gr::basic_block_sptr MultichannelFileSignalSource::get_right_block() +{ + return valve_; +} diff --git a/src/algorithms/signal_source/adapters/multichannel_file_signal_source.h b/src/algorithms/signal_source/adapters/multichannel_file_signal_source.h new file mode 100644 index 000000000..a800fb909 --- /dev/null +++ b/src/algorithms/signal_source/adapters/multichannel_file_signal_source.h @@ -0,0 +1,131 @@ +/*! + * \file file_signal_source.h + * \brief Interface of a class that reads signals samples from a file + * and adapts it to a SignalSourceInterface + * \author Carlos Aviles, 2010. carlos.avilesr(at)googlemail.com + * + * This class represents a file signal source. Internally it uses a GNU Radio's + * gr_file_source as a connector to the data. + * + * ------------------------------------------------------------------------- + * + * Copyright (C) 2010-2018 (see AUTHORS file for a list of contributors) + * + * GNSS-SDR is a software defined Global Navigation + * Satellite Systems receiver + * + * This file is part of GNSS-SDR. + * + * GNSS-SDR is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GNSS-SDR is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GNSS-SDR. If not, see . + * + * ------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_MULTICHANNEL_FILE_SIGNAL_SOURCE_H_ +#define GNSS_SDR_MULTICHANNEL_FILE_SIGNAL_SOURCE_H_ + +#include "gnss_block_interface.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class ConfigurationInterface; + +/*! + * \brief Class that reads signals samples from a file + * and adapts it to a SignalSourceInterface + */ +class MultichannelFileSignalSource : public GNSSBlockInterface +{ +public: + MultichannelFileSignalSource(ConfigurationInterface* configuration, const std::string& role, + unsigned int in_streams, unsigned int out_streams, + boost::shared_ptr queue); + + virtual ~MultichannelFileSignalSource(); + + inline std::string role() override + { + return role_; + } + + /*! + * \brief Returns "File_Signal_Source". + */ + inline std::string implementation() override + { + return "Multichannel_File_Signal_Source"; + } + + inline size_t item_size() override + { + return item_size_; + } + + void connect(gr::top_block_sptr top_block) override; + void disconnect(gr::top_block_sptr top_block) override; + gr::basic_block_sptr get_left_block() override; + gr::basic_block_sptr get_right_block() override; + + inline std::string filename() const + { + return filename_vec_.at(0); + } + + inline std::string item_type() const + { + return item_type_; + } + + inline bool repeat() const + { + return repeat_; + } + + inline int64_t sampling_frequency() const + { + return sampling_frequency_; + } + + inline uint64_t samples() const + { + return samples_; + } + +private: + uint64_t samples_; + int64_t sampling_frequency_; + uint32_t n_channels_; + std::vector filename_vec_; + std::string item_type_; + bool repeat_; + std::string role_; + uint32_t in_streams_; + uint32_t out_streams_; + std::vector file_source_vec_; + boost::shared_ptr valve_; + gr::blocks::file_sink::sptr sink_; + std::vector throttle_vec_; + boost::shared_ptr queue_; + size_t item_size_; + // Throttle control + bool enable_throttle_control_; +}; + +#endif /*GNSS_SDR_MULTICHANNEL_FILE_SIGNAL_SOURCE_H_*/ diff --git a/src/algorithms/signal_source/libs/gnss_sdr_valve.cc b/src/algorithms/signal_source/libs/gnss_sdr_valve.cc index 19626a5d4..d599684ff 100644 --- a/src/algorithms/signal_source/libs/gnss_sdr_valve.cc +++ b/src/algorithms/signal_source/libs/gnss_sdr_valve.cc @@ -44,8 +44,8 @@ Gnss_Sdr_Valve::Gnss_Sdr_Valve(size_t sizeof_stream_item, uint64_t nitems, gr::msg_queue::sptr queue, bool stop_flowgraph) : gr::sync_block("valve", - gr::io_signature::make(1, 1, sizeof_stream_item), - gr::io_signature::make(1, 1, sizeof_stream_item)), + gr::io_signature::make(1, 20, sizeof_stream_item), + gr::io_signature::make(1, 20, sizeof_stream_item)), d_nitems(nitems), d_ncopied_items(0), d_queue(std::move(queue)), @@ -99,11 +99,17 @@ int Gnss_Sdr_Valve::work(int noutput_items, { return 0; } - memcpy(output_items[0], input_items[0], n * input_signature()->sizeof_stream_item(0)); + //multichannel support + for (int ch = 0; ch < output_items.size(); ch++) + { + memcpy(output_items[ch], input_items[ch], n * input_signature()->sizeof_stream_item(ch)); + } d_ncopied_items += n; return n; } - - memcpy(output_items[0], input_items[0], noutput_items * input_signature()->sizeof_stream_item(0)); + for (int ch = 0; ch < output_items.size(); ch++) + { + memcpy(output_items[ch], input_items[ch], noutput_items * input_signature()->sizeof_stream_item(ch)); + } return noutput_items; } diff --git a/src/core/receiver/gnss_block_factory.cc b/src/core/receiver/gnss_block_factory.cc index b4ede27ac..7976d99da 100644 --- a/src/core/receiver/gnss_block_factory.cc +++ b/src/core/receiver/gnss_block_factory.cc @@ -97,6 +97,7 @@ #include "ishort_to_cshort.h" #include "labsat_signal_source.h" #include "mmse_resampler_conditioner.h" +#include "multichannel_file_signal_source.h" #include "notch_filter.h" #include "notch_filter_lite.h" #include "nsr_file_signal_source.h" @@ -1261,6 +1262,21 @@ std::unique_ptr GNSSBlockFactory::GetBlock( block = std::move(block_); } + catch (const std::exception& e) + { + std::cout << "GNSS-SDR program ended." << std::endl; + exit(1); + } + } + else if (implementation == "Multichannel_File_Signal_Source") + { + try + { + std::unique_ptr block_(new MultichannelFileSignalSource(configuration.get(), role, in_streams, + out_streams, queue)); + block = std::move(block_); + } + catch (const std::exception& e) { std::cout << "GNSS-SDR program ended." << std::endl; diff --git a/src/core/receiver/gnss_flowgraph.cc b/src/core/receiver/gnss_flowgraph.cc index 4282d26ee..8af07e1e7 100644 --- a/src/core/receiver/gnss_flowgraph.cc +++ b/src/core/receiver/gnss_flowgraph.cc @@ -215,7 +215,7 @@ void GNSSFlowgraph::connect() } DLOG(INFO) << "blocks connected internally"; - // Signal Source (i) > Signal conditioner (i) > +// Signal Source (i) > Signal conditioner (i) > #ifndef ENABLE_FPGA int RF_Channels = 0; int signal_conditioner_ID = 0; @@ -249,10 +249,13 @@ void GNSSFlowgraph::connect() DLOG(INFO) << "sig_source_.at(i)->get_right_block()->output_signature()->max_streams()=" << sig_source_.at(i)->get_right_block()->output_signature()->max_streams(); DLOG(INFO) << "sig_conditioner_.at(signal_conditioner_ID)->get_left_block()->input_signature()=" << sig_conditioner_.at(signal_conditioner_ID)->get_left_block()->input_signature()->max_streams(); - if (sig_source_.at(i)->get_right_block()->output_signature()->max_streams() > 1) + if (sig_source_.at(i)->get_right_block()->output_signature()->max_streams() > 1 or sig_source_.at(i)->get_right_block()->output_signature()->max_streams() == -1) { - LOG(INFO) << "connecting sig_source_ " << i << " stream " << j << " to conditioner " << j; - top_block_->connect(sig_source_.at(i)->get_right_block(), j, sig_conditioner_.at(signal_conditioner_ID)->get_left_block(), 0); + if (sig_conditioner_.size() > signal_conditioner_ID) + { + LOG(INFO) << "connecting sig_source_ " << i << " stream " << j << " to conditioner " << j; + top_block_->connect(sig_source_.at(i)->get_right_block(), j, sig_conditioner_.at(signal_conditioner_ID)->get_left_block(), 0); + } } else { @@ -819,7 +822,7 @@ void GNSSFlowgraph::disconnect() for (int j = 0; j < RF_Channels; j++) { - if (sig_source_.at(i)->get_right_block()->output_signature()->max_streams() > 1) + if (sig_source_.at(i)->get_right_block()->output_signature()->max_streams() > 1 or sig_source_.at(i)->get_right_block()->output_signature()->max_streams() == -1) { top_block_->disconnect(sig_source_.at(i)->get_right_block(), j, sig_conditioner_.at(signal_conditioner_ID)->get_left_block(), 0); }