From e0eb4bd769345dfa72adffd16fa599f0e41b26f9 Mon Sep 17 00:00:00 2001 From: Jim Melton Date: Thu, 11 Feb 2021 12:47:17 -0700 Subject: [PATCH] initial implementation of common file source base class Only the file_signal_source uses it at this time; changing multi-channel to use it can be done with a little work --- .../signal_source/adapters/CMakeLists.txt | 14 + .../adapters/file_signal_source.cc | 315 +------------- .../adapters/file_signal_source.h | 58 +-- .../adapters/file_source_base.cc | 396 ++++++++++++++++++ .../signal_source/adapters/file_source_base.h | 139 ++++++ .../adapters/signal_source_base.h | 7 +- 6 files changed, 556 insertions(+), 373 deletions(-) create mode 100644 src/algorithms/signal_source/adapters/file_source_base.cc create mode 100644 src/algorithms/signal_source/adapters/file_source_base.h diff --git a/src/algorithms/signal_source/adapters/CMakeLists.txt b/src/algorithms/signal_source/adapters/CMakeLists.txt index 93377a6ad..c0f60592c 100644 --- a/src/algorithms/signal_source/adapters/CMakeLists.txt +++ b/src/algorithms/signal_source/adapters/CMakeLists.txt @@ -93,6 +93,7 @@ endif() set(SIGNAL_SOURCE_ADAPTER_SOURCES signal_source_base.cc + file_source_base.cc file_signal_source.cc multichannel_file_signal_source.cc gen_signal_source.cc @@ -108,6 +109,7 @@ set(SIGNAL_SOURCE_ADAPTER_SOURCES set(SIGNAL_SOURCE_ADAPTER_HEADERS signal_source_base.h + file_source_base.h file_signal_source.h multichannel_file_signal_source.h gen_signal_source.h @@ -170,6 +172,18 @@ if(GNURADIO_USES_STD_POINTERS) ) endif() +# This should really be at a higher level and apply universally to the +# build system +if(FILESYSTEM_FOUND) + target_compile_definitions(signal_source_adapters PUBLIC -DHAS_STD_FILESYSTEM=1) + if(find_experimental) + target_compile_definitions(signal_source_adapters PUBLIC -DHAS_STD_FILESYSTEM_EXPERIMENTAL=1) + endif() + target_link_libraries(signal_source_adapters PUBLIC std::filesystem) +else() + target_link_libraries(signal_source_adapters PUBLIC Boost::filesystem Boost::system) +endif() + if(ENABLE_RAW_UDP AND PCAP_FOUND) target_link_libraries(signal_source_adapters PRIVATE diff --git a/src/algorithms/signal_source/adapters/file_signal_source.cc b/src/algorithms/signal_source/adapters/file_signal_source.cc index 120bda664..5c112a7a2 100644 --- a/src/algorithms/signal_source/adapters/file_signal_source.cc +++ b/src/algorithms/signal_source/adapters/file_signal_source.cc @@ -19,7 +19,6 @@ #include "file_signal_source.h" #include "configuration_interface.h" #include "gnss_sdr_flags.h" -#include "gnss_sdr_valve.h" #include #include #include @@ -32,324 +31,16 @@ using namespace std::string_literals; FileSignalSource::FileSignalSource(ConfigurationInterface const* configuration, std::string const& role, unsigned int in_streams, unsigned int out_streams, Concurrent_Queue* queue) - : SignalSourceBase(configuration, role, "File_Signal_Source"s) -, in_streams_(in_streams), out_streams_(out_streams) + : FileSourceBase(configuration, role, "File_Signal_Source"s, queue) { - const std::string default_filename("./example_capture.dat"); - const std::string default_item_type("short"); - const std::string default_dump_filename("./my_capture.dat"); - - const double default_seconds_to_skip = 0.0; - - samples_ = configuration->property(role + ".samples", static_cast(0)); - sampling_frequency_ = configuration->property(role + ".sampling_frequency", static_cast(0)); - filename_ = configuration->property(role + ".filename", default_filename); - - // override value with commandline flag, if present - if (FLAGS_signal_source != "-") - { - filename_ = FLAGS_signal_source; - } - if (FLAGS_s != "-") - { - filename_ = FLAGS_s; - } - - item_type_ = configuration->property(role + ".item_type", default_item_type); - repeat_ = configuration->property(role + ".repeat", false); - dump_ = configuration->property(role + ".dump", false); - dump_filename_ = configuration->property(role + ".dump_filename", default_dump_filename); - enable_throttle_control_ = configuration->property(role + ".enable_throttle_control", false); - - const double seconds_to_skip = configuration->property(role + ".seconds_to_skip", default_seconds_to_skip); - const size_t 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 - { - file_source_ = gr::blocks::file_source::make(item_size_, filename_.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"; - if (not file_source_->seek(samples_to_skip, SEEK_SET)) - { - LOG(INFO) << "Error skipping bytes!"; - } - } - } - catch (const std::exception& e) - { - if (filename_ == default_filename) - { - std::cerr - << "The configuration file has not been found.\n" - << "Please create a configuration file based on the examples at the 'conf/' folder\n" - << "and then generate your own GNSS Software Defined Receiver by doing:\n" - << "$ gnss-sdr --config_file=/path/to/my_GNSS_SDR_configuration.conf\n"; - } - else - { - std::cerr - << "The receiver was configured to work with a file signal source\n" - << "but the specified file is unreachable by GNSS-SDR.\n" - << "Please modify your configuration file\n" - << "and point SignalSource.filename to a valid raw data file. Then:\n" - << "$ gnss-sdr --config_file=/path/to/my_GNSS_SDR_configuration.conf\n" - << "Examples of configuration files available at:\n" - << GNSSSDR_INSTALL_DIR "/share/gnss-sdr/conf/\n"; - } - - LOG(INFO) << "file_signal_source: Unable to open the samples file " - << filename_.c_str() << ", exiting the program."; - throw(e); - } - - DLOG(INFO) << "file_source(" << file_source_->unique_id() << ")"; - - 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_.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_.c_str() << '\n'; - LOG(ERROR) << "file_signal_source: Unable to open the samples file " << filename_.c_str(); - } - std::streamsize ss = std::cout.precision(); - std::cout << std::setprecision(16); - std::cout << "Processing file " << filename_ << ", which contains " << static_cast(size) << " [bytes]\n"; - std::cout.precision(ss); - - if (size > 0) - { - const int64_t bytes_to_skip = samples_to_skip * item_size_; - const 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 = 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]\n"; - - valve_ = gnss_sdr_make_valve(item_size_, samples_, queue); - DLOG(INFO) << "valve(" << valve_->unique_id() << ")"; - - if (dump_) - { - sink_ = gr::blocks::file_sink::make(item_size_, dump_filename_.c_str()); - DLOG(INFO) << "file_sink(" << sink_->unique_id() << ")"; - } - - if (enable_throttle_control_) - { - throttle_ = gr::blocks::throttle::make(item_size_, sampling_frequency_); - } - - DLOG(INFO) << "File source filename " << filename_; - 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_; - DLOG(INFO) << "Dump " << dump_; - DLOG(INFO) << "Dump filename " << dump_filename_; - if (in_streams_ > 0) + if (in_streams > 0) { LOG(ERROR) << "A signal source does not have an input stream"; } - if (out_streams_ > 1) + if (out_streams > 1) { LOG(ERROR) << "This implementation only supports one output stream"; } } -void FileSignalSource::connect(gr::top_block_sptr top_block) -{ - if (samples_ > 0) - { - if (enable_throttle_control_ == true) - { - top_block->connect(file_source_, 0, throttle_, 0); - DLOG(INFO) << "connected file source to throttle"; - top_block->connect(throttle_, 0, valve_, 0); - DLOG(INFO) << "connected throttle to valve"; - if (dump_) - { - top_block->connect(valve_, 0, sink_, 0); - DLOG(INFO) << "connected valve to file sink"; - } - } - else - { - top_block->connect(file_source_, 0, valve_, 0); - DLOG(INFO) << "connected file source to valve"; - if (dump_) - { - top_block->connect(valve_, 0, sink_, 0); - DLOG(INFO) << "connected valve to file sink"; - } - } - } - else - { - if (enable_throttle_control_ == true) - { - top_block->connect(file_source_, 0, throttle_, 0); - DLOG(INFO) << "connected file source to throttle"; - if (dump_) - { - top_block->connect(file_source_, 0, sink_, 0); - DLOG(INFO) << "connected file source to sink"; - } - } - else - { - if (dump_) - { - top_block->connect(file_source_, 0, sink_, 0); - DLOG(INFO) << "connected file source to sink"; - } - } - } -} - - -void FileSignalSource::disconnect(gr::top_block_sptr top_block) -{ - if (samples_ > 0) - { - if (enable_throttle_control_ == true) - { - top_block->disconnect(file_source_, 0, throttle_, 0); - DLOG(INFO) << "disconnected file source to throttle"; - top_block->disconnect(throttle_, 0, valve_, 0); - DLOG(INFO) << "disconnected throttle to valve"; - if (dump_) - { - top_block->disconnect(valve_, 0, sink_, 0); - DLOG(INFO) << "disconnected valve to file sink"; - } - } - else - { - top_block->disconnect(file_source_, 0, valve_, 0); - DLOG(INFO) << "disconnected file source to valve"; - if (dump_) - { - top_block->disconnect(valve_, 0, sink_, 0); - DLOG(INFO) << "disconnected valve to file sink"; - } - } - } - else - { - if (enable_throttle_control_ == true) - { - top_block->disconnect(file_source_, 0, throttle_, 0); - DLOG(INFO) << "disconnected file source to throttle"; - if (dump_) - { - top_block->disconnect(file_source_, 0, sink_, 0); - DLOG(INFO) << "disconnected file source to sink"; - } - } - else - { - if (dump_) - { - top_block->disconnect(file_source_, 0, sink_, 0); - DLOG(INFO) << "disconnected file source to sink"; - } - } - } -} - - -gr::basic_block_sptr FileSignalSource::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 FileSignalSource::get_right_block() -{ - if (samples_ > 0) - { - return valve_; - } - if (enable_throttle_control_ == true) - { - return throttle_; - } - return file_source_; -} diff --git a/src/algorithms/signal_source/adapters/file_signal_source.h b/src/algorithms/signal_source/adapters/file_signal_source.h index 754698446..556eb3042 100644 --- a/src/algorithms/signal_source/adapters/file_signal_source.h +++ b/src/algorithms/signal_source/adapters/file_signal_source.h @@ -21,7 +21,7 @@ #ifndef GNSS_SDR_FILE_SIGNAL_SOURCE_H #define GNSS_SDR_FILE_SIGNAL_SOURCE_H -#include "signal_source_base.h" +#include "file_source_base.h" #include "concurrent_queue.h" #include @@ -46,7 +46,7 @@ class ConfigurationInterface; * \brief Class that reads signals samples from a file * and adapts it to a SignalSourceInterface */ -class FileSignalSource : public SignalSourceBase +class FileSignalSource : public FileSourceBase { public: FileSignalSource(ConfigurationInterface const* configuration, std::string const& role, @@ -55,61 +55,7 @@ public: ~FileSignalSource() = default; - 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_; - } - - 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: - gr::blocks::file_source::sptr file_source_; - gnss_shared_ptr valve_; - gr::blocks::file_sink::sptr sink_; - gr::blocks::throttle::sptr throttle_; - - std::string item_type_; - std::string filename_; - std::string dump_filename_; - - uint64_t samples_; - int64_t sampling_frequency_; - size_t item_size_; - - uint32_t in_streams_; - uint32_t out_streams_; - - bool enable_throttle_control_; - bool repeat_; - bool dump_; }; diff --git a/src/algorithms/signal_source/adapters/file_source_base.cc b/src/algorithms/signal_source/adapters/file_source_base.cc new file mode 100644 index 000000000..cea57cec3 --- /dev/null +++ b/src/algorithms/signal_source/adapters/file_source_base.cc @@ -0,0 +1,396 @@ +/*! + * \file file_source_base.cc + * \brief Implementation of the base class for file-oriented signal_source GNSS blocks + * \author Jim Melton, 2021. jim.melton(at)sncorp.com + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2021 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#include "file_source_base.h" + +#include "configuration_interface.h" +#include "gnss_sdr_flags.h" +#include "gnss_sdr_filesystem.h" +#include "gnss_sdr_valve.h" +#include +#include +#include // ceil, floor + + +using namespace std::string_literals; + +void FileSourceBase::connect(gr::top_block_sptr top_block) +{ + init(); + + auto source = gr::basic_block_sptr(); + auto output = gr::basic_block_sptr(); + + // THROTTLE + if (enable_throttle_control_) + { + // if we are throttling... + throttle_ = gr::blocks::throttle::make(item_size_, sampling_frequency_); + + top_block->connect(file_source_, 0, throttle_, 0); + DLOG(INFO) << "connected file source to throttle"; + + source = throttle_; + } + else + { + // no throttle; let 'er rip + source = file_source_; + } + + // VALVE + if (samples_ > 0) + { + // if a number of samples is specified, honor it by creating a valve + // In practice, this is always true + valve_ = gnss_sdr_make_valve(item_size_, samples_, queue_); + DLOG(INFO) << "valve(" << valve_->unique_id() << ")"; + + top_block->connect(source, 0, valve_, 0); + DLOG(INFO) << "connected source to valve"; + + output = valve_; + } + else + { + // TODO: dumping a file source is unlikely, but should this be the raw file source or the + // throttle if there is one? I'm leaning towards "output=source" + output = file_source_; + } + + // DUMP + if (dump_) + { + sink_ = gr::blocks::file_sink::make(item_size_, dump_filename_.c_str()); + DLOG(INFO) << "file_sink(" << sink_->unique_id() << ")"; + + top_block->connect(output, 0, sink_, 0); + DLOG(INFO) << "connected output to file sink"; + } +} + + +void FileSourceBase::disconnect(gr::top_block_sptr top_block) +{ + if (samples_ > 0) + { + if (enable_throttle_control_ == true) + { + top_block->disconnect(file_source_, 0, throttle_, 0); + DLOG(INFO) << "disconnected file source to throttle"; + top_block->disconnect(throttle_, 0, valve_, 0); + DLOG(INFO) << "disconnected throttle to valve"; + if (dump_) + { + top_block->disconnect(valve_, 0, sink_, 0); + DLOG(INFO) << "disconnected valve to file sink"; + } + } + else + { + top_block->disconnect(file_source_, 0, valve_, 0); + DLOG(INFO) << "disconnected file source to valve"; + if (dump_) + { + top_block->disconnect(valve_, 0, sink_, 0); + DLOG(INFO) << "disconnected valve to file sink"; + } + } + } + else + { + if (enable_throttle_control_ == true) + { + top_block->disconnect(file_source_, 0, throttle_, 0); + DLOG(INFO) << "disconnected file source to throttle"; + if (dump_) + { + top_block->disconnect(file_source_, 0, sink_, 0); + DLOG(INFO) << "disconnected file source to sink"; + } + } + else + { + if (dump_) + { + top_block->disconnect(file_source_, 0, sink_, 0); + DLOG(INFO) << "disconnected file source to sink"; + } + } + } +} + + +gr::basic_block_sptr FileSourceBase::get_left_block() +{ + // TODO: is this right? Shouldn't the left block be a nullptr? + LOG(WARNING) << "Left block of a signal source should not be retrieved"; + return gr::blocks::file_source::sptr(); +} + + +gr::basic_block_sptr FileSourceBase::get_right_block() +{ + if (samples_ > 0) + { + return valve_; + } + if (enable_throttle_control_ == true) + { + return throttle_; + } + return file_source_; +} + + +std::string FileSourceBase::filename() const +{ + return filename_; +} + +std::string FileSourceBase::item_type() const +{ + return item_type_; +} + +size_t FileSourceBase::item_size() +{ + return item_size_; +} +size_t FileSourceBase::item_size() const +{ + return item_size_; +} + +bool FileSourceBase::repeat() const +{ + return repeat_; +} + +int64_t FileSourceBase::sampling_frequency() const +{ + return sampling_frequency_; +} + +uint64_t FileSourceBase::samples() const +{ + return samples_; +} + + +void FileSourceBase::init() +{ + auto item_tuple = itemTypeToSize(); + item_size_ = std::get<0>(item_tuple); + is_complex_ = std::get<1>(item_tuple); + + try + { + // TODO: why are we manually seeking, instead of passing the samples_to_skip to the file_source factory? + auto samples_to_skip = samplesToSkip(); + + file_source_ = gr::blocks::file_source::make(item_size(), filename().data(), repeat()); + + if (samples_to_skip > 0) + { + LOG(INFO) << "Skipping " << samples_to_skip << " samples of the input file"; + if (not file_source_->seek(samples_to_skip, SEEK_SET)) + { + LOG(ERROR) << "Error skipping bytes!"; + } + } + } + catch (const std::exception& e) + { + std::cerr + << "The receiver was configured to work with a file-based signal source\n" + << "but the specified file is unreachable by GNSS-SDR.\n" + << "[" << filename() << "]\n" + << "\n" + << "Please modify your configuration file\n" + << "and point SignalSource.filename to a valid raw data file. Then:\n" + << "$ gnss-sdr --config_file=/path/to/my_GNSS_SDR_configuration.conf\n" + << "Examples of configuration files available at:\n" + << GNSSSDR_INSTALL_DIR "/share/gnss-sdr/conf/\n" + << std::endl; + + LOG(ERROR) << "file_signal_source: Unable to open the samples file " + << filename() << ", exiting the program."; + throw; + } + + DLOG(INFO) << implementation() << "(" << file_source_->unique_id() << ")"; + + // At this point, we know that the file exists + samples_ = computeSamplesInFile(); + auto signal_duration_s = 1.0 * samples_ / 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]\n"; + + + DLOG(INFO) << "File source filename " << filename_; + 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_; + DLOG(INFO) << "Dump " << dump_; + DLOG(INFO) << "Dump filename " << dump_filename_; +} + + +FileSourceBase::FileSourceBase(ConfigurationInterface const* configuration, std::string role, std::string impl, + Concurrent_Queue* queue) + : SignalSourceBase(configuration, role, impl) + , filename_(configuration->property(role + ".filename"s, "../data/example_capture.dat"s)) + , file_source_() + + , item_type_(configuration->property(role + ".item_type"s, "short"s)) + , item_size_(0) // invalid + , is_complex_(false) + + , header_size_(configuration->property(role + ".header_size"s, 0UL)) + , seconds_to_skip_(configuration->property(role + ".seconds_to_skip", 0.0)) + , repeat_(configuration->property(role + ".repeat"s, false)) + + , samples_(configuration->property(role + ".samples"s, 0UL)) + , sampling_frequency_(configuration->property(role + ".sampling_frequency"s, 0UL)) + , valve_() + , queue_(queue) + + , enable_throttle_control_(configuration->property(role + ".enable_throttle_control"s, false)) + , throttle_() + + , dump_(configuration->property(role + ".dump"s, false)) + , dump_filename_(configuration->property(role + ".dump_filename"s, "../data/my_capture.dat"s)) + , sink_() +{ + // override value with commandline flag, if present + if (FLAGS_signal_source != "-") + { + filename_ = FLAGS_signal_source; + } + if (FLAGS_s != "-") + { + filename_ = FLAGS_s; + } + +} + +std::tuple FileSourceBase::itemTypeToSize() const +{ + auto is_complex = false; + auto item_size = size_t(0); + + 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); + } + + return std::make_tuple(item_size, is_complex); +} + +size_t FileSourceBase::samplesToSkip() const +{ + auto samples_to_skip = size_t(0); + + 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_; + } + + return samples_to_skip; +} + +size_t FileSourceBase::computeSamplesInFile() const +{ + auto n_samples = size_t(samples()); + + + // this could throw, but the existence of the file has been proven before we get here. + auto size = fs::file_size(filename()); + n_samples = std::floor(1.0 * size / item_size()); + + auto to_skip = samplesToSkip(); + + /*! + * 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 at least + * the last 2 milliseconds, and enable always the valve block + */ + auto tail = static_cast(std::ceil(0.002 * sampling_frequency())); + + DLOG(INFO) << "Total samples in the file= " << n_samples; + std::cout << "Processing file " << filename() << ", which contains " << n_samples << " samples (" << size << " bytes)\n"; + + if (n_samples > (to_skip + tail)) + { + // process all the samples available in the file excluding up to the last 2 ms + n_samples -= to_skip + tail; + } + else + { + // this will terminate the program + LOG(FATAL) << "Skipping " << to_skip << " samples from the front and truncating 2ms (" << tail << " samples)\n" + << "is greater than the number of samples in the file (" << n_samples << ")"; + } + + return n_samples; +} diff --git a/src/algorithms/signal_source/adapters/file_source_base.h b/src/algorithms/signal_source/adapters/file_source_base.h new file mode 100644 index 000000000..c4581e645 --- /dev/null +++ b/src/algorithms/signal_source/adapters/file_source_base.h @@ -0,0 +1,139 @@ +/*! + * \file file_source_base.h + * \brief Header file of the base class to file-oriented signal_source GNSS blocks. + * \author Jim Melton, 2021. jim.melton(at)sncorp.com + * + * + * ----------------------------------------------------------------------------- + * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. + * This file is part of GNSS-SDR. + * + * Copyright (C) 2010-2021 (see AUTHORS file for a list of contributors) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + */ + +#ifndef GNSS_SDR_FILE_SOURCE_BASE_H +#define GNSS_SDR_FILE_SOURCE_BASE_H + +#include "signal_source_base.h" + +#include "concurrent_queue.h" +#include +#include +#include + +// for dump +#include + + +#include +#include + + +class ConfigurationInterface; + +// This class supports the following properties: +// +// .filename - the path to the input file +// - may be overridden by the -signal_source or -s command-line arguments +// .samples - number of samples to process (default 0) +// - if not specified or 0, read the entire file; otherwise stop after that many samples +// .sampling_frequency - the frequency of the sampled data (samples/second) +// .item_type - data type of the samples (default "short") +// .header_size - the size of a prefixed header to skip in "samples" (default 0) +// .seconds_to_skip - number of seconds of lead-in data to skip over (default 0) +// .enable_throttle_control - whether to stop reading if the upstream buffer is full (default false) +// .repeat - whether to rewind and continue at end of file (default false) +// +// (probably abstracted to the base class) +// .dump - whether to archive input data +// .dump_filename - if dumping, path to file for output + + +class FileSourceBase : public SignalSourceBase +{ +public: + + //! virtual overrides + 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; + + + //! the file to read + std::string filename() const; + + //! the item type + std::string item_type() const; + + //! the configured size of each item + size_t item_size() override; + virtual size_t item_size() const; // what the interface **should** have declared + + //! Whether to repeat reading after end-of-file + bool repeat() const; + + //! the sampling frequency of the source file + int64_t sampling_frequency() const; + + //! the number of samples in the file + uint64_t samples() const; + + //! perform post-construction initialization + void init(); + +protected: + //! Constructor + FileSourceBase(ConfigurationInterface const* configuration, std::string role, std::string impl, + Concurrent_Queue* queue); + + //! compute the item size, from the item_type(). Subclasses may constrain types that don't make + // sense. The return of this method is a tuple of item_size and is_complex + virtual std::tuple itemTypeToSize() const; + + //! compute the number of samples to skip + virtual size_t samplesToSkip() const; + + //! compute the number of samples in the file + size_t computeSamplesInFile() const; + +private: + std::string filename_; + gr::blocks::file_source::sptr file_source_; + + std::string item_type_; + size_t item_size_; + bool is_complex_; // a misnomer; if I/Q are interleaved as integer values + + size_t header_size_; // length (in samples) of the header (if any) + double seconds_to_skip_; + bool repeat_; + + // The valve allows only the configured number of samples through, then it closes. + + // The framework passes the queue as a naked pointer, rather than a shared pointer, so this + // class has two choices: construct the valve in the ctor, or hold onto the pointer, possibly + // beyond its lifetime. Fortunately, the queue is only used to create the valve, so the + // likelihood of holding a stale pointer is mitigated + uint64_t samples_; + int64_t sampling_frequency_; + gnss_shared_ptr valve_; + Concurrent_Queue* queue_; + + bool enable_throttle_control_; + gr::blocks::throttle::sptr throttle_; + + bool dump_; + std::string dump_filename_; + gr::blocks::file_sink::sptr sink_; + + + +}; + + +#endif diff --git a/src/algorithms/signal_source/adapters/signal_source_base.h b/src/algorithms/signal_source/adapters/signal_source_base.h index ea8a397da..fe7c9af7d 100644 --- a/src/algorithms/signal_source/adapters/signal_source_base.h +++ b/src/algorithms/signal_source/adapters/signal_source_base.h @@ -6,13 +6,10 @@ * * ----------------------------------------------------------------------------- * - * Copyright (C) 2010-2020 (see AUTHORS file for a list of contributors) - * - * GNSS-SDR is a software defined Global Navigation - * Satellite Systems receiver - * + * GNSS-SDR is a Global Navigation Satellite System software-defined receiver. * This file is part of GNSS-SDR. * + * Copyright (C) 2010-2021 (see AUTHORS file for a list of contributors) * SPDX-License-Identifier: GPL-3.0-or-later * * -----------------------------------------------------------------------------