mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-08-16 00:14:00 -04:00
Add signed 16-bit PCM 2 channel .wav file support to File Input and File Record plugins
This commit is contained in:
@@ -500,7 +500,7 @@ void FileSinkGUI::on_showFileDialog_clicked(bool checked)
|
||||
this,
|
||||
tr("Save record file"),
|
||||
m_settings.m_fileRecordName,
|
||||
tr("SDR I/Q Files (*.sdriq)")
|
||||
tr("SDR I/Q Files (*.sdriq *.wav)")
|
||||
);
|
||||
|
||||
fileDialog.setOptions(QFileDialog::DontUseNativeDialog);
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include "filesinkmessages.h"
|
||||
#include "filesinksink.h"
|
||||
#include "dsp/filerecord.h"
|
||||
#include "dsp/wavfilerecord.h"
|
||||
|
||||
FileSinkSink::FileSinkSink() :
|
||||
m_nbCaptures(0),
|
||||
@@ -34,8 +36,11 @@ FileSinkSink::FileSinkSink() :
|
||||
m_squelchOpen(false),
|
||||
m_postSquelchCounter(0),
|
||||
m_msCount(0),
|
||||
m_byteCount(0)
|
||||
{}
|
||||
m_byteCount(0),
|
||||
m_bytesPerSample(sizeof(Sample))
|
||||
{
|
||||
m_fileSink = new FileRecord();
|
||||
}
|
||||
|
||||
FileSinkSink::~FileSinkSink()
|
||||
{}
|
||||
@@ -46,16 +51,16 @@ void FileSinkSink::startRecording()
|
||||
{
|
||||
// set the length of pre record time
|
||||
qint64 mSShift = (m_preRecordFill * 1000) / m_sinkSampleRate;
|
||||
m_fileSink.setMsShift(-mSShift);
|
||||
m_fileSink->setMsShift(-mSShift);
|
||||
|
||||
// notify capture start
|
||||
if (!m_fileSink.startRecording())
|
||||
if (!m_fileSink->startRecording())
|
||||
{
|
||||
// qWarning already output in startRecording, just need to send to GUI
|
||||
if (m_msgQueueToGUI)
|
||||
{
|
||||
FileSinkMessages::MsgReportRecordFileError *msg
|
||||
= FileSinkMessages::MsgReportRecordFileError::create(QString("Failed to open %1").arg(m_fileSink.getCurrentFileName()));
|
||||
= FileSinkMessages::MsgReportRecordFileError::create(QString("Failed to open %1").arg(m_fileSink->getCurrentFileName()));
|
||||
m_msgQueueToGUI->push(msg);
|
||||
}
|
||||
return;
|
||||
@@ -66,7 +71,7 @@ void FileSinkSink::startRecording()
|
||||
if (m_msgQueueToGUI)
|
||||
{
|
||||
FileSinkMessages::MsgReportRecordFileName *msg1
|
||||
= FileSinkMessages::MsgReportRecordFileName::create(m_fileSink.getCurrentFileName());
|
||||
= FileSinkMessages::MsgReportRecordFileName::create(m_fileSink->getCurrentFileName());
|
||||
m_msgQueueToGUI->push(msg1);
|
||||
FileSinkMessages::MsgReportRecording *msg2 = FileSinkMessages::MsgReportRecording::create(true);
|
||||
m_msgQueueToGUI->push(msg2);
|
||||
@@ -77,13 +82,13 @@ void FileSinkSink::startRecording()
|
||||
m_preRecordBuffer.readBegin(m_preRecordFill, &p1Begin, &p1End, &p2Begin, &p2End);
|
||||
|
||||
if (p1Begin != p1End) {
|
||||
m_fileSink.feed(p1Begin, p1End, false);
|
||||
m_fileSink->feed(p1Begin, p1End, false);
|
||||
}
|
||||
if (p2Begin != p2End) {
|
||||
m_fileSink.feed(p2Begin, p2End, false);
|
||||
m_fileSink->feed(p2Begin, p2End, false);
|
||||
}
|
||||
|
||||
m_byteCount += m_preRecordFill * sizeof(Sample);
|
||||
m_byteCount += m_preRecordFill * m_bytesPerSample;
|
||||
|
||||
if (m_sinkSampleRate > 0) {
|
||||
m_msCount += (m_preRecordFill * 1000) / m_sinkSampleRate;
|
||||
@@ -97,13 +102,13 @@ void FileSinkSink::stopRecording()
|
||||
{
|
||||
m_preRecordBuffer.reset();
|
||||
|
||||
if (!m_fileSink.stopRecording())
|
||||
if (!m_fileSink->stopRecording())
|
||||
{
|
||||
// qWarning already output stopRecording, just need to send to GUI
|
||||
if (m_msgQueueToGUI)
|
||||
{
|
||||
FileSinkMessages::MsgReportRecordFileError *msg
|
||||
= FileSinkMessages::MsgReportRecordFileError::create(QString("Error while writing to %1").arg(m_fileSink.getCurrentFileName()));
|
||||
= FileSinkMessages::MsgReportRecordFileError::create(QString("Error while writing to %1").arg(m_fileSink->getCurrentFileName()));
|
||||
m_msgQueueToGUI->push(msg);
|
||||
}
|
||||
}
|
||||
@@ -151,18 +156,18 @@ void FileSinkSink::feed(const SampleVector::const_iterator& begin, const SampleV
|
||||
|
||||
if (m_squelchOpen)
|
||||
{
|
||||
m_fileSink.feed(beginw, endw, true);
|
||||
m_fileSink->feed(beginw, endw, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (nbToWrite < m_postSquelchCounter)
|
||||
{
|
||||
m_fileSink.feed(beginw, endw, true);
|
||||
m_fileSink->feed(beginw, endw, true);
|
||||
m_postSquelchCounter -= nbToWrite;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_fileSink.feed(beginw, endw + m_postSquelchCounter, true);
|
||||
m_fileSink->feed(beginw, endw + m_postSquelchCounter, true);
|
||||
nbToWrite = m_postSquelchCounter;
|
||||
m_postSquelchCounter = 0;
|
||||
|
||||
@@ -170,7 +175,7 @@ void FileSinkSink::feed(const SampleVector::const_iterator& begin, const SampleV
|
||||
}
|
||||
}
|
||||
|
||||
m_byteCount += nbToWrite * sizeof(Sample);
|
||||
m_byteCount += nbToWrite * m_bytesPerSample;
|
||||
|
||||
if (m_sinkSampleRate > 0) {
|
||||
m_msCount += (nbToWrite * 1000) / m_sinkSampleRate;
|
||||
@@ -178,9 +183,9 @@ void FileSinkSink::feed(const SampleVector::const_iterator& begin, const SampleV
|
||||
}
|
||||
else if (m_record)
|
||||
{
|
||||
m_fileSink.feed(beginw, endw, true);
|
||||
m_fileSink->feed(beginw, endw, true);
|
||||
int nbSamples = endw - beginw;
|
||||
m_byteCount += nbSamples * sizeof(Sample);
|
||||
m_byteCount += nbSamples * m_bytesPerSample;
|
||||
|
||||
if (m_sinkSampleRate > 0) {
|
||||
m_msCount += (nbSamples * 1000) / m_sinkSampleRate;
|
||||
@@ -240,7 +245,7 @@ void FileSinkSink::applyChannelSettings(
|
||||
{
|
||||
DSPSignalNotification *notif = new DSPSignalNotification(sinkSampleRate, centerFrequency);
|
||||
DSPSignalNotification *notifToSpectrum = new DSPSignalNotification(*notif);
|
||||
m_fileSink.getInputMessageQueue()->push(notif);
|
||||
m_fileSink->getInputMessageQueue()->push(notif);
|
||||
m_spectrumSink->getInputMessageQueue()->push(notifToSpectrum);
|
||||
|
||||
if (m_msgQueueToGUI)
|
||||
@@ -277,7 +282,7 @@ void FileSinkSink::applySettings(const FileSinkSettings& settings, bool force)
|
||||
if (dotBreakout.size() > 1) {
|
||||
QString extension = dotBreakout.last();
|
||||
|
||||
if (extension != "sdriq") {
|
||||
if ((extension != "sdriq") && (extension != "wav")) {
|
||||
dotBreakout.last() = "sdriq";
|
||||
}
|
||||
}
|
||||
@@ -291,11 +296,18 @@ void FileSinkSink::applySettings(const FileSinkSettings& settings, bool force)
|
||||
QString fileBase;
|
||||
FileRecordInterface::RecordType recordType = FileRecordInterface::guessTypeFromFileName(fileRecordName, fileBase);
|
||||
|
||||
if (recordType == FileRecordInterface::RecordTypeSdrIQ)
|
||||
if ((recordType == FileRecordInterface::RecordTypeSdrIQ) || (recordType == FileRecordInterface::RecordTypeWav))
|
||||
{
|
||||
m_fileSink.setFileName(fileBase);
|
||||
delete m_fileSink;
|
||||
if (recordType == FileRecordInterface::RecordTypeSdrIQ) {
|
||||
m_fileSink = new FileRecord(m_sinkSampleRate, m_centerFrequency);
|
||||
} else {
|
||||
m_fileSink = new WavFileRecord(m_sinkSampleRate, m_centerFrequency);
|
||||
}
|
||||
m_fileSink->setFileName(fileBase);
|
||||
m_msCount = 0;
|
||||
m_byteCount = 0;
|
||||
m_bytesPerSample = m_fileSink->getBytesPerSample();
|
||||
m_nbCaptures = 0;
|
||||
m_recordEnabled = true;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#define INCLUDE_FILESINKSINK_H_
|
||||
|
||||
#include "dsp/channelsamplesink.h"
|
||||
#include "dsp/filerecord.h"
|
||||
#include "dsp/filerecordinterface.h"
|
||||
#include "dsp/decimatorc.h"
|
||||
#include "dsp/samplesimplefifo.h"
|
||||
#include "dsp/ncof.h"
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
|
||||
|
||||
FileRecord *getFileSink() { return &m_fileSink; }
|
||||
FileRecordInterface *getFileSink() { return m_fileSink; }
|
||||
void setSpectrumSink(SpectrumVis* spectrumSink) { m_spectrumSink = spectrumSink; }
|
||||
void startRecording();
|
||||
void stopRecording();
|
||||
@@ -66,7 +66,7 @@ private:
|
||||
DecimatorC m_decimator;
|
||||
SampleVector m_sampleBuffer;
|
||||
FileSinkSettings m_settings;
|
||||
FileRecord m_fileSink;
|
||||
FileRecordInterface *m_fileSink;
|
||||
unsigned int m_nbCaptures;
|
||||
SampleSimpleFifo m_preRecordBuffer;
|
||||
unsigned int m_preRecordFill;
|
||||
@@ -81,6 +81,7 @@ private:
|
||||
int m_deviceUId;
|
||||
uint64_t m_msCount;
|
||||
uint64_t m_byteCount;
|
||||
int m_bytesPerSample;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FILESINKSINK_H_
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
Use this plugin to record its channel IQ data in [sdriq](../../samplesource/fileinput/readme.md#introduction) format. The baseband sample rate can be decimated by a factor of two and its center shifted to accomodate different requirements than recording the full baseband. More than one such plugin can be used in the same baseband to record different parts of the baseband spectrum. Of course in this case file output collision should be avoided.
|
||||
Use this plugin to record its channel IQ data in [sdriq](../../samplesource/fileinput/readme.md#introduction) or signed 16-bit PCM `.wav` format. The baseband sample rate can be decimated by a factor of two and its center shifted to accomodate different requirements than recording the full baseband. More than one such plugin can be used in the same baseband to record different parts of the baseband spectrum. Of course in this case file output collision should be avoided.
|
||||
|
||||
Such files can be read in SDRangel using the [File input plugin](../../samplesource/fileinput/readme.md).
|
||||
|
||||
Each recording is written in a new file with the starting timestamp before the `.sdriq` extension in `yyyy-MM-ddTHH_mm_ss_zzz` format. It keeps the first dot limted groups of the filename before the `.sdriq` extension if there are two such groups or before the two last groups if there are more than two groups. Examples:
|
||||
Each recording is written in a new file with the starting timestamp before the `.sdriq` extension in `yyyy-MM-ddTHH_mm_ss_zzz` format. It keeps the first dot limted groups of the filename before the `.sdriq` or `.wav` extension if there are two such groups or before the two last groups if there are more than two groups. Examples:
|
||||
|
||||
- Given file name: `test.sdriq` then a recording file will be like: `test.2020-08-05T21_39_07_974.sdriq`
|
||||
- Given file name: `test.2020-08-05T20_36_15_974.sdriq` then a recording file will be like (with timestamp updated): `test.2020-08-05T21_41_21_173.sdriq`
|
||||
@@ -14,7 +14,7 @@ Each recording is written in a new file with the starting timestamp before the `
|
||||
- Given file name: `record.test.first.sdriq` then a recording file will be like: `reocrd.test.2020-08-05T21_39_52_974.sdriq`
|
||||
|
||||
If a filename is given without `.sdriq` extension then the `.sdriq` extension is appended automatically before the above algorithm is applied.
|
||||
If a filename is given with an extension different of `.sdriq` then the extension is replaced by `.sdriq` automatically before the above algorithm is applied.
|
||||
If a filename is given with an extension different of `.sdriq` or `.wav` then the extension is replaced by `.sdriq` automatically before the above algorithm is applied.
|
||||
|
||||
<h2>Interface</h2>
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <QDebug>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QRegExp>
|
||||
|
||||
#include "SWGDeviceSettings.h"
|
||||
#include "SWGFileInputSettings.h"
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "dsp/dspdevicesourceengine.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/filerecord.h"
|
||||
#include "dsp/wavfilerecord.h"
|
||||
#include "device/deviceapi.h"
|
||||
|
||||
#include "fileinput.h"
|
||||
@@ -99,7 +101,74 @@ void FileInput::openFileStream()
|
||||
#endif
|
||||
quint64 fileSize = m_ifstream.tellg();
|
||||
|
||||
if (fileSize > sizeof(FileRecord::Header))
|
||||
if (m_settings.m_fileName.endsWith(".wav"))
|
||||
{
|
||||
WavFileRecord::Header header;
|
||||
m_ifstream.seekg(0, std::ios_base::beg);
|
||||
bool headerOK = WavFileRecord::readHeader(m_ifstream, header);
|
||||
m_sampleRate = header.m_sampleRate;
|
||||
if (header.m_auxiHeader.m_size > 0)
|
||||
{
|
||||
// Some WAV files written by SDR tools have auxi header
|
||||
m_centerFrequency = header.m_auxi.m_centerFreq;
|
||||
m_startingTimeStamp = QDateTime(QDate(
|
||||
header.m_auxi.m_startTime.m_year,
|
||||
header.m_auxi.m_startTime.m_month,
|
||||
header.m_auxi.m_startTime.m_day
|
||||
), QTime(
|
||||
header.m_auxi.m_startTime.m_hour,
|
||||
header.m_auxi.m_startTime.m_minute,
|
||||
header.m_auxi.m_startTime.m_second,
|
||||
header.m_auxi.m_startTime.m_milliseconds
|
||||
)).toMSecsSinceEpoch() / 1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Attempt to extract time and frequency from filename
|
||||
QRegExp dateTimeRE("([12][0-9][0-9][0-9]).?([01][0-9]).?([0-3][0-9]).?([0-2][0-9]).?([0-5][0-9]).?([0-5][0-9])");
|
||||
if (dateTimeRE.indexIn(m_settings.m_fileName) != -1)
|
||||
{
|
||||
m_startingTimeStamp = QDateTime(QDate(
|
||||
dateTimeRE.capturedTexts()[1].toInt(),
|
||||
dateTimeRE.capturedTexts()[2].toInt(),
|
||||
dateTimeRE.capturedTexts()[3].toInt()
|
||||
), QTime(
|
||||
dateTimeRE.capturedTexts()[4].toInt(),
|
||||
dateTimeRE.capturedTexts()[5].toInt(),
|
||||
dateTimeRE.capturedTexts()[6].toInt()
|
||||
)).toMSecsSinceEpoch() / 1000;
|
||||
}
|
||||
// Attempt to extract centre frequency from filename
|
||||
QRegExp freqkRE("(([0-9]+)kHz)");
|
||||
QRegExp freqRE("(([0-9]+)Hz)");
|
||||
if (freqkRE.indexIn(m_settings.m_fileName))
|
||||
{
|
||||
m_centerFrequency = freqkRE.capturedTexts()[2].toLongLong() * 1000LL;
|
||||
}
|
||||
else if (freqRE.indexIn(m_settings.m_fileName))
|
||||
{
|
||||
m_centerFrequency = freqRE.capturedTexts()[2].toLongLong();
|
||||
}
|
||||
}
|
||||
m_sampleSize = header.m_bitsPerSample;
|
||||
|
||||
if (headerOK && (m_sampleRate > 0) && (m_sampleSize > 0))
|
||||
{
|
||||
m_recordLengthMuSec = ((fileSize - m_ifstream.tellg()) * 1000000UL) / ((m_sampleSize == 24 ? 8 : 4) * m_sampleRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical("FileInput::openFileStream: invalid .wav file");
|
||||
m_recordLengthMuSec = 0;
|
||||
}
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportHeaderCRC *report = MsgReportHeaderCRC::create(headerOK);
|
||||
getMessageQueueToGUI()->push(report);
|
||||
}
|
||||
}
|
||||
else if (fileSize > sizeof(FileRecord::Header))
|
||||
{
|
||||
FileRecord::Header header;
|
||||
m_ifstream.seekg(0,std::ios_base::beg);
|
||||
|
||||
@@ -302,7 +302,7 @@ void FileInputGUI::on_showFileDialog_clicked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
QString fileName = QFileDialog::getOpenFileName(this,
|
||||
tr("Open I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)"), 0, QFileDialog::DontUseNativeDialog);
|
||||
tr("Open I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq *.wav)"), 0, QFileDialog::DontUseNativeDialog);
|
||||
|
||||
if (fileName != "")
|
||||
{
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
This plugin reads a file of I/Q samples that have been previously saved with the file record button of other sampling source devices. The file starts with a 32 byte header of all unsigned integer of various sizes containing meta data:
|
||||
This plugin reads a file of I/Q samples that have been previously saved with the file record button of other sampling source devices. The plugin supports SDRangel's own .sdriq file format as well as signed 16-bit PCM, 2 channel .wav files (including support for optional auxi headers, containing centre frequency).
|
||||
|
||||
An .sdriq file starts with a 32 byte header of all unsigned integer of various sizes containing meta data:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -66,7 +68,7 @@ This is the center frequency of reception in kHz when the record was taken and w
|
||||
|
||||
<h3>4: Open file</h3>
|
||||
|
||||
Opens a file dialog to select the input file. It expects a default extension of `.sdriq`. This button is disabled when the stream is running. You need to pause (button 11) to make it active and thus be able to select another file.
|
||||
Opens a file dialog to select the input file. It expects an extension of `.sdriq` or `.wav`. This button is disabled when the stream is running. You need to pause (button 11) to make it active and thus be able to select another file.
|
||||
|
||||
<h3>5: File path</h3>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user