diff --git a/doc/img/SigMFFileSink_plugin.png b/doc/img/SigMFFileSink_plugin.png index cf7058ec5..2199faa8b 100644 Binary files a/doc/img/SigMFFileSink_plugin.png and b/doc/img/SigMFFileSink_plugin.png differ diff --git a/doc/img/SigMFFileSink_plugin.xcf b/doc/img/SigMFFileSink_plugin.xcf index 53065e200..6713995db 100644 Binary files a/doc/img/SigMFFileSink_plugin.xcf and b/doc/img/SigMFFileSink_plugin.xcf differ diff --git a/plugins/channelrx/sigmffilesink/readme.md b/plugins/channelrx/sigmffilesink/readme.md index d3c6bc809..9def7f794 100644 --- a/plugins/channelrx/sigmffilesink/readme.md +++ b/plugins/channelrx/sigmffilesink/readme.md @@ -33,26 +33,32 @@ If the fixed position slider is engaged (7) this control is disabled. Use this control to decimate the baseband samples by a power of two. Consequently the baseband sample rate is reduced by this factor in the channel. -

3: Channel (sink) sample rate

+

3: Number of sample bits

+ +Recording number of bits for an I or Q sample. Can be 8, 16 or 32 resulting in ci8_le, ci16_le or ci32_le file format respectively. 32 bit samples are actually coded as 24 bits i.e the 8 most significant bits are zero. + +This recording format is defined for the whole file you cannot have different formats per capture. + +

4: Channel (sink) sample rate

Shows the channel sink sample rate in kS/s. The record capture is effectively recorded at this rate. -

4: Number of record captures

+

5: Number of record captures

SigMF can collate many sequences or captures in the same data file (it uses an index in its meta data). This is the number of captures already recorded (not counting the current one if recording). -

5: Recording time

+

6: Recording time

This is the current recording time of the whole file (all captures) -

6: Record size

+

7: Record size

This is the total number of bytes including all captures. This corresponds to the size of the `.sigmf-data` file. The number is possibly suffixed by a multiplier character: - **k**: _kilo_ for kilobytes - **M**: _mega_ for megabytes - **G**: _giga_ for gigabytes -

7: Fixed frequency shift positions

+

8: Fixed frequency shift positions

Use the checkbox to move the shift frequency at definite positions where the chain of half band decimation filters match an exact bandwidth and shift. The effect is to bypass the last interpolator and NCO and thus can save CPU cycles. This may be useful at high sample rates at the expense of not getting exactly on the desired spot. @@ -62,7 +68,7 @@ When this is engaged the frequency control (1) is disabled. This is a GUI only feature when using API it is up to the API client to calculate the desired position. Starting from the center any position lower or higher at the bandwidth divided by two times the decimation factor yields the desired property. -

8: Spectrum squelch

+

9: Spectrum squelch

Recording can be triggered by specifying a power level. If any peak in the spectrum exceeds this level then recording is triggered. This button only activates the detection system. When squelch is open the level button (9) lits up in green (as on the screenshot). To activate triggered recording you have to use (12). @@ -70,36 +76,36 @@ You can try to see for which squelch level you obtain the desired triggering bef Note that spectrum polling is done every 200 ms. If the signal of interest is shorter you may want to tweak the spectrum controls using the "Maximum" averaging type and a number of averaging samples making the averaging period (appearing in the tooltip) larger than 200 ms. Please refer to spectrum controls documentation in the main window readme (link in 15) for more details. -

9: Squelch level

+

10: Squelch level

This is the squelch level as discussed above. To try to find the correct value you can use the spectrum display (15). -

10: Pre recording period

+

11: Pre recording period

This is the number of seconds of data that will be prepended before the start of recording point. Thus you can make sure that the signal of interest will be fully recorded. Works in both spectrum squelch triggered and manual mode. -

11: Post recording period

+

12: Post recording period

This applies to spectrum squelch triggered recording only. This is the number of seconds recorded after the squelch closes. If the squelch opens again during this period then the counter is reset and recording will stop only after this period of time is elapsed without the squelch re-opening. This is useful if you want to record a bunch of transient bursts or just make sure that the recording does not stop too abruptly. -

