SigMF File Sink: implement 16 and 8 bits sample sizes. Fixes #1921

pull/2007/head
f4exb 2024-03-04 00:55:00 +01:00
rodzic 54ab8f61ca
commit e97ea32f3b
12 zmienionych plików z 171 dodań i 19 usunięć

Plik binarny nie jest wyświetlany.

Przed

Szerokość:  |  Wysokość:  |  Rozmiar: 48 KiB

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 115 KiB

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -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.
<h3>3: Channel (sink) sample rate</h3>
<h3>3: Number of sample bits</h3>
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.
<h3>4: Channel (sink) sample rate</h3>
Shows the channel sink sample rate in kS/s. The record capture is effectively recorded at this rate.
<h3>4: Number of record captures</h3>
<h3>5: Number of record captures</h3>
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).
<h3>5: Recording time</h3>
<h3>6: Recording time</h3>
This is the current recording time of the whole file (all captures)
<h3>6: Record size</h3>
<h3>7: Record size</h3>
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
<h3>7: Fixed frequency shift positions</h3>
<h3>8: Fixed frequency shift positions</h3>
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.
<h3>8: Spectrum squelch</h3>
<h3>9: Spectrum squelch</h3>
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.
<h3>9: Squelch level</h3>
<h3>10: Squelch level</h3>
This is the squelch level as discussed above. To try to find the correct value you can use the spectrum display (15).
<h3>10: Pre recording period<h3>
<h3>11: Pre recording period<h3>
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.
<h3>11: Post recording period</h3>
<h3>12: Post recording period</h3>
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.
<h3>12: Enable/disable spectrum squelch triggered recording</h3>
<h3>13: Enable/disable spectrum squelch triggered recording</h3>
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.
<h3>13: Record button</h3>
<h3>14: Record button</h3>
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.
<h3>14: Select output file</h3>
<h3>15: Select output file</h3>
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.
<h3>15: Channel spectrum</h3>
<h3>16: Channel spectrum</h3>
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)

Wyświetl plik

@ -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<QString> reverseAPIKeys;

Wyświetl plik

@ -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<int>::of(&QComboBox::currentIndexChanged), this, &SigMFFileSinkGUI::on_decimationFactor_currentIndexChanged);
QObject::connect(ui->recordSampleSize, QOverload<int>::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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -183,6 +183,41 @@
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="recordSampleSizeLabel">
<property name="text">
<string>b</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="recordSampleSize">
<property name="maximumSize">
<size>
<width>55</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Record sample size I or Q (bits)</string>
</property>
<item>
<property name="text">
<string>8</string>
</property>
</item>
<item>
<property name="text">
<string>16</string>
</property>
</item>
<item>
<property name="text">
<string>32</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -72,7 +72,7 @@ void SigMFFileSinkSink::startRecording()
m_fileSink.feed(p2Begin, p2End, false);
}
m_byteCount += m_preRecordFill * sizeof(Sample);
m_byteCount += m_preRecordFill * ((1<<m_settings.m_log2RecordSampleSize)/4); // sizeof(Sample);
if (m_sinkSampleRate > 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<<m_settings.m_log2RecordSampleSize)/4); // sizeof(Sample);
if (m_sinkSampleRate > 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<<m_settings.m_log2RecordSampleSize)/4); // sizeof(Sample);
if (m_sinkSampleRate > 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;
}

Wyświetl plik

@ -51,6 +51,8 @@ SigMFFileRecord::SigMFFileRecord() :
m_metaRecord = new sigmf::SigMF<sigmf::Global<core::DescrT, sdrangel::DescrT>,
sigmf::Capture<core::DescrT, sdrangel::DescrT>,
sigmf::Annotation<core::DescrT> >();
// 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::Global<core::DescrT, sdrangel::DescrT>,
sigmf::Capture<core::DescrT, sdrangel::DescrT>,
sigmf::Annotation<core::DescrT> >();
// 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<<m_log2RecordSampleSize)/4); // sizeof(Sample);
m_recordStart = false;
}
@ -245,7 +249,8 @@ void SigMFFileRecord::makeHeader()
m_metaRecord->global.access<sdrangel::GlobalT>().arch = QString(QSysInfo::currentCpuArchitecture()).toStdString();
m_metaRecord->global.access<sdrangel::GlobalT>().os = QString(QSysInfo::prettyProductName()).toStdString();
QString endianSuffix = QSysInfo::ByteOrder == QSysInfo::LittleEndian ? "le" : "be";
int size = 8*sizeof(FixReal);
int size = 1<<m_log2RecordSampleSize;
qDebug("SigMFFileRecord::makeHeader: %u %u", m_log2RecordSampleSize, (1<<m_log2RecordSampleSize));
m_metaRecord->global.access<core::GlobalT>().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<const char*>(&*(begin)), (end - begin)*sizeof(Sample));
// m_sampleFile.write(reinterpret_cast<const char*>(&*(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<<m_log2RecordSampleSize;
if (2*desiredIorQSampleSize == 8*sizeof(Sample)) // if size of sample matches desired sample size write directly
{
m_sampleFile.write(reinterpret_cast<const char*>(&*(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<const char*>(&*(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<const char*>(&*(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<const char*>(&*(m_samples8.begin())), nsamples*sizeof(Sample8));
}
}
}
void SigMFFileRecord::start()
{
}

Wyświetl plik

@ -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::Global<core::DescrT, sdrangel::DescrT>,
sigmf::Capture<core::DescrT, sdrangel::DescrT>,
sigmf::Annotation<core::DescrT> > *m_metaRecord;
std::vector<Sample8> m_samples8;
std::vector<Sample16> m_samples16;
std::vector<Sample32> 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