From 6c82c36958737765345270b4055afc757efe4bca Mon Sep 17 00:00:00 2001 From: f4exb Date: Wed, 19 Oct 2016 18:42:57 +0200 Subject: [PATCH] Tx ph.1: Added FileSink (1) --- plugins/samplesink/filesink/CMakeLists.txt | 45 +++ plugins/samplesink/filesink/filesink.pro | 33 ++ plugins/samplesink/filesink/filesinkgui.cpp | 355 ++++++++++++++++++ plugins/samplesink/filesink/filesinkgui.h | 99 +++++ plugins/samplesink/filesink/filesinkgui.ui | 330 ++++++++++++++++ .../samplesink/filesink/filesinkoutput.cpp | 244 ++++++++++++ plugins/samplesink/filesink/filesinkoutput.h | 236 ++++++++++++ .../samplesink/filesink/filesinkplugin.cpp | 82 ++++ plugins/samplesink/filesink/filesinkplugin.h | 48 +++ .../samplesink/filesink/filesinkthread.cpp | 134 +++++++ plugins/samplesink/filesink/filesinkthread.h | 72 ++++ 11 files changed, 1678 insertions(+) create mode 100644 plugins/samplesink/filesink/CMakeLists.txt create mode 100644 plugins/samplesink/filesink/filesink.pro create mode 100644 plugins/samplesink/filesink/filesinkgui.cpp create mode 100644 plugins/samplesink/filesink/filesinkgui.h create mode 100644 plugins/samplesink/filesink/filesinkgui.ui create mode 100644 plugins/samplesink/filesink/filesinkoutput.cpp create mode 100644 plugins/samplesink/filesink/filesinkoutput.h create mode 100644 plugins/samplesink/filesink/filesinkplugin.cpp create mode 100644 plugins/samplesink/filesink/filesinkplugin.h create mode 100644 plugins/samplesink/filesink/filesinkthread.cpp create mode 100644 plugins/samplesink/filesink/filesinkthread.h 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 +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+
+ + + + +
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