12: Enable/disable spectrum squelch triggered recording

+

13: Enable/disable spectrum squelch triggered recording

Use this button to effectively apply spectrum squelch to recording. In this mode recording on and off will be under the control of the squelch system. Thus when active the normal record button (13) is disabled. However its color changes to reflect the recording status as described next. -

13: Record button

+

14: Record button

Use this button to start/stop recording. Note that with SigMF start/stop recording is starting/stopping a new capture in the same file. Until the file is changed with (14) the same file will be used until the device is stopped or channel plugin is dismissed. The button turns red if recording is active. -

14: Select output file

+

15: Select output file

Use this button to open a file dialog that lets you specify the location and name of the output files. SigMF creates two files actually one meta file with extension `.sigmf-meta` and one data file with extension `.sigmf-data` that contains the actual IQ data. There you specify the name of the meta file (`.sigmf-meta` extension) and a data file with `.sigmf-data` extension will be created using the same root name as the meta file. The path of the selected meta file appears at the right of the button. If it is empty or invalid recording will not be effective. -

15: Channel spectrum

+

16: Channel spectrum

This is the spectrum display of the IQ stream seen by the channel. Details on the spectrum view and controls can be found [here](../../../sdrgui/gui/spectrum.md) diff --git a/plugins/channelrx/sigmffilesink/sigmffilesink.cpp b/plugins/channelrx/sigmffilesink/sigmffilesink.cpp index b3e4a8d0e..a382f59eb 100644 --- a/plugins/channelrx/sigmffilesink/sigmffilesink.cpp +++ b/plugins/channelrx/sigmffilesink/sigmffilesink.cpp @@ -328,6 +328,7 @@ void SigMFFileSink::applySettings(const SigMFFileSinkSettings& settings, bool fo << "m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset << "m_log2Decim: " << settings.m_log2Decim << "m_fileRecordName: " << settings.m_fileRecordName + << "m_log2RecordSampleSize: " << settings.m_log2RecordSampleSize << "force: " << force; QList reverseAPIKeys; diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkgui.cpp b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.cpp index a2bdb97f2..50886d450 100644 --- a/plugins/channelrx/sigmffilesink/sigmffilesinkgui.cpp +++ b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.cpp @@ -83,6 +83,7 @@ bool SigMFFileSinkGUI::handleMessage(const Message& message) ui->deltaFrequency->setValueRange(false, 8, -m_basebandSampleRate/2, m_basebandSampleRate/2); ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2)); displayRate(); + updateAbsoluteCenterFrequency(); if (m_fixedPosition) { @@ -274,6 +275,7 @@ void SigMFFileSinkGUI::displaySettings() ui->postSquelchTime->setValue(m_settings.m_squelchPostRecordTime); ui->postSquelchTimeText->setText(tr("%1").arg(m_settings.m_squelchPostRecordTime)); ui->squelchedRecording->setChecked(m_settings.m_squelchRecordingEnable); + ui->recordSampleSize->setCurrentIndex((int) m_settings.m_log2RecordSampleSize - 3); if (!m_settings.m_spectrumSquelchMode) { @@ -431,6 +433,12 @@ void SigMFFileSinkGUI::on_decimationFactor_currentIndexChanged(int index) } } +void SigMFFileSinkGUI::on_recordSampleSize_currentIndexChanged(int index) +{ + m_settings.m_log2RecordSampleSize = index + 3; + applySettings(); +} + void SigMFFileSinkGUI::on_fixedPosition_toggled(bool checked) { m_fixedPosition = checked; @@ -506,6 +514,7 @@ void SigMFFileSinkGUI::on_squelchedRecording_toggled(bool checked) void SigMFFileSinkGUI::on_record_toggled(bool checked) { + ui->recordSampleSize->setEnabled(!checked); m_sigMFFileSink->record(checked); } @@ -603,6 +612,7 @@ void SigMFFileSinkGUI::makeUIConnections() { QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &SigMFFileSinkGUI::on_deltaFrequency_changed); QObject::connect(ui->decimationFactor, QOverload::of(&QComboBox::currentIndexChanged), this, &SigMFFileSinkGUI::on_decimationFactor_currentIndexChanged); + QObject::connect(ui->recordSampleSize, QOverload::of(&QComboBox::currentIndexChanged), this, &SigMFFileSinkGUI::on_recordSampleSize_currentIndexChanged); QObject::connect(ui->fixedPosition, &QCheckBox::toggled, this, &SigMFFileSinkGUI::on_fixedPosition_toggled); QObject::connect(ui->position, &QSlider::valueChanged, this, &SigMFFileSinkGUI::on_position_valueChanged); QObject::connect(ui->spectrumSquelch, &ButtonSwitch::toggled, this, &SigMFFileSinkGUI::on_spectrumSquelch_toggled); diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkgui.h b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.h index 45384d1ed..17ce8c6b9 100644 --- a/plugins/channelrx/sigmffilesink/sigmffilesinkgui.h +++ b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.h @@ -113,6 +113,7 @@ private slots: void handleSourceMessages(); void on_deltaFrequency_changed(qint64 value); void on_decimationFactor_currentIndexChanged(int index); + void on_recordSampleSize_currentIndexChanged(int index); void on_fixedPosition_toggled(bool checked); void on_position_valueChanged(int value); void on_spectrumSquelch_toggled(bool checked); diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinkgui.ui b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.ui index 3bd4c59db..bbeaec289 100644 --- a/plugins/channelrx/sigmffilesink/sigmffilesinkgui.ui +++ b/plugins/channelrx/sigmffilesink/sigmffilesinkgui.ui @@ -183,6 +183,41 @@ + + + + b + + + + + + + + 55 + 16777215 + + + + Record sample size I or Q (bits) + + + + 8 + + + + + 16 + + + + + 32 + + + + diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinksettings.cpp b/plugins/channelrx/sigmffilesink/sigmffilesinksettings.cpp index eea0cad08..653c6c8bd 100644 --- a/plugins/channelrx/sigmffilesink/sigmffilesinksettings.cpp +++ b/plugins/channelrx/sigmffilesink/sigmffilesinksettings.cpp @@ -44,6 +44,7 @@ void SigMFFileSinkSettings::resetToDefaults() m_preRecordTime = 0; m_squelchPostRecordTime = 0; m_squelchRecordingEnable = false; + m_log2RecordSampleSize = 5; m_streamIndex = 0; m_useReverseAPI = false; m_reverseAPIAddress = "127.0.0.1"; @@ -91,6 +92,7 @@ QByteArray SigMFFileSinkSettings::serialize() const s.writeS32(21, m_workspaceIndex); s.writeBlob(22, m_geometryBytes); s.writeBool(23, m_hidden); + s.writeU32(24, m_log2RecordSampleSize); return s.final(); } @@ -163,6 +165,8 @@ bool SigMFFileSinkSettings::deserialize(const QByteArray& data) d.readS32(21, &m_workspaceIndex, 0); d.readBlob(22, &m_geometryBytes); d.readBool(23, &m_hidden, false); + d.readU32(24, &tmp, 5); + m_log2RecordSampleSize = (tmp < 3 ? 3 : tmp > 5 ? 5 : tmp); return true; } diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinksettings.h b/plugins/channelrx/sigmffilesink/sigmffilesinksettings.h index da2a9cbee..3ce6fce24 100644 --- a/plugins/channelrx/sigmffilesink/sigmffilesinksettings.h +++ b/plugins/channelrx/sigmffilesink/sigmffilesinksettings.h @@ -39,6 +39,7 @@ struct SigMFFileSinkSettings int m_preRecordTime; int m_squelchPostRecordTime; bool m_squelchRecordingEnable; + uint32_t m_log2RecordSampleSize; int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx). bool m_useReverseAPI; QString m_reverseAPIAddress; diff --git a/plugins/channelrx/sigmffilesink/sigmffilesinksink.cpp b/plugins/channelrx/sigmffilesink/sigmffilesinksink.cpp index e56ac64af..31502079f 100644 --- a/plugins/channelrx/sigmffilesink/sigmffilesinksink.cpp +++ b/plugins/channelrx/sigmffilesink/sigmffilesinksink.cpp @@ -72,7 +72,7 @@ void SigMFFileSinkSink::startRecording() m_fileSink.feed(p2Begin, p2End, false); } - m_byteCount += m_preRecordFill * sizeof(Sample); + m_byteCount += m_preRecordFill * ((1< 0) { m_msCount += (m_preRecordFill * 1000) / m_sinkSampleRate; @@ -149,7 +149,7 @@ void SigMFFileSinkSink::feed(const SampleVector::const_iterator& begin, const Sa } } - m_byteCount += nbToWrite * sizeof(Sample); + m_byteCount += nbToWrite * ((1< 0) { m_msCount += (nbToWrite * 1000) / m_sinkSampleRate; @@ -159,7 +159,7 @@ void SigMFFileSinkSink::feed(const SampleVector::const_iterator& begin, const Sa { m_fileSink.feed(beginw, endw, true); int nbSamples = endw - beginw; - m_byteCount += nbSamples * sizeof(Sample); + m_byteCount += nbSamples * ((1< 0) { m_msCount += (nbSamples * 1000) / m_sinkSampleRate; @@ -293,6 +293,10 @@ void SigMFFileSinkSink::applySettings(const SigMFFileSinkSettings& settings, boo } } + if ((settings.m_log2RecordSampleSize != m_settings.m_log2RecordSampleSize) || force) { + m_fileSink.setLog2RecordSampleSize(settings.m_log2RecordSampleSize); + } + m_settings = settings; m_settings.m_fileRecordName = fileRecordName; } diff --git a/sdrbase/dsp/sigmffilerecord.cpp b/sdrbase/dsp/sigmffilerecord.cpp index 1c8de2d0d..2c0a70a85 100644 --- a/sdrbase/dsp/sigmffilerecord.cpp +++ b/sdrbase/dsp/sigmffilerecord.cpp @@ -51,6 +51,8 @@ SigMFFileRecord::SigMFFileRecord() : m_metaRecord = new sigmf::SigMF, sigmf::Capture, sigmf::Annotation >(); + // sizeof(FixReal) is either 2 or 4 thus log2 sample size is either 4 (size 16) or 5 (size 32) + m_log2RecordSampleSize = (sizeof(FixReal) / 2) + 3; } SigMFFileRecord::SigMFFileRecord(const QString& fileName, const QString& hardwareId) : @@ -71,6 +73,8 @@ SigMFFileRecord::SigMFFileRecord(const QString& fileName, const QString& hardwar m_metaRecord = new sigmf::SigMF, sigmf::Capture, sigmf::Annotation >(); + // sizeof(FixReal) is either 2 or 4 thus log2 sample size is either 4 (size 16) or 5 (size 32) + m_log2RecordSampleSize = (sizeof(FixReal) / 2) + 3; } SigMFFileRecord::~SigMFFileRecord() @@ -139,7 +143,7 @@ void SigMFFileRecord::setFileName(const QString& fileName) m_sampleFileName = m_fileName + ".sigmf-data"; m_sampleFile.open(m_sampleFileName.toStdString().c_str(), std::ios::binary & std::ios::app); m_initialBytesCount = (uint64_t) m_sampleFile.tellp(); - m_sampleStart = m_initialBytesCount / sizeof(Sample); + m_sampleStart = m_initialBytesCount / ((1<global.access().arch = QString(QSysInfo::currentCpuArchitecture()).toStdString(); m_metaRecord->global.access().os = QString(QSysInfo::prettyProductName()).toStdString(); QString endianSuffix = QSysInfo::ByteOrder == QSysInfo::LittleEndian ? "le" : "be"; - int size = 8*sizeof(FixReal); + int size = 1<global.access().datatype = QString("ci%1_%2").arg(size).arg(endianSuffix).toStdString(); } @@ -294,11 +299,75 @@ void SigMFFileRecord::feed(const SampleVector::const_iterator& begin, const Samp if (begin < end) // if there is something to put out { - m_sampleFile.write(reinterpret_cast(&*(begin)), (end - begin)*sizeof(Sample)); + // m_sampleFile.write(reinterpret_cast(&*(begin)), (end - begin)*sizeof(Sample)); + feedConv(begin, end); m_sampleCount += end - begin; } } +void SigMFFileRecord::feedConv(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end) +{ + uint32_t desiredIorQSampleSize = 1<(&*(begin)), (end - begin)*sizeof(Sample)); + } + else + { + uint32_t nsamples = (end - begin); + + // Only the 24 LSBits of the 32 bits samples are significant + if (desiredIorQSampleSize == 32) // can only be 16 bit samples => x8 (16 -> 24) + { + if (nsamples > m_samples32.size()) { + m_samples32.resize(nsamples); + } + std::transform( + begin, + end, + m_samples32.begin(), + [](const Sample& s) -> Sample32 { + return Sample32{s.real()<<8, s.imag()<<8}; + } + ); + m_sampleFile.write(reinterpret_cast(&*(m_samples32.begin())), nsamples*sizeof(Sample32)); + } + else if (desiredIorQSampleSize == 16) // can only be 32 bit samples size => /8 (24 -> 16) + { + if (nsamples > m_samples16.size()) { + m_samples16.resize(nsamples); + } + std::transform( + begin, + end, + m_samples16.begin(), + [](const Sample& s) -> Sample16 { + return Sample16{(qint16)(s.real()>>8), (qint16)(s.imag()>>8)}; + } + ); + m_sampleFile.write(reinterpret_cast(&*(m_samples16.begin())), nsamples*sizeof(Sample16)); + } + else // can only be 8 bit desired sample size + { + // divide by 8 for 16 -> 8 (sizeof(sample) == 4) or 16 for 24 -> 8 (sizeod(Sample) == 8) + // thus division of a I or Q sample is done with >>(2*sizeof(sample)) operation + if (nsamples > m_samples8.size()) { + m_samples8.resize(nsamples); + } + std::transform( + begin, + end, + m_samples8.begin(), + [](const Sample& s) -> Sample8 { + return Sample8{(qint8)(s.real()>>(2*sizeof(Sample))), (qint8)(s.imag()>>(2*sizeof(Sample)))}; + } + ); + m_sampleFile.write(reinterpret_cast(&*(m_samples8.begin())), nsamples*sizeof(Sample8)); + } + } +} + void SigMFFileRecord::start() { } diff --git a/sdrbase/dsp/sigmffilerecord.h b/sdrbase/dsp/sigmffilerecord.h index 0f96db953..e55f4fb8d 100644 --- a/sdrbase/dsp/sigmffilerecord.h +++ b/sdrbase/dsp/sigmffilerecord.h @@ -57,8 +57,24 @@ public: unsigned int getNbCaptures() const; uint64_t getInitialMsCount() const { return m_initialMsCount; } uint64_t getInitialBytesCount() const { return m_initialBytesCount; } + void setLog2RecordSampleSize(uint32_t log2RecordSampleSize) { m_log2RecordSampleSize = log2RecordSampleSize; } private: + struct Sample8 { + qint8 m_real; + qint8 m_imag; + }; + + struct Sample16 { + qint16 m_real; + qint16 m_imag; + }; + + struct Sample32 { + qint32 m_real; + qint32 m_imag; + }; + QString m_hardwareId; QString m_fileName; QString m_sampleFileName; @@ -75,12 +91,17 @@ private: quint64 m_sampleCount; quint64 m_initialMsCount; quint64 m_initialBytesCount; + uint32_t m_log2RecordSampleSize; sigmf::SigMF, sigmf::Capture, sigmf::Annotation > *m_metaRecord; + std::vector m_samples8; + std::vector m_samples16; + std::vector m_samples32; void makeHeader(); void makeCapture(); void clearMeta(); + void feedConv(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end); }; #endif // INCLUDE_SIGMF_FILERECORD_H