diff --git a/.gitignore b/.gitignore index 908f1e1..20e4048 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,9 @@ build/ sigpack* -json.hpp +flatbuffers +json +libsigmf VkFFT SPIRV-Tools *~ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index f76adc2..712123b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.16) project(uhd_sample_recorder) set(CMAKE_BUILD_TYPE Release) set(SRC_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/..") +set(CMAKE_CXX_STANDARD 20) include(FindPkgConfig) include(CTest) @@ -11,6 +12,7 @@ find_package(Vulkan REQUIRED) find_package(Armadillo REQUIRED) find_package(UHD 3.15.0 REQUIRED) find_package(nlohmann_json 3.11.2 REQUIRED) +find_package(libsigmf REQUIRED) find_package( Boost ${Boost_Version} @@ -51,4 +53,4 @@ add_test(NAME sample_pipeline_test COMMAND sample_pipeline_test) add_executable(uhd_sample_recorder uhd_sample_recorder.cpp) target_link_libraries(uhd_sample_recorder sample_pipeline sample_writer - ${Boost_LIBRARIES} ${UHD_LIBRARIES}) + ${Boost_LIBRARIES} ${UHD_LIBRARIES} libsigmf::libsigmf) diff --git a/lib/sample_pipeline.cpp b/lib/sample_pipeline.cpp index d295378..f912428 100644 --- a/lib/sample_pipeline.cpp +++ b/lib/sample_pipeline.cpp @@ -260,11 +260,15 @@ void sample_pipeline_start(const std::string &file, const std::string &fft_file, writer_threads->add_thread(new boost::thread(fft_out_worker)); } -void sample_pipeline_stop(size_t overflows) { +void sample_pipeline_stop(size_t overflows, const std::string &file, + size_t rate, size_t freq, double timestamp, + double gain, const std::string &sigmf_format) { samples_input_done = true; writer_threads->join_all(); sample_writer->close(overflows); fft_sample_writer->close(overflows); + sample_writer->write_sigmf(file + ".sigmf-meta", timestamp, sigmf_format, + rate, freq, gain); if (useVkFFT) { free_vkfft(); } diff --git a/lib/sample_pipeline.h b/lib/sample_pipeline.h index defa7f3..b13b695 100644 --- a/lib/sample_pipeline.h +++ b/lib/sample_pipeline.h @@ -10,6 +10,8 @@ void sample_pipeline_start(const std::string &file, const std::string &fft_file, size_t nfft_ds_, size_t rate, size_t batches, size_t sample_id); size_t get_samp_size(); -void sample_pipeline_stop(size_t overflows); +void sample_pipeline_stop(size_t overflows, const std::string &file, + size_t rate, size_t freq, double timestamp, + double gain, const std::string &sigmf_format); void set_sample_pipeline_types(const std::string &type, std::string &cpu_format); diff --git a/lib/sample_pipeline_test.cpp b/lib/sample_pipeline_test.cpp index 47fc13b..3cdc493 100644 --- a/lib/sample_pipeline_test.cpp +++ b/lib/sample_pipeline_test.cpp @@ -10,7 +10,7 @@ BOOST_AUTO_TEST_CASE(SmokeTest) { set_sample_pipeline_types("short", cpu_format); BOOST_TEST(cpu_format == "sc16"); sample_pipeline_start("", "", 1e6, 1, false, 0, 0, 1, 1, 1e6, 0, 0); - sample_pipeline_stop(0); + sample_pipeline_stop(0, "", 1e6, 1e6, 1.1, -1, "ci16_le"); } BOOST_AUTO_TEST_CASE(RandomFFTTest) { @@ -33,7 +33,7 @@ BOOST_AUTO_TEST_CASE(RandomFFTTest) { memcpy(buffer_p, samples.memptr(), samples.size() * sizeof(std::complex)); enqueue_samples(write_ptr); - sample_pipeline_stop(0); + sample_pipeline_stop(0, file, 1e3 * 1024, 100e6, 1.1, -1, "cf32_le"); arma::Col> disk_samples; disk_samples.copy_size(samples); FILE *samples_fp = fopen(file.c_str(), "rb"); diff --git a/lib/sample_writer.cpp b/lib/sample_writer.cpp index d6eb802..dbe290a 100644 --- a/lib/sample_writer.cpp +++ b/lib/sample_writer.cpp @@ -3,6 +3,7 @@ #include #include #include +#include std::string get_prefix_file(const std::string &file, const std::string &prefix) { @@ -63,3 +64,33 @@ void SampleWriter::close(size_t overflows) { } } } + +void SampleWriter::write_sigmf(const std::string &filename, double timestamp, + const std::string &datatype, double sample_rate, + double frequency, double gain) { + sigmf::SigMF< + sigmf::Global, + sigmf::Capture, + sigmf::Annotation> + record; + record.global.access().datatype = datatype; + record.global.access().sample_rate = sample_rate; + auto capture = + sigmf::Capture(); + capture.get().sample_start = 0; + capture.get().global_index = 0; + capture.get().frequency = frequency; + std::ostringstream ts_ss; + time_t timestamp_t = static_cast(timestamp); + ts_ss << std::put_time(gmtime(×tamp_t), "%FT%TZ"); + capture.get().datetime = ts_ss.str(); + capture.get().source_file = + basename(file_.c_str()); + capture.get().gain = gain; + record.captures.emplace_back(capture); + std::string dotfilename = get_dotfile(filename); + std::ofstream jsonfile(dotfilename); + jsonfile << record.to_json(); + jsonfile.close(); + rename(dotfilename.c_str(), filename.c_str()); +} diff --git a/lib/sample_writer.h b/lib/sample_writer.h index be3387e..51f47c3 100644 --- a/lib/sample_writer.h +++ b/lib/sample_writer.h @@ -11,6 +11,9 @@ class SampleWriter { void open(const std::string &file, size_t zlevel); void close(size_t overflows); void write(const char *data, size_t len); + void write_sigmf(const std::string &filename, double timestamp, + const std::string &datatype, double sample_rate, + double frequency, double gain); private: boost::scoped_ptr outbuf_p; diff --git a/lib/uhd_sample_recorder.cpp b/lib/uhd_sample_recorder.cpp index ed420e5..9c9a071 100644 --- a/lib/uhd_sample_recorder.cpp +++ b/lib/uhd_sample_recorder.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -176,6 +177,11 @@ void sample_record(uhd::usrp::multi_usrp::sptr usrp, const std::string &type, std::cerr << "max_samps_per_packet from stream: " << max_samps_per_packet << std::endl; + const std::string endian_str = + std::endian::native == std::endian::little ? "_le" : "_be"; + const std::string sigmf_format = + type == "short" ? "ci16" + endian_str : "cf32" + endian_str; + if (nfft) { std::cerr << "using FFT point size " << nfft << std::endl; @@ -200,10 +206,16 @@ void sample_record(uhd::usrp::multi_usrp::sptr usrp, const std::string &type, bool overflows = run_stream(rx_stream, time_requested, max_samples, num_requested_samples); + double timestamp = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count() / + 1e3; stream_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS; rx_stream->issue_stream_cmd(stream_cmd); std::cerr << "stream stopped" << std::endl; - sample_pipeline_stop(overflows); + sample_pipeline_stop(overflows, file, rate, freq, timestamp, gain, + sigmf_format); std::cerr << "pipeline stopped" << std::endl; } @@ -216,7 +228,7 @@ int parse_args(int argc, char *argv[]) { "file", po::value(&file)->default_value(""), "name of the file to write binary samples to")( "type", po::value(&type)->default_value("short"), - "sample type: double, float, or short")( + "sample type: float, or short")( "nsamps", po::value(&total_num_samps)->default_value(0), "total number of samples to receive")( "duration", po::value(&total_time)->default_value(0),