From 3dba9a0ea97d1c894708a132b521dceb3cd4ba75 Mon Sep 17 00:00:00 2001 From: f4exb Date: Tue, 28 Jul 2015 23:54:17 +0200 Subject: [PATCH] Implemented minimalist recording --- CMakeLists.txt | 2 + Readme.md | 2 +- include-gpl/dsp/filesink.h | 79 ++++++++++++ include-gpl/mainwindow.h | 6 + plugins/samplesource/bladerf/bladerfgui.cpp | 2 +- sdrbase/dsp/filesink.cpp | 126 ++++++++++++++++++++ sdrbase/mainwindow.cpp | 26 ++++ sdrbase/mainwindow.ui | 19 +++ 8 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 include-gpl/dsp/filesink.h create mode 100644 sdrbase/dsp/filesink.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 49860b5c1..28b3ad353 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ set(sdrbase_SOURCES sdrbase/dsp/fftengine.cpp sdrbase/dsp/fftfilt.cxx sdrbase/dsp/fftwindow.cpp + sdrbase/dsp/filesink.cpp sdrbase/dsp/interpolator.cpp sdrbase/dsp/inthalfbandfilter.cpp sdrbase/dsp/lowpass.cpp @@ -131,6 +132,7 @@ set(sdrbase_HEADERS include-gpl/dsp/fftfilt.h include-gpl/dsp/fftwengine.h include-gpl/dsp/fftwindow.h + include-gpl/dsp/filesink.h include-gpl/dsp/gfft.h include-gpl/dsp/interpolator.h include-gpl/dsp/inthalfbandfilter.h diff --git a/Readme.md b/Readme.md index dd340216a..108db09ad 100644 --- a/Readme.md +++ b/Readme.md @@ -127,6 +127,7 @@ Done since the fork - Spectrum histogram clear - Trigger line display for all trigger modes - Coarse and fine trigger level sliders + - Minimalist recording ===== To Do @@ -134,7 +135,6 @@ To Do - Level calibration - Enhance presets management (Edit, Move, Import/Export from/to human readable format like JSON) - - recording capability - Tx channels for Rx/Tx boards like BladeRF - Enhance WFM (stereo, RDS?) - Even more demods ... diff --git a/include-gpl/dsp/filesink.h b/include-gpl/dsp/filesink.h new file mode 100644 index 000000000..60f58a765 --- /dev/null +++ b/include-gpl/dsp/filesink.h @@ -0,0 +1,79 @@ +#ifndef INCLUDE_FILESINK_H +#define INCLUDE_FILESINK_H + +#include +#include +#include + +#include +#include "dsp/samplesink.h" +#include "util/export.h" +#include "util/message.h" + +class MessageQueue; + +class SDRANGELOVE_API FileSink : public SampleSink { +public: + + struct Header + { + int sampleRate; + quint64 centerFrequency; + std::time_t startTimeStamp; + }; + + FileSink(); + ~FileSink(); + + quint64 getByteCount() const { return m_byteCount; } + + void configure(MessageQueue* msgQueue, const std::string& filename, int sampleRate, quint64 centerFrequency); + + void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool positiveOnly); + void start(); + void stop(); + void startRecording(); + void stopRecording(); + bool handleMessage(Message* message); + static void readHeader(std::ifstream& samplefile, Header& header); + +private: + class MsgConfigureFileSink : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const std::string& getFileName() const { return m_fileName; } + int getSampleRate() const { return m_sampleRate; } + quint64 getCenterFrequency() const { return m_centerFrequency; } + + static MsgConfigureFileSink* create(const std::string& fileName, int sampleRate, quint64 centerFrequency) + { + return new MsgConfigureFileSink(fileName, sampleRate, centerFrequency); + } + + private: + std::string m_fileName; + int m_sampleRate; + quint64 m_centerFrequency; + + MsgConfigureFileSink(const std::string& fileName, int sampleRate, quint64 centerFrequency) : + Message(), + m_fileName(fileName), + m_sampleRate(sampleRate), + m_centerFrequency(centerFrequency) + { } + }; + + std::string m_fileName; + int m_sampleRate; + quint64 m_centerFrequency; + bool m_recordOn; + bool m_recordStart; + std::ofstream m_sampleFile; + quint64 m_byteCount; + + void handleConfigure(const std::string& fileName, int sampleRate, quint64 centerFrequency); + void writeHeader(); +}; + +#endif // INCLUDE_FILESINK_H diff --git a/include-gpl/mainwindow.h b/include-gpl/mainwindow.h index 835df7673..72fff0ec0 100644 --- a/include-gpl/mainwindow.h +++ b/include-gpl/mainwindow.h @@ -31,6 +31,7 @@ class AudioDeviceInfo; class DSPEngine; class Indicator; class SpectrumVis; +class FileSink; class SampleSource; class PluginAPI; class PluginGUI; @@ -76,6 +77,7 @@ private: Settings m_settings; SpectrumVis* m_spectrumVis; + FileSink *m_fileSink; DSPEngine* m_dspEngine; @@ -83,6 +85,7 @@ private: int m_lastEngineState; QLabel* m_sampleRateWidget; + Indicator* m_recording; Indicator* m_engineIdle; Indicator* m_engineRunning; Indicator* m_engineError; @@ -93,6 +96,7 @@ private: int m_sampleRate; quint64 m_centerFrequency; + std::string m_sampleFileName; PluginManager* m_pluginManager; @@ -114,6 +118,8 @@ private slots: void updateStatus(); void on_action_Start_triggered(); void on_action_Stop_triggered(); + void on_action_Start_Recording_triggered(); + void on_action_Stop_Recording_triggered(); void on_dcOffset_toggled(bool checked); void on_iqImbalance_toggled(bool checked); void on_action_View_Fullscreen_toggled(bool checked); diff --git a/plugins/samplesource/bladerf/bladerfgui.cpp b/plugins/samplesource/bladerf/bladerfgui.cpp index 2e0873896..0e8a18e61 100644 --- a/plugins/samplesource/bladerf/bladerfgui.cpp +++ b/plugins/samplesource/bladerf/bladerfgui.cpp @@ -140,7 +140,7 @@ void BladerfGui::displaySettings() ui->vga1Text->setText(tr("%1dB").arg(m_settings.m_vga1)); ui->vga1->setValue(m_settings.m_vga1); - ui->vga2Text->setText(tr("%1dBS").arg(m_settings.m_vga2)); + ui->vga2Text->setText(tr("%1dB").arg(m_settings.m_vga2)); ui->vga2->setValue(m_settings.m_vga2); ui->xb200->setCurrentIndex(getXb200Index(m_settings.m_xb200, m_settings.m_xb200Path, m_settings.m_xb200Filter)); diff --git a/sdrbase/dsp/filesink.cpp b/sdrbase/dsp/filesink.cpp new file mode 100644 index 000000000..cb3f0e8d9 --- /dev/null +++ b/sdrbase/dsp/filesink.cpp @@ -0,0 +1,126 @@ +#include "dsp/filesink.h" +#include "util/simpleserializer.h" +#include "util/messagequeue.h" + +#include + +MESSAGE_CLASS_DEFINITION(FileSink::MsgConfigureFileSink, Message) + +FileSink::FileSink() : + SampleSink(), + m_fileName(std::string("test.sdriq")), + m_sampleRate(0), + m_centerFrequency(0), + m_recordOn(false), + m_recordStart(false), + m_byteCount(0) +{ +} + +FileSink::~FileSink() +{ + stopRecording(); +} + +void FileSink::configure(MessageQueue* msgQueue, const std::string& filename, int sampleRate, quint64 centerFrequency) +{ + Message* cmd = MsgConfigureFileSink::create(filename, sampleRate, centerFrequency); + cmd->submit(msgQueue, this); +} + +void FileSink::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool positiveOnly) +{ + // if no recording is active, send the samples to /dev/null + if(!m_recordOn) + return; + + if (begin < end) // if there is something to put out + { + if (m_recordStart) + { + writeHeader(); + m_recordStart = false; + } + + m_sampleFile.write(reinterpret_cast(&*(begin)), (end - begin)*sizeof(Sample)); + m_byteCount += end - begin; + } +} + +void FileSink::start() +{ +} + +void FileSink::stop() +{ + stopRecording(); +} + +void FileSink::startRecording() +{ + if (!m_sampleFile.is_open()) + { + std::cerr << "FileSink::startRecording" << std::endl; + m_sampleFile.open(m_fileName.c_str(), std::ios::binary); + m_recordOn = true; + m_recordStart = true; + m_byteCount = 0; + } +} + +void FileSink::stopRecording() +{ + if (m_sampleFile.is_open()) + { + std::cerr << "FileSink::stopRecording" << std::endl; + m_sampleFile.close(); + m_recordOn = false; + m_recordStart = false; + } +} + +bool FileSink::handleMessage(Message* message) +{ + if (MsgConfigureFileSink::match(message)) + { + MsgConfigureFileSink* conf = (MsgConfigureFileSink*) message; + handleConfigure(conf->getFileName(), conf->getSampleRate(), conf->getCenterFrequency()); + std::cerr << "FileSink::handleMessage:" + << " fileName: " << m_fileName + << " sampleRate: " << m_sampleRate + << " centerFrequency: " << m_centerFrequency << std::endl; + message->completed(); + return true; + } + else + { + return false; + } +} + +void FileSink::handleConfigure(const std::string& fileName, int sampleRate, quint64 centerFrequency) +{ + if ((fileName != m_fileName) || (m_sampleRate != sampleRate) || (m_centerFrequency != centerFrequency)) + { + stopRecording(); + } + + m_fileName = fileName; + m_sampleRate = sampleRate; + m_centerFrequency = centerFrequency; +} + +void FileSink::writeHeader() +{ + m_sampleFile.write((const char *) &m_sampleRate, sizeof(int)); + m_sampleFile.write((const char *) &m_centerFrequency, sizeof(quint64)); + std::time_t ts = time(0); + m_sampleFile.write((const char *) &ts, sizeof(std::time_t)); +} + +void FileSink::readHeader(std::ifstream& sampleFile, Header& header) +{ + sampleFile.read((char *) &(header.sampleRate), sizeof(int)); + sampleFile.read((char *) &(header.centerFrequency), sizeof(quint64)); + sampleFile.read((char *) &(header.startTimeStamp), sizeof(std::time_t)); +} diff --git a/sdrbase/mainwindow.cpp b/sdrbase/mainwindow.cpp index e7f31f0f1..bc87890c4 100644 --- a/sdrbase/mainwindow.cpp +++ b/sdrbase/mainwindow.cpp @@ -30,11 +30,13 @@ #include "gui/rollupwidget.h" #include "dsp/dspengine.h" #include "dsp/spectrumvis.h" +#include "dsp/filesink.h" #include "dsp/dspcommands.h" #include "plugin/plugingui.h" #include "plugin/pluginapi.h" #include "plugin/plugingui.h" +#include #include MainWindow::MainWindow(QWidget* parent) : @@ -49,6 +51,7 @@ MainWindow::MainWindow(QWidget* parent) : m_inputGUI(0), m_sampleRate(0), m_centerFrequency(0), + m_sampleFileName(std::string("./test.sdriq")), m_pluginManager(new PluginManager(this, m_dspEngine)) { ui->setupUi(this); @@ -94,6 +97,9 @@ MainWindow::MainWindow(QWidget* parent) : m_spectrumVis = new SpectrumVis(ui->glSpectrum); m_dspEngine->addSink(m_spectrumVis); + m_fileSink = new FileSink(); + m_dspEngine->addSink(m_fileSink); + ui->glSpectrumGUI->setBuddies(m_dspEngine->getMessageQueue(), m_spectrumVis, ui->glSpectrum); loadSettings(); @@ -119,7 +125,9 @@ MainWindow::~MainWindow() m_pluginManager->freeAll(); + m_dspEngine->removeSink(m_fileSink); m_dspEngine->removeSink(m_spectrumVis); + delete m_fileSink; delete m_spectrumVis; delete m_pluginManager; @@ -221,6 +229,10 @@ void MainWindow::createStatusBar() m_sampleRateWidget->setToolTip(tr("Sample Rate")); statusBar()->addPermanentWidget(m_sampleRateWidget); + m_recording = new Indicator(tr("Rec"), this); + m_recording->setToolTip(tr("Recording")); + statusBar()->addPermanentWidget(m_recording); + m_engineIdle = new Indicator(tr("Idle"), this); m_engineIdle->setToolTip(tr("DSP engine is idle")); statusBar()->addPermanentWidget(m_engineIdle); @@ -309,6 +321,8 @@ void MainWindow::handleMessages() updateCenterFreqDisplay(); updateSampleRate(); message->completed(); + std::cerr << "MainWindow::handleMessages: m_fileSink->configure" << std::endl; + m_fileSink->configure(m_dspEngine->getMessageQueue(), m_sampleFileName, m_sampleRate, m_centerFrequency); } else { if(!m_pluginManager->handleMessage(message)) message->completed(); @@ -363,6 +377,18 @@ void MainWindow::on_action_Stop_triggered() m_dspEngine->stopAcquistion(); } +void MainWindow::on_action_Start_Recording_triggered() +{ + m_recording->setColor(Qt::red); + m_fileSink->startRecording(); +} + +void MainWindow::on_action_Stop_Recording_triggered() +{ + m_recording->setColor(Qt::gray); + m_fileSink->stopRecording(); +} + void MainWindow::on_dcOffset_toggled(bool checked) { m_settings.getCurrent()->setDCOffsetCorrection(checked); diff --git a/sdrbase/mainwindow.ui b/sdrbase/mainwindow.ui index 0ad431798..95c09e382 100644 --- a/sdrbase/mainwindow.ui +++ b/sdrbase/mainwindow.ui @@ -75,6 +75,9 @@ + + + @@ -456,6 +459,22 @@ Loaded &Plugins... + + + Start Recording + + + F7 + + + + + Stop Recording + + + F8 + + presetDock channelDock