Skip to content

Commit

Permalink
Merge pull request #1182 from Niautanor/sigmf-record
Browse files Browse the repository at this point in the history
Allow recording IQ samples in the SigMF format
  • Loading branch information
argilo authored Oct 6, 2023
2 parents 376f097 + 4c4f9d5 commit ef84e43
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ The following people and organisations have contributed to gqrx:
* Kenji Rikitake, JJ1BDX
* Kitware Inc.
* Konrad Beckmann
* Luna Gräfje
* luzpaz
* Marco Savelli
* Markus Kolb
Expand Down
1 change: 1 addition & 0 deletions resources/news.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
2.17.1: In progress...

NEW: Delete key clears the waterfall.
NEW: I/Q tool can save recordings in SigMF format.
IMPROVED: Reduced CPU utilization of waterfall display.
CHANGED: DMG release requires macOS 12.7 or later.

Expand Down
45 changes: 39 additions & 6 deletions src/applications/gqrx/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <QDialogButtonBox>
#include <QFile>
#include <QGroupBox>
#include <QJsonDocument>
#include <QKeySequence>
#include <QLineEdit>
#include <QMessageBox>
Expand Down Expand Up @@ -317,7 +318,7 @@ MainWindow::MainWindow(const QString& cfgfile, bool edit_conf, QWidget *parent)
connect(&DXCSpots::Get(), SIGNAL(dxcSpotsUpdated()), this, SLOT(updateClusterSpots()));

// I/Q playback
connect(iq_tool, SIGNAL(startRecording(QString)), this, SLOT(startIqRecording(QString)));
connect(iq_tool, SIGNAL(startRecording(QString, QString)), this, SLOT(startIqRecording(QString, QString)));
connect(iq_tool, SIGNAL(stopRecording()), this, SLOT(stopIqRecording()));
connect(iq_tool, SIGNAL(startPlayback(QString,float,qint64)), this, SLOT(startIqPlayback(QString,float,qint64)));
connect(iq_tool, SIGNAL(stopPlayback()), this, SLOT(stopIqPlayback()));
Expand Down Expand Up @@ -1629,21 +1630,53 @@ void MainWindow::stopAudioStreaming()
}

/** Start I/Q recording. */
void MainWindow::startIqRecording(const QString& recdir)
void MainWindow::startIqRecording(const QString& recdir, const QString& format)
{
qDebug() << __func__;
// generate file name using date, time, rf freq in kHz and BW in Hz
// gqrx_iq_yyyymmdd_hhmmss_freq_bw_fc.raw
auto freq = qRound64(rx->get_rf_freq());
auto sr = qRound64(rx->get_input_rate());
auto dec = (quint32)(rx->get_input_decim());
auto lastRec = QDateTime::currentDateTimeUtc().
toString("%1/gqrx_yyyyMMdd_hhmmss_%2_%3_fc.'raw'")
.arg(recdir).arg(freq).arg(sr/dec);
auto currentDate = QDateTime::currentDateTimeUtc();
auto filenameTemplate = currentDate.toString("%1/gqrx_yyyyMMdd_hhmmss_%2_%3_fc.%4").arg(recdir).arg(freq).arg(sr/dec);
bool sigmf = (format == "SigMF");
auto lastRec = filenameTemplate.arg(sigmf ? "sigmf-data" : "raw");

QFile metaFile(filenameTemplate.arg("sigmf-meta"));
bool ok = true;
if (sigmf) {
auto meta = QJsonDocument { QJsonObject {
{"global", QJsonObject {
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
{"core:datatype", "cf32_be"},
#else
{"core:datatype", "cf32_le"},
#endif
{"core:sample_rate", sr/dec},
{"core:version", "1.0.0"},
{"core:recorder", "Gqrx " VERSION},
}}, {"captures", QJsonArray {
QJsonObject {
{"core:sample_start", 0},
{"core:frequency", freq},
{"core:datetime", currentDate.toString(Qt::ISODateWithMs)},
},
}}, {"annotations", QJsonArray {}},
}}.toJson();

if (!metaFile.open(QIODevice::WriteOnly) || metaFile.write(meta) != meta.size()) {
ok = false;
}
}

// start recorder; fails if recording already in progress
if (rx->start_iq_recording(lastRec.toStdString()))
if (!ok || rx->start_iq_recording(lastRec.toStdString()))
{
// remove metadata file if we managed to open it
if (sigmf && metaFile.isOpen())
metaFile.remove();

// reset action status
ui->statusBar->showMessage(tr("Error starting I/Q recoder"));

Expand Down
2 changes: 1 addition & 1 deletion src/applications/gqrx/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ private slots:
void stopAudioStreaming();

/* I/Q playback and recording*/
void startIqRecording(const QString& recdir);
void startIqRecording(const QString& recdir, const QString& format);
void stopIqRecording();
void startIqPlayback(const QString& filename, float samprate, qint64 center_freq);
void stopIqPlayback();
Expand Down
3 changes: 2 additions & 1 deletion src/qtgui/iq_tool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ CIqTool::CIqTool(QWidget *parent) :
//ui->recDirEdit->setText(QDir::currentPath());

recdir = new QDir(QDir::homePath(), "*.raw");
recdir->setNameFilters(recdir->nameFilters() << "*.sigmf-data");

error_palette = new QPalette();
error_palette->setColor(QPalette::Text, Qt::red);
Expand Down Expand Up @@ -173,7 +174,7 @@ void CIqTool::on_recButton_clicked(bool checked)
if (checked)
{
ui->playButton->setEnabled(false);
emit startRecording(recdir->path());
emit startRecording(recdir->path(), ui->formatCombo->currentText());

refreshDir();
ui->listWidget->setCurrentRow(ui->listWidget->count()-1);
Expand Down
2 changes: 1 addition & 1 deletion src/qtgui/iq_tool.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class CIqTool : public QDialog
void readSettings(QSettings *settings);

signals:
void startRecording(const QString recdir);
void startRecording(const QString recdir, const QString format);
void stopRecording();
void startPlayback(const QString filename, float samprate, qint64 center_freq);
void stopPlayback();
Expand Down
24 changes: 24 additions & 0 deletions src/qtgui/iq_tool.ui
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="formatLabel">
<property name="text">
<string>Format:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="formatCombo">
<property name="toolTip">
<string>File format</string>
</property>
<item>
<property name="text">
<string>Raw</string>
</property>
</item>
<item>
<property name="text">
<string>SigMF</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="recDirLabel">
<property name="text">
Expand Down

0 comments on commit ef84e43

Please sign in to comment.