diff --git a/plugins/samplesink/filesink/CMakeLists.txt b/plugins/samplesink/filesink/CMakeLists.txt
new file mode 100644
index 000000000..f3562a0ac
--- /dev/null
+++ b/plugins/samplesink/filesink/CMakeLists.txt
@@ -0,0 +1,45 @@
+project(filesink)
+
+set(filesink_SOURCES
+ filesinkgui.cpp
+ filesinkoutput.cpp
+ filesinkplugin.cpp
+ filesinkthread.cpp
+)
+
+set(filesink_HEADERS
+ filesinkgui.h
+ filesinkoutput.h
+ filesinkplugin.h
+ filesinkthread.h
+)
+
+set(filesink_FORMS
+ filesinkgui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+qt5_wrap_ui(filesink_FORMS_HEADERS ${filesink_FORMS})
+
+add_library(outputfilesink SHARED
+ ${filesink_SOURCES}
+ ${filesink_HEADERS_MOC}
+ ${filesink_FORMS_HEADERS}
+)
+
+target_link_libraries(outputfilesink
+ ${QT_LIBRARIES}
+ sdrbase
+)
+
+qt5_use_modules(outputfilesink Core Widgets OpenGL Multimedia)
+
+install(TARGETS outputfilesink DESTINATION lib/plugins/samplesink)
diff --git a/plugins/samplesink/filesink/filesink.pro b/plugins/samplesink/filesink/filesink.pro
new file mode 100644
index 000000000..4a26dd8e3
--- /dev/null
+++ b/plugins/samplesink/filesink/filesink.pro
@@ -0,0 +1,33 @@
+#--------------------------------------------------------
+#
+# Pro file for Android and Windows builds with Qt Creator
+#
+#--------------------------------------------------------
+
+TEMPLATE = lib
+CONFIG += plugin
+
+QT += core gui widgets multimedia opengl
+
+TARGET = outputfilesink
+INCLUDEPATH += $$PWD
+INCLUDEPATH += ../../../sdrbase
+
+CONFIG(Release):build_subdir = release
+CONFIG(Debug):build_subdir = debug
+
+SOURCES += filesinkgui.cpp\
+ filesinkoutput.cpp\
+ filesinkplugin.cpp\
+ filesinkthread.cpp
+
+HEADERS += filesinkgui.h\
+ filesinkoutput.h\
+ filesinkplugin.h\
+ filesinkthread.h
+
+FORMS += filesinkgui.ui
+
+LIBS += -L../../../sdrbase/$${build_subdir} -lsdrbase
+
+RESOURCES = ../../../sdrbase/resources/res.qrc
diff --git a/plugins/samplesink/filesink/filesinkgui.cpp b/plugins/samplesink/filesink/filesinkgui.cpp
new file mode 100644
index 000000000..bf07e5411
--- /dev/null
+++ b/plugins/samplesink/filesink/filesinkgui.cpp
@@ -0,0 +1,355 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "ui_filesinkgui.h"
+#include "plugin/pluginapi.h"
+#include "gui/colormapper.h"
+#include "gui/glspectrum.h"
+#include "dsp/dspengine.h"
+#include "dsp/dspcommands.h"
+
+#include "mainwindow.h"
+
+#include "device/devicesinkapi.h"
+#include "filesinkgui.h"
+
+FileSinkGui::FileSinkGui(DeviceSinkAPI *deviceAPI, QWidget* parent) :
+ QWidget(parent),
+ ui(new Ui::FileSinkGui),
+ m_deviceAPI(deviceAPI),
+ m_settings(),
+ m_sampleSink(NULL),
+ m_generation(false),
+ m_fileName("..."),
+ m_sampleRate(0),
+ m_centerFrequency(0),
+ m_startingTimeStamp(0),
+ m_samplesCount(0),
+ m_tickCount(0),
+ m_enableNavTime(false),
+ m_lastEngineState((DSPDeviceSinkEngine::State)-1)
+{
+ ui->setupUi(this);
+ ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::ReverseGold));
+ ui->centerFrequency->setValueRange(7, 0, pow(10,7));
+ ui->fileNameText->setText(m_fileName);
+
+ ui->samplerate->clear();
+ for (int i = 0; i < FileSinkSampleRates::getNbRates(); i++)
+ {
+ ui->samplerate->addItem(QString::number(FileSinkSampleRates::getRate(i)));
+ }
+
+ connect(&(m_deviceAPI->getMainWindow()->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick()));
+ connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
+ m_statusTimer.start(500);
+
+ displaySettings();
+
+ m_sampleSink = new FileSinkOutput(m_deviceAPI->getMainWindow()->getMasterTimer());
+ connect(m_sampleSink->getOutputMessageQueueToGUI(), SIGNAL(messageEnqueued()), this, SLOT(handleSinkMessages()));
+ m_deviceAPI->setSink(m_sampleSink);
+
+ connect(m_deviceAPI->getDeviceOutputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleDSPMessages()), Qt::QueuedConnection);
+}
+
+FileSinkGui::~FileSinkGui()
+{
+ delete ui;
+}
+
+void FileSinkGui::destroy()
+{
+ delete this;
+}
+
+void FileSinkGui::setName(const QString& name)
+{
+ setObjectName(name);
+}
+
+QString FileSinkGui::getName() const
+{
+ return objectName();
+}
+
+void FileSinkGui::resetToDefaults()
+{
+ m_settings.resetToDefaults();
+ displaySettings();
+ sendSettings();
+}
+
+qint64 FileSinkGui::getCenterFrequency() const
+{
+ return m_centerFrequency;
+}
+
+void FileSinkGui::setCenterFrequency(qint64 centerFrequency)
+{
+ m_centerFrequency = centerFrequency;
+ displaySettings();
+ sendSettings();
+}
+
+QByteArray FileSinkGui::serialize() const
+{
+ return m_settings.serialize();
+}
+
+bool FileSinkGui::deserialize(const QByteArray& data)
+{
+ if(m_settings.deserialize(data)) {
+ displaySettings();
+ sendSettings();
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+void FileSinkGui::handleDSPMessages()
+{
+ Message* message;
+
+ while ((message = m_deviceAPI->getDeviceOutputMessageQueue()->pop()) != 0)
+ {
+ qDebug("FileSinkGui::handleDSPMessages: message: %s", message->getIdentifier());
+
+ if (DSPSignalNotification::match(*message))
+ {
+ DSPSignalNotification* notif = (DSPSignalNotification*) message;
+ m_deviceSampleRate = notif->getSampleRate();
+ m_deviceCenterFrequency = notif->getCenterFrequency();
+ qDebug("FileSinkGui::handleDSPMessages: SampleRate:%d, CenterFrequency:%llu", notif->getSampleRate(), notif->getCenterFrequency());
+ updateSampleRateAndFrequency();
+
+ delete message;
+ }
+ }
+}
+
+bool FileSinkGui::handleMessage(const Message& message)
+{
+ if (FileSinkOutput::MsgReportFileSinkGeneration::match(message))
+ {
+ m_generation = ((FileSinkOutput::MsgReportFileSinkGeneration&)message).getAcquisition();
+ updateWithGeneration();
+ return true;
+ }
+ else if (FileSinkOutput::MsgReportFileSinkStreamData::match(message))
+ {
+ m_sampleRate = ((FileSinkOutput::MsgReportFileSinkStreamData&)message).getSampleRate();
+ m_centerFrequency = ((FileSinkOutput::MsgReportFileSinkStreamData&)message).getCenterFrequency();
+ m_startingTimeStamp = ((FileSinkOutput::MsgReportFileSinkStreamData&)message).getStartingTimeStamp();
+ updateWithStreamData();
+ return true;
+ }
+ else if (FileSinkOutput::MsgReportFileSinkStreamTiming::match(message))
+ {
+ m_samplesCount = ((FileSinkOutput::MsgReportFileSinkStreamTiming&)message).getSamplesCount();
+ updateWithStreamTime();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void FileSinkGui::handleSinkMessages()
+{
+ Message* message;
+
+ while ((message = m_sampleSink->getOutputMessageQueueToGUI()->pop()) != 0)
+ {
+ //qDebug("FileSourceGui::handleSourceMessages: message: %s", message->getIdentifier());
+
+ if (handleMessage(*message))
+ {
+ delete message;
+ }
+ }
+}
+
+void FileSinkGui::updateSampleRateAndFrequency()
+{
+ m_deviceAPI->getSpectrum()->setSampleRate(m_deviceSampleRate);
+ m_deviceAPI->getSpectrum()->setCenterFrequency(m_deviceCenterFrequency);
+ ui->deviceRateText->setText(tr("%1k").arg((float)m_deviceSampleRate / 1000));
+}
+
+void FileSinkGui::displaySettings()
+{
+}
+
+void FileSinkGui::sendSettings()
+{
+}
+
+void FileSinkGui::on_startStop_toggled(bool checked)
+{
+ if (checked)
+ {
+ if (m_deviceAPI->initGeneration())
+ {
+ m_deviceAPI->startGeneration();
+ DSPEngine::instance()->startAudio();
+ }
+ }
+ else
+ {
+ m_deviceAPI->stopGeneration();
+ DSPEngine::instance()->stopAudio();
+ }
+}
+
+void FileSinkGui::updateStatus()
+{
+ int state = m_deviceAPI->state();
+
+ if(m_lastEngineState != state)
+ {
+ switch(state)
+ {
+ case DSPDeviceSourceEngine::StNotStarted:
+ ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
+ break;
+ case DSPDeviceSourceEngine::StIdle:
+ ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
+ break;
+ case DSPDeviceSourceEngine::StRunning:
+ ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
+ break;
+ case DSPDeviceSourceEngine::StError:
+ ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
+ QMessageBox::information(this, tr("Message"), m_deviceAPI->errorMessage());
+ break;
+ default:
+ break;
+ }
+
+ m_lastEngineState = state;
+ }
+}
+
+void FileSinkGui::on_play_toggled(bool checked)
+{
+ FileSinkOutput::MsgConfigureFileSinkWork* message = FileSinkOutput::MsgConfigureFileSinkWork::create(checked);
+ m_sampleSink->getInputMessageQueue()->push(message);
+}
+
+void FileSinkGui::on_showFileDialog_clicked(bool checked)
+{
+ QString fileName = QFileDialog::getOpenFileName(this,
+ tr("Save I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)"));
+
+ if (fileName != "")
+ {
+ m_fileName = fileName;
+ ui->fileNameText->setText(m_fileName);
+ configureFileName();
+ }
+}
+
+void FileSinkGui::configureFileName()
+{
+ qDebug() << "FileSinkGui::configureFileName: " << m_fileName.toStdString().c_str();
+ FileSinkOutput::MsgConfigureFileSinkName* message = FileSinkOutput::MsgConfigureFileSinkName::create(m_fileName);
+ m_sampleSink->getInputMessageQueue()->push(message);
+}
+
+void FileSinkGui::updateWithGeneration()
+{
+ ui->play->setEnabled(m_generation);
+ ui->play->setChecked(m_generation);
+ ui->showFileDialog->setEnabled(!m_generation);
+}
+
+void FileSinkGui::updateWithStreamData()
+{
+ ui->centerFrequency->setValue(m_centerFrequency/1000);
+ ui->sampleRateText->setText(tr("%1k").arg((float)m_sampleRate / 1000));
+ ui->play->setEnabled(m_generation);
+}
+
+void FileSinkGui::updateWithStreamTime()
+{
+ int t_sec = 0;
+ int t_msec = 0;
+
+ if (m_sampleRate > 0){
+ t_msec = ((m_samplesCount * 1000) / m_sampleRate) % 1000;
+ t_sec = m_samplesCount / m_sampleRate;
+ }
+
+ QTime t(0, 0, 0, 0);
+ t = t.addSecs(t_sec);
+ t = t.addMSecs(t_msec);
+ QString s_timems = t.toString("hh:mm:ss.zzz");
+ ui->relTimeText->setText(s_timems);
+}
+
+void FileSinkGui::tick()
+{
+ if ((++m_tickCount & 0xf) == 0) {
+ FileSinkOutput::MsgConfigureFileSinkStreamTiming* message = FileSinkOutput::MsgConfigureFileSinkStreamTiming::create();
+ m_sampleSink->getInputMessageQueue()->push(message);
+ }
+}
+
+unsigned int FileSinkSampleRates::m_rates[] = {48};
+unsigned int FileSinkSampleRates::m_nb_rates = 1;
+
+unsigned int FileSinkSampleRates::getRate(unsigned int rate_index)
+{
+ if (rate_index < m_nb_rates)
+ {
+ return m_rates[rate_index];
+ }
+ else
+ {
+ return m_rates[0];
+ }
+}
+
+unsigned int FileSinkSampleRates::getRateIndex(unsigned int rate)
+{
+ for (unsigned int i=0; i < m_nb_rates; i++)
+ {
+ if (rate/1000 == m_rates[i])
+ {
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+unsigned int FileSinkSampleRates::getNbRates()
+{
+ return FileSinkSampleRates::m_nb_rates;
+}
+
diff --git a/plugins/samplesink/filesink/filesinkgui.h b/plugins/samplesink/filesink/filesinkgui.h
new file mode 100644
index 000000000..109ae4a52
--- /dev/null
+++ b/plugins/samplesink/filesink/filesinkgui.h
@@ -0,0 +1,99 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_FILESINKGUI_H
+#define INCLUDE_FILESINKGUI_H
+
+#include
+
+#include "filesinkoutput.h"
+#include "plugin/plugingui.h"
+
+
+class DeviceSinkAPI;
+class DeviceSampleSink;
+
+namespace Ui {
+ class FileSourceGui;
+}
+
+class FileSinkGui : public QWidget, public PluginGUI {
+ Q_OBJECT
+
+public:
+ explicit FileSinkGui(DeviceSinkAPI *deviceAPI, QWidget* parent = NULL);
+ virtual ~FileSinkGui();
+ void destroy();
+
+ void setName(const QString& name);
+ QString getName() const;
+
+ void resetToDefaults();
+ virtual qint64 getCenterFrequency() const;
+ virtual void setCenterFrequency(qint64 centerFrequency);
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+ virtual bool handleMessage(const Message& message);
+
+private:
+ Ui::FileSourceGui* ui;
+
+ DeviceSinkAPI* m_deviceAPI;
+ FileSinkOutput::Settings m_settings;
+ QTimer m_statusTimer;
+ std::vector m_gains;
+ DeviceSampleSink* m_sampleSink;
+ bool m_generation;
+ QString m_fileName;
+ int m_sampleRate;
+ quint64 m_centerFrequency;
+ std::time_t m_startingTimeStamp;
+ int m_samplesCount;
+ std::size_t m_tickCount;
+ int m_deviceSampleRate;
+ quint64 m_deviceCenterFrequency; //!< Center frequency in device
+ int m_lastEngineState;
+
+ void displaySettings();
+ void displayTime();
+ void sendSettings();
+ void updateSampleRateAndFrequency();
+ void configureFileName();
+ void updateWithGeneration();
+ void updateWithStreamData();
+ void updateWithStreamTime();
+
+private slots:
+ void handleDSPMessages();
+ void handleSinkMessages();
+ void on_startStop_toggled(bool checked);
+ void on_play_toggled(bool checked);
+ void on_showFileDialog_clicked(bool checked);
+ void updateStatus();
+ void tick();
+};
+
+class FileSinkSampleRates {
+public:
+ static unsigned int getRate(unsigned int rate_index);
+ static unsigned int getRateIndex(unsigned int rate);
+ static unsigned int getNbRates();
+private:
+ static unsigned int m_rates[1];
+ static unsigned int m_nb_rates;
+};
+
+#endif // INCLUDE_FILESINKGUI_H
diff --git a/plugins/samplesink/filesink/filesinkgui.ui b/plugins/samplesink/filesink/filesinkgui.ui
new file mode 100644
index 000000000..85b38486e
--- /dev/null
+++ b/plugins/samplesink/filesink/filesinkgui.ui
@@ -0,0 +1,330 @@
+
+
+ FileSinkGui
+
+
+
+ 0
+ 0
+ 246
+ 190
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 246
+ 190
+
+
+
+
+ Sans Serif
+ 9
+
+
+
+ FileSource
+
+
+
+ 3
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+
+ 2
+
+ -
+
+
-
+
+
-
+
+
-
+
+
+ start/stop acquisition
+
+
+
+
+
+
+ :/record_off.png
+ :/record_on.png:/record_off.png
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 50
+ 0
+
+
+
+ I/Q sample rate kS/s
+
+
+ 00000k
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ false
+
+
+
+ 0
+ 0
+
+
+
+
+ 32
+ 16
+
+
+
+
+ Monospace
+ 20
+
+
+
+ SizeVerCursor
+
+
+ Qt::StrongFocus
+
+
+ Record center frequency in kHz
+
+
+
+ -
+
+
+ kHz
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
-
+
+
+
+ 24
+ 24
+
+
+
+
+ 24
+ 24
+
+
+
+ Open file
+
+
+
+
+
+
+ :/preset-load.png:/preset-load.png
+
+
+
+ -
+
+
+ false
+
+
+ File currently opened
+
+
+ ...
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
-
+
+
+ SR
+
+
+
+ -
+
+
+ Sample rate selection (kS/s)
+
+
+
+ -
+
+
+ kS/s
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ false
+
+
+
+ 90
+ 0
+
+
+
+ Record time from start
+
+
+ 00:00:00.000
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+ ValueDial
+ QWidget
+
+ 1
+
+
+ ButtonSwitch
+ QToolButton
+
+
+
+
+
+
+
+
diff --git a/plugins/samplesink/filesink/filesinkoutput.cpp b/plugins/samplesink/filesink/filesinkoutput.cpp
new file mode 100644
index 000000000..a150a241a
--- /dev/null
+++ b/plugins/samplesink/filesink/filesinkoutput.cpp
@@ -0,0 +1,244 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+#include
+
+#include "util/simpleserializer.h"
+#include "dsp/dspcommands.h"
+#include "dsp/dspengine.h"
+#include "dsp/filerecord.h"
+
+#include "filesinkgui.h"
+#include "filesinkoutput.h"
+#include "filesinkthread.h"
+
+MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgConfigureFileSink, Message)
+MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgConfigureFileSinkName, Message)
+MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgConfigureFileSinkWork, Message)
+MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgConfigureFileSinkSeek, Message)
+MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgConfigureFileSinkStreamTiming, Message)
+MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgReportFileSinkGeneration, Message)
+MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgReportFileSinkStreamData, Message)
+MESSAGE_CLASS_DEFINITION(FileSinkOutput::MsgReportFileSinkStreamTiming, Message)
+
+FileSinkOutput::Settings::Settings() :
+ m_fileName("./test.sdriq")
+{
+}
+
+void FileSinkOutput::Settings::resetToDefaults()
+{
+ m_fileName = "./test.sdriq";
+}
+
+QByteArray FileSinkOutput::Settings::serialize() const
+{
+ SimpleSerializer s(1);
+ s.writeString(1, m_fileName);
+ return s.final();
+}
+
+bool FileSinkOutput::Settings::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if(!d.isValid()) {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1) {
+ int intval;
+ d.readString(1, &m_fileName, "./test.sdriq");
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+FileSinkOutput::FileSinkOutput(const QTimer& masterTimer) :
+ m_settings(),
+ m_fileSourceThread(0),
+ m_deviceDescription(),
+ m_fileName("..."),
+ m_sampleRate(0),
+ m_centerFrequency(0),
+ m_recordLength(0),
+ m_startingTimeStamp(0),
+ m_masterTimer(masterTimer)
+{
+}
+
+FileSinkOutput::~FileSinkOutput()
+{
+ stop();
+}
+
+void FileSinkOutput::openFileStream()
+{
+ //stopInput();
+
+ if (m_ofstream.is_open()) {
+ m_ofstream.close();
+ }
+
+ m_ofstream.open(m_fileName.toStdString().c_str(), std::ios::binary);
+ FileRecord::Header header;
+
+ header.sampleRate = m_sampleRate;
+ header.centerFrequency = m_centerFrequency;
+ header.startTimeStamp = m_startingTimeStamp; // TODO: set timestamp
+
+ qDebug() << "FileSinkOutput::openFileStream: " << m_fileName.toStdString().c_str();
+
+ MsgReportFileSinkStreamData *report = MsgReportFileSinkStreamData::create(m_sampleRate,
+ m_centerFrequency,
+ m_startingTimeStamp); // file stream data
+
+ getOutputMessageQueueToGUI()->push(report);
+}
+
+bool FileSinkOutput::init(const Message& message)
+{
+ return false;
+}
+
+bool FileSinkOutput::start(int device)
+{
+ QMutexLocker mutexLocker(&m_mutex);
+ qDebug() << "FileSinkOutput::start";
+
+ //openFileStream();
+
+ if((m_fileSourceThread = new FileSinkThread(&m_ifstream, &m_sampleFifo)) == 0) {
+ qFatal("out of memory");
+ stop();
+ return false;
+ }
+
+ m_fileSourceThread->setSamplerate(m_sampleRate);
+ m_fileSourceThread->connectTimer(m_masterTimer);
+ m_fileSourceThread->startWork();
+ m_deviceDescription = "FileSink";
+
+ mutexLocker.unlock();
+ //applySettings(m_generalSettings, m_settings, true);
+ qDebug("FileSinkOutput::start: started");
+
+ MsgReportFileSinkGeneration *report = MsgReportFileSinkGeneration::create(true); // acquisition on
+ getOutputMessageQueueToGUI()->push(report);
+
+ return true;
+}
+
+void FileSinkOutput::stop()
+{
+ qDebug() << "FileSourceInput::stop";
+ QMutexLocker mutexLocker(&m_mutex);
+
+ if(m_fileSourceThread != 0)
+ {
+ m_fileSourceThread->stopWork();
+ delete m_fileSourceThread;
+ m_fileSourceThread = 0;
+ }
+
+ m_deviceDescription.clear();
+
+ MsgReportFileSinkGeneration *report = MsgReportFileSinkGeneration::create(false); // acquisition off
+ getOutputMessageQueueToGUI()->push(report);
+}
+
+const QString& FileSinkOutput::getDeviceDescription() const
+{
+ return m_deviceDescription;
+}
+
+int FileSinkOutput::getSampleRate() const
+{
+ return m_sampleRate;
+}
+
+quint64 FileSinkOutput::getCenterFrequency() const
+{
+ return m_centerFrequency;
+}
+
+std::time_t FileSinkOutput::getStartingTimeStamp() const
+{
+ return m_startingTimeStamp;
+}
+
+bool FileSinkOutput::handleMessage(const Message& message)
+{
+ if (MsgConfigureFileSinkName::match(message))
+ {
+ MsgConfigureFileSinkName& conf = (MsgConfigureFileSinkName&) message;
+ m_fileName = conf.getFileName();
+ openFileStream();
+ return true;
+ }
+ else if (MsgConfigureFileSinkWork::match(message))
+ {
+ MsgConfigureFileSinkWork& conf = (MsgConfigureFileSinkWork&) message;
+ bool working = conf.isWorking();
+
+ if (m_fileSourceThread != 0)
+ {
+ if (working)
+ {
+ m_fileSourceThread->startWork();
+ /*
+ MsgReportFileSourceStreamTiming *report =
+ MsgReportFileSourceStreamTiming::create(m_fileSourceThread->getSamplesCount());
+ getOutputMessageQueueToGUI()->push(report);*/
+ }
+ else
+ {
+ m_fileSourceThread->stopWork();
+ }
+ }
+
+ return true;
+ }
+ else if (MsgConfigureFileSinkSeek::match(message))
+ {
+ MsgConfigureFileSinkSeek& conf = (MsgConfigureFileSinkSeek&) message;
+ int seekPercentage = conf.getPercentage();
+ seekFileStream(seekPercentage);
+
+ return true;
+ }
+ else if (MsgConfigureFileSinkStreamTiming::match(message))
+ {
+ MsgReportFileSinkStreamTiming *report;
+
+ if (m_fileSourceThread != 0)
+ {
+ report = MsgReportFileSinkStreamTiming::create(m_fileSourceThread->getSamplesCount());
+ getOutputMessageQueueToGUI()->push(report);
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
diff --git a/plugins/samplesink/filesink/filesinkoutput.h b/plugins/samplesink/filesink/filesinkoutput.h
new file mode 100644
index 000000000..24d33b3a2
--- /dev/null
+++ b/plugins/samplesink/filesink/filesinkoutput.h
@@ -0,0 +1,236 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_FILESINKOUTPUT_H
+#define INCLUDE_FILESINKOUTPUT_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+class FileSinkThread;
+
+class FileSinkOutput : public DeviceSampleSink {
+public:
+ struct Settings {
+ QString m_fileName;
+
+ Settings();
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+ };
+
+ class MsgConfigureFileSink : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const Settings& getSettings() const { return m_settings; }
+
+ static MsgConfigureFileSink* create(const Settings& settings)
+ {
+ return new MsgConfigureFileSink(settings);
+ }
+
+ private:
+ Settings m_settings;
+
+ MsgConfigureFileSink(const Settings& settings) :
+ Message(),
+ m_settings(settings)
+ { }
+ };
+
+ class MsgConfigureFileSinkName : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ const QString& getFileName() const { return m_fileName; }
+
+ static MsgConfigureFileSinkName* create(const QString& fileName)
+ {
+ return new MsgConfigureFileSinkName(fileName);
+ }
+
+ private:
+ QString m_fileName;
+
+ MsgConfigureFileSinkName(const QString& fileName) :
+ Message(),
+ m_fileName(fileName)
+ { }
+ };
+
+ class MsgConfigureFileSinkWork : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ bool isWorking() const { return m_working; }
+
+ static MsgConfigureFileSinkWork* create(bool working)
+ {
+ return new MsgConfigureFileSinkWork(working);
+ }
+
+ private:
+ bool m_working;
+
+ MsgConfigureFileSinkWork(bool working) :
+ Message(),
+ m_working(working)
+ { }
+ };
+
+ class MsgConfigureFileSinkStreamTiming : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+
+ static MsgConfigureFileSinkStreamTiming* create()
+ {
+ return new MsgConfigureFileSinkStreamTiming();
+ }
+
+ private:
+
+ MsgConfigureFileSinkStreamTiming() :
+ Message()
+ { }
+ };
+
+ class MsgConfigureFileSinkSeek : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ int getPercentage() const { return m_seekPercentage; }
+
+ static MsgConfigureFileSinkSeek* create(int seekPercentage)
+ {
+ return new MsgConfigureFileSinkSeek(seekPercentage);
+ }
+
+ protected:
+ int m_seekPercentage; //!< percentage of seek position from the beginning 0..100
+
+ MsgConfigureFileSinkSeek(int seekPercentage) :
+ Message(),
+ m_seekPercentage(seekPercentage)
+ { }
+ };
+
+ class MsgReportFileSinkGeneration : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ bool getAcquisition() const { return m_acquisition; }
+
+ static MsgReportFileSinkGeneration* create(bool acquisition)
+ {
+ return new MsgReportFileSinkGeneration(acquisition);
+ }
+
+ protected:
+ bool m_acquisition;
+
+ MsgReportFileSinkGeneration(bool acquisition) :
+ Message(),
+ m_acquisition(acquisition)
+ { }
+ };
+
+ class MsgReportFileSinkStreamData : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ int getSampleRate() const { return m_sampleRate; }
+ quint64 getCenterFrequency() const { return m_centerFrequency; }
+ std::time_t getStartingTimeStamp() const { return m_startingTimeStamp; }
+
+ static MsgReportFileSinkStreamData* create(int sampleRate,
+ quint64 centerFrequency,
+ std::time_t startingTimeStamp)
+ {
+ return new MsgReportFileSinkStreamData(sampleRate, centerFrequency, startingTimeStamp);
+ }
+
+ protected:
+ int m_sampleRate;
+ quint64 m_centerFrequency;
+ std::time_t m_startingTimeStamp;
+
+ MsgReportFileSinkStreamData(int sampleRate,
+ quint64 centerFrequency,
+ std::time_t startingTimeStamp) :
+ Message(),
+ m_sampleRate(sampleRate),
+ m_centerFrequency(centerFrequency),
+ m_startingTimeStamp(startingTimeStamp)
+ { }
+ };
+
+ class MsgReportFileSinkStreamTiming : public Message {
+ MESSAGE_CLASS_DECLARATION
+
+ public:
+ std::size_t getSamplesCount() const { return m_samplesCount; }
+
+ static MsgReportFileSinkStreamTiming* create(std::size_t samplesCount)
+ {
+ return new MsgReportFileSinkStreamTiming(samplesCount);
+ }
+
+ protected:
+ std::size_t m_samplesCount;
+
+ MsgReportFileSinkStreamTiming(std::size_t samplesCount) :
+ Message(),
+ m_samplesCount(samplesCount)
+ { }
+ };
+
+ FileSinkOutput(const QTimer& masterTimer);
+ virtual ~FileSinkOutput();
+
+ virtual bool init(const Message& message);
+ virtual bool start(int device);
+ virtual void stop();
+
+ virtual const QString& getDeviceDescription() const;
+ virtual int getSampleRate() const;
+ virtual quint64 getCenterFrequency() const;
+ std::time_t getStartingTimeStamp() const;
+
+ virtual bool handleMessage(const Message& message);
+
+private:
+ QMutex m_mutex;
+ Settings m_settings;
+ std::ofstream m_ofstream;
+ FileSinkThread* m_fileSourceThread;
+ QString m_deviceDescription;
+ QString m_fileName;
+ int m_sampleRate;
+ quint64 m_centerFrequency;
+ std::time_t m_startingTimeStamp;
+ const QTimer& m_masterTimer;
+
+ void openFileStream();
+};
+
+#endif // INCLUDE_FILESINKOUTPUT_H
diff --git a/plugins/samplesink/filesink/filesinkplugin.cpp b/plugins/samplesink/filesink/filesinkplugin.cpp
new file mode 100644
index 000000000..d070dc127
--- /dev/null
+++ b/plugins/samplesink/filesink/filesinkplugin.cpp
@@ -0,0 +1,82 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+
+#include "device/devicesinkapi.h"
+#include "filesinkgui.h"
+#include "filesinkplugin.h"
+
+const PluginDescriptor FileSinkPlugin::m_pluginDescriptor = {
+ QString("File sink output"),
+ QString("2.2.0"),
+ QString("(c) Edouard Griffiths, F4EXB"),
+ QString("https://github.com/f4exb/sdrangel"),
+ true,
+ QString("https://github.com/f4exb/sdrangel")
+};
+
+const QString FileSinkPlugin::m_deviceTypeID = FILESINK_DEVICE_TYPE_ID;
+
+FileSinkPlugin::FileSinkPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& FileSinkPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void FileSinkPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ pluginAPI->registerSampleSource(m_deviceTypeID, this);
+}
+
+PluginInterface::SamplingDevices FileSinkPlugin::enumSampleSinks()
+{
+ SamplingDevices result;
+ int count = 1;
+
+ for(int i = 0; i < count; i++)
+ {
+ QString displayedName(QString("FileSink[%1]").arg(i));
+
+ result.append(SamplingDevice(displayedName,
+ m_deviceTypeID,
+ QString::null,
+ i));
+ }
+
+ return result;
+}
+
+PluginGUI* FileSinkPlugin::createSampleSinkPluginGUI(const QString& sinkId, QWidget **widget, DeviceSinkAPI *deviceAPI)
+{
+ if(sinkId == m_deviceTypeID)
+ {
+ FileSinkGui* gui = new FileSinkGui(deviceAPI);
+ *widget = gui;
+ return gui;
+ }
+ else
+ {
+ return 0;
+ }
+}
diff --git a/plugins/samplesink/filesink/filesinkplugin.h b/plugins/samplesink/filesink/filesinkplugin.h
new file mode 100644
index 000000000..03249c049
--- /dev/null
+++ b/plugins/samplesink/filesink/filesinkplugin.h
@@ -0,0 +1,48 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_FILESINKPLUGIN_H
+#define INCLUDE_FILESINKPLUGIN_H
+
+#include
+#include "plugin/plugininterface.h"
+
+#define FILESINK_DEVICE_TYPE_ID "sdrangel.samplesink.filesink"
+
+class PluginAPI;
+class DeviceSinkAPI;
+
+class FileSinkPlugin : public QObject, public PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+ Q_PLUGIN_METADATA(IID FILESINK_DEVICE_TYPE_ID)
+
+public:
+ explicit FileSinkPlugin(QObject* parent = NULL);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ virtual SamplingDevices enumSampleSinks();
+ virtual PluginGUI* createSampleSinkPluginGUI(const QString& sourceId, QWidget **widget, DeviceSinkAPI *deviceAPI);
+
+ static const QString m_deviceTypeID;
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+};
+
+#endif // INCLUDE_FILESOURCEPLUGIN_H
diff --git a/plugins/samplesink/filesink/filesinkthread.cpp b/plugins/samplesink/filesink/filesinkthread.cpp
new file mode 100644
index 000000000..b3eb65eac
--- /dev/null
+++ b/plugins/samplesink/filesink/filesinkthread.cpp
@@ -0,0 +1,134 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include
+#include
+#include
+#include
+
+#include "dsp/samplesourcefifo.h"
+#include "filesinkthread.h"
+
+FileSinkThread::FileSinkThread(std::ofstream *samplesStream, SampleSinkFifo* sampleFifo, QObject* parent) :
+ QThread(parent),
+ m_running(false),
+ m_ofstream(samplesStream),
+ m_buf(0),
+ m_bufsize(0),
+ m_samplesChunkSize(0),
+ m_sampleFifo(sampleFifo),
+ m_samplerate(0),
+ m_throttlems(FILESINK_THROTTLE_MS),
+ m_throttleToggle(false)
+{
+ assert(m_ofstream != 0);
+}
+
+FileSinkThread::~FileSinkThread()
+{
+ if (m_running) {
+ stopWork();
+ }
+
+ if (m_buf != 0) {
+ free(m_buf);
+ }
+}
+
+void FileSinkThread::startWork()
+{
+ qDebug() << "FileSinkThread::startWork: ";
+
+ if (m_ofstream->is_open())
+ {
+ qDebug() << "FileSinkThread::startWork: file stream open, starting...";
+ m_startWaitMutex.lock();
+ m_elapsedTimer.start();
+ start();
+ while(!m_running)
+ m_startWaiter.wait(&m_startWaitMutex, 100);
+ m_startWaitMutex.unlock();
+ }
+ else
+ {
+ qDebug() << "FileSinkThread::startWork: file stream closed, not starting.";
+ }
+}
+
+void FileSinkThread::stopWork()
+{
+ qDebug() << "FileSinkThread::stopWork";
+ m_running = false;
+ wait();
+}
+
+void FileSinkThread::setSamplerate(int samplerate)
+{
+ qDebug() << "FileSinkThread::setSamplerate:"
+ << " new:" << samplerate
+ << " old:" << m_samplerate;
+
+ if (samplerate != m_samplerate)
+ {
+ if (m_running) {
+ stopWork();
+ }
+
+ m_samplerate = samplerate;
+ m_samplesChunkSize = (m_samplerate * m_throttlems) / 1000;
+ }
+}
+
+void FileSinkThread::run()
+{
+ int res;
+
+ m_running = true;
+ m_startWaiter.wakeAll();
+
+ while(m_running) // actual work is in the tick() function
+ {
+ sleep(1);
+ }
+
+ m_running = false;
+}
+
+void FileSinkThread::connectTimer(const QTimer& timer)
+{
+ qDebug() << "FileSinkThread::connectTimer";
+ connect(&timer, SIGNAL(timeout()), this, SLOT(tick()));
+}
+
+void FileSinkThread::tick()
+{
+ if (m_running)
+ {
+ qint64 throttlems = m_elapsedTimer.restart();
+
+ if (throttlems != m_throttlems)
+ {
+ m_throttlems = throttlems;
+ m_samplesChunkSize = (m_samplerate * (m_throttlems+(m_throttleToggle ? 1 : 0))) / 1000;
+ m_throttleToggle = !m_throttleToggle;
+ }
+
+ SampleVector::iterator beginRead;
+
+ m_sampleFifo->read(beginRead, m_samplesChunkSize);
+ m_ofstream->write(reinterpret_cast(*beginRead), m_samplesChunkSize*4);
+ }
+}
diff --git a/plugins/samplesink/filesink/filesinkthread.h b/plugins/samplesink/filesink/filesinkthread.h
new file mode 100644
index 000000000..b12533eaf
--- /dev/null
+++ b/plugins/samplesink/filesink/filesinkthread.h
@@ -0,0 +1,72 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2016 Edouard Griffiths, F4EXB //
+// //
+// 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 //
+// //
+// 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 . //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_FILESINKTHREAD_H
+#define INCLUDE_FILESINKTHREAD_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "dsp/inthalfbandfilter.h"
+
+#define FILESINK_THROTTLE_MS 50
+
+class SampleSourceFifo;
+
+class FileSinkThread : public QThread {
+ Q_OBJECT
+
+public:
+ FileSinkThread(std::ofstream *samplesStream, SampleSourceFifo* sampleFifo, QObject* parent = 0);
+ ~FileSinkThread();
+
+ void startWork();
+ void stopWork();
+ void setSamplerate(int samplerate);
+ void setBuffer(std::size_t chunksize);
+ bool isRunning() const { return m_running; }
+
+ void connectTimer(const QTimer& timer);
+
+private:
+ QMutex m_startWaitMutex;
+ QWaitCondition m_startWaiter;
+ bool m_running;
+
+ std::ofstream* m_ofstream;
+ quint8 *m_buf;
+ std::size_t m_bufsize;
+ std::size_t m_samplesChunkSize;
+ SampleSourceFifo* m_sampleFifo;
+
+ int m_samplerate;
+ int m_throttlems;
+ QElapsedTimer m_elapsedTimer;
+ bool m_throttleToggle;
+
+ void run();
+
+private slots:
+ void tick();
+};
+
+#endif // INCLUDE_FILESINKTHREAD_H