2020-11-10 05:06:39 -05:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
2023-11-18 06:02:48 -05:00
|
|
|
// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
|
|
|
|
// written by Christian Daniel //
|
|
|
|
// Copyright (C) 2015-2021 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
|
|
|
// Copyright (C) 2021 Christoph Berg <myon@debian.org> //
|
2020-11-10 05:06:39 -05:00
|
|
|
// //
|
|
|
|
// This program is free software; you can redistribute it and/or modify //
|
|
|
|
// it under the terms of the GNU General Public License as published by //
|
|
|
|
// the Free Software Foundation as version 3 of the License, or //
|
|
|
|
// (at your option) any later version. //
|
|
|
|
// //
|
|
|
|
// This program is distributed in the hope that it will be useful, //
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
|
|
|
// GNU General Public License V3 for more details. //
|
|
|
|
// //
|
|
|
|
// You should have received a copy of the GNU General Public License //
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
#include "dsp/dspcommands.h"
|
|
|
|
#include "dsp/sigmffilerecord.h"
|
|
|
|
#include "dsp/spectrumvis.h"
|
|
|
|
|
|
|
|
#include "sigmffilesinkmessages.h"
|
|
|
|
#include "sigmffilesinksink.h"
|
|
|
|
|
|
|
|
SigMFFileSinkSink::SigMFFileSinkSink() :
|
|
|
|
m_preRecordBuffer(48000),
|
|
|
|
m_preRecordFill(0),
|
2020-11-15 06:02:04 -05:00
|
|
|
m_spectrumSink(nullptr),
|
|
|
|
m_msgQueueToGUI(nullptr),
|
2020-11-10 05:06:39 -05:00
|
|
|
m_recordEnabled(false),
|
|
|
|
m_record(false),
|
|
|
|
m_squelchOpen(false),
|
|
|
|
m_postSquelchCounter(0),
|
|
|
|
m_msCount(0),
|
|
|
|
m_byteCount(0)
|
|
|
|
{}
|
|
|
|
|
|
|
|
SigMFFileSinkSink::~SigMFFileSinkSink()
|
|
|
|
{}
|
|
|
|
|
|
|
|
void SigMFFileSinkSink::startRecording()
|
|
|
|
{
|
|
|
|
if (m_recordEnabled) // File is open for writing and valid
|
|
|
|
{
|
|
|
|
// set the length of pre record time
|
|
|
|
qint64 mSShift = (m_preRecordFill * 1000) / m_sinkSampleRate;
|
|
|
|
m_fileSink.setMsShift(-mSShift);
|
|
|
|
|
|
|
|
// notify capture start
|
|
|
|
m_fileSink.startRecording();
|
|
|
|
m_record = true;
|
|
|
|
|
2021-02-28 06:53:07 -05:00
|
|
|
if (m_msgQueueToGUI)
|
|
|
|
{
|
|
|
|
SigMFFileSinkMessages::MsgReportRecording *msg = SigMFFileSinkMessages::MsgReportRecording::create(true);
|
|
|
|
m_msgQueueToGUI->push(msg);
|
|
|
|
}
|
|
|
|
|
2020-11-10 05:06:39 -05:00
|
|
|
// copy pre record samples
|
|
|
|
SampleVector::iterator p1Begin, p1End, p2Begin, p2End;
|
|
|
|
m_preRecordBuffer.readBegin(m_preRecordFill, &p1Begin, &p1End, &p2Begin, &p2End);
|
|
|
|
|
|
|
|
if (p1Begin != p1End) {
|
|
|
|
m_fileSink.feed(p1Begin, p1End, false);
|
|
|
|
}
|
|
|
|
if (p2Begin != p2End) {
|
|
|
|
m_fileSink.feed(p2Begin, p2End, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_byteCount += m_preRecordFill * sizeof(Sample);
|
|
|
|
|
|
|
|
if (m_sinkSampleRate > 0) {
|
|
|
|
m_msCount += (m_preRecordFill * 1000) / m_sinkSampleRate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SigMFFileSinkSink::stopRecording()
|
|
|
|
{
|
|
|
|
if (m_record)
|
|
|
|
{
|
|
|
|
m_preRecordBuffer.reset();
|
|
|
|
m_fileSink.stopRecording();
|
2021-02-28 06:53:07 -05:00
|
|
|
|
|
|
|
if (m_msgQueueToGUI)
|
|
|
|
{
|
|
|
|
SigMFFileSinkMessages::MsgReportRecording *msg = SigMFFileSinkMessages::MsgReportRecording::create(false);
|
|
|
|
m_msgQueueToGUI->push(msg);
|
|
|
|
}
|
|
|
|
|
2020-11-10 05:06:39 -05:00
|
|
|
m_record = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SigMFFileSinkSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
|
|
|
{
|
|
|
|
SampleVector::const_iterator beginw = begin;
|
|
|
|
SampleVector::const_iterator endw = end;
|
|
|
|
|
|
|
|
if (m_decimator.getDecim() != 1)
|
|
|
|
{
|
|
|
|
for (SampleVector::const_iterator it = begin; it < end; ++it)
|
|
|
|
{
|
|
|
|
Complex c(it->real(), it->imag());
|
|
|
|
c *= m_nco.nextIQ();
|
|
|
|
Complex ci;
|
|
|
|
|
|
|
|
if (m_decimator.decimate(c, ci)) {
|
|
|
|
m_sampleBuffer.push_back(Sample(ci.real(), ci.imag()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
beginw = m_sampleBuffer.begin();
|
|
|
|
endw = m_sampleBuffer.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!m_record && (m_settings.m_preRecordTime != 0)) {
|
|
|
|
m_preRecordFill = m_preRecordBuffer.write(beginw, endw);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_settings.m_squelchRecordingEnable)
|
|
|
|
{
|
|
|
|
int nbToWrite = endw - beginw;
|
|
|
|
|
|
|
|
if (m_squelchOpen)
|
|
|
|
{
|
|
|
|
m_fileSink.feed(beginw, endw, true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (nbToWrite < m_postSquelchCounter)
|
|
|
|
{
|
|
|
|
m_fileSink.feed(beginw, endw, true);
|
|
|
|
m_postSquelchCounter -= nbToWrite;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_fileSink.feed(beginw, endw + m_postSquelchCounter, true);
|
|
|
|
nbToWrite = m_postSquelchCounter;
|
|
|
|
m_postSquelchCounter = 0;
|
|
|
|
|
|
|
|
stopRecording();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_byteCount += nbToWrite * sizeof(Sample);
|
|
|
|
|
|
|
|
if (m_sinkSampleRate > 0) {
|
|
|
|
m_msCount += (nbToWrite * 1000) / m_sinkSampleRate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (m_record)
|
|
|
|
{
|
|
|
|
m_fileSink.feed(beginw, endw, true);
|
|
|
|
int nbSamples = endw - beginw;
|
|
|
|
m_byteCount += nbSamples * sizeof(Sample);
|
|
|
|
|
|
|
|
if (m_sinkSampleRate > 0) {
|
|
|
|
m_msCount += (nbSamples * 1000) / m_sinkSampleRate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_spectrumSink) {
|
|
|
|
m_spectrumSink->feed(beginw, endw, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_decimator.getDecim() != 1) {
|
|
|
|
m_sampleBuffer.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SigMFFileSinkSink::applyChannelSettings(
|
|
|
|
int channelSampleRate,
|
|
|
|
int sinkSampleRate,
|
|
|
|
int channelFrequencyOffset,
|
|
|
|
int64_t centerFrequency,
|
|
|
|
bool force)
|
|
|
|
{
|
|
|
|
qDebug() << "SigMFFileSinkSink::applyChannelSettings:"
|
|
|
|
<< " channelSampleRate: " << channelSampleRate
|
|
|
|
<< " sinkSampleRate: " << sinkSampleRate
|
|
|
|
<< " channelFrequencyOffset: " << channelFrequencyOffset
|
|
|
|
<< " centerFrequency: " << centerFrequency
|
|
|
|
<< " force: " << force;
|
|
|
|
|
|
|
|
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
|
|
|
|
(m_channelSampleRate != channelSampleRate) || force)
|
|
|
|
{
|
|
|
|
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((m_channelSampleRate != channelSampleRate)
|
|
|
|
|| (m_sinkSampleRate != sinkSampleRate) || force)
|
|
|
|
{
|
|
|
|
int decim = channelSampleRate / sinkSampleRate;
|
|
|
|
|
2021-01-19 06:36:56 -05:00
|
|
|
for (int i = 0; i < 7; i++) // find log2 between 0 and 6
|
2020-11-10 05:06:39 -05:00
|
|
|
{
|
2020-11-15 06:02:04 -05:00
|
|
|
if ((decim & 1) == 1)
|
2020-11-10 05:06:39 -05:00
|
|
|
{
|
|
|
|
qDebug() << "SigMFFileSinkSink::applyChannelSettings: log2decim: " << i;
|
|
|
|
m_decimator.setLog2Decim(i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
decim >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((m_centerFrequency != centerFrequency)
|
|
|
|
|| (m_channelFrequencyOffset != channelFrequencyOffset)
|
|
|
|
|| (m_sinkSampleRate != sinkSampleRate) || force)
|
|
|
|
{
|
|
|
|
DSPSignalNotification *notif = new DSPSignalNotification(sinkSampleRate, centerFrequency);
|
|
|
|
DSPSignalNotification *notifToSpectrum = new DSPSignalNotification(*notif);
|
|
|
|
m_fileSink.getInputMessageQueue()->push(notif);
|
|
|
|
m_spectrumSink->getInputMessageQueue()->push(notifToSpectrum);
|
|
|
|
|
|
|
|
if (m_msgQueueToGUI)
|
|
|
|
{
|
|
|
|
SigMFFileSinkMessages::MsgConfigureSpectrum *msg = SigMFFileSinkMessages::MsgConfigureSpectrum::create(
|
|
|
|
centerFrequency, sinkSampleRate);
|
|
|
|
m_msgQueueToGUI->push(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((m_sinkSampleRate != sinkSampleRate) || force) {
|
|
|
|
m_preRecordBuffer.setSize(m_settings.m_preRecordTime * sinkSampleRate);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_channelSampleRate = channelSampleRate;
|
|
|
|
m_channelFrequencyOffset = channelFrequencyOffset;
|
|
|
|
m_sinkSampleRate = sinkSampleRate;
|
|
|
|
m_centerFrequency = centerFrequency;
|
|
|
|
m_preRecordBuffer.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SigMFFileSinkSink::applySettings(const SigMFFileSinkSettings& settings, bool force)
|
|
|
|
{
|
|
|
|
qDebug() << "SigMFFileSinkSink::applySettings:"
|
|
|
|
<< "m_fileRecordName: " << settings.m_fileRecordName
|
|
|
|
<< "force: " << force;
|
|
|
|
|
2021-02-19 09:54:08 -05:00
|
|
|
QString fileRecordName = settings.m_fileRecordName;
|
|
|
|
|
2020-11-10 05:06:39 -05:00
|
|
|
if ((settings.m_fileRecordName != m_settings.m_fileRecordName) || force)
|
|
|
|
{
|
2021-02-19 09:54:08 -05:00
|
|
|
QStringList dotBreakout = settings.m_fileRecordName.split(QLatin1Char('.'));
|
|
|
|
|
|
|
|
if (dotBreakout.size() > 1) {
|
|
|
|
QString extension = dotBreakout.last();
|
|
|
|
|
|
|
|
if (extension != "sigmf-meta") {
|
|
|
|
dotBreakout.last() = "sigmf-meta";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dotBreakout.append("sigmf-meta");
|
|
|
|
}
|
|
|
|
|
|
|
|
fileRecordName = dotBreakout.join(QLatin1Char('.'));
|
|
|
|
|
2020-11-10 05:06:39 -05:00
|
|
|
QString fileBase;
|
2021-02-19 09:54:08 -05:00
|
|
|
FileRecordInterface::RecordType recordType = FileRecordInterface::guessTypeFromFileName(fileRecordName, fileBase);
|
2020-11-10 05:06:39 -05:00
|
|
|
|
|
|
|
if (recordType == FileRecordInterface::RecordTypeSigMF)
|
|
|
|
{
|
|
|
|
m_fileSink.setFileName(fileBase);
|
|
|
|
m_fileSink.setHardwareId(m_deviceHwId);
|
2021-02-20 06:16:34 -05:00
|
|
|
m_msCount = m_fileSink.getInitialMsCount();
|
|
|
|
m_byteCount = m_fileSink.getInitialBytesCount();
|
2020-11-10 05:06:39 -05:00
|
|
|
m_recordEnabled = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_recordEnabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((settings.m_preRecordTime != m_settings.m_squelchPostRecordTime) || force)
|
|
|
|
{
|
|
|
|
m_preRecordBuffer.setSize(settings.m_preRecordTime * m_sinkSampleRate);
|
|
|
|
|
|
|
|
if (settings.m_preRecordTime == 0) {
|
|
|
|
m_preRecordFill = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_settings = settings;
|
2021-02-19 09:54:08 -05:00
|
|
|
m_settings.m_fileRecordName = fileRecordName;
|
2020-11-10 05:06:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
void SigMFFileSinkSink::squelchRecording(bool squelchOpen)
|
|
|
|
{
|
|
|
|
if (!m_recordEnabled || !m_settings.m_squelchRecordingEnable) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (squelchOpen)
|
|
|
|
{
|
2021-02-28 06:53:07 -05:00
|
|
|
if (!m_record) {
|
2020-11-10 05:06:39 -05:00
|
|
|
startRecording();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_postSquelchCounter = m_settings.m_squelchPostRecordTime * m_sinkSampleRate;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_squelchOpen = squelchOpen;
|
2020-11-15 06:02:04 -05:00
|
|
|
}
|