summaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
authorChristian Daniel <cd@maintech.de>2013-03-22 11:18:30 +0100
committerChristian Daniel <cd@maintech.de>2013-03-22 11:18:30 +0100
commiteca56e35be841396f6c57bab540a3e1503253d56 (patch)
tree4a0a29cd53a84339e56754c32bf67391c83494fe /plugins
parent2c8c930b39fe069b36d81caa00401d9ac182a9d8 (diff)
monster rework
- pluginify whole project - reorganize directory structure - fix PortAudio detection script - implement generic channelizer - fix several OpenGL problems - rework presets - add audio mixing - too many more
Diffstat (limited to 'plugins')
-rw-r--r--plugins/CMakeLists.txt4
-rw-r--r--plugins/demod/CMakeLists.txt4
-rw-r--r--plugins/demod/nfm/CMakeLists.txt44
-rw-r--r--plugins/demod/nfm/nfmdemod.cpp140
-rw-r--r--plugins/demod/nfm/nfmdemod.h102
-rw-r--r--plugins/demod/nfm/nfmdemodgui.cpp179
-rw-r--r--plugins/demod/nfm/nfmdemodgui.h64
-rw-r--r--plugins/demod/nfm/nfmdemodgui.ui211
-rw-r--r--plugins/demod/nfm/nfmplugin.cpp52
-rw-r--r--plugins/demod/nfm/nfmplugin.h28
-rw-r--r--plugins/demod/tetra/CMakeLists.txt44
-rw-r--r--plugins/demod/tetra/tetrademod.cpp106
-rw-r--r--plugins/demod/tetra/tetrademod.h67
-rw-r--r--plugins/demod/tetra/tetrademodgui.cpp110
-rw-r--r--plugins/demod/tetra/tetrademodgui.h56
-rw-r--r--plugins/demod/tetra/tetrademodgui.ui56
-rw-r--r--plugins/demod/tetra/tetraplugin.cpp52
-rw-r--r--plugins/demod/tetra/tetraplugin.h28
-rw-r--r--plugins/samplesource/CMakeLists.txt7
-rw-r--r--plugins/samplesource/osmosdr/CMakeLists.txt47
-rw-r--r--plugins/samplesource/osmosdr/osmosdrgui.cpp305
-rw-r--r--plugins/samplesource/osmosdr/osmosdrgui.h76
-rw-r--r--plugins/samplesource/osmosdr/osmosdrgui.ui773
-rw-r--r--plugins/samplesource/osmosdr/osmosdrinput.cpp479
-rw-r--r--plugins/samplesource/osmosdr/osmosdrinput.h99
-rw-r--r--plugins/samplesource/osmosdr/osmosdrplugin.cpp67
-rw-r--r--plugins/samplesource/osmosdr/osmosdrplugin.h26
-rw-r--r--plugins/samplesource/osmosdr/osmosdrthread.cpp116
-rw-r--r--plugins/samplesource/osmosdr/osmosdrthread.h66
-rw-r--r--plugins/samplesource/osmosdr/osmosdrupgrade.cpp518
-rw-r--r--plugins/samplesource/osmosdr/osmosdrupgrade.h60
-rw-r--r--plugins/samplesource/osmosdr/osmosdrupgrade.ui213
-rw-r--r--plugins/samplesource/rtlsdr/CMakeLists.txt47
-rw-r--r--plugins/samplesource/rtlsdr/rtlsdrgui.cpp149
-rw-r--r--plugins/samplesource/rtlsdr/rtlsdrgui.h51
-rw-r--r--plugins/samplesource/rtlsdr/rtlsdrgui.ui229
-rw-r--r--plugins/samplesource/rtlsdr/rtlsdrinput.cpp230
-rw-r--r--plugins/samplesource/rtlsdr/rtlsdrinput.h105
-rw-r--r--plugins/samplesource/rtlsdr/rtlsdrplugin.cpp67
-rw-r--r--plugins/samplesource/rtlsdr/rtlsdrplugin.h26
-rw-r--r--plugins/samplesource/rtlsdr/rtlsdrthread.cpp172
-rw-r--r--plugins/samplesource/rtlsdr/rtlsdrthread.h67
42 files changed, 5342 insertions, 0 deletions
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
new file mode 100644
index 0000000..fffde4b
--- /dev/null
+++ b/plugins/CMakeLists.txt
@@ -0,0 +1,4 @@
+project(plugins)
+
+add_subdirectory(demod)
+add_subdirectory(samplesource)
diff --git a/plugins/demod/CMakeLists.txt b/plugins/demod/CMakeLists.txt
new file mode 100644
index 0000000..78804e6
--- /dev/null
+++ b/plugins/demod/CMakeLists.txt
@@ -0,0 +1,4 @@
+project(demod)
+
+add_subdirectory(nfm)
+add_subdirectory(tetra)
diff --git a/plugins/demod/nfm/CMakeLists.txt b/plugins/demod/nfm/CMakeLists.txt
new file mode 100644
index 0000000..ee36a4d
--- /dev/null
+++ b/plugins/demod/nfm/CMakeLists.txt
@@ -0,0 +1,44 @@
+project(nfm)
+
+set(nfm_SOURCES
+ nfmdemod.cpp
+ nfmdemodgui.cpp
+ nfmplugin.cpp
+)
+
+set(nfm_HEADERS
+ nfmdemod.h
+ nfmdemodgui.h
+ nfmplugin.h
+)
+
+set(nfm_FORMS
+ nfmdemodgui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_SOURCE_DIR}/include-gpl
+ ${OPENGL_INCLUDE_DIR}
+)
+
+include(${QT_USE_FILE})
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+qt4_wrap_cpp(nfm_HEADERS_MOC ${nfm_HEADERS})
+qt4_wrap_ui(nfm_FORMS_HEADERS ${nfm_FORMS})
+
+add_library(demodnfm SHARED
+ ${nfm_SOURCES}
+ ${nfm_HEADERS_MOC}
+ ${nfm_FORMS_HEADERS}
+)
+
+target_link_libraries(demodnfm
+ ${QT_LIBRARIES}
+ ${OPENGL_LIBRARIES}
+)
diff --git a/plugins/demod/nfm/nfmdemod.cpp b/plugins/demod/nfm/nfmdemod.cpp
new file mode 100644
index 0000000..7324852
--- /dev/null
+++ b/plugins/demod/nfm/nfmdemod.cpp
@@ -0,0 +1,140 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include <QTime>
+#include <stdio.h>
+#include "nfmdemod.h"
+#include "audio/audiooutput.h"
+#include "dsp/dspcommands.h"
+
+MessageRegistrator NFMDemod::MsgConfigureNFMDemod::ID("MsgConfigureNFMDemod");
+
+NFMDemod::NFMDemod(AudioFifo* audioFifo, SampleSink* sampleSink) :
+ m_sampleSink(sampleSink),
+ m_audioFifo(audioFifo)
+{
+ m_rfBandwidth = 12500;
+ m_volume = 2.0;
+ m_squelchLevel = pow(10.0, -40.0 / 20.0);
+ m_sampleRate = 500000;
+ m_frequency = 0;
+ m_squelchLevel *= m_squelchLevel;
+
+ m_nco.setFreq(m_frequency, m_sampleRate);
+ m_interpolator.create(1, 32, 32 * m_sampleRate, 12500);
+ m_sampleDistanceRemain = (Real)m_sampleRate / 44100.0;
+
+ m_lowpass.create(21, 44100, 3000);
+
+ m_audioBuffer.resize(256);
+ m_audioBufferFill = 0;
+}
+
+NFMDemod::~NFMDemod()
+{
+}
+
+void NFMDemod::configure(MessageQueue* messageQueue, Real rfBandwidth, Real afBandwidth, Real volume, Real squelch)
+{
+ Message* cmd = MsgConfigureNFMDemod::create(rfBandwidth, afBandwidth, volume, squelch);
+ cmd->submit(messageQueue, this);
+}
+
+void NFMDemod::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
+{
+ size_t count = end - begin;
+
+ Complex ci;
+ bool consumed;
+
+ for(SampleVector::const_iterator it = begin; it < end; ++it) {
+ Complex c(it->real() / 32768.0, it->imag() / 32768.0);
+ c *= m_nco.nextIQ();
+
+ consumed = false;
+ if(m_interpolator.interpolate(&m_sampleDistanceRemain, c, &consumed, &ci)) {
+ m_sampleBuffer.push_back(Sample(ci.real() * 32768.0, ci.imag() * 32768.0));
+
+ if((ci.real() * ci.real() + ci.imag() * ci.imag()) >= m_squelchLevel)
+ m_squelchState = m_sampleRate / 100;
+
+ if(m_squelchState > 0) {
+ m_squelchState--;
+ Complex d = ci * conj(m_lastSample);
+ m_lastSample = ci;
+ Real demod = atan2(d.imag(), d.real()) / M_PI;
+ demod = m_lowpass.filter(demod);
+ demod *= m_volume;
+ qint16 sample = demod * 32767;
+
+ m_audioBuffer[m_audioBufferFill].l = sample;
+ m_audioBuffer[m_audioBufferFill].r = sample;
+ ++m_audioBufferFill;
+ if(m_audioBufferFill >= m_audioBuffer.size()) {
+ if(m_audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 0) != m_audioBufferFill)
+ ;//qDebug("lost samples");
+ m_audioBufferFill = 0;
+ }
+ }
+
+ m_sampleDistanceRemain += (Real)m_sampleRate / 44100.0;
+ }
+ }
+ if(m_audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill, 0) != m_audioBufferFill)
+ ;//qDebug("lost samples");
+ m_audioBufferFill = 0;
+
+ if(m_sampleSink != NULL)
+ m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), firstOfBurst);
+ m_sampleBuffer.clear();
+}
+
+void NFMDemod::start()
+{
+ m_squelchState = 0;
+}
+
+void NFMDemod::stop()
+{
+}
+
+bool NFMDemod::handleMessage(Message* cmd)
+{
+ if(cmd->id() == DSPSignalNotification::ID()) {
+ DSPSignalNotification* signal = (DSPSignalNotification*)cmd;
+ qDebug("%d samples/sec, %lld Hz offset", signal->getSampleRate(), signal->getFrequencyOffset());
+ m_sampleRate = signal->getSampleRate();
+ m_nco.setFreq(-signal->getFrequencyOffset(), m_sampleRate);
+ m_interpolator.create(25, 32, 32 * m_sampleRate, m_rfBandwidth / 2.0);
+ m_sampleDistanceRemain = m_sampleRate / 44100.0;
+ m_squelchState = 0;
+ cmd->completed();
+ return true;
+ } else if(cmd->id() == MsgConfigureNFMDemod::ID()) {
+ MsgConfigureNFMDemod* cfg = (MsgConfigureNFMDemod*)cmd;
+ m_rfBandwidth = cfg->getRFBandwidth();
+ m_interpolator.create(25, 32, 32 * m_sampleRate, m_rfBandwidth / 2.0);
+ m_lowpass.create(21, 44100, cfg->getAFBandwidth());
+ m_squelchLevel = pow(10.0, cfg->getSquelch() / 20.0);
+ m_squelchLevel *= m_squelchLevel;
+ m_volume = cfg->getVolume();
+ cmd->completed();
+ return true;
+ } else {
+ return false;
+ }
+}
diff --git a/plugins/demod/nfm/nfmdemod.h b/plugins/demod/nfm/nfmdemod.h
new file mode 100644
index 0000000..c3e1e6e
--- /dev/null
+++ b/plugins/demod/nfm/nfmdemod.h
@@ -0,0 +1,102 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_NFMDEMOD_H
+#define INCLUDE_NFMDEMOD_H
+
+#include <vector>
+#include "dsp/samplesink.h"
+#include "dsp/nco.h"
+#include "dsp/interpolator.h"
+#include "dsp/lowpass.h"
+#include "audio/audiofifo.h"
+#include "util/message.h"
+
+class AudioFifo;
+
+class NFMDemod : public SampleSink {
+public:
+ NFMDemod(AudioFifo* audioFifo, SampleSink* sampleSink);
+ ~NFMDemod();
+
+ void configure(MessageQueue* messageQueue, Real rfBandwidth, Real afBandwidth, Real volume, Real squelch);
+
+ void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst);
+ void start();
+ void stop();
+ bool handleMessage(Message* cmd);
+
+private:
+ class MsgConfigureNFMDemod : public Message {
+ public:
+ static MessageRegistrator ID;
+
+ Real getRFBandwidth() const { return m_rfBandwidth; }
+ Real getAFBandwidth() const { return m_afBandwidth; }
+ Real getVolume() const { return m_volume; }
+ Real getSquelch() const { return m_squelch; }
+
+ static MsgConfigureNFMDemod* create(Real rfBandwidth, Real afBandwidth, Real volume, Real squelch)
+ {
+ return new MsgConfigureNFMDemod(rfBandwidth, afBandwidth, volume, squelch);
+ }
+
+ private:
+ Real m_rfBandwidth;
+ Real m_afBandwidth;
+ Real m_volume;
+ Real m_squelch;
+
+ MsgConfigureNFMDemod(Real rfBandwidth, Real afBandwidth, Real volume, Real squelch) :
+ Message(ID()),
+ m_rfBandwidth(rfBandwidth),
+ m_afBandwidth(afBandwidth),
+ m_volume(volume),
+ m_squelch(squelch)
+ { }
+ };
+
+ struct AudioSample {
+ qint16 l;
+ qint16 r;
+ };
+ typedef std::vector<AudioSample> AudioVector;
+
+ Real m_rfBandwidth;
+ Real m_volume;
+ Real m_squelchLevel;
+ int m_sampleRate;
+ int m_frequency;
+
+ NCO m_nco;
+ Interpolator m_interpolator;
+ Real m_sampleDistanceRemain;
+ Lowpass<Real> m_lowpass;
+
+ int m_squelchState;
+
+ Complex m_lastSample;
+
+ AudioVector m_audioBuffer;
+ uint m_audioBufferFill;
+ AudioFifo* m_audioFifo;
+
+ SampleSink* m_sampleSink;
+ SampleVector m_sampleBuffer;
+};
+
+#endif // INCLUDE_NFMDEMOD_H
diff --git a/plugins/demod/nfm/nfmdemodgui.cpp b/plugins/demod/nfm/nfmdemodgui.cpp
new file mode 100644
index 0000000..a630dd4
--- /dev/null
+++ b/plugins/demod/nfm/nfmdemodgui.cpp
@@ -0,0 +1,179 @@
+#include <QDockWidget>
+#include <QMainWindow>
+#include "nfmdemodgui.h"
+#include "ui_nfmdemodgui.h"
+#include "nfmdemodgui.h"
+#include "ui_nfmdemodgui.h"
+#include "dsp/threadedsamplesink.h"
+#include "dsp/channelizer.h"
+#include "nfmdemod.h"
+#include "dsp/spectrumvis.h"
+#include "gui/glspectrum.h"
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+
+const int NFMDemodGUI::m_rfBW[] = {
+ 5000, 6250, 8330, 10000, 12500, 15000, 20000, 25000, 40000
+};
+
+NFMDemodGUI* NFMDemodGUI::create(PluginAPI* pluginAPI)
+{
+ QDockWidget* dock = pluginAPI->createMainWindowDock(Qt::RightDockWidgetArea, tr("NFM Demodulator"));
+ dock->setObjectName(QString::fromUtf8("NFM Demodulator"));
+ NFMDemodGUI* gui = new NFMDemodGUI(pluginAPI, dock);
+ dock->setWidget(gui);
+ return gui;
+}
+
+void NFMDemodGUI::destroy()
+{
+ delete m_dockWidget;
+}
+
+void NFMDemodGUI::setWidgetName(const QString& name)
+{
+ qDebug("NFM: %s", qPrintable(name));
+ m_dockWidget->setObjectName(name);
+}
+
+void NFMDemodGUI::resetToDefaults()
+{
+ ui->rfBW->setValue(4);
+ ui->afBW->setValue(3);
+ ui->volume->setValue(20);
+ ui->squelch->setValue(-40);
+ applySettings();
+}
+
+QByteArray NFMDemodGUI::serialize() const
+{
+ SimpleSerializer s(1);
+ s.writeS32(1, m_channelMarker->getCenterFrequency());
+ s.writeS32(2, ui->rfBW->value());
+ s.writeS32(3, ui->afBW->value());
+ s.writeS32(4, ui->volume->value());
+ s.writeS32(5, ui->squelch->value());
+ return s.final();
+}
+
+bool NFMDemodGUI::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if(!d.isValid()) {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1) {
+ qint32 tmp;
+ d.readS32(1, &tmp, 0);
+ m_channelMarker->setCenterFrequency(tmp);
+ d.readS32(2, &tmp, 4);
+ ui->rfBW->setValue(tmp);
+ d.readS32(3, &tmp, 3);
+ ui->afBW->setValue(tmp);
+ d.readS32(4, &tmp, 20);
+ ui->volume->setValue(tmp);
+ d.readS32(5, &tmp, -40);
+ ui->squelch->setValue(tmp);
+ applySettings();
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+bool NFMDemodGUI::handleMessage(Message* message)
+{
+ return false;
+}
+
+void NFMDemodGUI::viewChanged()
+{
+ applySettings();
+}
+
+void NFMDemodGUI::on_rfBW_valueChanged(int value)
+{
+ ui->rfBWText->setText(QString("%1 kHz").arg(m_rfBW[value] / 1000.0));
+ m_channelMarker->setBandwidth(m_rfBW[value]);
+ applySettings();
+}
+
+void NFMDemodGUI::on_afBW_valueChanged(int value)
+{
+ ui->afBWText->setText(QString("%1 kHz").arg(value));
+ applySettings();
+}
+
+void NFMDemodGUI::on_volume_valueChanged(int value)
+{
+ ui->volumeText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
+ applySettings();
+}
+
+void NFMDemodGUI::on_squelch_valueChanged(int value)
+{
+ ui->squelchText->setText(QString("%1 dB").arg(value));
+ applySettings();
+}
+
+NFMDemodGUI::NFMDemodGUI(PluginAPI* pluginAPI, QDockWidget* dockWidget, QWidget* parent) :
+ PluginGUI(parent),
+ ui(new Ui::NFMDemodGUI),
+ m_pluginAPI(pluginAPI),
+ m_dockWidget(dockWidget)
+{
+ ui->setupUi(this);
+
+ m_audioFifo = new AudioFifo(4, 44100 / 4);
+ m_spectrumVis = new SpectrumVis(ui->glSpectrum);
+ m_nfmDemod = new NFMDemod(m_audioFifo, m_spectrumVis);
+ m_channelizer = new Channelizer(m_nfmDemod);
+ m_threadedSampleSink = new ThreadedSampleSink(m_channelizer);
+ m_pluginAPI->addAudioSource(m_audioFifo);
+ m_pluginAPI->addSampleSink(m_threadedSampleSink);
+
+ ui->glSpectrum->setCenterFrequency(0);
+ ui->glSpectrum->setSampleRate(44100);
+ ui->glSpectrum->setDisplayWaterfall(true);
+ ui->glSpectrum->setDisplayLiveSpectrum(true);
+ m_spectrumVis->configure(m_threadedSampleSink->getMessageQueue(), 64, 10, FFTWindow::BlackmanHarris);
+
+ m_channelMarker = new ChannelMarker(this);
+ m_channelMarker->setColor(Qt::red);
+ m_channelMarker->setBandwidth(12500);
+ m_channelMarker->setCenterFrequency(0);
+ m_channelMarker->setVisible(true);
+ connect(m_channelMarker, SIGNAL(changed()), this, SLOT(viewChanged()));
+ m_pluginAPI->addChannelMarker(m_channelMarker);
+
+ applySettings();
+}
+
+NFMDemodGUI::~NFMDemodGUI()
+{
+ m_pluginAPI->removeAudioSource(m_audioFifo);
+ m_pluginAPI->removeSampleSink(m_threadedSampleSink);
+ delete m_threadedSampleSink;
+ delete m_channelizer;
+ delete m_nfmDemod;
+ delete m_spectrumVis;
+ delete m_audioFifo;
+ delete m_channelMarker;
+ delete ui;
+}
+
+void NFMDemodGUI::applySettings()
+{
+ m_channelizer->configure(m_threadedSampleSink->getMessageQueue(),
+ 44100,
+ m_channelMarker->getCenterFrequency());
+ m_nfmDemod->configure(m_threadedSampleSink->getMessageQueue(),
+ m_rfBW[ui->rfBW->value()],
+ ui->afBW->value() * 1000.0,
+ ui->volume->value() / 10.0,
+ ui->squelch->value());
+}
diff --git a/plugins/demod/nfm/nfmdemodgui.h b/plugins/demod/nfm/nfmdemodgui.h
new file mode 100644
index 0000000..1381b5a
--- /dev/null
+++ b/plugins/demod/nfm/nfmdemodgui.h
@@ -0,0 +1,64 @@
+#ifndef INCLUDE_NFMDEMODGUI_H
+#define INCLUDE_NFMDEMODGUI_H
+
+#include "plugin/plugingui.h"
+
+class QDockWidget;
+
+class PluginAPI;
+class ChannelMarker;
+
+class AudioFifo;
+class ThreadedSampleSink;
+class Channelizer;
+class NFMDemod;
+class SpectrumVis;
+
+namespace Ui {
+ class NFMDemodGUI;
+}
+
+class NFMDemodGUI : public PluginGUI {
+ Q_OBJECT
+
+public:
+ static NFMDemodGUI* create(PluginAPI* pluginAPI);
+ void destroy();
+
+ void setWidgetName(const QString& name);
+
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+
+ bool handleMessage(Message* message);
+
+private slots:
+ void viewChanged();
+ void on_rfBW_valueChanged(int value);
+ void on_afBW_valueChanged(int value);
+ void on_volume_valueChanged(int value);
+ void on_squelch_valueChanged(int value);
+
+private:
+ Ui::NFMDemodGUI* ui;
+ PluginAPI* m_pluginAPI;
+ QDockWidget* m_dockWidget;
+ ChannelMarker* m_channelMarker;
+
+ AudioFifo* m_audioFifo;
+ ThreadedSampleSink* m_threadedSampleSink;
+ Channelizer* m_channelizer;
+ NFMDemod* m_nfmDemod;
+ SpectrumVis* m_spectrumVis;
+
+ static const QString m_demodName;
+ static const int m_rfBW[];
+
+ explicit NFMDemodGUI(PluginAPI* pluginAPI, QDockWidget* dockWidget, QWidget* parent = NULL);
+ ~NFMDemodGUI();
+
+ void applySettings();
+};
+
+#endif // INCLUDE_NFMDEMODGUI_H
diff --git a/plugins/demod/nfm/nfmdemodgui.ui b/plugins/demod/nfm/nfmdemodgui.ui
new file mode 100644
index 0000000..5087276
--- /dev/null
+++ b/plugins/demod/nfm/nfmdemodgui.ui
@@ -0,0 +1,211 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NFMDemodGUI</class>
+ <widget class="QWidget" name="NFMDemodGUI">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>208</width>
+ <height>226</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="GLSpectrum" name="glSpectrum" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>150</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="margin">
+ <number>2</number>
+ </property>
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="0" column="1">
+ <widget class="QSlider" name="rfBW">
+ <property name="maximum">
+ <number>8</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>4</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>RF Bandwidth</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>AF Bandwidth</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Squelch</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Volume</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="rfBWText">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>12.5kHz</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSlider" name="afBW">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>20</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>3</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QSlider" name="volume">
+ <property name="maximum">
+ <number>100</number>
+ </property>
+ <property name="value">
+ <number>20</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QSlider" name="squelch">
+ <property name="minimum">
+ <number>-100</number>
+ </property>
+ <property name="maximum">
+ <number>0</number>
+ </property>
+ <property name="value">
+ <number>-40</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QLabel" name="afBWText">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>3 kHz</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QLabel" name="volumeText">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>2.0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="2">
+ <widget class="QLabel" name="squelchText">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>-40dB</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>GLSpectrum</class>
+ <extends>QWidget</extends>
+ <header>gui/glspectrum.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/demod/nfm/nfmplugin.cpp b/plugins/demod/nfm/nfmplugin.cpp
new file mode 100644
index 0000000..6c1c063
--- /dev/null
+++ b/plugins/demod/nfm/nfmplugin.cpp
@@ -0,0 +1,52 @@
+#include <QtPlugin>
+#include <QAction>
+#include "plugin/pluginapi.h"
+#include "nfmplugin.h"
+#include "nfmdemodgui.h"
+
+const PluginDescriptor NFMPlugin::m_pluginDescriptor = {
+ displayedName: QString("NFM Demodulator"),
+ version: QString("---"),
+ copyright: QString("(c) maintech GmbH (written by Christian Daniel)"),
+ website: QString("http://www.maintech.de"),
+ licenseIsGPL: true,
+ sourceCodeURL: QString("http://www.maintech.de")
+};
+
+NFMPlugin::NFMPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& NFMPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void NFMPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ // register NFM demodulator
+ QAction* action = new QAction(tr("&NFM"), this);
+ connect(action, SIGNAL(triggered()), this, SLOT(createInstanceNFM()));
+ m_pluginAPI->registerDemodulator("de.maintech.sdrangelove.demod.nfm", this, action);
+}
+
+PluginGUI* NFMPlugin::createDemod(const QString& demodName)
+{
+ if(demodName == "de.maintech.sdrangelove.demod.nfm") {
+ PluginGUI* gui = NFMDemodGUI::create(m_pluginAPI);
+ m_pluginAPI->registerDemodulatorInstance("de.maintech.sdrangelove.demod.nfm", gui);
+ return gui;
+ } else {
+ return NULL;
+ }
+}
+
+void NFMPlugin::createInstanceNFM()
+{
+ m_pluginAPI->registerDemodulatorInstance("de.maintech.sdrangelove.demod.nfm", NFMDemodGUI::create(m_pluginAPI));
+}
+
+Q_EXPORT_PLUGIN2(nfmPlugin, NFMPlugin);
diff --git a/plugins/demod/nfm/nfmplugin.h b/plugins/demod/nfm/nfmplugin.h
new file mode 100644
index 0000000..18e6c40
--- /dev/null
+++ b/plugins/demod/nfm/nfmplugin.h
@@ -0,0 +1,28 @@
+#ifndef INCLUDE_NFMPLUGIN_H
+#define INCLUDE_NFMPLUGIN_H
+
+#include <QObject>
+#include "plugin/plugininterface.h"
+
+class NFMPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+
+public:
+ explicit NFMPlugin(QObject* parent = NULL);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ PluginGUI* createDemod(const QString& demodName);
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+
+ PluginAPI* m_pluginAPI;
+
+private slots:
+ void createInstanceNFM();
+};
+
+#endif // INCLUDE_NFMPLUGIN_H
diff --git a/plugins/demod/tetra/CMakeLists.txt b/plugins/demod/tetra/CMakeLists.txt
new file mode 100644
index 0000000..7d1612a
--- /dev/null
+++ b/plugins/demod/tetra/CMakeLists.txt
@@ -0,0 +1,44 @@
+project(tetra)
+
+set(tetra_SOURCES
+ tetrademod.cpp
+ tetrademodgui.cpp
+ tetraplugin.cpp
+)
+
+set(tetra_HEADERS
+ tetrademod.h
+ tetrademodgui.h
+ tetraplugin.h
+)
+
+set(tetra_FORMS
+ tetrademodgui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_SOURCE_DIR}/include-gpl
+ ${OPENGL_INCLUDE_DIR}
+)
+
+include(${QT_USE_FILE})
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+qt4_wrap_cpp(tetra_HEADERS_MOC ${tetra_HEADERS})
+qt4_wrap_ui(tetra_FORMS_HEADERS ${tetra_FORMS})
+
+add_library(demodtetra SHARED
+ ${tetra_SOURCES}
+ ${tetra_HEADERS_MOC}
+ ${tetra_FORMS_HEADERS}
+)
+
+target_link_libraries(demodtetra
+ ${QT_LIBRARIES}
+ ${OPENGL_LIBRARIES}
+)
diff --git a/plugins/demod/tetra/tetrademod.cpp b/plugins/demod/tetra/tetrademod.cpp
new file mode 100644
index 0000000..4cf8ea1
--- /dev/null
+++ b/plugins/demod/tetra/tetrademod.cpp
@@ -0,0 +1,106 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include <stdio.h>
+#include "tetrademod.h"
+#include "dsp/dspcommands.h"
+
+MessageRegistrator TetraDemod::MsgConfigureTetraDemod::ID("MsgConfigureTetraDemod");
+
+static FILE* f = NULL;
+
+TetraDemod::TetraDemod(SampleSink* sampleSink) :
+ m_sampleSink(sampleSink)
+{
+ m_sampleRate = 500000;
+ m_frequency = 0;
+
+ m_nco.setFreq(m_frequency, m_sampleRate);
+ m_interpolator.create(1, 32, 32 * m_sampleRate, 36000);
+ m_sampleDistanceRemain = (Real)m_sampleRate / 36000.0;
+}
+
+TetraDemod::~TetraDemod()
+{
+}
+
+void TetraDemod::configure(MessageQueue* messageQueue)
+{
+ Message* cmd = MsgConfigureTetraDemod::create();
+ cmd->submit(messageQueue, this);
+}
+
+void TetraDemod::feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst)
+{
+ size_t count = end - begin;
+
+ Complex ci;
+ bool consumed;
+
+ for(SampleVector::const_iterator it = begin; it < end; ++it) {
+ Complex c(it->real() / 32768.0, it->imag() / 32768.0);
+ c *= m_nco.nextIQ();
+
+ consumed = false;
+ if(m_interpolator.interpolate(&m_sampleDistanceRemain, c, &consumed, &ci)) {
+ m_sampleBuffer.push_back(Sample(ci.real() * 32768.0, ci.imag() * 32768.0));
+
+ m_sampleDistanceRemain += (Real)m_sampleRate / 36000.0;
+ }
+ }
+
+ if(f != NULL) {
+ fwrite(&m_sampleBuffer[0], m_sampleBuffer.size(), sizeof(m_sampleBuffer[0]), f);
+ }
+
+ if(m_sampleSink != NULL)
+ m_sampleSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), firstOfBurst);
+ m_sampleBuffer.clear();
+}
+
+void TetraDemod::start()
+{
+}
+
+void TetraDemod::stop()
+{
+}
+
+bool TetraDemod::handleMessage(Message* cmd)
+{
+ if(cmd->id() == DSPSignalNotification::ID()) {
+ DSPSignalNotification* signal = (DSPSignalNotification*)cmd;
+ qDebug("%d samples/sec, %lld Hz offset", signal->getSampleRate(), signal->getFrequencyOffset());
+ m_sampleRate = signal->getSampleRate();
+ m_nco.setFreq(-signal->getFrequencyOffset(), m_sampleRate);
+ m_interpolator.create(51, 32, 32 * m_sampleRate, 25000 / 2);
+ m_sampleDistanceRemain = m_sampleRate / 36000.0;
+ cmd->completed();
+ return true;
+ } else if(cmd->id() == MsgConfigureTetraDemod::ID()) {
+ if(f == NULL) {
+ f = fopen("/tmp/tetra.iq", "wb");
+ qDebug("started writing samples");
+ } else {
+ fclose(f);
+ f = NULL;
+ qDebug("stopped writing samples");
+ }
+ } else {
+ return false;
+ }
+}
diff --git a/plugins/demod/tetra/tetrademod.h b/plugins/demod/tetra/tetrademod.h
new file mode 100644
index 0000000..a2a474b
--- /dev/null
+++ b/plugins/demod/tetra/tetrademod.h
@@ -0,0 +1,67 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_TETRADEMOD_H
+#define INCLUDE_TETRADEMOD_H
+
+#include "dsp/samplesink.h"
+#include "dsp/nco.h"
+#include "dsp/interpolator.h"
+#include "util/message.h"
+
+class MessageQueue;
+
+class TetraDemod : public SampleSink {
+public:
+ TetraDemod(SampleSink* sampleSink);
+ ~TetraDemod();
+
+ void configure(MessageQueue* messageQueue);
+
+ void feed(SampleVector::const_iterator begin, SampleVector::const_iterator end, bool firstOfBurst);
+ void start();
+ void stop();
+ bool handleMessage(Message* cmd);
+
+private:
+ class MsgConfigureTetraDemod : public Message {
+ public:
+ static MessageRegistrator ID;
+
+ static MsgConfigureTetraDemod* create()
+ {
+ return new MsgConfigureTetraDemod();
+ }
+
+ private:
+ MsgConfigureTetraDemod() :
+ Message(ID())
+ { }
+ };
+
+ int m_sampleRate;
+ int m_frequency;
+
+ NCO m_nco;
+ Interpolator m_interpolator;
+ Real m_sampleDistanceRemain;
+
+ SampleSink* m_sampleSink;
+ SampleVector m_sampleBuffer;
+};
+
+#endif // INCLUDE_TETRADEMOD_H
diff --git a/plugins/demod/tetra/tetrademodgui.cpp b/plugins/demod/tetra/tetrademodgui.cpp
new file mode 100644
index 0000000..a0659b5
--- /dev/null
+++ b/plugins/demod/tetra/tetrademodgui.cpp
@@ -0,0 +1,110 @@
+#include <QDockWidget>
+#include <QMainWindow>
+#include "tetrademodgui.h"
+#include "ui_tetrademodgui.h"
+#include "dsp/threadedsamplesink.h"
+#include "dsp/channelizer.h"
+#include "tetrademod.h"
+#include "dsp/spectrumvis.h"
+#include "gui/glspectrum.h"
+#include "plugin/pluginapi.h"
+
+TetraDemodGUI* TetraDemodGUI::create(PluginAPI* pluginAPI)
+{
+ QDockWidget* dock = pluginAPI->createMainWindowDock(Qt::RightDockWidgetArea, tr("Tetra Demodulator"));
+ dock->setObjectName(QString::fromUtf8("Tetra Demodulator"));
+ TetraDemodGUI* gui = new TetraDemodGUI(pluginAPI, dock);
+ dock->setWidget(gui);
+ return gui;
+}
+
+void TetraDemodGUI::destroy()
+{
+ delete m_dockWidget;
+}
+
+void TetraDemodGUI::setWidgetName(const QString& name)
+{
+ m_dockWidget->setObjectName(name);
+}
+
+void TetraDemodGUI::resetToDefaults()
+{
+}
+
+QByteArray TetraDemodGUI::serializeGeneral() const
+{
+ return QByteArray();
+}
+
+bool TetraDemodGUI::deserializeGeneral(const QByteArray& data)
+{
+ return false;
+}
+
+QByteArray TetraDemodGUI::serialize() const
+{
+ return QByteArray();
+}
+
+bool TetraDemodGUI::deserialize(const QByteArray& data)
+{
+ return false;
+}
+
+bool TetraDemodGUI::handleMessage(Message* message)
+{
+ return false;
+}
+
+void TetraDemodGUI::viewChanged()
+{
+ m_channelizer->configure(m_threadedSampleSink->getMessageQueue(), 36000, m_channelMarker->getCenterFrequency());
+}
+
+TetraDemodGUI::TetraDemodGUI(PluginAPI* pluginAPI, QDockWidget* dockWidget, QWidget* parent) :
+ PluginGUI(parent),
+ ui(new Ui::TetraDemodGUI),
+ m_pluginAPI(pluginAPI),
+ m_dockWidget(dockWidget)
+{
+ ui->setupUi(this);
+
+ m_spectrumVis = new SpectrumVis(ui->glSpectrum);
+ m_tetraDemod = new TetraDemod(m_spectrumVis);
+ m_channelizer = new Channelizer(m_tetraDemod);
+ m_threadedSampleSink = new ThreadedSampleSink(m_channelizer);
+ m_pluginAPI->addSampleSink(m_threadedSampleSink);
+
+ ui->glSpectrum->setCenterFrequency(0);
+ ui->glSpectrum->setSampleRate(36000);
+ ui->glSpectrum->setDisplayWaterfall(true);
+ ui->glSpectrum->setDisplayLiveSpectrum(true);
+ m_spectrumVis->configure(m_threadedSampleSink->getMessageQueue(), 64, 10, FFTWindow::BlackmanHarris);
+
+ m_channelMarker = new ChannelMarker(this);
+ m_channelMarker->setColor(Qt::darkGreen);
+ m_channelMarker->setBandwidth(25000);
+ m_channelMarker->setCenterFrequency(0);
+ m_channelMarker->setVisible(true);
+ connect(m_channelMarker, SIGNAL(changed()), this, SLOT(viewChanged()));
+ m_pluginAPI->addChannelMarker(m_channelMarker);
+
+ viewChanged();
+}
+
+TetraDemodGUI::~TetraDemodGUI()
+{
+ m_pluginAPI->removeSampleSink(m_threadedSampleSink);
+ delete m_threadedSampleSink;
+ delete m_channelizer;
+ delete m_tetraDemod;
+ delete m_spectrumVis;
+ delete m_channelMarker;
+ delete ui;
+}
+
+void TetraDemodGUI::on_test_clicked()
+{
+ m_tetraDemod->configure(m_threadedSampleSink->getMessageQueue());
+}
diff --git a/plugins/demod/tetra/tetrademodgui.h b/plugins/demod/tetra/tetrademodgui.h
new file mode 100644
index 0000000..9eacae6
--- /dev/null
+++ b/plugins/demod/tetra/tetrademodgui.h
@@ -0,0 +1,56 @@
+#ifndef INCLUDE_TETRADEMODGUI_H
+#define INCLUDE_TETRADEMODGUI_H
+
+#include "plugin/plugingui.h"
+
+class QDockWidget;
+
+class PluginAPI;
+class ChannelMarker;
+
+class ThreadedSampleSink;
+class Channelizer;
+class TetraDemod;
+class SpectrumVis;
+
+namespace Ui {
+ class TetraDemodGUI;
+}
+
+class TetraDemodGUI : public PluginGUI {
+ Q_OBJECT
+
+public:
+ static TetraDemodGUI* create(PluginAPI* pluginAPI);
+ void destroy();
+
+ void setWidgetName(const QString& name);
+
+ void resetToDefaults();
+ QByteArray serializeGeneral() const;
+ bool deserializeGeneral(const QByteArray& data);
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+
+ bool handleMessage(Message* message);
+
+private slots:
+ void on_test_clicked();
+ void viewChanged();
+
+private:
+ explicit TetraDemodGUI(PluginAPI* pluginAPI, QDockWidget* dockWidget, QWidget* parent = NULL);
+ ~TetraDemodGUI();
+
+ Ui::TetraDemodGUI* ui;
+ PluginAPI* m_pluginAPI;
+ QDockWidget* m_dockWidget;
+ ChannelMarker* m_channelMarker;
+
+ ThreadedSampleSink* m_threadedSampleSink;
+ Channelizer* m_channelizer;
+ TetraDemod* m_tetraDemod;
+ SpectrumVis* m_spectrumVis;
+};
+
+#endif // INCLUDE_TETRADEMODGUI_H
diff --git a/plugins/demod/tetra/tetrademodgui.ui b/plugins/demod/tetra/tetrademodgui.ui
new file mode 100644
index 0000000..feaef10
--- /dev/null
+++ b/plugins/demod/tetra/tetrademodgui.ui
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TetraDemodGUI</class>
+ <widget class="QWidget" name="TetraDemodGUI">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>200</width>
+ <height>179</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="GLSpectrum" name="glSpectrum" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>200</width>
+ <height>150</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="test">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>GLSpectrum</class>
+ <extends>QWidget</extends>
+ <header>gui/glspectrum.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/demod/tetra/tetraplugin.cpp b/plugins/demod/tetra/tetraplugin.cpp
new file mode 100644
index 0000000..fe63e98
--- /dev/null
+++ b/plugins/demod/tetra/tetraplugin.cpp
@@ -0,0 +1,52 @@
+#include <QtPlugin>
+#include <QAction>
+#include "plugin/pluginapi.h"
+#include "tetraplugin.h"
+#include "tetrademodgui.h"
+
+const PluginDescriptor TetraPlugin::m_pluginDescriptor = {
+ displayedName: QString("Tetra Demodulator"),
+ version: QString("---"),
+ copyright: QString("(c) maintech GmbH (written by Christian Daniel)"),
+ website: QString("http://www.maintech.de"),
+ licenseIsGPL: true,
+ sourceCodeURL: QString("http://www.maintech.de")
+};
+
+TetraPlugin::TetraPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& TetraPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void TetraPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ // register Tetra demodulator
+ QAction* action = new QAction(tr("&Tetra"), this);
+ connect(action, SIGNAL(triggered()), this, SLOT(createInstanceTetra()));
+ m_pluginAPI->registerDemodulator("de.maintech.sdrangelove.demod.tetra", this, action);
+}
+
+PluginGUI* TetraPlugin::createDemod(const QString& demodName)
+{
+ if(demodName == "de.maintech.sdrangelove.demod.tetra") {
+ PluginGUI* gui = TetraDemodGUI::create(m_pluginAPI);
+ m_pluginAPI->registerDemodulatorInstance("de.maintech.sdrangelove.demod.tetra", gui);
+ return gui;
+ } else {
+ return NULL;
+ }
+}
+
+void TetraPlugin::createInstanceTetra()
+{
+ m_pluginAPI->registerDemodulatorInstance("de.maintech.sdrangelove.demod.tetra", TetraDemodGUI::create(m_pluginAPI));
+}
+
+Q_EXPORT_PLUGIN2(tetraPlugin, TetraPlugin);
diff --git a/plugins/demod/tetra/tetraplugin.h b/plugins/demod/tetra/tetraplugin.h
new file mode 100644
index 0000000..b3b6fd1
--- /dev/null
+++ b/plugins/demod/tetra/tetraplugin.h
@@ -0,0 +1,28 @@
+#ifndef INCLUDE_TETRAPLUGIN_H
+#define INCLUDE_TETRAPLUGIN_H
+
+#include <QObject>
+#include "plugin/plugininterface.h"
+
+class TetraPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+
+public:
+ explicit TetraPlugin(QObject* parent = NULL);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ PluginGUI* createDemod(const QString& demodName);
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+
+ PluginAPI* m_pluginAPI;
+
+private slots:
+ void createInstanceTetra();
+};
+
+#endif // INCLUDE_TETRAPLUGIN_H
diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt
new file mode 100644
index 0000000..1a82fc6
--- /dev/null
+++ b/plugins/samplesource/CMakeLists.txt
@@ -0,0 +1,7 @@
+project(samplesource)
+
+find_package(LibOsmoSDR REQUIRED)
+find_package(LibRTLSDR REQUIRED)
+
+add_subdirectory(osmosdr)
+add_subdirectory(rtlsdr)
diff --git a/plugins/samplesource/osmosdr/CMakeLists.txt b/plugins/samplesource/osmosdr/CMakeLists.txt
new file mode 100644
index 0000000..d3b1b61
--- /dev/null
+++ b/plugins/samplesource/osmosdr/CMakeLists.txt
@@ -0,0 +1,47 @@
+project(osmosdr)
+
+set(osmosdr_SOURCES
+ osmosdrgui.cpp
+ osmosdrinput.cpp
+ osmosdrplugin.cpp
+ osmosdrthread.cpp
+)
+
+set(osmosdr_HEADERS
+ osmosdrgui.h
+ osmosdrinput.h
+ osmosdrplugin.h
+ osmosdrthread.h
+)
+
+set(osmosdr_FORMS
+ osmosdrgui.ui
+)
+
+include_directories(
+ .
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_SOURCE_DIR}/include-gpl
+ ${LIBOSMOSDR_INCLUDE_DIR}
+)
+
+include(${QT_USE_FILE})
+add_definitions(${QT_DEFINITIONS})
+add_definitions(-DQT_PLUGIN)
+add_definitions(-DQT_SHARED)
+
+qt4_wrap_cpp(osmosdr_HEADERS_MOC ${osmosdr_HEADERS})
+qt4_wrap_ui(osmosdr_FORMS_HEADERS ${osmosdr_FORMS})
+
+add_library(inputosmosdr SHARED
+ ${osmosdr_SOURCES}
+ ${osmosdr_HEADERS_MOC}
+ ${osmosdr_FORMS_HEADERS}
+)
+
+target_link_libraries(inputosmosdr
+ ${QT_LIBRARIES}
+ ${LIBOSMOSDR_LIBRARIES}
+ ${LIBUSB_LIBRARIES}
+)
diff --git a/plugins/samplesource/osmosdr/osmosdrgui.cpp b/plugins/samplesource/osmosdr/osmosdrgui.cpp
new file mode 100644
index 0000000..ddaf4d3
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrgui.cpp
@@ -0,0 +1,305 @@
+#include "osmosdrgui.h"
+#include "ui_osmosdrgui.h"
+#include "plugin/pluginapi.h"
+
+OsmoSDRGui::OsmoSDRGui(PluginAPI* pluginAPI, QWidget* parent) :
+ PluginGUI(parent),
+ ui(new Ui::OsmoSDRGui),
+ m_pluginAPI(pluginAPI),
+ m_settings(),
+ m_sampleSource(NULL)
+{
+ ui->setupUi(this);
+ ui->centerFrequency->setValueRange(7, 20000U, 2200000U);
+ connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
+ displaySettings();
+
+ m_sampleSource = new OsmoSDRInput(m_pluginAPI->getMainWindowMessageQueue());
+ m_pluginAPI->setSampleSource(m_sampleSource);
+}
+
+OsmoSDRGui::~OsmoSDRGui()
+{
+ delete ui;
+}
+
+void OsmoSDRGui::destroy()
+{
+ delete this;
+}
+
+void OsmoSDRGui::resetToDefaults()
+{
+ m_generalSettings.resetToDefaults();
+ m_settings.resetToDefaults();
+ displaySettings();
+ sendSettings();
+}
+
+QByteArray OsmoSDRGui::serializeGeneral() const
+{
+ return m_generalSettings.serialize();
+}
+
+bool OsmoSDRGui::deserializeGeneral(const QByteArray&data)
+{
+ if(m_generalSettings.deserialize(data)) {
+ displaySettings();
+ sendSettings();
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+quint64 OsmoSDRGui::getCenterFrequency() const
+{
+ return m_generalSettings.m_centerFrequency;
+}
+
+QByteArray OsmoSDRGui::serialize() const
+{
+ return m_settings.serialize();
+}
+
+bool OsmoSDRGui::deserialize(const QByteArray& data)
+{
+ if(m_settings.deserialize(data)) {
+ displaySettings();
+ sendSettings();
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+bool OsmoSDRGui::handleMessage(Message* message)
+{
+ return false;
+ /*
+ if(message->id() == OsmoSDRInput::MsgReportOsmoSDR::ID()) {
+ m_gains = ((RTLSDRInput::MsgReportRTLSDR*)message)->getGains();
+ displaySettings();
+ message->completed();
+ return true;
+ } else {
+ return false;
+ }*/
+}
+#if 0
+
+OsmoSDRGui::OsmoSDRGui(MessageQueue* msgQueue, QWidget* parent) :
+ SampleSourceGUI(parent),
+ ui(new Ui::OsmoSDRGui),
+ m_msgQueue(msgQueue),
+ m_settings()
+{
+ ui->setupUi(this);
+ ui->centerFrequency->setValueRange(7, 20000U, 2200000U);
+ connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
+ displaySettings();
+}
+
+OsmoSDRGui::~OsmoSDRGui()
+{
+ delete ui;
+}
+
+QString OsmoSDRGui::serializeSettings() const
+{
+ return m_settings.serialize();
+}
+
+bool OsmoSDRGui::deserializeSettings(const QString& settings)
+{
+ if(m_settings.deserialize(settings)) {
+ displaySettings();
+ sendSettings();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool OsmoSDRGui::handleSourceMessage(DSPCmdSourceToGUI* cmd)
+{
+ return false;
+}
+#endif
+
+void OsmoSDRGui::displaySettings()
+{
+ ui->centerFrequency->setValue(m_generalSettings.m_centerFrequency / 1000);
+ ui->iqSwap->setChecked(m_settings.m_swapIQ);
+ ui->decimation->setValue(m_settings.m_decimation);
+ ui->e4000LNAGain->setValue(e4kLNAGainToIdx(m_settings.m_lnaGain));
+
+ ui->e4000MixerGain->setCurrentIndex((m_settings.m_mixerGain - 40) / 80);
+ if(m_settings.m_mixerEnhancement == 0)
+ ui->e4000MixerEnh->setCurrentIndex(0);
+ else ui->e4000MixerEnh->setCurrentIndex((m_settings.m_mixerEnhancement + 10) / 20);
+
+ ui->e4000if1->setCurrentIndex((m_settings.m_if1gain + 30) / 90);
+ ui->e4000if2->setCurrentIndex(m_settings.m_if2gain / 30);
+ ui->e4000if3->setCurrentIndex(m_settings.m_if3gain / 30);
+ ui->e4000if4->setCurrentIndex(m_settings.m_if4gain / 10);
+ ui->e4000if5->setCurrentIndex(m_settings.m_if5gain / 30 - 1);
+ ui->e4000if6->setCurrentIndex(m_settings.m_if6gain / 30 - 1);
+ ui->filterI1->setValue(m_settings.m_opAmpI1);
+ ui->filterI2->setValue(m_settings.m_opAmpI2);
+ ui->filterQ1->setValue(m_settings.m_opAmpQ1);
+ ui->filterQ2->setValue(m_settings.m_opAmpQ2);
+
+ ui->e4kI->setValue(m_settings.m_dcOfsI);
+ ui->e4kQ->setValue(m_settings.m_dcOfsQ);
+}
+
+void OsmoSDRGui::sendSettings()
+{
+ if(!m_updateTimer.isActive())
+ m_updateTimer.start(100);
+}
+
+int OsmoSDRGui::e4kLNAGainToIdx(int gain) const
+{
+ static const quint32 gainList[13] = {
+ -50, -25, 0, 25, 50, 75, 100, 125, 150, 175, 200, 250, 300
+ };
+ for(int i = 0; i < 13; i++) {
+ if(gainList[i] == gain)
+ return i;
+ }
+ return 0;
+}
+
+int OsmoSDRGui::e4kIdxToLNAGain(int idx) const
+{
+ static const quint32 gainList[13] = {
+ -50, -25, 0, 25, 50, 75, 100, 125, 150, 175, 200, 250, 300
+ };
+ if((idx < 0) || (idx >= 13))
+ return -50;
+ else return gainList[idx];
+}
+
+void OsmoSDRGui::on_iqSwap_toggled(bool checked)
+{
+ m_settings.m_swapIQ = checked;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4000MixerGain_currentIndexChanged(int index)
+{
+ m_settings.m_mixerGain = index * 80 + 40;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4000MixerEnh_currentIndexChanged(int index)
+{
+ if(index == 0)
+ m_settings.m_mixerEnhancement = 0;
+ else m_settings.m_mixerEnhancement = index * 20 - 10;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4000if1_currentIndexChanged(int index)
+{
+ m_settings.m_if1gain = index * 90 - 30;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4000if2_currentIndexChanged(int index)
+{
+ m_settings.m_if2gain = index * 30;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4000if3_currentIndexChanged(int index)
+{
+ m_settings.m_if3gain = index * 30;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4000if4_currentIndexChanged(int index)
+{
+ m_settings.m_if4gain = index * 10;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4000if5_currentIndexChanged(int index)
+{
+ m_settings.m_if5gain = (index + 1) * 30;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4000if6_currentIndexChanged(int index)
+{
+ m_settings.m_if6gain = (index + 1) * 30;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_centerFrequency_changed(quint64 value)
+{
+ m_generalSettings.m_centerFrequency = value * 1000;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_filterI1_valueChanged(int value)
+{
+ m_settings.m_opAmpI1 = value;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_filterI2_valueChanged(int value)
+{
+ m_settings.m_opAmpI2 = value;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_filterQ1_valueChanged(int value)
+{
+ m_settings.m_opAmpQ1 = value;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_filterQ2_valueChanged(int value)
+{
+ m_settings.m_opAmpQ2 = value;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_decimation_valueChanged(int value)
+{
+ ui->decimationDisplay->setText(tr("1:%1").arg(1 << value));
+ m_settings.m_decimation = value;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4000LNAGain_valueChanged(int value)
+{
+ int gain = e4kIdxToLNAGain(value);
+ ui->e4000LNAGainDisplay->setText(tr("%1.%2").arg(gain / 10).arg(abs(gain % 10)));
+ m_settings.m_lnaGain = gain;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4kI_valueChanged(int value)
+{
+ m_settings.m_dcOfsI = value;
+ sendSettings();
+}
+
+void OsmoSDRGui::on_e4kQ_valueChanged(int value)
+{
+ m_settings.m_dcOfsQ = value;
+ sendSettings();
+}
+
+void OsmoSDRGui::updateHardware()
+{
+ m_updateTimer.stop();
+ Message* msg = OsmoSDRInput::MsgConfigureOsmoSDR::create(m_generalSettings, m_settings);
+ msg->submit(m_pluginAPI->getDSPEngineMessageQueue());
+}
diff --git a/plugins/samplesource/osmosdr/osmosdrgui.h b/plugins/samplesource/osmosdr/osmosdrgui.h
new file mode 100644
index 0000000..770b48e
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrgui.h
@@ -0,0 +1,76 @@
+#ifndef INCLUDE_OSMOSDRGUI_H
+#define INCLUDE_OSMOSDRGUI_H
+
+#include <QTimer>
+#include "plugin/plugingui.h"
+#include "osmosdrinput.h"
+
+class PluginAPI;
+
+namespace Ui {
+ class OsmoSDRGui;
+}
+
+class OsmoSDRGui : public PluginGUI {
+ Q_OBJECT
+
+public:
+ explicit OsmoSDRGui(PluginAPI* pluginAPI, QWidget* parent = NULL);
+ ~OsmoSDRGui();
+ void destroy();
+
+ void resetToDefaults();
+ QByteArray serializeGeneral() const;
+ bool deserializeGeneral(const QByteArray&data);
+ quint64 getCenterFrequency() const;
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+ bool handleMessage(Message* message);
+
+private:
+ /*
+ Ui::OsmoSDRGui* ui;
+
+ MessageQueue* m_msgQueue;
+ OsmoSDRInput::Settings m_settings;
+ QTimer m_updateTimer;
+ */
+
+ Ui::OsmoSDRGui* ui;
+
+ PluginAPI* m_pluginAPI;
+ SampleSource::GeneralSettings m_generalSettings;
+ OsmoSDRInput::Settings m_settings;
+ QTimer m_updateTimer;
+ std::vector<int> m_gains;
+ SampleSource* m_sampleSource;
+
+ void displaySettings();
+ void sendSettings();
+ int e4kLNAGainToIdx(int gain) const;
+ int e4kIdxToLNAGain(int idx) const;
+
+private slots:
+ void on_iqSwap_toggled(bool checked);
+ void on_e4000MixerGain_currentIndexChanged(int index);
+ void on_e4000MixerEnh_currentIndexChanged(int index);
+ void on_e4000if1_currentIndexChanged(int index);
+ void on_e4000if2_currentIndexChanged(int index);
+ void on_e4000if3_currentIndexChanged(int index);
+ void on_e4000if4_currentIndexChanged(int index);
+ void on_e4000if5_currentIndexChanged(int index);
+ void on_e4000if6_currentIndexChanged(int index);
+ void on_centerFrequency_changed(quint64 value);
+ void on_filterI1_valueChanged(int value);
+ void on_filterI2_valueChanged(int value);
+ void on_filterQ1_valueChanged(int value);
+ void on_filterQ2_valueChanged(int value);
+ void on_decimation_valueChanged(int value);
+ void on_e4000LNAGain_valueChanged(int value);
+ void on_e4kI_valueChanged(int value);
+ void on_e4kQ_valueChanged(int value);
+
+ void updateHardware();
+};
+
+#endif // INCLUDE_OSMOSDRGUI_H
diff --git a/plugins/samplesource/osmosdr/osmosdrgui.ui b/plugins/samplesource/osmosdr/osmosdrgui.ui
new file mode 100644
index 0000000..1ad4a88
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrgui.ui
@@ -0,0 +1,773 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>OsmoSDRGui</class>
+ <widget class="QWidget" name="OsmoSDRGui">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>253</width>
+ <height>224</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>OsmoSDR</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="margin">
+ <number>2</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="ValueDial" name="centerFrequency" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <family>Monospace</family>
+ <pointsize>20</pointsize>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="toolTip">
+ <string>Tuner center frequency in kHz</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_13">
+ <property name="text">
+ <string>Decimation</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="decimation">
+ <property name="toolTip">
+ <string>Signal decimation factor</string>
+ </property>
+ <property name="maximum">
+ <number>5</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="tickPosition">
+ <enum>QSlider::TicksAbove</enum>
+ </property>
+ <property name="tickInterval">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="decimationDisplay">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>1:1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_11">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>LNA</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSlider" name="e4000LNAGain">
+ <property name="toolTip">
+ <string>LNA amplification</string>
+ </property>
+ <property name="maximum">
+ <number>12</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="e4000LNAGainDisplay">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>-5.0</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>10</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_19">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Mix</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="e4000MixerGain">
+ <property name="toolTip">
+ <string>Tuner mixer amplification</string>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QComboBox::AdjustToMinimumContentsLength</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>4</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>12</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_20">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Enh.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="e4000MixerEnh">
+ <property name="toolTip">
+ <string>Tuner LNA amplification enhancement</string>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QComboBox::AdjustToMinimumContentsLength</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>off</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>5</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>7</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line_5">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="iqSwap">
+ <property name="text">
+ <string>Swap IQ</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="e4000if2">
+ <property name="toolTip">
+ <string>Tuner IF 2 amplification</string>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QComboBox::AdjustToContents</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>0</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>9</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QComboBox" name="e4000if4">
+ <property name="toolTip">
+ <string>Tuner IF 4 amplification</string>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QComboBox::AdjustToContents</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>0</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>1</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>2</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="label_18">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>IF4</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="label_25">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>IF5</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QComboBox" name="e4000if5">
+ <property name="toolTip">
+ <string>Tuner IF 5 amplification</string>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QComboBox::AdjustToContents</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>9</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>12</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>15</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QLabel" name="label_26">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>IF6</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QComboBox" name="e4000if3">
+ <property name="toolTip">
+ <string>Tuner IF 3 amplification</string>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QComboBox::AdjustToContents</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>0</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>9</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_23">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>IF2</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QComboBox" name="e4000if6">
+ <property name="toolTip">
+ <string>Tuner IF 6 amplification</string>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QComboBox::AdjustToContents</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>6</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>9</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>12</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>15</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_24">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>IF3</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="e4000if1">
+ <property name="toolTip">
+ <string>Tuner IF 1 amplification</string>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QComboBox::AdjustToContents</enum>
+ </property>
+ <item>
+ <property name="text">
+ <string>-3</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>6</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>IF1</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="0" column="2">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Poti 2</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>I</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QSlider" name="filterI2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>I DC offset adjustment</string>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSlider" name="filterI1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>I path amplification</string>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSlider" name="filterQ1">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Q path amplification</string>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QSlider" name="filterQ2">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Q DC offset adjustment</string>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <widget class="QSlider" name="e4kQ">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Q DC offset adjustment</string>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>E4000</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Q</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Poti 1</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3">
+ <widget class="QSlider" name="e4kI">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Q path amplification</string>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ValueDial</class>
+ <extends>QWidget</extends>
+ <header>gui/valuedial.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/samplesource/osmosdr/osmosdrinput.cpp b/plugins/samplesource/osmosdr/osmosdrinput.cpp
new file mode 100644
index 0000000..8e724cf
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrinput.cpp
@@ -0,0 +1,479 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include <string.h>
+#include <errno.h>
+#include "osmosdrinput.h"
+#include "osmosdrthread.h"
+#include "osmosdrgui.h"
+#include "util/simpleserializer.h"
+
+MessageRegistrator OsmoSDRInput::MsgConfigureOsmoSDR::ID("MsgConfigureOsmoSDR");
+
+OsmoSDRInput::Settings::Settings() :
+ m_swapIQ(false),
+ m_decimation(3),
+ m_lnaGain(-50),
+ m_mixerGain(40),
+ m_mixerEnhancement(0),
+ m_if1gain(-30),
+ m_if2gain(0),
+ m_if3gain(0),
+ m_if4gain(0),
+ m_if5gain(30),
+ m_if6gain(30),
+ m_opAmpI1(0),
+ m_opAmpI2(0),
+ m_opAmpQ1(0),
+ m_opAmpQ2(0),
+ m_dcOfsI(0),
+ m_dcOfsQ(0)
+{
+}
+
+void OsmoSDRInput::Settings::resetToDefaults()
+{
+ m_swapIQ = false;
+ m_decimation = 3;
+ m_lnaGain = -50;
+ m_mixerGain = 40;
+ m_mixerEnhancement = 0;
+ m_if1gain = -30;
+ m_if2gain = 0;
+ m_if3gain = 0;
+ m_if4gain = 0;
+ m_if5gain = 30;
+ m_if6gain = 30;
+ m_opAmpI1 = 0;
+ m_opAmpI2 = 0;
+ m_opAmpQ1 = 0;
+ m_opAmpQ2 = 0;
+ m_dcOfsI = 0;
+ m_dcOfsQ = 0;
+}
+
+QByteArray OsmoSDRInput::Settings::serialize() const
+{
+ SimpleSerializer s(1);
+ s.writeBool(1, m_swapIQ);
+ s.writeS32(2, m_decimation);
+ s.writeS32(3, m_lnaGain);
+ s.writeS32(4, m_mixerGain);
+ s.writeS32(5, m_mixerEnhancement);
+ s.writeS32(6, m_if1gain);
+ s.writeS32(7, m_if2gain);
+ s.writeS32(8, m_if3gain);
+ s.writeS32(9, m_if4gain);
+ s.writeS32(10, m_if5gain);
+ s.writeS32(11, m_if6gain);
+ s.writeS32(12, m_opAmpI1);
+ s.writeS32(13, m_opAmpI2);
+ s.writeS32(14, m_opAmpQ1);
+ s.writeS32(15, m_opAmpQ2);
+ s.writeS32(16, m_dcOfsI);
+ s.writeS32(17, m_dcOfsQ);
+ return s.final();
+}
+
+bool OsmoSDRInput::Settings::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if(!d.isValid()) {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1) {
+ d.readBool(1, &m_swapIQ, false);
+ d.readS32(2, &m_decimation, 3);
+ d.readS32(3, &m_lnaGain, -50);
+ d.readS32(4, &m_mixerGain, 40);
+ d.readS32(5, &m_mixerEnhancement, 0);
+ d.readS32(6, &m_if1gain, -30);
+ d.readS32(7, &m_if2gain, 0);
+ d.readS32(8, &m_if3gain, 0);
+ d.readS32(9, &m_if4gain, 0);
+ d.readS32(10, &m_if5gain, 30);
+ d.readS32(11, &m_if6gain, 30);
+ d.readS32(12, &m_opAmpI1, 0);
+ d.readS32(13, &m_opAmpI2, 0);
+ d.readS32(14, &m_opAmpQ1, 0);
+ d.readS32(15, &m_opAmpQ2, 0);
+ d.readS32(16, &m_dcOfsI, 0);
+ d.readS32(17, &m_dcOfsQ, 0);
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+#if 0
+OsmoSDRInput::Settings::Settings() :
+{
+}
+
+QString OsmoSDRInput::Settings::serialize() const
+{
+ return QString("osmosdr:a:%1:%2:%3:%4:%5:%6:%7:%8:%9:%10:%11:%12:%13:%14:%15:%16:%17:%18")
+ .arg(centerFrequency)
+ .arg(swapIQ ? 1 : 0)
+ .arg(decimation)
+ .arg(lnaGain)
+ .arg(mixerGain)
+ .arg(mixerEnhancement)
+ .arg(if1gain)
+ .arg(if2gain)
+ .arg(if3gain)
+ .arg(if4gain)
+ .arg(if5gain)
+ .arg(if6gain)
+ .arg(opAmpI1)
+ .arg(opAmpI2)
+ .arg(opAmpQ1)
+ .arg(opAmpQ2)
+ .arg(dcOfsI)
+ .arg(dcOfsQ);
+}
+
+bool OsmoSDRInput::Settings::deserialize(const QString& settings)
+{
+ QStringList list = settings.split(":");
+ if(list.size() < 2)
+ return false;
+ if(list[0] != "osmosdr")
+ return false;
+
+ if(list[1] == "a") {
+ bool ok;
+ if(list.size() != 20)
+ return false;
+ centerFrequency = list[2].toLongLong(&ok);
+ if(!ok)
+ return false;
+ swapIQ = (list[3].toInt(&ok) != 0) ? true : false;
+ if(!ok)
+ return false;
+ decimation = list[4].toInt(&ok);
+ if(!ok)
+ return false;
+ lnaGain = list[5].toInt(&ok);
+ if(!ok)
+ return false;
+ mixerGain = list[6].toInt(&ok);
+ if(!ok)
+ return false;
+ mixerEnhancement = list[7].toInt(&ok);
+ if(!ok)
+ return false;
+ if1gain = list[8].toInt(&ok);
+ if(!ok)
+ return false;
+ if2gain = list[9].toInt(&ok);
+ if(!ok)
+ return false;
+ if3gain = list[10].toInt(&ok);
+ if(!ok)
+ return false;
+ if4gain = list[11].toInt(&ok);
+ if(!ok)
+ return false;
+ if5gain = list[12].toInt(&ok);
+ if(!ok)
+ return false;
+ if6gain = list[13].toInt(&ok);
+ if(!ok)
+ return false;
+ opAmpI1 = list[14].toInt(&ok);
+ if(!ok)
+ return false;
+ opAmpI2 = list[15].toInt(&ok);
+ if(!ok)
+ return false;
+ opAmpQ1 = list[16].toInt(&ok);
+ if(!ok)
+ return false;
+ opAmpQ2 = list[17].toInt(&ok);
+ if(!ok)
+ return false;
+ dcOfsI = list[18].toInt(&ok);
+ if(!ok)
+ return false;
+ dcOfsQ = list[19].toInt(&ok);
+ if(!ok)
+ return false;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+MessageRegistrator OsmoSDRInput::MsgConfigureSourceOsmoSDR::ID("MsgConfigureSourceOsmoSDR");
+#endif
+OsmoSDRInput::OsmoSDRInput(MessageQueue* msgQueueToGUI) :
+ SampleSource(msgQueueToGUI),
+ m_settings(),
+ m_dev(NULL),
+ m_osmoSDRThread(NULL),
+ m_deviceDescription()
+{
+}
+
+OsmoSDRInput::~OsmoSDRInput()
+{
+ stopInput();
+}
+
+bool OsmoSDRInput::startInput(int device)
+{
+ QMutexLocker mutexLocker(&m_mutex);
+
+ if(m_dev != NULL)
+ stopInput();
+
+ char vendor[256];
+ char product[256];
+ char serial[256];
+ int res;
+
+ if(!m_sampleFifo.setSize(524288)) {
+ qCritical("Could not allocate SampleFifo");
+ return false;
+ }
+
+ if((res = osmosdr_open(&m_dev, device)) < 0) {
+ qCritical("could not open OsmoSDR #%d: %s", device, strerror(errno));
+ return false;
+ }
+
+ vendor[0] = '\0';
+ product[0] = '\0';
+ serial[0] = '\0';
+ if((res = osmosdr_get_usb_strings(m_dev, vendor, product, serial)) < 0) {
+ qCritical("error accessing USB device");
+ goto failed;
+ }
+ qDebug("OsmoSDRInput open: %s %s, SN: %s", vendor, product, serial);
+ m_deviceDescription = QString("%1 (SN %2)").arg(product).arg(serial);
+
+ if((res = osmosdr_set_tuner_gain_mode(m_dev, 1)) < 0) {
+ qCritical("error setting tuner gain mode");
+ goto failed;
+ }
+
+ if((res = osmosdr_reset_buffer(m_dev)) < 0) {
+ qCritical("could not reset USB EP buffers: %s", strerror(errno));
+ goto failed;
+ }
+
+ if((m_osmoSDRThread = new OsmoSDRThread(m_dev, &m_sampleFifo)) == NULL) {
+ qFatal("out of memory");
+ goto failed;
+ }
+ m_osmoSDRThread->startWork();
+
+ mutexLocker.unlock();
+ applySettings(m_generalSettings, m_settings, true);
+
+ qDebug("OsmoSDRInput: start");
+
+ return true;
+
+failed:
+ stopInput();
+ return false;
+}
+
+void OsmoSDRInput::stopInput()
+{
+ QMutexLocker mutexLocker(&m_mutex);
+
+ if(m_osmoSDRThread != NULL) {
+ m_osmoSDRThread->stopWork();
+ delete m_osmoSDRThread;
+ m_osmoSDRThread = NULL;
+ }
+ if(m_dev != NULL) {
+ osmosdr_close(m_dev);
+ m_dev = NULL;
+ }
+ m_deviceDescription.clear();
+}
+
+const QString& OsmoSDRInput::getDeviceDescription() const
+{
+ return m_deviceDescription;
+}
+
+int OsmoSDRInput::getSampleRate() const
+{
+ return 4000000 / (1 << m_settings.m_decimation);
+}
+
+quint64 OsmoSDRInput::getCenterFrequency() const
+{
+ return m_generalSettings.m_centerFrequency;
+}
+
+bool OsmoSDRInput::handleMessage(Message* message)
+{
+ if(message->id() == MsgConfigureOsmoSDR::ID()) {
+ MsgConfigureOsmoSDR* conf = (MsgConfigureOsmoSDR*)message;
+ if(!applySettings(conf->getGeneralSettings(), conf->getSettings(), false))
+ qDebug("OsmoSDR config error");
+ message->completed();
+ return true;
+ } else {
+ return false;
+ }
+
+ /*
+ if(cmd->sourceType() != DSPCmdConfigureSourceOsmoSDR::SourceType)
+ return false;
+ if(!applySettings(((DSPCmdConfigureSourceOsmoSDR*)cmd)->getSettings(), false))
+ qDebug("OsmoSDR config error");
+ cmd->completed();
+ return true;
+ */
+ return false;
+}
+
+bool OsmoSDRInput::applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force)
+{
+ QMutexLocker mutexLocker(&m_mutex);
+
+ if((m_generalSettings.m_centerFrequency != generalSettings.m_centerFrequency) || force) {
+ m_generalSettings.m_centerFrequency = generalSettings.m_centerFrequency;
+ if(m_dev != NULL) {
+ if(osmosdr_set_center_freq(m_dev, m_generalSettings.m_centerFrequency) != 0)
+ qDebug("osmosdr_set_center_freq(%lld) failed", m_generalSettings.m_centerFrequency);
+ }
+ }
+
+ if((m_settings.m_swapIQ != settings.m_swapIQ) || force) {
+ m_settings.m_swapIQ = settings.m_swapIQ;
+ if(m_dev != NULL) {
+ if(osmosdr_set_fpga_iq_swap(m_dev, m_settings.m_swapIQ ? 1 : 0) == 0)
+ qDebug("osmosdr_set_fpga_iq_swap() failed");
+ }
+ }
+
+ if((m_settings.m_decimation != settings.m_decimation) || force) {
+ m_settings.m_decimation = settings.m_decimation;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_fpga_decimation(m_dev, m_settings.m_decimation))
+ qDebug("osmosdr_set_fpga_decimation() failed");
+ }
+ }
+
+ if((m_settings.m_lnaGain != settings.m_lnaGain) || force) {
+ m_settings.m_lnaGain = settings.m_lnaGain;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_lna_gain(m_dev, m_settings.m_lnaGain))
+ qDebug("osmosdr_set_tuner_lna_gain() failed");
+ }
+ }
+
+ if((m_settings.m_mixerGain != settings.m_mixerGain) || force) {
+ m_settings.m_mixerGain = settings.m_mixerGain;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_mixer_gain(m_dev, m_settings.m_mixerGain))
+ qDebug("osmosdr_set_tuner_mixer_gain() failed");
+ }
+ }
+
+ if((m_settings.m_mixerEnhancement != settings.m_mixerEnhancement) || force) {
+ m_settings.m_mixerEnhancement = settings.m_mixerEnhancement;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_mixer_enh(m_dev, m_settings.m_mixerEnhancement))
+ qDebug("osmosdr_set_tuner_mixer_enh() failed");
+ }
+ }
+
+ if((m_settings.m_if1gain != settings.m_if1gain) || force) {
+ m_settings.m_if1gain = settings.m_if1gain;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_if_gain(m_dev, 1, m_settings.m_if1gain))
+ qDebug("osmosdr_set_tuner_if_gain(1) failed");
+ }
+ }
+
+ if((m_settings.m_if2gain != settings.m_if2gain) || force) {
+ m_settings.m_if2gain = settings.m_if2gain;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_if_gain(m_dev, 2, m_settings.m_if2gain))
+ qDebug("osmosdr_set_tuner_if_gain(2) failed");
+ }
+ }
+
+ if((m_settings.m_if3gain != settings.m_if3gain) || force) {
+ m_settings.m_if3gain = settings.m_if3gain;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_if_gain(m_dev, 3, m_settings.m_if3gain))
+ qDebug("osmosdr_set_tuner_if_gain(3) failed");
+ }
+ }
+
+ if((m_settings.m_if4gain != settings.m_if4gain) || force) {
+ m_settings.m_if4gain = settings.m_if4gain;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_if_gain(m_dev, 4, m_settings.m_if4gain))
+ qDebug("osmosdr_set_tuner_if_gain(4) failed");
+ }
+ }
+
+ if((m_settings.m_if5gain != settings.m_if5gain) || force) {
+ m_settings.m_if5gain = settings.m_if5gain;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_if_gain(m_dev, 5, m_settings.m_if5gain))
+ qDebug("osmosdr_set_tuner_if_gain(5) failed");
+ }
+ }
+
+ if((m_settings.m_if6gain != settings.m_if6gain) || force) {
+ m_settings.m_if6gain = settings.m_if6gain;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_if_gain(m_dev, 6, m_settings.m_if6gain))
+ qDebug("osmosdr_set_tuner_if_gain(6) failed");
+ }
+ }
+
+ if((m_settings.m_opAmpI1 != settings.m_opAmpI1) || (m_settings.m_opAmpI2 != settings.m_opAmpI2) ||
+ (m_settings.m_opAmpQ1 != settings.m_opAmpQ1) || (m_settings.m_opAmpQ2 != settings.m_opAmpQ2) ||
+ force) {
+ m_settings.m_opAmpI1 = settings.m_opAmpI1;
+ m_settings.m_opAmpI2 = settings.m_opAmpI2;
+ m_settings.m_opAmpQ1 = settings.m_opAmpQ1;
+ m_settings.m_opAmpQ2 = settings.m_opAmpQ2;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_iq_amp(m_dev, m_settings.m_opAmpI1, m_settings.m_opAmpI2, m_settings.m_opAmpQ1, m_settings.m_opAmpQ2))
+ qDebug("osmosdr_set_iq_amp(1) failed");
+ }
+ }
+
+ if((m_settings.m_dcOfsI != settings.m_dcOfsI) || (m_settings.m_dcOfsQ != settings.m_dcOfsQ) ||
+ force) {
+ m_settings.m_dcOfsI = settings.m_dcOfsI;
+ m_settings.m_dcOfsQ = settings.m_dcOfsQ;
+ if(m_dev != NULL) {
+ if(!osmosdr_set_tuner_dc_offset(m_dev, m_settings.m_dcOfsI, m_settings.m_dcOfsQ))
+ qDebug("osmosdr_set_tuner_dc_offset() failed");
+ }
+ }
+
+ return true;
+}
diff --git a/plugins/samplesource/osmosdr/osmosdrinput.h b/plugins/samplesource/osmosdr/osmosdrinput.h
new file mode 100644
index 0000000..9cea94d
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrinput.h
@@ -0,0 +1,99 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_OSMOSDRINPUT_H
+#define INCLUDE_OSMOSDRINPUT_H
+
+#include "dsp/samplesource/samplesource.h"
+#include <osmosdr.h>
+#include <QString>
+
+class OsmoSDRThread;
+
+class OsmoSDRInput : public SampleSource {
+public:
+ struct Settings {
+ bool m_swapIQ;
+ qint32 m_decimation;
+ qint32 m_lnaGain;
+ qint32 m_mixerGain;
+ qint32 m_mixerEnhancement;
+ qint32 m_if1gain;
+ qint32 m_if2gain;
+ qint32 m_if3gain;
+ qint32 m_if4gain;
+ qint32 m_if5gain;
+ qint32 m_if6gain;
+ qint32 m_opAmpI1;
+ qint32 m_opAmpI2;
+ qint32 m_opAmpQ1;
+ qint32 m_opAmpQ2;
+ qint32 m_dcOfsI;
+ qint32 m_dcOfsQ;
+
+ Settings();
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+ };
+
+ class MsgConfigureOsmoSDR : public Message {
+ public:
+ static MessageRegistrator ID;
+
+ const GeneralSettings& getGeneralSettings() const { return m_generalSettings; }
+ const Settings& getSettings() const { return m_settings; }
+
+ static MsgConfigureOsmoSDR* create(const GeneralSettings& generalSettings, const Settings& settings)
+ {
+ return new MsgConfigureOsmoSDR(generalSettings, settings);
+ }
+
+ protected:
+ GeneralSettings m_generalSettings;
+ Settings m_settings;
+
+ MsgConfigureOsmoSDR(const GeneralSettings& generalSettings, const Settings& settings) :
+ Message(ID()),
+ m_generalSettings(generalSettings),
+ m_settings(settings)
+ { }
+ };
+
+ OsmoSDRInput(MessageQueue* msgQueueToGUI);
+ ~OsmoSDRInput();
+
+ bool startInput(int device);
+ void stopInput();
+
+ const QString& getDeviceDescription() const;
+ int getSampleRate() const;
+ quint64 getCenterFrequency() const;
+
+ bool handleMessage(Message* message);
+
+private:
+ QMutex m_mutex;
+ Settings m_settings;
+ osmosdr_dev_t* m_dev;
+ OsmoSDRThread* m_osmoSDRThread;
+ QString m_deviceDescription;
+
+ bool applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force);
+};
+
+#endif // INCLUDE_OSMOSDRINPUT_H
diff --git a/plugins/samplesource/osmosdr/osmosdrplugin.cpp b/plugins/samplesource/osmosdr/osmosdrplugin.cpp
new file mode 100644
index 0000000..2b06922
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrplugin.cpp
@@ -0,0 +1,67 @@
+#include <QtPlugin>
+#include <QAction>
+#include <osmosdr.h>
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+#include "osmosdrplugin.h"
+#include "osmosdrgui.h"
+
+const PluginDescriptor OsmoSDRPlugin::m_pluginDescriptor = {
+ displayedName: QString("OsmoSDR Input"),
+ version: QString("---"),
+ copyright: QString("(c) Christian Daniel"),
+ website: QString("http://sdr.osmocom.org/trac/wiki/osmo-sdr"),
+ licenseIsGPL: true,
+ sourceCodeURL: QString("http://cgit.osmocom.org/cgit/osmo-sdr")
+};
+
+OsmoSDRPlugin::OsmoSDRPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& OsmoSDRPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void OsmoSDRPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ m_pluginAPI->registerSampleSource("org.osmocom.sdr.samplesource.osmo-sdr", this);
+}
+
+PluginInterface::SampleSourceDevices OsmoSDRPlugin::enumSampleSources()
+{
+ SampleSourceDevices result;
+ int count = osmosdr_get_device_count();
+ char vendor[256];
+ char product[256];
+ char serial[256];
+
+ for(int i = 0; i < count; i++) {
+ vendor[0] = '\0';
+ product[0] = '\0';
+ serial[0] = '\0';
+
+ if(osmosdr_get_device_usb_strings(i, vendor, product, serial) != 0)
+ continue;
+ QString displayedName(QString("OsmoSDR #%1 (#%2)").arg(i + 1).arg(serial));
+ SimpleSerializer s(1);
+ s.writeS32(1, i);
+ result.append(SampleSourceDevice(displayedName, "org.osmocom.sdr.samplesource.osmo-sdr", s.final()));
+ }
+ return result;
+}
+
+PluginGUI* OsmoSDRPlugin::createSampleSource(const QString& sourceName, const QByteArray& address)
+{
+ if(sourceName == "org.osmocom.sdr.samplesource.osmo-sdr") {
+ return new OsmoSDRGui(m_pluginAPI);
+ } else {
+ return NULL;
+ }
+}
+
+Q_EXPORT_PLUGIN2(osmosdrPlugin, OsmoSDRPlugin);
diff --git a/plugins/samplesource/osmosdr/osmosdrplugin.h b/plugins/samplesource/osmosdr/osmosdrplugin.h
new file mode 100644
index 0000000..65f541b
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrplugin.h
@@ -0,0 +1,26 @@
+#ifndef INCLUDE_OSMOSDRPLUGIN_H
+#define INCLUDE_OSMOSDRPLUGIN_H
+
+#include <QObject>
+#include "plugin/plugininterface.h"
+
+class OsmoSDRPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+
+public:
+ explicit OsmoSDRPlugin(QObject* parent = NULL);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ SampleSourceDevices enumSampleSources();
+ PluginGUI* createSampleSource(const QString& sourceName, const QByteArray& address);
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+
+ PluginAPI* m_pluginAPI;
+};
+
+#endif // INCLUDE_OSMOSDRPLUGIN_H
diff --git a/plugins/samplesource/osmosdr/osmosdrthread.cpp b/plugins/samplesource/osmosdr/osmosdrthread.cpp
new file mode 100644
index 0000000..25fcfdb
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrthread.cpp
@@ -0,0 +1,116 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include <stdio.h>
+#include <errno.h>
+#include "osmosdrthread.h"
+#include "dsp/samplefifo.h"
+
+OsmoSDRThread::OsmoSDRThread(osmosdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent) :
+ QThread(parent),
+ m_running(false),
+ m_dev(dev),
+ m_sampleFifo(sampleFifo)
+{
+}
+
+OsmoSDRThread::~OsmoSDRThread()
+{
+ stopWork();
+}
+
+void OsmoSDRThread::startWork()
+{
+ m_startWaitMutex.lock();
+ start();
+ while(!m_running)
+ m_startWaiter.wait(&m_startWaitMutex, 100);
+ m_startWaitMutex.unlock();
+}
+
+void OsmoSDRThread::stopWork()
+{
+ m_running = false;
+ wait();
+}
+
+void OsmoSDRThread::run()
+{
+ int res;
+
+ m_running = true;
+ m_startWaiter.wakeAll();
+
+ //m_f = fopen("/tmp/samples.bin", "wb");
+
+ while(m_running) {
+ if((res = osmosdr_read_async(m_dev, &OsmoSDRThread::callbackHelper, this, 16, sizeof(Sample) * 8192 * 2)) < 0) {
+ qCritical("OsmoSDRThread: async error: %s", strerror(errno));
+ break;
+ }
+ }
+
+ m_running = false;
+}
+
+void OsmoSDRThread::checkData(const quint8* buf, qint32 len)
+{
+ const Sample* s = (const Sample*)buf;
+ len /= sizeof(Sample);
+
+ while(len) {
+ if((s->i != m_nextI) || (s->q != m_nextQ)) {
+ qDebug("continuity error after %llu samples", m_samplePos);
+ m_samplePos = 0;
+ m_nextI = s->i - 1;
+ m_nextQ = s->q + 1;
+ } else {
+ m_nextI--;
+ m_nextQ++;
+ m_samplePos++;
+ }
+ len--;
+ s++;
+ }
+}
+
+void OsmoSDRThread::callback(const quint8* buf, qint32 len)
+{
+ /*
+ qDebug("%d", len);
+*/
+ /*
+ for(int i = 0; i < len / 2; i += 2) {
+ ((qint16*)buf)[i] = sin((float)(cntr) * 1024* 2.0 * M_PI / 65536.0) * 32000.0;
+ ((qint16*)buf)[i + 1] = -cos((float)(cntr++) * 1024*2.0 * M_PI / 65536.0) * 32000.0;
+ }
+ */
+
+ //m_sampleFifo->write((SampleVector::const_iterator)((Sample*)buf), (SampleVector::const_iterator)((Sample*)(buf + len)));
+ //fwrite(buf, 1, len, m_f);
+ //checkData(buf, len);
+
+ m_sampleFifo->write(buf, len);
+ if(!m_running)
+ osmosdr_cancel_async(m_dev);
+}
+
+void OsmoSDRThread::callbackHelper(unsigned char* buf, uint32_t len, void* ctx)
+{
+ OsmoSDRThread* thread = (OsmoSDRThread*)ctx;
+ thread->callback(buf, len);
+}
diff --git a/plugins/samplesource/osmosdr/osmosdrthread.h b/plugins/samplesource/osmosdr/osmosdrthread.h
new file mode 100644
index 0000000..0aa0142
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrthread.h
@@ -0,0 +1,66 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_OSMOSDRTHREAD_H
+#define INCLUDE_OSMOSDRTHREAD_H
+
+#include <QThread>
+#include <QMutex>
+#include <QWaitCondition>
+#include <osmosdr.h>
+
+class SampleFifo;
+
+class OsmoSDRThread : public QThread {
+ Q_OBJECT
+
+public:
+ OsmoSDRThread(osmosdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent = NULL);
+ ~OsmoSDRThread();
+
+ void startWork();
+ void stopWork();
+
+private:
+#pragma pack(push, 1)
+ struct Sample {
+ qint16 i;
+ qint16 q;
+ };
+#pragma pack(pop)
+
+ qint16 m_nextI;
+ qint16 m_nextQ;
+ quint64 m_samplePos;
+
+ QMutex m_startWaitMutex;
+ QWaitCondition m_startWaiter;
+ bool m_running;
+ FILE* m_f;
+
+ osmosdr_dev_t* m_dev;
+ SampleFifo* m_sampleFifo;
+
+ void run();
+
+ void checkData(const quint8* buf, qint32 len);
+ void callback(const quint8* buf, qint32 len);
+
+ static void callbackHelper(unsigned char* buf, uint32_t len, void* ctx);
+};
+
+#endif // INCLUDE_OSMOSDRTHREAD_H
diff --git a/plugins/samplesource/osmosdr/osmosdrupgrade.cpp b/plugins/samplesource/osmosdr/osmosdrupgrade.cpp
new file mode 100644
index 0000000..bba600b
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrupgrade.cpp
@@ -0,0 +1,518 @@
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QCursor>
+#include <QThread>
+#include <libusb.h>
+#include "osmosdrupgrade.h"
+#include "ui_osmosdrupgrade.h"
+
+#define OSMOSDR_USB_VID 0x16c0
+#define OSMOSDR_USB_PID 0x0763
+
+// DFU commands
+#define DFU_DETACH 0x00
+#define DFU_DNLOAD 0x01
+#define DFU_UPLOAD 0x02
+#define DFU_GETSTATUS 0x03
+#define DFU_CLRSTATUS 0x04
+#define DFU_GETSTATE 0x05
+#define DFU_ABORT 0x06
+
+// DFU states
+#define ST_appIDLE 0
+#define ST_appDETACH 1
+#define ST_dfuIDLE 2
+#define ST_dfuDNLOAD_SYNC 3
+#define ST_dfuDNBUSY 4
+#define ST_dfuDNLOAD_IDLE 5
+#define ST_dfuMANIFEST_SYNC 6
+#define ST_dfuMANIFEST 7
+#define ST_dfuMANIFEST_WAIT_RST 8
+#define ST_dfuUPLOAD_IDLE 9
+#define ST_dfuERROR 10
+
+#define DFU_PACKETSIZE 512
+
+#pragma pack(push, 1)
+struct dfu_getstatus {
+ quint8 bStatus;
+ quint8 bwPollTimeout[3];
+ quint8 bState;
+ quint8 iString;
+};
+#pragma pack(pop)
+
+
+OsmoSDRUpgrade::OsmoSDRUpgrade(QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::OsmoSDRUpgrade),
+ m_usb(NULL)
+{
+ ui->setupUi(this);
+ connect(&m_searchDeviceTimer, SIGNAL(timeout()), this, SLOT(searchDeviceTick()));
+}
+
+OsmoSDRUpgrade::~OsmoSDRUpgrade()
+{
+ delete ui;
+}
+
+void OsmoSDRUpgrade::on_browse_clicked()
+{
+ QString filename = QFileDialog::getOpenFileName(this, tr("OsmoSDR Firmware File"), QString(), tr("OsmoSDR Firmware (*.ofw);;All Files (*)"));
+ if(filename.isEmpty())
+ return;
+
+ QFile file(filename);
+ if(file.size() > 10 * 1024 * 1024) {
+ QMessageBox::critical(this, tr("File Open Error"), tr("File is too big to be a firmware update for OsmoSDR."));
+ return;
+ }
+
+ QByteArray dfuApp;
+ QByteArray radioApp;
+ QByteArray fpgaBin;
+ mz_zip_archive zip;
+ memset(&zip, 0x00, sizeof(zip));
+
+ if(!mz_zip_reader_init_file(&zip, qPrintable(filename), 0)) {
+ mz_zip_reader_end(&zip);
+ QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
+ return;
+ }
+
+ int dfuAppIndex = mz_zip_reader_locate_file(&zip, "dfuapp.bin", NULL, 0);
+ int radioAppIndex = mz_zip_reader_locate_file(&zip, "radioapp.bin", NULL, 0);
+ int fpgaIndex = mz_zip_reader_locate_file(&zip, "fpga.bin", NULL, 0);
+
+ if((dfuAppIndex < 0) && (radioAppIndex < 0) && (fpgaIndex < 0)) {
+ mz_zip_reader_end(&zip);
+ QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
+ return;
+ }
+
+ if(!mz_zip_reader_extract_to_callback(&zip, dfuAppIndex, zipHelper, &dfuApp, 0)) {
+ mz_zip_reader_end(&zip);
+ QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
+ return;
+ }
+
+ if(!mz_zip_reader_extract_to_callback(&zip, radioAppIndex, zipHelper, &radioApp, 0)) {
+ mz_zip_reader_end(&zip);
+ QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
+ return;
+ }
+
+ if(!mz_zip_reader_extract_to_callback(&zip, fpgaIndex, zipHelper, &fpgaBin, 0)) {
+ mz_zip_reader_end(&zip);
+ QMessageBox::critical(this, tr("File Open Error"), tr("File is not a valid OsmoSDR update."));
+ return;
+ }
+
+ mz_zip_reader_end(&zip);
+ m_dfuApp = dfuApp;
+ m_radioApp = radioApp;
+ m_fpgaBin = fpgaBin;
+
+ if(dfuAppIndex >= 0) {
+ ui->dfu->setEnabled(true);
+ ui->dfuSize->setText(tr("%1").arg(m_dfuApp.size()));
+ } else {
+ ui->dfu->setEnabled(false);
+ ui->dfuSize->setText(tr("-"));
+ }
+ if(radioAppIndex >= 0) {
+ ui->radio->setEnabled(true);
+ ui->radioSize->setText(tr("%1").arg(m_radioApp.size()));
+ } else {
+ ui->radio->setEnabled(false);
+ ui->dfuSize->setText(tr("-"));
+ }
+ if(fpgaIndex >= 0) {
+ ui->fpga->setEnabled(true);
+ ui->fpgaSize->setText(tr("%1").arg(m_fpgaBin.size()));
+ } else {
+ ui->fpga->setEnabled(false);
+ ui->fpgaSize->setText(tr("-"));
+ }
+
+
+ if((dfuAppIndex >= 0) && (radioAppIndex < 0) && (fpgaIndex < 0)) {
+ ui->dfu->setChecked(true);
+ ui->radio->setChecked(false);
+ ui->fpga->setChecked(false);
+ } else {
+ ui->dfu->setChecked(false);
+ ui->radio->setChecked(radioAppIndex >= 0);
+ ui->fpga->setChecked(fpgaIndex >= 0);
+ }
+
+ ui->fwBox->setEnabled(true);
+ ui->progressBox->setEnabled(true);
+
+ QFileInfo fi(file.fileName());
+ ui->filename->setText(fi.fileName());
+}
+
+void OsmoSDRUpgrade::on_dfu_toggled(bool checked)
+{
+ updateFlashButton();
+}
+
+void OsmoSDRUpgrade::on_radio_toggled(bool checked)
+{
+ updateFlashButton();
+}
+
+void OsmoSDRUpgrade::on_fpga_toggled(bool checked)
+{
+ updateFlashButton();
+}
+
+void OsmoSDRUpgrade::updateFlashButton()
+{
+ if(ui->dfu->isChecked() || ui->radio->isChecked() || ui->fpga->isChecked())
+ ui->progressBox->setEnabled(true);
+ else ui->progressBox->setEnabled(false);
+}
+
+void OsmoSDRUpgrade::on_start_clicked()
+{
+ ui->close->setEnabled(false);
+ ui->start->setEnabled(false);
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+
+ ui->log->clear();
+ log(tr("Starting flash operation..."));
+
+ if(libusb_init(&m_usb) < 0) {
+ fail(tr("Could not initialize libusb."));
+ return;
+ }
+
+ switchToDFU();
+
+ m_searchTries = 0;
+ m_searchDeviceTimer.start(250);
+}
+
+void OsmoSDRUpgrade::searchDeviceTick()
+{
+ m_searchTries++;
+ log(tr("Searching device (try %1)").arg(m_searchTries));
+
+ libusb_device_handle* device = libusb_open_device_with_vid_pid(m_usb, OSMOSDR_USB_VID, OSMOSDR_USB_PID);
+ if(device == NULL) {
+ if(m_searchTries >= 10) {
+ m_searchDeviceTimer.stop();
+ finish();
+ }
+ return;
+ }
+
+ m_searchDeviceTimer.stop();
+
+ libusb_device_descriptor deviceDescriptor;
+
+ if(libusb_get_descriptor(device, LIBUSB_DT_DEVICE, 0, (unsigned char*)&deviceDescriptor, sizeof(deviceDescriptor)) < 0) {
+ libusb_close(device);
+ fail(tr("Could not read device descriptor."));
+ return;
+ }
+
+ char sn[64];
+ memset(sn, 0x00, sizeof(sn));
+ libusb_get_string_descriptor_ascii(device, deviceDescriptor.iSerialNumber, (unsigned char*)sn, sizeof(sn));
+ sn[sizeof(sn) - 1] = '\0';
+
+ log(tr("OsmoSDR found (SN %1)").arg(sn));
+
+ quint8 buffer[255];
+ if(libusb_get_descriptor(device, LIBUSB_DT_CONFIG, 0, buffer, sizeof(buffer)) < 16) {
+ libusb_close(device);
+ fail(tr("Could not get a configuration descriptor"));
+ return;
+ }
+
+ if(buffer[15] != 1) {
+ libusb_close(device);
+ fail(tr("Device not in DFU mode as it should be"));
+ return;
+ }
+
+ quint8 state;
+
+ if(dfuGetState(device, &state) < 0) {
+ libusb_reset_device(device);
+ if(dfuGetState(device, &state) < 0) {
+ libusb_close(device);
+ fail(tr("Cannot get device DFU state"));
+ return;
+ }
+ }
+ if((state != ST_dfuIDLE) && (state != ST_dfuDNLOAD_IDLE)) {
+ dfuGetStatus(device);
+ libusb_close(device);
+ fail(tr("Device is not in dfuIDLE or dfuDNLOAD_IDLE state"));
+ return;
+ }
+
+ if(libusb_claim_interface(device, 0) < 0) {
+ libusb_close(device);
+ fail(tr("Could not claim interface"));
+ return;
+ }
+
+ QByteArray* image;
+ int altSetting;
+
+ while(true) {
+ if(ui->dfu->isChecked()) {
+ log(tr("Starting download of DFU application"));
+ image = &m_dfuApp;
+ altSetting = 1;
+ } else if(ui->radio->isChecked()) {
+ log(tr("Starting download of radio application"));
+ image = &m_radioApp;
+ altSetting = 0;
+ } else if(ui->fpga->isChecked()) {
+ log(tr("Starting download of FPGA image"));
+ image = &m_fpgaBin;
+ altSetting = 2;
+ } else {
+ log(tr("Switching back to radio application mode"));
+ dfuDetach(device);
+ break;
+ }
+
+ if(libusb_set_interface_alt_setting(device, 0, altSetting) < 0) {
+ libusb_close(device);
+ fail(tr("Could not set alternate interface setting to %1").arg(altSetting));
+ return;
+ }
+
+ ui->progress->setMaximum((image->size() / DFU_PACKETSIZE) + ((image->size() % DFU_PACKETSIZE) ? 1 : 0));
+
+ int blocknum = 0;
+ while((state == ST_dfuIDLE) || (state == ST_dfuDNLOAD_IDLE)) {
+ int offset = blocknum * DFU_PACKETSIZE;
+ int blocklen = (int)image->size() - offset;
+ if(blocklen < 0)
+ blocklen = 0;
+ if(blocklen > DFU_PACKETSIZE)
+ blocklen = DFU_PACKETSIZE;
+ if(dfuDownloadBlock(device, blocknum, (const quint8*)(image->data() + offset), blocklen) < 0) {
+ if(dfuGetState(device, &state) < 0) {
+ if(ui->dfu->isChecked())
+ break;
+ libusb_close(device);
+ fail(tr("Cannot get device DFU state"));
+ return;
+ }
+ dfuGetStatus(device);
+ libusb_close(device);
+ fail(tr("Error downloading block %1").arg(blocknum));
+ return;
+ }
+ dfuGetStatus(device);
+ if(blocklen == 0)
+ break;
+ blocknum++;
+ ui->progress->setValue(blocknum);
+ QApplication::processEvents();
+ }
+ log(tr("Download complete (state %1)").arg(state));
+
+ do {
+ if(dfuGetState(device, &state) < 0) {
+ if(ui->dfu->isChecked()) {
+ break;
+ } else {
+ libusb_close(device);
+ fail(tr("Cannot get device DFU state"));
+ return;
+ }
+ }
+ dfuGetStatus(device);
+ } while(state == ST_dfuMANIFEST);
+
+ if(ui->dfu->isChecked()) {
+ ui->dfu->setChecked(false);
+ m_searchTries = 0;
+ m_searchDeviceTimer.start(250);
+ libusb_close(device);
+ return;
+ } else if(ui->radio->isChecked()) {
+ ui->radio->setChecked(false);
+ } else if(ui->fpga->isChecked()) {
+ ui->fpga->setChecked(false);
+ }
+
+ QApplication::processEvents();
+ }
+
+ log(tr("Upgrade finished successfully"));
+ libusb_close(device);
+ finish();
+}
+
+void OsmoSDRUpgrade::switchToDFU()
+{
+ libusb_device_handle* device = libusb_open_device_with_vid_pid(m_usb, OSMOSDR_USB_VID, OSMOSDR_USB_PID);
+ if(device == NULL) {
+ log(tr("No OsmoSDR VID:PID %1:%2 found").arg(OSMOSDR_USB_VID, 4, 16, QChar('0')).arg(OSMOSDR_USB_PID, 4, 16, QChar('0')));
+ return;
+ }
+
+ quint8 buffer[255];
+ if(libusb_get_descriptor(device, LIBUSB_DT_CONFIG, 0, buffer, sizeof(buffer)) < 16) {
+ log(tr("Could not get a valid configuration descriptor"));
+ libusb_close(device);
+ return;
+ }
+
+ if(buffer[15] != 1) {
+ if(libusb_claim_interface(device, 0) < 0) {
+ log(tr("Could not claim interface"));
+ libusb_close(device);
+ return;
+ }
+
+ log(tr("Switching device to DFU mode"));
+ libusb_control_transfer(device, LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, 0x07, 0x0003, 0, NULL, 0, 50);
+ libusb_release_interface(device, 0);
+ } else {
+ log(tr("Device is already in DFU mode"));
+ }
+
+ libusb_close(device);
+}
+
+int OsmoSDRUpgrade::dfuGetState(libusb_device_handle* device, quint8* state)
+{
+ int res;
+
+ res = libusb_control_transfer(
+ device,
+ 0xa1,
+ DFU_GETSTATE,
+ 0,
+ 0,
+ (unsigned char*)state,
+ 1,
+ 1000);
+
+ return res;
+}
+
+int OsmoSDRUpgrade::dfuGetStatus(libusb_device_handle* device)
+{
+ int res;
+ struct dfu_getstatus getstatus;
+/*
+ static const char* statedesc[] = {
+ "appIDLE",
+ "appDETACH",
+ "dfuIDLE",
+ "dfuDNLOAD-SYNC",
+ "dfuDNBUSY",
+ "dfuDNLOAD-IDLE",
+ "dfuMANIFEST-SYNC",
+ "dfuMANIFEST",
+ "dfuMANIFEST-WAIT-RESET",
+ "dfuUPLOAD-IDLE",
+ "dfuERROR"
+ };
+*/
+
+ res = libusb_control_transfer(
+ device,
+ 0xa1,
+ DFU_GETSTATUS,
+ 0,
+ 0,
+ (unsigned char*)&getstatus,
+ sizeof(getstatus),
+ 1000);
+/*
+ if(res >= 0)
+ log(tr("OsmoSDR bStatus: %1, bState: %2 (%3)").arg(getstatus.bStatus).arg(statedesc[getstatus.bState]).arg(getstatus.bState));
+*/
+ return res;
+}
+
+int OsmoSDRUpgrade::dfuClrStatus(libusb_device_handle* device)
+{
+ return libusb_control_transfer(
+ device,
+ 0x21,
+ DFU_CLRSTATUS,
+ 0,
+ 0,
+ NULL,
+ 0,
+ 1000);
+}
+
+int OsmoSDRUpgrade::dfuDownloadBlock(libusb_device_handle* device, quint16 block, const quint8* data, quint16 len)
+{
+ return libusb_control_transfer(
+ device,
+ 0x21,
+ DFU_DNLOAD,
+ block,
+ 0,
+ (unsigned char*)data,
+ len,
+ 10000);
+}
+
+int OsmoSDRUpgrade::dfuDetach(libusb_device_handle* device)
+{
+ return libusb_control_transfer(
+ device,
+ 0x21,
+ DFU_DETACH,
+ 0,
+ 0,
+ NULL,
+ 0,
+ 1000);
+}
+
+void OsmoSDRUpgrade::fail(const QString& msg)
+{
+ log(tr("Fatal error: %1").arg(msg));
+ QMessageBox::critical(this, tr("OsmoSDR Upgrade Failed"), msg);
+ finish();
+}
+
+void OsmoSDRUpgrade::finish()
+{
+ if(m_usb != NULL) {
+ libusb_exit(m_usb);
+ m_usb = NULL;
+ }
+ QApplication::restoreOverrideCursor();
+ ui->start->setEnabled(true);
+ ui->close->setEnabled(true);
+}
+
+void OsmoSDRUpgrade::log(const QString& msg)
+{
+ ui->log->appendPlainText(msg);
+ ui->log->moveCursor(QTextCursor::End);
+}
+
+void OsmoSDRUpgrade::reject()
+{
+ if(ui->close->isEnabled())
+ return QDialog::reject();
+}
+
+size_t OsmoSDRUpgrade::zipHelper(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)
+{
+ QByteArray* bytes = (QByteArray*)pOpaque;
+ bytes->append((const char*)pBuf, n);
+ return n;
+}
diff --git a/plugins/samplesource/osmosdr/osmosdrupgrade.h b/plugins/samplesource/osmosdr/osmosdrupgrade.h
new file mode 100644
index 0000000..efd930b
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrupgrade.h
@@ -0,0 +1,60 @@
+#ifndef INCLUDE_OSMOSDRUPGRADE_H
+#define INCLUDE_OSMOSDRUPGRADE_H
+
+#include <QDialog>
+#include <QTimer>
+#include "util/miniz.h"
+
+typedef struct libusb_context libusb_context;
+typedef struct libusb_device_handle libusb_device_handle;
+
+namespace Ui {
+ class OsmoSDRUpgrade;
+}
+
+class OsmoSDRUpgrade : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit OsmoSDRUpgrade(QWidget* parent = NULL);
+ ~OsmoSDRUpgrade();
+
+private slots:
+ void on_browse_clicked();
+ void on_dfu_toggled(bool checked);
+ void on_radio_toggled(bool checked);
+ void on_fpga_toggled(bool checked);
+ void on_start_clicked();
+ void searchDeviceTick();
+
+private:
+ Ui::OsmoSDRUpgrade* ui;
+
+ libusb_context* m_usb;
+
+ QByteArray m_dfuApp;
+ QByteArray m_radioApp;
+ QByteArray m_fpgaBin;
+
+ int m_searchTries;
+ QTimer m_searchDeviceTimer;
+
+ void updateFlashButton();
+
+ void switchToDFU();
+ int dfuGetState(libusb_device_handle* device, quint8* state);
+ int dfuGetStatus(libusb_device_handle* device);
+ int dfuClrStatus(libusb_device_handle* device);
+ int dfuDownloadBlock(libusb_device_handle* device, quint16 block, const quint8* data, quint16 len);
+ int dfuDetach(libusb_device_handle* device);
+
+ void fail(const QString& msg);
+ void finish();
+ void log(const QString& msg);
+
+ void reject();
+
+ static size_t zipHelper(void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n);
+};
+
+#endif // INCLUDE_OSMOSDRUPGRADE_H
diff --git a/plugins/samplesource/osmosdr/osmosdrupgrade.ui b/plugins/samplesource/osmosdr/osmosdrupgrade.ui
new file mode 100644
index 0000000..fbb2939
--- /dev/null
+++ b/plugins/samplesource/osmosdr/osmosdrupgrade.ui
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>OsmoSDRUpgrade</class>
+ <widget class="QDialog" name="OsmoSDRUpgrade">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>382</width>
+ <height>503</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>OsmoSDR Firmware Upgrade</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="groupBox">
+ <property name="title">
+ <string>Firmware Archive</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="filename">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>256</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="textFormat">
+ <enum>Qt::PlainText</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="browse">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="fwBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="title">
+ <string>Firmware</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="1">
+ <widget class="QLabel" name="radioSize">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="fpgaSize">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Size (Bytes)</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="dfu">
+ <property name="text">
+ <string>DFU Application</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="radio">
+ <property name="text">
+ <string>Radio Application</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="fpga">
+ <property name="text">
+ <string>FPGA Image</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="dfuSize">
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="progressBox">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="title">
+ <string>Progress</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QProgressBar" name="progress">
+ <property name="value">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="start">
+ <property name="text">
+ <string>Flash!</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QPlainTextEdit" name="log">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="close">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>close</sender>
+ <signal>clicked()</signal>
+ <receiver>OsmoSDRUpgrade</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>331</x>
+ <y>247</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>190</x>
+ <y>134</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/plugins/samplesource/rtlsdr/CMakeLists.txt b/plugins/samplesource/rtlsdr/CMakeLists.txt
new file mode 100644
index 0000000..916bded
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/CMakeLists.txt
@@ -0,0 +1,47 @@
+project(rtlsdr)
+
+set(rtlsdr_SOURCES
+ rtlsdrgui.cpp
+ rtlsdrinput.cpp
+ rtlsdrplugin.cpp
+ rtlsdrthread.cpp
+)
+
+set(rtlsdr_HEADERS
+ rtlsdrgui.h
+ rtlsdrinput.h
+ rtlsdrplugin.h
+ rtlsdrthread.h
+)
+
+set(rtlsdr_FORMS
+ rtlsdrgui.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(rtlsdr_HEADERS_MOC ${rtlsdr_HEADERS})
+qt4_wrap_ui(rtlsdr_FORMS_HEADERS ${rtlsdr_FORMS})
+
+add_library(inputrtlsdr SHARED
+ ${rtlsdr_SOURCES}
+ ${rtlsdr_HEADERS_MOC}
+ ${rtlsdr_FORMS_HEADERS}
+)
+
+target_link_libraries(inputrtlsdr
+ ${QT_LIBRARIES}
+ ${LIBRTLSDR_LIBRARIES}
+ ${LIBUSB_LIBRARIES}
+)
diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.cpp b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp
new file mode 100644
index 0000000..a3d825c
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/rtlsdrgui.cpp
@@ -0,0 +1,149 @@
+#include "rtlsdrgui.h"
+#include "ui_rtlsdrgui.h"
+#include "plugin/pluginapi.h"
+
+RTLSDRGui::RTLSDRGui(PluginAPI* pluginAPI, QWidget* parent) :
+ PluginGUI(parent),
+ ui(new Ui::RTLSDRGui),
+ m_pluginAPI(pluginAPI),
+ m_settings(),
+ m_sampleSource(NULL)
+{
+ ui->setupUi(this);
+ ui->centerFrequency->setValueRange(7, 20000U, 2200000U);
+ connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware()));
+ displaySettings();
+
+ m_sampleSource = new RTLSDRInput(m_pluginAPI->getMainWindowMessageQueue());
+ m_pluginAPI->setSampleSource(m_sampleSource);
+}
+
+RTLSDRGui::~RTLSDRGui()
+{
+ delete ui;
+}
+
+void RTLSDRGui::destroy()
+{
+ delete this;
+}
+
+void RTLSDRGui::resetToDefaults()
+{
+ m_generalSettings.resetToDefaults();
+ m_settings.resetToDefaults();
+ displaySettings();
+ sendSettings();
+}
+
+QByteArray RTLSDRGui::serializeGeneral() const
+{
+ return m_generalSettings.serialize();
+}
+
+bool RTLSDRGui::deserializeGeneral(const QByteArray&data)
+{
+ if(m_generalSettings.deserialize(data)) {
+ displaySettings();
+ sendSettings();
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+quint64 RTLSDRGui::getCenterFrequency() const
+{
+ return m_generalSettings.m_centerFrequency;
+}
+
+QByteArray RTLSDRGui::serialize() const
+{
+ return m_settings.serialize();
+}
+
+bool RTLSDRGui::deserialize(const QByteArray& data)
+{
+ if(m_settings.deserialize(data)) {
+ displaySettings();
+ sendSettings();
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+bool RTLSDRGui::handleMessage(Message* message)
+{
+ if(message->id() == RTLSDRInput::MsgReportRTLSDR::ID()) {
+ m_gains = ((RTLSDRInput::MsgReportRTLSDR*)message)->getGains();
+ displaySettings();
+ message->completed();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void RTLSDRGui::displaySettings()
+{
+ ui->centerFrequency->setValue(m_generalSettings.m_centerFrequency / 1000);
+ ui->decimation->setValue(m_settings.m_decimation);
+
+ if(m_gains.size() > 0) {
+ int dist = abs(m_settings.m_gain - m_gains[0]);
+ int pos = 0;
+ for(uint i = 1; i < m_gains.size(); i++) {
+ if(abs(m_settings.m_gain - m_gains[i]) < dist) {
+ dist = abs(m_settings.m_gain - m_gains[i]);
+ pos = i;
+ }
+ }
+ ui->gainText->setText(tr("%1.%2").arg(m_gains[pos] / 10).arg(abs(m_gains[pos] % 10)));
+ ui->gain->setMaximum(m_gains.size() - 1);
+ ui->gain->setEnabled(true);
+ ui->gain->setValue(pos);
+ } else {
+ ui->gain->setMaximum(0);
+ ui->gain->setEnabled(false);
+ ui->gain->setValue(0);
+ }
+}
+
+void RTLSDRGui::sendSettings()
+{
+ if(!m_updateTimer.isActive())
+ m_updateTimer.start(100);
+}
+
+void RTLSDRGui::on_centerFrequency_changed(quint64 value)
+{
+ m_generalSettings.m_centerFrequency = value * 1000;
+ sendSettings();
+}
+
+void RTLSDRGui::on_gain_valueChanged(int value)
+{
+ if(value > m_gains.size())
+ return;
+ int gain = m_gains[value];
+ ui->gainText->setText(tr("%1.%2").arg(gain / 10).arg(abs(gain % 10)));
+ m_settings.m_gain = gain;
+ sendSettings();
+}
+
+void RTLSDRGui::on_decimation_valueChanged(int value)
+{
+ ui->decimationText->setText(tr("1:%1").arg(1 << value));
+ m_settings.m_decimation = value;
+ sendSettings();
+}
+
+void RTLSDRGui::updateHardware()
+{
+ RTLSDRInput::MsgConfigureRTLSDR* message = RTLSDRInput::MsgConfigureRTLSDR::create(m_generalSettings, m_settings);
+ message->submit(m_pluginAPI->getDSPEngineMessageQueue());
+ m_updateTimer.stop();
+}
diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.h b/plugins/samplesource/rtlsdr/rtlsdrgui.h
new file mode 100644
index 0000000..5a1944d
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/rtlsdrgui.h
@@ -0,0 +1,51 @@
+#ifndef INCLUDE_RTLSDRGUI_H
+#define INCLUDE_RTLSDRGUI_H
+
+#include <QTimer>
+#include "plugin/plugingui.h"
+#include "rtlsdrinput.h"
+
+class PluginAPI;
+
+namespace Ui {
+ class RTLSDRGui;
+}
+
+class RTLSDRGui : public PluginGUI {
+ Q_OBJECT
+
+public:
+ explicit RTLSDRGui(PluginAPI* pluginAPI, QWidget* parent = NULL);
+ ~RTLSDRGui();
+ void destroy();
+
+ void resetToDefaults();
+ QByteArray serializeGeneral() const;
+ bool deserializeGeneral(const QByteArray&data);
+ quint64 getCenterFrequency() const;
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+ bool handleMessage(Message* message);
+
+private:
+ Ui::RTLSDRGui* ui;
+
+ PluginAPI* m_pluginAPI;
+ SampleSource::GeneralSettings m_generalSettings;
+ RTLSDRInput::Settings m_settings;
+ QTimer m_updateTimer;
+ std::vector<int> m_gains;
+ SampleSource* m_sampleSource;
+
+ void displaySettings();
+ void sendSettings();
+
+private slots:
+ void on_centerFrequency_changed(quint64 value);
+ void on_gain_valueChanged(int value);
+ void on_decimation_valueChanged(int value);
+
+ void updateHardware();
+};
+
+#endif // INCLUDE_RTLSDRGUI_H
diff --git a/plugins/samplesource/rtlsdr/rtlsdrgui.ui b/plugins/samplesource/rtlsdr/rtlsdrgui.ui
new file mode 100644
index 0000000..ce2ea4c
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/rtlsdrgui.ui
@@ -0,0 +1,229 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>RTLSDRGui</class>
+ <widget class="QWidget" name="RTLSDRGui">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>132</width>
+ <height>82</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="windowTitle">
+ <string>RTL-SDR</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="ValueDial" name="centerFrequency" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>32</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <family>Monospace</family>
+ <pointsize>20</pointsize>
+ </font>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ <property name="toolTip">
+ <string>Tuner center frequency in kHz</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_4">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="0" column="1">
+ <widget class="QSlider" name="decimation">
+ <property name="toolTip">
+ <string>Signal decimation factor</string>
+ </property>
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Decimation</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="decimationText">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>1:1</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Gain</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSlider" name="gain">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip">
+ <string>LNA amplification</string>
+ </property>
+ <property name="maximum">
+ <number>0</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QLabel" name="gainText">
+ <property name="minimumSize">
+ <size>
+ <width>40</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>---</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ValueDial</class>
+ <extends>QWidget</extends>
+ <header>gui/valuedial.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.cpp b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp
new file mode 100644
index 0000000..655d8d7
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/rtlsdrinput.cpp
@@ -0,0 +1,230 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include <string.h>
+#include <errno.h>
+#include "rtlsdrinput.h"
+#include "rtlsdrthread.h"
+#include "rtlsdrgui.h"
+#include "util/simpleserializer.h"
+
+MessageRegistrator RTLSDRInput::MsgConfigureRTLSDR::ID("MsgConfigureRTLSDR");
+MessageRegistrator RTLSDRInput::MsgReportRTLSDR::ID("MsgReportRTLSDR");
+
+RTLSDRInput::Settings::Settings() :
+ m_gain(0),
+ m_decimation(0)
+{
+}
+
+void RTLSDRInput::Settings::resetToDefaults()
+{
+ m_gain = 0;
+ m_decimation = 0;
+}
+
+QByteArray RTLSDRInput::Settings::serialize() const
+{
+ SimpleSerializer s(1);
+ s.writeS32(1, m_gain);
+ s.writeS32(2, m_decimation);
+ return s.final();
+}
+
+bool RTLSDRInput::Settings::deserialize(const QByteArray& data)
+{
+ SimpleDeserializer d(data);
+
+ if(!d.isValid()) {
+ resetToDefaults();
+ return false;
+ }
+
+ if(d.getVersion() == 1) {
+ d.readS32(1, &m_gain, 0);
+ d.readS32(2, &m_decimation, 0);
+ return true;
+ } else {
+ resetToDefaults();
+ return false;
+ }
+}
+
+RTLSDRInput::RTLSDRInput(MessageQueue* msgQueueToGUI) :
+ SampleSource(msgQueueToGUI),
+ m_settings(),
+ m_dev(NULL),
+ m_rtlSDRThread(NULL),
+ m_deviceDescription()
+{
+}
+
+RTLSDRInput::~RTLSDRInput()
+{
+ stopInput();
+}
+
+bool RTLSDRInput::startInput(int device)
+{
+ QMutexLocker mutexLocker(&m_mutex);
+
+ if(m_dev != NULL)
+ stopInput();
+
+ char vendor[256];
+ char product[256];
+ char serial[256];
+ int res;
+ int numberOfGains;
+
+ if(!m_sampleFifo.setSize(524288)) {
+ qCritical("Could not allocate SampleFifo");
+ return false;
+ }
+
+ if((res = rtlsdr_open(&m_dev, device)) < 0) {
+ qCritical("could not open RTLSDR #%d: %s", device, strerror(errno));
+ return false;
+ }
+
+ vendor[0] = '\0';
+ product[0] = '\0';
+ serial[0] = '\0';
+ if((res = rtlsdr_get_usb_strings(m_dev, vendor, product, serial)) < 0) {
+ qCritical("error accessing USB device");
+ goto failed;
+ }
+ qDebug("RTLSDRInput open: %s %s, SN: %s", vendor, product, serial);
+ m_deviceDescription = QString("%1 (SN %2)").arg(product).arg(serial);
+
+ if((res = rtlsdr_set_sample_rate(m_dev, 2000000)) < 0) {
+ qCritical("could not set sample rate: %s", strerror(errno));
+ goto failed;
+ }
+
+ if((res = rtlsdr_set_tuner_gain_mode(m_dev, 1)) < 0) {
+ qCritical("error setting tuner gain mode");
+ goto failed;
+ }
+ if((res = rtlsdr_set_agc_mode(m_dev, 0)) < 0) {
+ qCritical("error setting agc mode");
+ goto failed;
+ }
+
+ numberOfGains = rtlsdr_get_tuner_gains(m_dev, NULL);
+ if(numberOfGains < 0) {
+ qCritical("error getting number of gain values supported");
+ goto failed;
+ }
+ m_gains.resize(numberOfGains);
+ if(rtlsdr_get_tuner_gains(m_dev, &m_gains[0]) < 0) {
+ qCritical("error getting gain values");
+ goto failed;
+ }
+ if((res = rtlsdr_reset_buffer(m_dev)) < 0) {
+ qCritical("could not reset USB EP buffers: %s", strerror(errno));
+ goto failed;
+ }
+
+ if((m_rtlSDRThread = new RTLSDRThread(m_dev, &m_sampleFifo)) == NULL) {
+ qFatal("out of memory");
+ goto failed;
+ }
+ m_rtlSDRThread->startWork();
+
+ mutexLocker.unlock();
+ applySettings(m_generalSettings, m_settings, true);
+
+ qDebug("RTLSDRInput: start");
+ MsgReportRTLSDR::create(m_gains)->submit(m_guiMessageQueue);
+
+ return true;
+
+failed:
+ stopInput();
+ return false;
+}
+
+void RTLSDRInput::stopInput()
+{
+ QMutexLocker mutexLocker(&m_mutex);
+
+ if(m_rtlSDRThread != NULL) {
+ m_rtlSDRThread->stopWork();
+ delete m_rtlSDRThread;
+ m_rtlSDRThread = NULL;
+ }
+ if(m_dev != NULL) {
+ rtlsdr_close(m_dev);
+ m_dev = NULL;
+ }
+ m_deviceDescription.clear();
+}
+
+const QString& RTLSDRInput::getDeviceDescription() const
+{
+ return m_deviceDescription;
+}
+
+int RTLSDRInput::getSampleRate() const
+{
+ return 2000000 / (1 << m_settings.m_decimation);
+}
+
+quint64 RTLSDRInput::getCenterFrequency() const
+{
+ return m_generalSettings.m_centerFrequency;
+}
+
+bool RTLSDRInput::handleMessage(Message* message)
+{
+ if(message->id() == MsgConfigureRTLSDR::ID()) {
+ MsgConfigureRTLSDR* conf = (MsgConfigureRTLSDR*)message;
+ if(!applySettings(conf->getGeneralSettings(), conf->getSettings(), false))
+ qDebug("RTLSDR config error");
+ message->completed();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool RTLSDRInput::applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force)
+{
+ QMutexLocker mutexLocker(&m_mutex);
+
+ if((m_generalSettings.m_centerFrequency != generalSettings.m_centerFrequency) || force) {
+ m_generalSettings.m_centerFrequency = generalSettings.m_centerFrequency;
+ if(m_dev != NULL) {
+ if(rtlsdr_set_center_freq(m_dev, m_generalSettings.m_centerFrequency) != 0)
+ qDebug("osmosdr_set_center_freq(%lld) failed", m_generalSettings.m_centerFrequency);
+ }
+ }
+ if((m_settings.m_gain != settings.m_gain) || force) {
+ m_settings.m_gain = settings.m_gain;
+ if(m_dev != NULL) {
+ if(rtlsdr_set_tuner_gain(m_dev, m_settings.m_gain) != 0)
+ qDebug("rtlsdr_set_tuner_gain() failed");
+ }
+ }
+ if((m_settings.m_decimation != settings.m_decimation) || force) {
+ m_settings.m_decimation = settings.m_decimation;
+ if(m_dev != NULL)
+ m_rtlSDRThread->setDecimation(m_settings.m_decimation);
+ }
+ return true;
+}
diff --git a/plugins/samplesource/rtlsdr/rtlsdrinput.h b/plugins/samplesource/rtlsdr/rtlsdrinput.h
new file mode 100644
index 0000000..a92702e
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/rtlsdrinput.h
@@ -0,0 +1,105 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_RTLSDRINPUT_H
+#define INCLUDE_RTLSDRINPUT_H
+
+#include "dsp/samplesource/samplesource.h"
+#include <rtl-sdr.h>
+#include <QString>
+
+class RTLSDRThread;
+
+class RTLSDRInput : public SampleSource {
+public:
+ struct Settings {
+ qint32 m_gain;
+ qint32 m_decimation;
+
+ Settings();
+ void resetToDefaults();
+ QByteArray serialize() const;
+ bool deserialize(const QByteArray& data);
+ };
+
+ class MsgConfigureRTLSDR : public Message {
+ public:
+ static MessageRegistrator ID;
+
+ const GeneralSettings& getGeneralSettings() const { return m_generalSettings; }
+ const Settings& getSettings() const { return m_settings; }
+
+ static MsgConfigureRTLSDR* create(const GeneralSettings& generalSettings, const Settings& settings)
+ {
+ return new MsgConfigureRTLSDR(generalSettings, settings);
+ }
+
+ private:
+ GeneralSettings m_generalSettings;
+ Settings m_settings;
+
+ MsgConfigureRTLSDR(const GeneralSettings& generalSettings, const Settings& settings) :
+ Message(ID()),
+ m_generalSettings(generalSettings),
+ m_settings(settings)
+ { }
+ };
+
+ class MsgReportRTLSDR : public Message {
+ public:
+ static MessageRegistrator ID;
+
+ const std::vector<int>& getGains() const { return m_gains; }
+
+ static MsgReportRTLSDR* create(const std::vector<int>& gains)
+ {
+ return new MsgReportRTLSDR(gains);
+ }
+
+ protected:
+ std::vector<int> m_gains;
+
+ MsgReportRTLSDR(const std::vector<int>& gains) :
+ Message(ID()),
+ m_gains(gains)
+ { }
+ };
+
+ RTLSDRInput(MessageQueue* msgQueueToGUI);
+ ~RTLSDRInput();
+
+ bool startInput(int device);
+ void stopInput();
+
+ const QString& getDeviceDescription() const;
+ int getSampleRate() const;
+ quint64 getCenterFrequency() const;
+
+ bool handleMessage(Message* message);
+
+private:
+ QMutex m_mutex;
+ Settings m_settings;
+ rtlsdr_dev_t* m_dev;
+ RTLSDRThread* m_rtlSDRThread;
+ QString m_deviceDescription;
+ std::vector<int> m_gains;
+
+ bool applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force);
+};
+
+#endif // INCLUDE_RTLSDRINPUT_H
diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp
new file mode 100644
index 0000000..b5ad76c
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.cpp
@@ -0,0 +1,67 @@
+#include <QtPlugin>
+#include <QAction>
+#include <rtl-sdr.h>
+#include "plugin/pluginapi.h"
+#include "util/simpleserializer.h"
+#include "rtlsdrplugin.h"
+#include "rtlsdrgui.h"
+
+const PluginDescriptor RTLSDRPlugin::m_pluginDescriptor = {
+ displayedName: QString("RTL-SDR Input"),
+ version: QString("---"),
+ copyright: QString("(c) librtlsdr Authors (see source URL)"),
+ website: QString("http://sdr.osmocom.org/trac/wiki/rtl-sdr"),
+ licenseIsGPL: true,
+ sourceCodeURL: QString("http://cgit.osmocom.org/cgit/rtl-sdr")
+};
+
+RTLSDRPlugin::RTLSDRPlugin(QObject* parent) :
+ QObject(parent)
+{
+}
+
+const PluginDescriptor& RTLSDRPlugin::getPluginDescriptor() const
+{
+ return m_pluginDescriptor;
+}
+
+void RTLSDRPlugin::initPlugin(PluginAPI* pluginAPI)
+{
+ m_pluginAPI = pluginAPI;
+
+ m_pluginAPI->registerSampleSource("org.osmocom.sdr.samplesource.rtl-sdr", this);
+}
+
+PluginInterface::SampleSourceDevices RTLSDRPlugin::enumSampleSources()
+{
+ SampleSourceDevices result;
+ int count = rtlsdr_get_device_count();
+ char vendor[256];
+ char product[256];
+ char serial[256];
+
+ for(int i = 0; i < count; i++) {
+ vendor[0] = '\0';
+ product[0] = '\0';
+ serial[0] = '\0';
+
+ if(rtlsdr_get_device_usb_strings(i, vendor, product, serial) != 0)
+ continue;
+ QString displayedName(QString("RTL-SDR #%1 (%2 #%3)").arg(i + 1).arg(product).arg(serial));
+ SimpleSerializer s(1);
+ s.writeS32(1, i);
+ result.append(SampleSourceDevice(displayedName, "org.osmocom.sdr.samplesource.rtl-sdr", s.final()));
+ }
+ return result;
+}
+
+PluginGUI* RTLSDRPlugin::createSampleSource(const QString& sourceName, const QByteArray& address)
+{
+ if(sourceName == "org.osmocom.sdr.samplesource.rtl-sdr") {
+ return new RTLSDRGui(m_pluginAPI);
+ } else {
+ return NULL;
+ }
+}
+
+Q_EXPORT_PLUGIN2(rtlsdrPlugin, RTLSDRPlugin);
diff --git a/plugins/samplesource/rtlsdr/rtlsdrplugin.h b/plugins/samplesource/rtlsdr/rtlsdrplugin.h
new file mode 100644
index 0000000..f559a97
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/rtlsdrplugin.h
@@ -0,0 +1,26 @@
+#ifndef INCLUDE_RTLSDRPLUGIN_H
+#define INCLUDE_RTLSDRPLUGIN_H
+
+#include <QObject>
+#include "plugin/plugininterface.h"
+
+class RTLSDRPlugin : public QObject, PluginInterface {
+ Q_OBJECT
+ Q_INTERFACES(PluginInterface)
+
+public:
+ explicit RTLSDRPlugin(QObject* parent = NULL);
+
+ const PluginDescriptor& getPluginDescriptor() const;
+ void initPlugin(PluginAPI* pluginAPI);
+
+ SampleSourceDevices enumSampleSources();
+ PluginGUI* createSampleSource(const QString& sourceName, const QByteArray& address);
+
+private:
+ static const PluginDescriptor m_pluginDescriptor;
+
+ PluginAPI* m_pluginAPI;
+};
+
+#endif // INCLUDE_RTLSDRPLUGIN_H
diff --git a/plugins/samplesource/rtlsdr/rtlsdrthread.cpp b/plugins/samplesource/rtlsdr/rtlsdrthread.cpp
new file mode 100644
index 0000000..8c8a17c
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/rtlsdrthread.cpp
@@ -0,0 +1,172 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#include <stdio.h>
+#include <errno.h>
+#include "rtlsdrthread.h"
+#include "dsp/samplefifo.h"
+
+#define BLOCKSIZE 16384
+
+RTLSDRThread::RTLSDRThread(rtlsdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent) :
+ QThread(parent),
+ m_running(false),
+ m_dev(dev),
+ m_convertBuffer(BLOCKSIZE),
+ m_sampleFifo(sampleFifo),
+ m_decimation(1)
+{
+}
+
+RTLSDRThread::~RTLSDRThread()
+{
+ stopWork();
+}
+
+void RTLSDRThread::startWork()
+{
+ m_startWaitMutex.lock();
+ start();
+ while(!m_running)
+ m_startWaiter.wait(&m_startWaitMutex, 100);
+ m_startWaitMutex.unlock();
+}
+
+void RTLSDRThread::stopWork()
+{
+ m_running = false;
+ wait();
+}
+
+void RTLSDRThread::setDecimation(int decimation)
+{
+ m_decimation = decimation;
+}
+
+void RTLSDRThread::run()
+{
+ int res;
+
+ m_running = true;
+ m_startWaiter.wakeAll();
+
+ while(m_running) {
+ if((res = rtlsdr_read_async(m_dev, &RTLSDRThread::callbackHelper, this, 16, 2 * BLOCKSIZE)) < 0) {
+ qCritical("RTLSDRThread: async error: %s", strerror(errno));
+ break;
+ }
+ }
+
+ m_running = false;
+}
+
+void RTLSDRThread::decimate2(SampleVector::iterator* it, const quint8* buf, qint32 len)
+{
+ for(int pos = 0; pos < len; pos += 2) {
+ Sample s((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8);
+ if(m_decimator2.workDecimateCenter(&s)) {
+ **it = s;
+ ++(*it);
+ }
+ }
+}
+
+void RTLSDRThread::decimate4(SampleVector::iterator* it, const quint8* buf, qint32 len)
+{
+ for(int pos = 0; pos < len; pos += 2) {
+ Sample s((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8);
+ if(m_decimator2.workDecimateCenter(&s)) {
+ if(m_decimator4.workDecimateCenter(&s)) {
+ **it = s;
+ ++(*it);
+ }
+ }
+ }
+}
+
+void RTLSDRThread::decimate8(SampleVector::iterator* it, const quint8* buf, qint32 len)
+{
+ for(int pos = 0; pos < len; pos += 2) {
+ Sample s((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8);
+ if(m_decimator2.workDecimateCenter(&s)) {
+ if(m_decimator4.workDecimateCenter(&s)) {
+ if(m_decimator8.workDecimateCenter(&s)) {
+ **it = s;
+ ++(*it);
+ }
+ }
+ }
+ }
+}
+
+void RTLSDRThread::decimate16(SampleVector::iterator* it, const quint8* buf, qint32 len)
+{
+ for(int pos = 0; pos < len; pos += 2) {
+ Sample s((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8);
+ if(m_decimator2.workDecimateCenter(&s)) {
+ if(m_decimator4.workDecimateCenter(&s)) {
+ if(m_decimator8.workDecimateCenter(&s)) {
+ if(m_decimator16.workDecimateCenter(&s)) {
+ **it = s;
+ ++(*it);
+ }
+ }
+ }
+ }
+ }
+}
+
+void RTLSDRThread::callback(const quint8* buf, qint32 len)
+{
+ SampleVector::iterator it = m_convertBuffer.begin();
+
+ switch(m_decimation) {
+ case 0: // 1:1 = no decimation
+ for(int pos = 0; pos < len; pos += 2) {
+ *it = Sample((((qint8)buf[pos]) - 128) << 8, (((qint8)buf[pos + 1]) - 128) << 8);
+ ++it;
+ }
+ break;
+
+ case 1: // 1:2
+ decimate2(&it, buf, len);
+ break;
+
+ case 2: // 1:4
+ decimate4(&it, buf, len);
+ break;
+
+ case 3: // 1:8
+ decimate8(&it, buf, len);
+ break;
+
+ case 4: // 1:16
+ decimate16(&it, buf, len);
+ break;
+ }
+
+ m_sampleFifo->write(m_convertBuffer.begin(), it);
+
+ if(!m_running)
+ rtlsdr_cancel_async(m_dev);
+}
+
+void RTLSDRThread::callbackHelper(unsigned char* buf, uint32_t len, void* ctx)
+{
+ RTLSDRThread* thread = (RTLSDRThread*)ctx;
+ thread->callback(buf, len);
+}
diff --git a/plugins/samplesource/rtlsdr/rtlsdrthread.h b/plugins/samplesource/rtlsdr/rtlsdrthread.h
new file mode 100644
index 0000000..08837ef
--- /dev/null
+++ b/plugins/samplesource/rtlsdr/rtlsdrthread.h
@@ -0,0 +1,67 @@
+///////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2012 maintech GmbH, Otto-Hahn-Str. 15, 97204 Hoechberg, Germany //
+// written by Christian Daniel //
+// //
+// 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 <http://www.gnu.org/licenses/>. //
+///////////////////////////////////////////////////////////////////////////////////
+
+#ifndef INCLUDE_RTLSDRTHREAD_H
+#define INCLUDE_RTLSDRTHREAD_H
+
+#include <QThread>
+#include <QMutex>
+#include <QWaitCondition>
+#include <rtl-sdr.h>
+#include "dsp/samplefifo.h"
+#include "dsp/inthalfbandfilter.h"
+
+class RTLSDRThread : public QThread {
+ Q_OBJECT
+
+public:
+ RTLSDRThread(rtlsdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent = NULL);
+ ~RTLSDRThread();
+
+ void startWork();
+ void stopWork();
+
+ void setDecimation(int decimation);
+
+private:
+ QMutex m_startWaitMutex;
+ QWaitCondition m_startWaiter;
+ bool m_running;
+
+ rtlsdr_dev_t* m_dev;
+ SampleVector m_convertBuffer;
+ SampleFifo* m_sampleFifo;
+
+ int m_decimation;
+
+ IntHalfbandFilter m_decimator2;
+ IntHalfbandFilter m_decimator4;
+ IntHalfbandFilter m_decimator8;
+ IntHalfbandFilter m_decimator16;
+
+ void run();
+
+ void decimate2(SampleVector::iterator* it, const quint8* buf, qint32 len);
+ void decimate4(SampleVector::iterator* it, const quint8* buf, qint32 len);
+ void decimate8(SampleVector::iterator* it, const quint8* buf, qint32 len);
+ void decimate16(SampleVector::iterator* it, const quint8* buf, qint32 len);
+ void callback(const quint8* buf, qint32 len);
+
+ static void callbackHelper(unsigned char* buf, uint32_t len, void* ctx);
+};
+
+#endif // INCLUDE_RTLSDRTHREAD_H