diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index 032b0b2eb..5e1ec48eb 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -43,3 +43,4 @@ if(LIBUSB_FOUND AND LIBHACKRF_FOUND) endif(LIBUSB_FOUND AND LIBHACKRF_FOUND) add_subdirectory(filesource) +add_subdirectory(sdrdaemon) diff --git a/plugins/samplesource/sdrdaemon/CMakeLists.txt b/plugins/samplesource/sdrdaemon/CMakeLists.txt new file mode 100644 index 000000000..f693b6adb --- /dev/null +++ b/plugins/samplesource/sdrdaemon/CMakeLists.txt @@ -0,0 +1,49 @@ +project(sdrdaemon) + +set(sdrdaemon_SOURCES + sdrdaemongui.cpp + sdrdaemoninput.cpp + sdrdaemonplugin.cpp + sdrdaemonthread.cpp +) + +set(sdrdaemon_HEADERS + sdrdaemongui.h + sdrdaemoninput.h + sdrdaemonplugin.h + sdrdaemonthread.h +) + +set(sdrdaemon_FORMS + sdrdaemongui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include-gpl + ${LIBRTLSDR_INCLUDE_DIR} +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt4_wrap_cpp(sdrdaemon_HEADERS_MOC ${sdrdaemon_HEADERS}) +qt5_wrap_ui(sdrdaemon_FORMS_HEADERS ${sdrdaemon_FORMS}) + +add_library(inputsdrdaemon SHARED + ${sdrdaemon_SOURCES} + ${sdrdaemon_HEADERS_MOC} + ${sdrdaemon_FORMS_HEADERS} +) + +target_link_libraries(inputsdrdaemon + ${QT_LIBRARIES} + ${LIBUSB_LIBRARIES} + sdrbase +) + +qt5_use_modules(inputsdrdaemon Core Widgets OpenGL Multimedia) diff --git a/plugins/samplesource/sdrdaemon/sdrdaemongui.cpp b/plugins/samplesource/sdrdaemon/sdrdaemongui.cpp new file mode 100644 index 000000000..b8dd917e3 --- /dev/null +++ b/plugins/samplesource/sdrdaemon/sdrdaemongui.cpp @@ -0,0 +1,241 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 "ui_sdrdaemongui.h" +#include "plugin/pluginapi.h" +#include "gui/colormapper.h" +#include "dsp/dspengine.h" +#include "mainwindow.h" + +#include "sdrdaemongui.h" + +SDRdaemonGui::SDRdaemonGui(PluginAPI* pluginAPI, QWidget* parent) : + QWidget(parent), + ui(new Ui::SDRdaemonGui), + m_pluginAPI(pluginAPI), + m_settings(), + m_sampleSource(NULL), + m_acquisition(false), + m_fileName("..."), + m_sampleRate(0), + m_centerFrequency(0), + m_startingTimeStamp(0), + m_samplesCount(0), + m_tickCount(0) +{ + ui->setupUi(this); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::ReverseGold)); + ui->centerFrequency->setValueRange(7, 0, pow(10,7)); + ui->fileNameText->setText(m_fileName); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + connect(&(m_pluginAPI->getMainWindow()->getMasterTimer()), SIGNAL(timeout()), this, SLOT(tick())); + displaySettings(); + + m_sampleSource = new SDRdaemonInput(m_pluginAPI->getMainWindow()->getMasterTimer()); + connect(m_sampleSource->getOutputMessageQueueToGUI(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + DSPEngine::instance()->setSource(m_sampleSource); +} + +SDRdaemonGui::~SDRdaemonGui() +{ + delete ui; +} + +void SDRdaemonGui::destroy() +{ + delete this; +} + +void SDRdaemonGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString SDRdaemonGui::getName() const +{ + return objectName(); +} + +void SDRdaemonGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 SDRdaemonGui::getCenterFrequency() const +{ + return m_centerFrequency; +} + +void SDRdaemonGui::setCenterFrequency(qint64 centerFrequency) +{ + m_centerFrequency = centerFrequency; + displaySettings(); + sendSettings(); +} + +QByteArray SDRdaemonGui::serialize() const +{ + return m_settings.serialize(); +} + +bool SDRdaemonGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool SDRdaemonGui::handleMessage(const Message& message) +{ + if (SDRdaemonInput::MsgReportSDRdaemonAcquisition::match(message)) + { + m_acquisition = ((SDRdaemonInput::MsgReportSDRdaemonAcquisition&)message).getAcquisition(); + updateWithAcquisition(); + return true; + } + else if (SDRdaemonInput::MsgReportSDRdaemonStreamData::match(message)) + { + m_sampleRate = ((SDRdaemonInput::MsgReportSDRdaemonStreamData&)message).getSampleRate(); + m_centerFrequency = ((SDRdaemonInput::MsgReportSDRdaemonStreamData&)message).getCenterFrequency(); + m_startingTimeStamp = ((SDRdaemonInput::MsgReportSDRdaemonStreamData&)message).getStartingTimeStamp(); + updateWithStreamData(); + return true; + } + else if (SDRdaemonInput::MsgReportSDRdaemonStreamTiming::match(message)) + { + m_samplesCount = ((SDRdaemonInput::MsgReportSDRdaemonStreamTiming&)message).getSamplesCount(); + updateWithStreamTime(); + return true; + } + else + { + return false; + } +} + +void SDRdaemonGui::handleSourceMessages() +{ + Message* message; + + while ((message = m_sampleSource->getOutputMessageQueueToGUI()->pop()) != 0) + { + qDebug("SDRdaemonGui::handleSourceMessages: message: %s", message->getIdentifier()); + + if (handleMessage(*message)) + { + delete message; + } + } +} + +void SDRdaemonGui::displaySettings() +{ +} + +void SDRdaemonGui::sendSettings() +{ +} + +void SDRdaemonGui::updateHardware() +{ +} + +void SDRdaemonGui::on_play_toggled(bool checked) +{ + SDRdaemonInput::MsgConfigureSDRdaemonWork* message = SDRdaemonInput::MsgConfigureSDRdaemonWork::create(checked); + m_sampleSource->getInputMessageQueue()->push(message); +} + +void SDRdaemonGui::on_showFileDialog_clicked(bool checked) +{ + QString fileName = QFileDialog::getOpenFileName(this, + tr("Open I/Q record file"), ".", tr("SDR I/Q Files (*.sdriq)")); + + if (fileName != "") + { + m_fileName = fileName; + ui->fileNameText->setText(m_fileName); + configureFileName(); + } +} + +void SDRdaemonGui::configureFileName() +{ + qDebug() << "SDRdaemonGui::configureFileName: " << m_fileName.toStdString().c_str(); + SDRdaemonInput::MsgConfigureSDRdaemonName* message = SDRdaemonInput::MsgConfigureSDRdaemonName::create(m_fileName); + m_sampleSource->getInputMessageQueue()->push(message); +} + +void SDRdaemonGui::updateWithAcquisition() +{ + ui->play->setEnabled(m_acquisition); + ui->play->setChecked(m_acquisition); + ui->showFileDialog->setEnabled(!m_acquisition); +} + +void SDRdaemonGui::updateWithStreamData() +{ + ui->centerFrequency->setValue(m_centerFrequency/1000); + QString s = QString::number(m_sampleRate/1000.0, 'f', 0); + ui->sampleRateText->setText(tr("%1k").arg(s)); + ui->play->setEnabled(m_acquisition); + updateWithStreamTime(); // TODO: remove when time data is implemented +} + +void SDRdaemonGui::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_time = t.toString("hh:mm:ss.zzz"); + ui->relTimeText->setText(s_time); + + quint64 startingTimeStampMsec = m_startingTimeStamp * 1000; + QDateTime dt = QDateTime::fromMSecsSinceEpoch(startingTimeStampMsec); + dt = dt.addSecs(t_sec); + dt = dt.addMSecs(t_msec); + QString s_date = dt.toString("yyyyMMdd hh.mm.ss.zzz"); + ui->absTimeText->setText(s_date); +} + +void SDRdaemonGui::tick() +{ + if ((++m_tickCount & 0xf) == 0) { + SDRdaemonInput::MsgConfigureSDRdaemonStreamTiming* message = SDRdaemonInput::MsgConfigureSDRdaemonStreamTiming::create(); + m_sampleSource->getInputMessageQueue()->push(message); + } +} diff --git a/plugins/samplesource/sdrdaemon/sdrdaemongui.h b/plugins/samplesource/sdrdaemon/sdrdaemongui.h new file mode 100644 index 000000000..b9216f9b0 --- /dev/null +++ b/plugins/samplesource/sdrdaemon/sdrdaemongui.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_SDRDAEMONGUI_H +#define INCLUDE_SDRDAEMONGUI_H + +#include +#include "plugin/plugingui.h" + +#include "sdrdaemoninput.h" + +class PluginAPI; + +namespace Ui { + class SDRdaemonGui; +} + +class SDRdaemonGui : public QWidget, public PluginGUI { + Q_OBJECT + +public: + explicit SDRdaemonGui(PluginAPI* pluginAPI, QWidget* parent = NULL); + virtual ~SDRdaemonGui(); + 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::SDRdaemonGui* ui; + + PluginAPI* m_pluginAPI; + SDRdaemonInput::Settings m_settings; + QTimer m_updateTimer; + std::vector m_gains; + SampleSource* m_sampleSource; + bool m_acquisition; + QString m_fileName; + int m_sampleRate; + quint64 m_centerFrequency; + std::time_t m_startingTimeStamp; + int m_samplesCount; + std::size_t m_tickCount; + + void displaySettings(); + void displayTime(); + void sendSettings(); + void updateHardware(); + void configureFileName(); + void updateWithAcquisition(); + void updateWithStreamData(); + void updateWithStreamTime(); + +private slots: + void handleSourceMessages(); + void on_playLoop_toggled(bool checked); + void on_play_toggled(bool checked); + void on_showFileDialog_clicked(bool checked); + void tick(); +}; + +#endif // INCLUDE_SDRDAEMONGUI_H diff --git a/plugins/samplesource/sdrdaemon/sdrdaemongui.ui b/plugins/samplesource/sdrdaemon/sdrdaemongui.ui new file mode 100644 index 000000000..9dcf1e942 --- /dev/null +++ b/plugins/samplesource/sdrdaemon/sdrdaemongui.ui @@ -0,0 +1,346 @@ + + + SDRdaemonGui + + + + 0 + 0 + 198 + 133 + + + + + 0 + 0 + + + + + Sans Serif + 9 + + + + BladeRF + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + 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 + + + + + + + + + + 8 + + + + Record sample rate + + + 0k + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + false + + + Record absolute time + + + 20150101 00:00:00.000 + + + + + + + + + Qt::Horizontal + + + + + + + + + Play in a loop + + + + + + + :/playloop.png:/playloop.png + + + + 16 + 16 + + + + true + + + + + + + Stopped / Play / Pause + + + + + + + :/play.png + :/pause.png + :/stop.png + :/stop.png + :/play.png + :/pause.png + :/play.png + :/pause.png:/play.png + + + + 16 + 16 + + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + + + + false + + + Record time from start + + + 00:00:00.000 + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+ + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+
+ + + + +
diff --git a/plugins/samplesource/sdrdaemon/sdrdaemoninput.cpp b/plugins/samplesource/sdrdaemon/sdrdaemoninput.cpp new file mode 100644 index 000000000..896972e64 --- /dev/null +++ b/plugins/samplesource/sdrdaemon/sdrdaemoninput.cpp @@ -0,0 +1,290 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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/filesink.h" + +#include "sdrdaemongui.h" +#include "sdrdaemoninput.h" +#include "sdrdaemonthread.h" + +MESSAGE_CLASS_DEFINITION(SDRdaemonInput::MsgConfigureSDRdaemon, Message) +MESSAGE_CLASS_DEFINITION(SDRdaemonInput::MsgConfigureSDRdaemonName, Message) +MESSAGE_CLASS_DEFINITION(SDRdaemonInput::MsgConfigureSDRdaemonWork, Message) +MESSAGE_CLASS_DEFINITION(SDRdaemonInput::MsgConfigureSDRdaemonStreamTiming, Message) +MESSAGE_CLASS_DEFINITION(SDRdaemonInput::MsgReportSDRdaemonAcquisition, Message) +MESSAGE_CLASS_DEFINITION(SDRdaemonInput::MsgReportSDRdaemonStreamData, Message) +MESSAGE_CLASS_DEFINITION(SDRdaemonInput::MsgReportSDRdaemonStreamTiming, Message) + +SDRdaemonInput::Settings::Settings() : + m_fileName("./test.sdriq") +{ +} + +void SDRdaemonInput::Settings::resetToDefaults() +{ + m_fileName = "./test.sdriq"; +} + +QByteArray SDRdaemonInput::Settings::serialize() const +{ + SimpleSerializer s(1); + s.writeString(1, m_fileName); + return s.final(); +} + +bool SDRdaemonInput::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; + } +} + +SDRdaemonInput::SDRdaemonInput(const QTimer& masterTimer) : + m_settings(), + m_SDRdaemonThread(NULL), + m_deviceDescription(), + m_fileName("..."), + m_sampleRate(0), + m_centerFrequency(0), + m_startingTimeStamp(0), + m_masterTimer(masterTimer) +{ +} + +SDRdaemonInput::~SDRdaemonInput() +{ + stop(); +} + +void SDRdaemonInput::openFileStream() +{ + qDebug() << "SDRdaemonInput::openFileStream: " << m_fileName.toStdString().c_str(); + + //stopInput(); + + if (m_ifstream.is_open()) { + m_ifstream.close(); + } + + m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary); + FileSink::Header header; + FileSink::readHeader(m_ifstream, header); + + m_sampleRate = header.sampleRate; + m_centerFrequency = header.centerFrequency; + m_startingTimeStamp = header.startTimeStamp; + + MsgReportSDRdaemonStreamData *report = MsgReportSDRdaemonStreamData::create(m_sampleRate, m_centerFrequency, m_startingTimeStamp); // file stream data + getOutputMessageQueueToGUI()->push(report); +} + +bool SDRdaemonInput::init(const Message& message) +{ + return false; +} + +bool SDRdaemonInput::start(int device) +{ + QMutexLocker mutexLocker(&m_mutex); + qDebug() << "SDRdaemonInput::startInput"; + + if (m_ifstream.tellg() != 0) { + m_ifstream.clear(); + m_ifstream.seekg(0, std::ios::beg); + } + + if(!m_sampleFifo.setSize(96000 * 4)) { + qCritical("Could not allocate SampleFifo"); + return false; + } + + //openFileStream(); + + if((m_SDRdaemonThread = new SDRdaemonThread(&m_ifstream, &m_sampleFifo)) == NULL) { + qFatal("out of memory"); + stop(); + return false; + } + + m_SDRdaemonThread->setSamplerate(m_sampleRate); + m_SDRdaemonThread->connectTimer(m_masterTimer); + m_SDRdaemonThread->startWork(); + m_deviceDescription = "SDRdaemon"; + + mutexLocker.unlock(); + //applySettings(m_generalSettings, m_settings, true); + qDebug("SDRdaemonInput::startInput: started"); + + MsgReportSDRdaemonAcquisition *report = MsgReportSDRdaemonAcquisition::create(true); // acquisition on + getOutputMessageQueueToGUI()->push(report); + + return true; +} + +void SDRdaemonInput::stop() +{ + qDebug() << "SDRdaemonInput::stop"; + QMutexLocker mutexLocker(&m_mutex); + + if(m_SDRdaemonThread != 0) + { + m_SDRdaemonThread->stopWork(); + delete m_SDRdaemonThread; + m_SDRdaemonThread = 0; + } + + m_deviceDescription.clear(); + + MsgReportSDRdaemonAcquisition *report = MsgReportSDRdaemonAcquisition::create(false); // acquisition off + getOutputMessageQueueToGUI()->push(report); +} + +const QString& SDRdaemonInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int SDRdaemonInput::getSampleRate() const +{ + return m_sampleRate; +} + +quint64 SDRdaemonInput::getCenterFrequency() const +{ + return m_centerFrequency; +} + +std::time_t SDRdaemonInput::getStartingTimeStamp() const +{ + return m_startingTimeStamp; +} + +bool SDRdaemonInput::handleMessage(const Message& message) +{ + if (MsgConfigureSDRdaemonName::match(message)) + { + MsgConfigureSDRdaemonName& conf = (MsgConfigureSDRdaemonName&) message; + m_fileName = conf.getFileName(); + openFileStream(); + return true; + } + else if (MsgConfigureSDRdaemonWork::match(message)) + { + MsgConfigureSDRdaemonWork& conf = (MsgConfigureSDRdaemonWork&) message; + bool working = conf.isWorking(); + + if (m_SDRdaemonThread != 0) + { + if (working) + { + m_SDRdaemonThread->startWork(); + MsgReportSDRdaemonStreamTiming *report = + MsgReportSDRdaemonStreamTiming::create(m_SDRdaemonThread->getSamplesCount()); + getOutputMessageQueueToGUI()->push(report); + } + else + { + m_SDRdaemonThread->stopWork(); + } + } + + return true; + } + else if (MsgConfigureSDRdaemonStreamTiming::match(message)) + { + MsgReportSDRdaemonStreamTiming *report; + + if (m_SDRdaemonThread != 0) + { + report = MsgReportSDRdaemonStreamTiming::create(m_SDRdaemonThread->getSamplesCount()); + getOutputMessageQueueToGUI()->push(report); + } + + return true; + } + else + { + return false; + } +} + +bool SDRdaemonInput::applySettings(const Settings& settings, bool force) +{ + QMutexLocker mutexLocker(&m_mutex); + bool wasRunning = false; + + if((m_settings.m_fileName != settings.m_fileName) || force) + { + m_settings.m_fileName = settings.m_fileName; + + if (m_SDRdaemonThread != 0) + { + wasRunning = m_SDRdaemonThread->isRunning(); + + if (wasRunning) + { + m_SDRdaemonThread->stopWork(); + } + } + + if (m_ifstream.is_open()) + { + m_ifstream.close(); + } + + openFileStream(); + + if (m_SDRdaemonThread != 0) + { + m_SDRdaemonThread->setSamplerate(m_sampleRate); + + if (wasRunning) + { + m_SDRdaemonThread->startWork(); + } + } + + DSPSignalNotification *notif = new DSPSignalNotification(m_sampleRate, m_centerFrequency); + DSPEngine::instance()->getInputMessageQueue()->push(notif); + + qDebug() << "SDRdaemonInput::applySettings:" + << " file name: " << settings.m_fileName.toStdString().c_str() + << " center freq: " << m_centerFrequency << " Hz" + << " sample rate: " << m_sampleRate + << " Unix timestamp: " << m_startingTimeStamp; + } + + return true; +} diff --git a/plugins/samplesource/sdrdaemon/sdrdaemoninput.h b/plugins/samplesource/sdrdaemon/sdrdaemoninput.h new file mode 100644 index 000000000..e0c180b20 --- /dev/null +++ b/plugins/samplesource/sdrdaemon/sdrdaemoninput.h @@ -0,0 +1,213 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_SDRDAEMONINPUT_H +#define INCLUDE_SDRDAEMONINPUT_H + +#include "dsp/samplesource.h" +#include +#include +#include +#include +#include + +class SDRdaemonThread; + +class SDRdaemonInput : public SampleSource { +public: + struct Settings { + QString m_fileName; + + Settings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + }; + + class MsgConfigureSDRdaemon : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const Settings& getSettings() const { return m_settings; } + + static MsgConfigureSDRdaemon* create(const Settings& settings) + { + return new MsgConfigureSDRdaemon(settings); + } + + private: + Settings m_settings; + + MsgConfigureSDRdaemon(const Settings& settings) : + Message(), + m_settings(settings) + { } + }; + + class MsgConfigureSDRdaemonName : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const QString& getFileName() const { return m_fileName; } + + static MsgConfigureSDRdaemonName* create(const QString& fileName) + { + return new MsgConfigureSDRdaemonName(fileName); + } + + private: + QString m_fileName; + + MsgConfigureSDRdaemonName(const QString& fileName) : + Message(), + m_fileName(fileName) + { } + }; + + class MsgConfigureSDRdaemonWork : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool isWorking() const { return m_working; } + + static MsgConfigureSDRdaemonWork* create(bool working) + { + return new MsgConfigureSDRdaemonWork(working); + } + + private: + bool m_working; + + MsgConfigureSDRdaemonWork(bool working) : + Message(), + m_working(working) + { } + }; + + class MsgConfigureSDRdaemonStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + + static MsgConfigureSDRdaemonStreamTiming* create() + { + return new MsgConfigureSDRdaemonStreamTiming(); + } + + private: + + MsgConfigureSDRdaemonStreamTiming() : + Message() + { } + }; + + class MsgReportSDRdaemonAcquisition : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getAcquisition() const { return m_acquisition; } + + static MsgReportSDRdaemonAcquisition* create(bool acquisition) + { + return new MsgReportSDRdaemonAcquisition(acquisition); + } + + protected: + bool m_acquisition; + + MsgReportSDRdaemonAcquisition(bool acquisition) : + Message(), + m_acquisition(acquisition) + { } + }; + + class MsgReportSDRdaemonStreamData : 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 MsgReportSDRdaemonStreamData* create(int sampleRate, quint64 centerFrequency, std::time_t startingTimeStamp) + { + return new MsgReportSDRdaemonStreamData(sampleRate, centerFrequency, startingTimeStamp); + } + + protected: + int m_sampleRate; + quint64 m_centerFrequency; + std::time_t m_startingTimeStamp; + + MsgReportSDRdaemonStreamData(int sampleRate, quint64 centerFrequency, std::time_t startingTimeStamp) : + Message(), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency), + m_startingTimeStamp(startingTimeStamp) + { } + }; + + class MsgReportSDRdaemonStreamTiming : public Message { + MESSAGE_CLASS_DECLARATION + + public: + std::size_t getSamplesCount() const { return m_samplesCount; } + + static MsgReportSDRdaemonStreamTiming* create(std::size_t samplesCount) + { + return new MsgReportSDRdaemonStreamTiming(samplesCount); + } + + protected: + std::size_t m_samplesCount; + + MsgReportSDRdaemonStreamTiming(std::size_t samplesCount) : + Message(), + m_samplesCount(samplesCount) + { } + }; + + SDRdaemonInput(const QTimer& masterTimer); + virtual ~SDRdaemonInput(); + + 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::ifstream m_ifstream; + SDRdaemonThread* m_SDRdaemonThread; + QString m_deviceDescription; + QString m_fileName; + int m_sampleRate; + quint64 m_centerFrequency; + std::time_t m_startingTimeStamp; + const QTimer& m_masterTimer; + + bool applySettings(const Settings& settings, bool force); + void openFileStream(); +}; + +#endif // INCLUDE_SDRDAEMONINPUT_H diff --git a/plugins/samplesource/sdrdaemon/sdrdaemonplugin.cpp b/plugins/samplesource/sdrdaemon/sdrdaemonplugin.cpp new file mode 100644 index 000000000..5bdf64780 --- /dev/null +++ b/plugins/samplesource/sdrdaemon/sdrdaemonplugin.cpp @@ -0,0 +1,82 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 "sdrdaemongui.h" +#include "sdrdaemonplugin.h" + +const PluginDescriptor SDRdaemonPlugin::m_pluginDescriptor = { + QString("File source input"), + QString("---"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString SDRdaemonPlugin::m_deviceTypeID = SDRDAEMON_DEVICE_TYPE_ID; + +SDRdaemonPlugin::SDRdaemonPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& SDRdaemonPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void SDRdaemonPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + m_pluginAPI->registerSampleSource(m_deviceTypeID, this); +} + +PluginInterface::SampleSourceDevices SDRdaemonPlugin::enumSampleSources() +{ + SampleSourceDevices result; + int count = 1; + + for(int i = 0; i < count; i++) + { + QString displayedName(QString("SDRdaemon[%1]").arg(i)); + + result.append(SampleSourceDevice(displayedName, + m_deviceTypeID, + QString::null, + i)); + } + + return result; +} + +PluginGUI* SDRdaemonPlugin::createSampleSourcePluginGUI(const QString& sourceId) +{ + if(sourceId == m_deviceTypeID) + { + SDRdaemonGui* gui = new SDRdaemonGui(m_pluginAPI); + m_pluginAPI->setInputGUI(gui); + return gui; + } + else + { + return NULL; + } +} diff --git a/plugins/samplesource/sdrdaemon/sdrdaemonplugin.h b/plugins/samplesource/sdrdaemon/sdrdaemonplugin.h new file mode 100644 index 000000000..3dd77313f --- /dev/null +++ b/plugins/samplesource/sdrdaemon/sdrdaemonplugin.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_SDRDAEMONPLUGIN_H +#define INCLUDE_SDRDAEMONPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +#define SDRDAEMON_DEVICE_TYPE_ID "sdrangel.samplesource.sdrdaemon" + +class SDRdaemonPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID SDRDAEMON_DEVICE_TYPE_ID) + +public: + explicit SDRdaemonPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual SampleSourceDevices enumSampleSources(); + virtual PluginGUI* createSampleSourcePluginGUI(const QString& sourceId); + + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_SDRDAEMONPLUGIN_H diff --git a/plugins/samplesource/sdrdaemon/sdrdaemonthread.cpp b/plugins/samplesource/sdrdaemon/sdrdaemonthread.cpp new file mode 100644 index 000000000..4a261a2f9 --- /dev/null +++ b/plugins/samplesource/sdrdaemon/sdrdaemonthread.cpp @@ -0,0 +1,153 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 "dsp/samplefifo.h" +#include + +#include "sdrdaemonthread.h" + +const int SDRdaemonThread::m_rateDivider = 1000/SDRDAEMON_THROTTLE_MS; + +SDRdaemonThread::SDRdaemonThread(std::ifstream *samplesStream, SampleFifo* sampleFifo, QObject* parent) : + QThread(parent), + m_running(false), + m_ifstream(samplesStream), + m_buf(0), + m_bufsize(0), + m_chunksize(0), + m_sampleFifo(sampleFifo), + m_samplesCount(0), + m_samplerate(0) +{ + assert(m_ifstream != 0); +} + +SDRdaemonThread::~SDRdaemonThread() +{ + if (m_running) { + stopWork(); + } + + if (m_buf != 0) { + free(m_buf); + } +} + +void SDRdaemonThread::startWork() +{ + qDebug() << "SDRdaemonThread::startWork: "; + + if (m_ifstream->is_open()) + { + qDebug() << " - file stream open, starting..."; + m_startWaitMutex.lock(); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); + } + else + { + qDebug() << " - file stream closed, not starting."; + } +} + +void SDRdaemonThread::stopWork() +{ + qDebug() << "SDRdaemonThread::stopWork"; + m_running = false; + wait(); +} + +void SDRdaemonThread::setSamplerate(int samplerate) +{ + qDebug() << "SDRdaemonThread::setSamplerate:" + << " new:" << samplerate + << " old:" << m_samplerate; + + if (samplerate != m_samplerate) + { + if (m_running) { + stopWork(); + } + + m_samplerate = samplerate; + m_chunksize = (m_samplerate / m_rateDivider)*4; // TODO: implement FF and slow motion here. 4 corresponds to live. 2 is half speed, 8 is doulbe speed + m_bufsize = m_chunksize; + + if (m_buf == 0) { + qDebug() << " - Allocate buffer"; + m_buf = (quint8*) malloc(m_bufsize); + } else { + qDebug() << " - Re-allocate buffer"; + m_buf = (quint8*) realloc((void*) m_buf, m_bufsize); + } + + qDebug() << " - size: " << m_bufsize + << " #samples: " << (m_bufsize/4); + } + + //m_samplerate = samplerate; +} + +void SDRdaemonThread::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 SDRdaemonThread::connectTimer(const QTimer& timer) +{ + qDebug() << "SDRdaemonThread::connectTimer"; + connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); +} + +void SDRdaemonThread::tick() +{ + if (m_running) + { + // read samples directly feeding the SampleFifo (no callback) + m_ifstream->read(reinterpret_cast(m_buf), m_chunksize); + + if (m_ifstream->eof()) + { + m_sampleFifo->write(m_buf, m_ifstream->gcount()); + // TODO: handle loop playback situation + m_ifstream->clear(); + m_ifstream->seekg(0, std::ios::beg); + m_samplesCount = 0; + //stopWork(); + //m_ifstream->close(); + } + else + { + m_sampleFifo->write(m_buf, m_chunksize); + m_samplesCount += m_chunksize / 4; + } + } +} diff --git a/plugins/samplesource/sdrdaemon/sdrdaemonthread.h b/plugins/samplesource/sdrdaemon/sdrdaemonthread.h new file mode 100644 index 000000000..9d3ff1d3a --- /dev/null +++ b/plugins/samplesource/sdrdaemon/sdrdaemonthread.h @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_SDRDAEMONTHREAD_H +#define INCLUDE_SDRDAEMONTHREAD_H + +#include +#include +#include +#include +#include +#include +#include +#include "dsp/samplefifo.h" +#include "dsp/inthalfbandfilter.h" + +#define SDRDAEMON_THROTTLE_MS 50 + +class SDRdaemonThread : public QThread { + Q_OBJECT + +public: + SDRdaemonThread(std::ifstream *samplesStream, SampleFifo* sampleFifo, QObject* parent = NULL); + ~SDRdaemonThread(); + + void startWork(); + void stopWork(); + void setSamplerate(int samplerate); + bool isRunning() const { return m_running; } + std::size_t getSamplesCount() const { return m_samplesCount; } + + void connectTimer(const QTimer& timer); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + std::ifstream* m_ifstream; + quint8 *m_buf; + std::size_t m_bufsize; + std::size_t m_chunksize; + SampleFifo* m_sampleFifo; + std::size_t m_samplesCount; + + int m_samplerate; + static const int m_rateDivider; + + void run(); +private slots: + void tick(); +}; + +#endif // INCLUDE_SDRDAEMONTHREAD_H