diff --git a/Default/SourceConfig/FileSourcePage.cpp b/Default/SourceConfig/FileSourcePage.cpp index b8759f8..a60c2ec 100644 --- a/Default/SourceConfig/FileSourcePage.cpp +++ b/Default/SourceConfig/FileSourcePage.cpp @@ -111,6 +111,7 @@ FileSourcePage::guessParamsFromFileName() SigDiggerHelpers *hlp = SigDiggerHelpers::instance(); CaptureFileParams params; bool changes = false; + bool refresh = false; if (m_config == nullptr) return false; @@ -133,8 +134,22 @@ FileSourcePage::guessParamsFromFileName() m_config->setFreq(shiftedFc); changes = true; } + + if (params.havePath && params.path != m_config->getPath()) { + m_config->setPath(params.path); + ui->pathEdit->setText(QString::fromStdString(params.path)); + changes = refresh = true; + } + + if (params.haveFmt && params.format != m_config->getFormat()) { + m_config->setFormat(params.format); + changes = refresh = true; + } } + if (refresh) + refreshUi(); + return changes; } @@ -183,6 +198,7 @@ FileSourcePage::onBrowseCaptureFile() << "Raw complex 8-bit signed (*.s8 *.cs8)" << "Raw complex 16-bit signed (*.s16 *.cs16)" << "WAV files (*.wav)" + << "SigMF signal rcordings (*.sigmf-data *.sigmf-meta)" << "All files (*)"; break; diff --git a/Misc/FileViewer.cpp b/Misc/FileViewer.cpp index 61fbd81..931c602 100644 --- a/Misc/FileViewer.cpp +++ b/Misc/FileViewer.cpp @@ -20,6 +20,7 @@ FileViewer::FileViewer(QObject *parent) << "Raw complex 8-bit signed (*.s8 *.cs8)" << "Raw complex 16-bit signed (*.s16 *.cs16)" << "WAV files (*.wav)" + << "SigMF signal rcordings (*.sigmf-data *.sigmf-meta)" << "All files (*)"; m_dialog->setFileMode(QFileDialog::ExistingFile); @@ -51,11 +52,11 @@ FileViewer::processFile(QString path) return; } - if (!params.isRaw) { + if (params.format != SUSCAN_SOURCE_FORMAT_RAW_FLOAT32) { QMessageBox::warning( nullptr, "Unsupported file format", - "The selected file has been recognized, but its storage format is not raw. " + "The selected file has been recognized, but its storage format is not float32. " "FileViewer currently relies on memory-mapped files to display large files, " "and non-native sample formats cannot be converted on the go."); return; @@ -70,6 +71,9 @@ FileViewer::processFile(QString path) return; } + if (params.havePath) + path = QString::fromStdString(params.path); + QFile file(path); if (!file.open(QIODevice::ReadOnly)) { diff --git a/Misc/SigDiggerHelpers.cpp b/Misc/SigDiggerHelpers.cpp index 39d12d4..95cc5f5 100644 --- a/Misc/SigDiggerHelpers.cpp +++ b/Misc/SigDiggerHelpers.cpp @@ -29,6 +29,9 @@ #include #include #include +#include +#include +#include #ifndef SIGDIGGER_PKGVERSION # define SIGDIGGER_PKGVERSION \ @@ -41,25 +44,25 @@ using namespace SigDigger; -SigDiggerHelpers *SigDiggerHelpers::currInstance = nullptr; +SigDiggerHelpers *SigDiggerHelpers::m_currInstance = nullptr; SigDiggerHelpers * -SigDiggerHelpers::instance(void) +SigDiggerHelpers::instance() { - if (currInstance == nullptr) - currInstance = new SigDiggerHelpers(); + if (m_currInstance == nullptr) + m_currInstance = new SigDiggerHelpers(); - return currInstance; + return m_currInstance; } QString -SigDiggerHelpers::version(void) +SigDiggerHelpers::version() { return QString(SIGDIGGER_VERSION_STRING); } QString -SigDiggerHelpers::pkgversion(void) +SigDiggerHelpers::pkgversion() { return QString(SIGDIGGER_PKGVERSION); } @@ -236,10 +239,10 @@ SigDiggerHelpers::openSaveSamplesDialog( Palette * -SigDiggerHelpers::getGqrxPalette(void) +SigDiggerHelpers::getGqrxPalette() { static qreal color[256][3]; - if (this->gqrxPalette == nullptr) { + if (this->m_gqrxPalette == nullptr) { for (int i = 0; i < 256; i++) { if (i < 20) { // level 0: black background color[i][0] = color[i][1] = color[i][2] = 0; @@ -265,27 +268,171 @@ SigDiggerHelpers::getGqrxPalette(void) } } - gqrxPalette = new Palette("Gqrx", color); + m_gqrxPalette = new Palette("Gqrx", color); } - return gqrxPalette; + return m_gqrxPalette; } void -SigDiggerHelpers::deserializePalettes(void) +SigDiggerHelpers::deserializePalettes() { Suscan::Singleton *sus = Suscan::Singleton::get_instance(); - if (this->palettes.size() == 0) { - this->palettes.push_back(Palette("Suscan", wf_gradient)); - this->palettes.push_back(*this->getGqrxPalette()); + if (this->m_palettes.size() == 0) { + this->m_palettes.push_back(Palette("Suscan", wf_gradient)); + this->m_palettes.push_back(*this->getGqrxPalette()); } // Fill palette vector for (auto i = sus->getFirstPalette(); i != sus->getLastPalette(); i++) - this->palettes.push_back(Palette(*i)); + this->m_palettes.push_back(Palette(*i)); +} + +#define OBJECT_HAS_OBJ(obj, what) (obj.contains(what) && obj.value(what).isObject()) +#define OBJECT_HAS_STR(obj, what) (obj.contains(what) && obj.value(what).isString()) +#define OBJECT_HAS_DOUBLE(obj, what) (obj.contains(what) && obj.value(what).isDouble()) +#define OBJECT_HAS_ARRAY(obj, what) (obj.contains(what) && obj.value(what).isArray()) + +bool +SigDiggerHelpers::parseSigMFMeta(CaptureFileParams ¶ms, QFile &file) +{ + QJsonParseError err; + QJsonDocument metaData = QJsonDocument::fromJson(file.readAll(), &err); + + if (err.error != QJsonParseError::NoError) { + SU_ERROR( + "Failed to parse SigMF meta: %s\n", + err.errorString().toStdString().c_str()); + return false; + } + + QJsonObject root = metaData.object(); + + if (!OBJECT_HAS_OBJ(root, "global")) { + SU_ERROR("SigMF file does not contain the global key, discarding\n"); + return false; + } + + QJsonObject global = root.value("global").toObject(); + if (!OBJECT_HAS_STR(global, "core:datatype")) { + SU_ERROR("SigMF datatype is undefined\n"); + return false; + } + + if (!OBJECT_HAS_DOUBLE(global, "core:sample_rate")) { + SU_ERROR("SigMF sample rate is undefined\n"); + return false; + } + + // Parse datatype + QString dataType = global.value("core:datatype").toString(); + if (dataType == "ci8") { + params.haveFmt = true; + params.format = SUSCAN_SOURCE_FORMAT_RAW_SIGNED8; + } else if (dataType == "cu8") { + params.haveFmt = true; + params.format = SUSCAN_SOURCE_FORMAT_RAW_UNSIGNED8; + } else if (dataType == "ci16_le") { + params.haveFmt = true; + params.format = SUSCAN_SOURCE_FORMAT_RAW_SIGNED16; + } else if (dataType == "cf32_le") { + params.haveFmt = true; + params.format = SUSCAN_SOURCE_FORMAT_RAW_FLOAT32; + } else { + SU_ERROR( + "Failed to parse SigMF meta: unsupported datatype `%s`\n", + dataType.toStdString().c_str()); + return false; + } + + // Parse sample rate + qreal sampRate = global.value("core:sample_rate").toDouble(); + if (sampRate < 1) { + SU_ERROR("SigMF sample rate is invalid\n"); + return false; + } + + params.haveFs = true; + params.fs = sampRate; + + // Parse optional fields + if (OBJECT_HAS_ARRAY(root, "captures")) { + QJsonArray array = root.value("captures").toArray(); + if (array.size() > 0) { + auto value = array.at(0); + if (value.isObject()) { + QJsonObject firstCapture = value.toObject(); + + if (OBJECT_HAS_DOUBLE(firstCapture, "core:frequency")) { + qreal freq = firstCapture.value("core:frequency").toDouble(); + + params.haveFc = true; + params.fc = freq; + } + + if (OBJECT_HAS_STR(firstCapture, "core:datetime")) { + QString str = firstCapture.value("core:datetime").toString(); + + QDateTime dateTime = QDateTime::fromString(str, Qt::ISODate); + time_t sinceEpoch = dateTime.toSecsSinceEpoch(); + + params.haveTm = true; + params.tv.tv_sec = sinceEpoch; + params.tv.tv_usec = 0; + } + } + } + } + + return true; +} + +bool +SigDiggerHelpers::guessSigMFParams( + CaptureFileParams ¶ms, + QString const &path) +{ + QString captureName, extension; + QFile file; + QString dataFile, metaFile; + int extPos = path.lastIndexOf('.'); + + if (extPos == -1) + return false; + + extension = path.mid(extPos); + if (extension != ".sigmf-data" && extension != ".sigmf-meta") + return false; + + captureName = path.mid(0, extPos); + dataFile = captureName + ".sigmf-data"; + metaFile = captureName + ".sigmf-meta"; + + file.setFileName(dataFile); + if (!file.exists()) + return false; + + file.setFileName(metaFile); + if (!file.exists()) + return false; + + if (!file.open(QFile::ReadOnly)) { + SU_ERROR( + "Failed to read SigMF meta file: %s\n", + file.errorString().toStdString().c_str()); + return false; + } + + if (!parseSigMFMeta(params, file)) + return false; + + params.havePath = true; + params.path = dataFile.toStdString(); + + return true; } bool @@ -301,6 +448,11 @@ SigDiggerHelpers::guessCaptureFileParams( memset(¶ms.tm, 0, sizeof(struct tm)); + params.format = SUSCAN_SOURCE_FORMAT_RAW_FLOAT32; + + if (guessSigMFParams(params, path)) + return true; + if (sscanf( baseName.c_str(), "sigdigger_%08d_%06dZ_%d_%lg_float32_iq", @@ -313,7 +465,7 @@ SigDiggerHelpers::guessCaptureFileParams( params.haveDate = true; params.haveTime = true; params.isUTC = true; - params.isRaw = true; + params.haveFmt = true; } else if (sscanf( baseName.c_str(), "sigdigger_%d_%lg_float32_iq", @@ -321,7 +473,7 @@ SigDiggerHelpers::guessCaptureFileParams( &fc) == 2) { params.haveFc = true; params.haveFs = true; - params.isRaw = true; + params.haveFmt = true; } else if (sscanf( baseName.c_str(), "gqrx_%08d_%06d_%lg_%d_fc", @@ -333,7 +485,7 @@ SigDiggerHelpers::guessCaptureFileParams( params.haveFs = true; params.haveDate = true; params.haveTime = true; - params.isRaw = true; + params.haveFmt = true; } else if (sscanf( baseName.c_str(), "SDRSharp_%08d_%06dZ_%lg_IQ", @@ -370,7 +522,7 @@ SigDiggerHelpers::guessCaptureFileParams( params.haveFc = true; params.haveTm = true; params.isUTC = true; - params.isRaw = true; + params.haveFmt = true; } else { // Unrecognized return false; @@ -422,7 +574,7 @@ SigDiggerHelpers::populatePaletteCombo(QComboBox *cb) cb->clear(); // Populate combo - for (auto p : this->palettes) { + for (auto p : this->m_palettes) { cb->insertItem( ndx, QIcon(QPixmap::fromImage(p.getThumbnail())), @@ -458,10 +610,10 @@ SigDiggerHelpers::populateAntennaCombo( const Palette * SigDiggerHelpers::getPalette(int index) const { - if (index < 0 || index >= static_cast(this->palettes.size())) + if (index < 0 || index >= static_cast(this->m_palettes.size())) return nullptr; - return &this->palettes[static_cast(index)]; + return &this->m_palettes[static_cast(index)]; } int @@ -469,8 +621,8 @@ SigDiggerHelpers::getPaletteIndex(std::string const &name) const { unsigned int i; - for (i = 0; i < this->palettes.size(); ++i) - if (this->palettes[i].getName().compare(name) == 0) + for (i = 0; i < this->m_palettes.size(); ++i) + if (this->m_palettes[i].getName().compare(name) == 0) return static_cast(i); return -1; @@ -482,7 +634,7 @@ SigDiggerHelpers::getPalette(std::string const &name) const int index = this->getPaletteIndex(name); if (index >= 0) - return &this->palettes[index]; + return &this->m_palettes[index]; return nullptr; } @@ -491,9 +643,9 @@ SigDiggerHelpers::SigDiggerHelpers() { const char *localTZ = getenv("TZ"); - this->haveTZvar = localTZ != nullptr; + this->m_haveTZvar = localTZ != nullptr; if (localTZ != nullptr) - this->tzVar = localTZ; + this->m_tzVar = localTZ; this->deserializePalettes(); } @@ -507,12 +659,12 @@ SigDiggerHelpers::pushTZ(const char *tz) // Non-null TZ, push in saving stack if (prev != nullptr) { - this->tzs.push_front(prev); - front = &this->tzs.front(); + this->m_tzs.push_front(prev); + front = &this->m_tzs.front(); } // Push this one nonetheless - this->tzStack.push_front(front); + this->m_tzStack.push_front(front); if (tz != nullptr) setenv("TZ", tz, 1); @@ -523,19 +675,19 @@ SigDiggerHelpers::pushTZ(const char *tz) } bool -SigDiggerHelpers::popTZ(void) +SigDiggerHelpers::popTZ() { const std::string *front; - if (this->tzStack.empty()) + if (this->m_tzStack.empty()) return false; - front = this->tzStack.front(); + front = this->m_tzStack.front(); if (front != nullptr) { setenv("TZ", front->c_str(), 1); // Non-null TZ, pop from the saving stack - this->tzs.pop_front(); + this->m_tzs.pop_front(); } else { unsetenv("TZ"); } @@ -543,22 +695,22 @@ SigDiggerHelpers::popTZ(void) tzset(); // Pop it - this->tzStack.pop_front(); + this->m_tzStack.pop_front(); return true; } void -SigDiggerHelpers::pushLocalTZ(void) +SigDiggerHelpers::pushLocalTZ() { - if (this->haveTZvar) - this->pushTZ(this->tzVar.c_str()); + if (this->m_haveTZvar) + this->pushTZ(this->m_tzVar.c_str()); else this->pushTZ(nullptr); } void -SigDiggerHelpers::pushUTCTZ(void) +SigDiggerHelpers::pushUTCTZ() { this->pushTZ(""); } diff --git a/include/SigDiggerHelpers.h b/include/SigDiggerHelpers.h index 9215449..0a76e94 100644 --- a/include/SigDiggerHelpers.h +++ b/include/SigDiggerHelpers.h @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -30,6 +31,7 @@ #include class QComboBox; +class QFile; namespace SigDigger { class MultitaskController; @@ -42,42 +44,46 @@ namespace SigDigger { }; struct CaptureFileParams { - bool haveFc = false; - bool haveFs = false; + bool haveFc = false; + bool haveFs = false; bool haveDate = false; bool haveTime = false; - bool isUTC = false; - bool haveTm = false; - bool isRaw = false; + bool isUTC = false; + bool haveTm = false; + bool haveFmt = false; + bool havePath = false; - SUFREQ fc = 0; + std::string path; + SUFREQ fc = 0; unsigned int fs = 0; + enum suscan_source_format format = SUSCAN_SOURCE_FORMAT_RAW_FLOAT32; - struct tm tm; + struct tm tm; struct timeval tv = {0, 0}; }; class SigDiggerHelpers { - std::vector palettes; - Palette *gqrxPalette = nullptr; + std::vector m_palettes; + Palette *m_gqrxPalette = nullptr; + std::list m_tzs; + std::list m_tzStack; - std::list tzs; - std::list tzStack; + bool m_haveTZvar = false; + std::string m_tzVar; + static SigDiggerHelpers *m_currInstance; - bool haveTZvar = false; - std::string tzVar; - - static SigDiggerHelpers *currInstance; + // Private methods SigDiggerHelpers(); - - Palette *getGqrxPalette(void); + Palette *getGqrxPalette(); + bool guessSigMFParams(CaptureFileParams &, QString const &); + bool parseSigMFMeta(CaptureFileParams &, QFile &); public: - static unsigned int abiVersion(void); - static QString version(void); - static QString pkgversion(void); + static unsigned int abiVersion(); + static QString version(); + static QString pkgversion(); static void timerdup(struct timeval *); // Demod helpers @@ -94,7 +100,7 @@ namespace SigDigger { int end, Suscan::MultitaskController *); - static SigDiggerHelpers *instance(void); + static SigDiggerHelpers *instance(); int getPaletteIndex(std::string const &) const; const Palette *getPalette(std::string const &) const; const Palette *getPalette(int index) const; @@ -103,13 +109,13 @@ namespace SigDigger { static void populateAntennaCombo( Suscan::Source::Config &profile, QComboBox *combo); - void deserializePalettes(void); + void deserializePalettes(); - void pushLocalTZ(void); - void pushUTCTZ(void); + void pushLocalTZ(); + void pushUTCTZ(); void pushTZ(const char *); - bool popTZ(void); + bool popTZ(); static QString expandGlobalProperties(QString const &); };