diff --git a/plugins/samplesource/v4l/CMakeLists.txt b/plugins/samplesource/v4l/CMakeLists.txt new file mode 100644 index 000000000..baeb1d217 --- /dev/null +++ b/plugins/samplesource/v4l/CMakeLists.txt @@ -0,0 +1,50 @@ +project(v4l) + +set(v4l_SOURCES + v4lgui.cpp + v4linput.cpp + v4lplugin.cpp + v4lthread.cpp +) + +set(rtlsdr_HEADERS + v4lgui.h + v4linput.h + v4lplugin.h + v4lthread.h +) + +set(v4l_FORMS + v4lgui.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 ${v4l_HEADERS}) +qt5_wrap_ui(v4l_FORMS_HEADERS ${v4l_FORMS}) + +add_library(inputv4l SHARED + ${v4l_SOURCES} + ${v4l_HEADERS_MOC} + ${v4l_FORMS_HEADERS} +) + +target_link_libraries(inputv4l + ${QT_LIBRARIES} + ${LIBRTLSDR_LIBRARIES} + ${LIBUSB_LIBRARIES} + sdrbase +) + +qt5_use_modules(inputv4l Core Widgets OpenGL Multimedia) diff --git a/plugins/samplesource/v4l/v4lgui.cpp b/plugins/samplesource/v4l/v4lgui.cpp new file mode 100644 index 000000000..8c9ca6706 --- /dev/null +++ b/plugins/samplesource/v4l/v4lgui.cpp @@ -0,0 +1,154 @@ +#include "v4lgui.h" +#include "ui_v4lgui.h" +#include "plugin/pluginapi.h" + +V4LGui::V4LGui(PluginAPI* pluginAPI, QWidget* parent) : + QWidget(parent), + ui(new Ui::V4LGui), + 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 V4LInput(m_pluginAPI->getMainWindowMessageQueue()); + m_pluginAPI->setSampleSource(m_sampleSource); +} + +V4LGui::~V4LGui() +{ + delete ui; +} + +void V4LGui::destroy() +{ + delete this; +} + +void V4LGui::setName(const QString& name) +{ + setObjectName(name); +} + +void V4LGui::resetToDefaults() +{ + m_generalSettings.resetToDefaults(); + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +QByteArray V4LGui::serializeGeneral() const +{ + return m_generalSettings.serialize(); +} + +bool V4LGui::deserializeGeneral(const QByteArray&data) +{ + if(m_generalSettings.deserialize(data)) { + displaySettings(); + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +quint64 V4LGui::getCenterFrequency() const +{ + return m_generalSettings.m_centerFrequency; +} + +QByteArray V4LGui::serialize() const +{ + return m_settings.serialize(); +} + +bool V4LGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool V4LGui::handleMessage(Message* message) +{ + if(V4LInput::MsgReportV4L::match(message)) { + m_gains = ((V4LInput::MsgReportV4L*)message)->getGains(); + displaySettings(); + message->completed(); + return true; + } else { + return false; + } +} + +void V4LGui::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 V4LGui::sendSettings() +{ + if(!m_updateTimer.isActive()) + m_updateTimer.start(100); +} + +void V4LGui::on_centerFrequency_changed(quint64 value) +{ + m_generalSettings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void V4LGui::on_gain_valueChanged(int value) +{ + if(value > (int)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 V4LGui::on_decimation_valueChanged(int value) +{ + ui->decimationText->setText(tr("1:%1").arg(1 << value)); + m_settings.m_decimation = value; + sendSettings(); +} + +void V4LGui::updateHardware() +{ + V4LInput::MsgConfigureV4L* message = V4LInput::MsgConfigureV4L::create(m_generalSettings, m_settings); + message->submit(m_pluginAPI->getDSPEngineMessageQueue()); + m_updateTimer.stop(); +} diff --git a/plugins/samplesource/v4l/v4lgui.h b/plugins/samplesource/v4l/v4lgui.h new file mode 100644 index 000000000..f713ed1b3 --- /dev/null +++ b/plugins/samplesource/v4l/v4lgui.h @@ -0,0 +1,53 @@ +#ifndef INCLUDE_V4LGUI_H +#define INCLUDE_V4LGUI_H + +#include +#include "plugin/plugingui.h" +#include "v4linput.h" + +class PluginAPI; + +namespace Ui { + class V4LGui; +} + +class V4LGui : public QWidget, public PluginGUI { + Q_OBJECT + +public: + explicit V4LGui(PluginAPI* pluginAPI, QWidget* parent = NULL); + ~V4LGui(); + void destroy(); + + void setName(const QString& name); + + 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::V4LGui* ui; + + PluginAPI* m_pluginAPI; + SampleSource::GeneralSettings m_generalSettings; + V4LInput::Settings m_settings; + QTimer m_updateTimer; + std::vector 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_V4LGUI_H diff --git a/plugins/samplesource/v4l/v4lgui.ui b/plugins/samplesource/v4l/v4lgui.ui new file mode 100644 index 000000000..7dad38cb1 --- /dev/null +++ b/plugins/samplesource/v4l/v4lgui.ui @@ -0,0 +1,229 @@ + + + V4LGui + + + + 0 + 0 + 132 + 82 + + + + + 0 + 0 + + + + Video4Linux + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Monospace + 20 + + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + + + + 3 + + + + + Signal decimation factor + + + 4 + + + 1 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Zoom Out + + + + + + + + 40 + 0 + + + + 1:1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + 3 + + + + + + 0 + 0 + + + + Gain + + + + + + + false + + + LNA amplification + + + 0 + + + 1 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + --- + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+
+ + +
diff --git a/plugins/samplesource/v4l/v4linput.cpp b/plugins/samplesource/v4l/v4linput.cpp new file mode 100644 index 000000000..e10b5419a --- /dev/null +++ b/plugins/samplesource/v4l/v4linput.cpp @@ -0,0 +1,231 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "v4linput.h" +#include "v4lthread.h" +#include "v4lgui.h" +#include "util/simpleserializer.h" + +MESSAGE_CLASS_DEFINITION(V4LInput::MsgConfigureV4L, Message) +MESSAGE_CLASS_DEFINITION(V4LInput::MsgReportV4L, Message) + +V4LInput::Settings::Settings() : + m_gain(0), + m_decimation(0) +{ +} + +void V4LInput::Settings::resetToDefaults() +{ + m_gain = 0; + m_decimation = 0; +} + +QByteArray V4LInput::Settings::serialize() const +{ + SimpleSerializer s(1); + s.writeS32(1, m_gain); + s.writeS32(2, m_decimation); + return s.final(); +} + +bool V4LInput::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; + } +} + +V4LInput::V4LInput(MessageQueue* msgQueueToGUI) : + SampleSource(msgQueueToGUI), + m_settings(), + m_dev(NULL), + m_V4LThread(NULL), + m_deviceDescription() +{ +} + +V4LInput::~V4LInput() +{ + stopInput(); +} + +bool V4LInput::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; + } + qWarning("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, 1536000)) < 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_V4LThread = new V4LThread(m_dev, &m_sampleFifo)) == NULL) { + qFatal("out of memory"); + goto failed; + } + m_V4LThread->startWork(); + + mutexLocker.unlock(); + applySettings(m_generalSettings, m_settings, true); + + qDebug("RTLSDRInput: start"); + MsgReportV4L::create(m_gains)->submit(m_guiMessageQueue); + + return true; + +failed: + stopInput(); + return false; +} + +void V4LInput::stopInput() +{ + QMutexLocker mutexLocker(&m_mutex); + + if(m_V4LThread != NULL) { + m_V4LThread->stopWork(); + delete m_V4LThread; + m_V4LThread = NULL; + } + if(m_dev != NULL) { + rtlsdr_close(m_dev); + m_dev = NULL; + } + m_deviceDescription.clear(); +} + +const QString& V4LInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int V4LInput::getSampleRate() const +{ + return 96000 * (1 << m_settings.m_decimation); +} + +quint64 V4LInput::getCenterFrequency() const +{ + return m_generalSettings.m_centerFrequency; +} + +bool V4LInput::handleMessage(Message* message) +{ + if(MsgConfigureV4L::match(message)) { + MsgConfigureV4L* conf = (MsgConfigureV4L*)message; + if(!applySettings(conf->getGeneralSettings(), conf->getSettings(), false)) + qDebug("V4L config error"); + message->completed(); + return true; + } else { + return false; + } +} + +bool V4LInput::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 + + 384000) != 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_V4LThread->setDecimation(m_settings.m_decimation); + } + return true; +} diff --git a/plugins/samplesource/v4l/v4linput.h b/plugins/samplesource/v4l/v4linput.h new file mode 100644 index 000000000..a22db6cbd --- /dev/null +++ b/plugins/samplesource/v4l/v4linput.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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_V4LINPUT_H +#define INCLUDE_V4LINPUT_H + +#include "dsp/samplesource/samplesource.h" +#include +#include + +class V4LThread; + +class V4LInput : public SampleSource { +public: + struct Settings { + qint32 m_gain; + qint32 m_decimation; + + Settings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + }; + + class MsgConfigureV4L : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const GeneralSettings& getGeneralSettings() const { return m_generalSettings; } + const Settings& getSettings() const { return m_settings; } + + static MsgConfigureV4L* create(const GeneralSettings& generalSettings, const Settings& settings) + { + return new MsgConfigureV4L(generalSettings, settings); + } + + private: + GeneralSettings m_generalSettings; + Settings m_settings; + + MsgConfigureV4L(const GeneralSettings& generalSettings, const Settings& settings) : + Message(), + m_generalSettings(generalSettings), + m_settings(settings) + { } + }; + + class MsgReportV4L : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const std::vector& getGains() const { return m_gains; } + + static MsgReportV4L* create(const std::vector& gains) + { + return new MsgReportV4L(gains); + } + + protected: + std::vector m_gains; + + MsgReportV4L(const std::vector& gains) : + Message(), + m_gains(gains) + { } + }; + + V4LInput(MessageQueue* msgQueueToGUI); + ~V4LInput(); + + 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; + V4LThread* m_V4LThread; + QString m_deviceDescription; + std::vector m_gains; + + bool applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force); +}; + +#endif // INCLUDE_V4L_H diff --git a/plugins/samplesource/v4l/v4lplugin.cpp b/plugins/samplesource/v4l/v4lplugin.cpp new file mode 100644 index 000000000..f46b41d2e --- /dev/null +++ b/plugins/samplesource/v4l/v4lplugin.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "v4lplugin.h" +#include "v4lgui.h" + +const PluginDescriptor V4LPlugin::m_pluginDescriptor = { + QString("V4L Input"), + QString("---"), + QString("(c) librtlsdr authors"), + QString("http://sdr.osmocom.org/trac/wiki/rtl-sdr"), + true, + QString("http://cgit.osmocom.org/cgit/rtl-sdr") +}; + +V4LPlugin::V4LPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& V4LPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void V4LPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + + m_pluginAPI->registerSampleSource("org.osmocom.sdr.samplesource.v4l", this); +} + +PluginInterface::SampleSourceDevices V4LPlugin::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((uint32_t)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.v4l", s.final())); + } + return result; +} + +PluginGUI* V4LPlugin::createSampleSource(const QString& sourceName, const QByteArray& address) +{ + if(sourceName == "org.osmocom.sdr.samplesource.v4l") { + V4LGui* gui = new V4LGui(m_pluginAPI); + m_pluginAPI->setInputGUI(gui); + return gui; + } else { + return NULL; + } +} diff --git a/plugins/samplesource/v4l/v4lplugin.h b/plugins/samplesource/v4l/v4lplugin.h new file mode 100644 index 000000000..5352d4ab9 --- /dev/null +++ b/plugins/samplesource/v4l/v4lplugin.h @@ -0,0 +1,27 @@ +#ifndef INCLUDE_V4LPLUGIN_H +#define INCLUDE_V4LPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class V4LPlugin : public QObject, PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "org.osmocom.sdr.samplesource.v4l") + +public: + explicit V4LPlugin(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_V4LPLUGIN_H diff --git a/plugins/samplesource/v4l/v4lthread.cpp b/plugins/samplesource/v4l/v4lthread.cpp new file mode 100644 index 000000000..259addcef --- /dev/null +++ b/plugins/samplesource/v4l/v4lthread.cpp @@ -0,0 +1,190 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include "v4lthread.h" +#include "dsp/samplefifo.h" + +#define BLOCKSIZE 16384 + +V4LThread::V4LThread(rtlsdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_convertBuffer(BLOCKSIZE), + m_sampleFifo(sampleFifo), + m_decimation(2) +{ + m_localdecimation = 0; +} + +V4LThread::~V4LThread() +{ + stopWork(); +} + +void V4LThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void V4LThread::stopWork() +{ + m_running = false; + wait(); +} + +void V4LThread::setDecimation(int decimation) +{ + m_decimation = decimation; +} + +void V4LThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + while(m_running) { + if((res = rtlsdr_read_async(m_dev, &V4LThread::callbackHelper, this, 32, BLOCKSIZE)) < 0) { + qCritical("V4LThread: async error: %s", strerror(errno)); + break; + } + } + + m_running = false; +} + +void V4LThread::decimate2(SampleVector::iterator* it, const quint8* buf, qint32 len) +{ + qint16 xreal, yimag; + for (int pos = 0; pos < len + 7; pos += 8) { + xreal = buf[pos+0] - buf[pos+3]; + yimag = buf[pos+1] + buf[pos+2] - 255; + Sample s( xreal << 3, yimag << 3 ); + **it = s; + (*it)++; + xreal = buf[pos+7] - buf[pos+4]; + yimag = 255 - buf[pos+5] - buf[pos+6]; + Sample t( xreal << 3, yimag << 3 ); + **it = t; + (*it)++; + } +} +void V4LThread::decimate4(SampleVector::iterator* it, const quint8* buf, qint32 len) +{ + qint16 xreal, yimag; + for (int pos = 0; pos < len + 7; pos += 8) { + xreal = buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]; + yimag = buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]; + Sample s( xreal << 3, yimag << 3 ); + **it = s; + (*it)++; + } +} + +void V4LThread::decimate8(SampleVector::iterator* it, const quint8* buf, qint32 len) +{ + qint16 xreal, yimag; + for (int pos = 0; pos < len + 15; pos += 8) { + xreal = buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]; + yimag = buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]; + pos += 8; + xreal += buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]; + yimag += buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]; + + Sample s( xreal << 3, yimag << 3 ); + **it = s; + (*it)++; + } +} + +void V4LThread::decimate16(SampleVector::iterator* it, const quint8* buf, qint32 len) +{ + // Offset tuning: 4x downsample and rotate, then + // downsample 4x more. [ rotate: 0, 1, -3, 2, -4, -5, 7, -6] + qint16 xreal, yimag; + for (int step = 0; step < len - 31; step +=32) { + xreal = yimag = 0; + for (int pos = step; pos < step + 32; pos += 8) { + xreal += buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]; + yimag += buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]; + } + Sample s( xreal << 3, yimag << 3 ); + **it = s; + (*it)++; + } +} + +void V4LThread::callback(const quint8* buf, qint32 len) +{ + qint16 xreal, yimag, phase; + SampleVector::iterator it = m_convertBuffer.begin(); + int decimationFactor[] = {1, 1, 1, 2, 4, 0}; + + if (++m_localdecimation < decimationFactor[m_decimation]) return; + m_localdecimation = 0; + + switch(4 - m_decimation) { + case 0: // 1:1 = no decimation + // just rotation + phase = -(1<<2); + for (int pos = 0; pos < len + 3; pos += 4) { + phase *= -1; + xreal = phase * (2 * buf[pos+0] - 255); + yimag = phase * (2 * buf[pos+1] - 255); + *it++ = Sample(xreal, yimag); + xreal = phase * (255 - 2 * buf[pos+3]); + yimag = phase * (2 * buf[pos+2] - 255); + *it++ = Sample(xreal, yimag); + } + 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; + + default: + 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 V4LThread::callbackHelper(unsigned char* buf, uint32_t len, void* ctx) +{ + V4LThread* thread = (V4LThread*)ctx; + thread->callback(buf, len); +} diff --git a/plugins/samplesource/v4l/v4lthread.h b/plugins/samplesource/v4l/v4lthread.h new file mode 100644 index 000000000..36225d268 --- /dev/null +++ b/plugins/samplesource/v4l/v4lthread.h @@ -0,0 +1,63 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_V4LTHREAD_H +#define INCLUDE_V4LTHREAD_H + +#include +#include +#include +#include +#include "dsp/samplefifo.h" +#include "dsp/inthalfbandfilter.h" + +class V4LThread : public QThread { + Q_OBJECT + +public: + V4LThread(rtlsdr_dev_t* dev, SampleFifo* sampleFifo, QObject* parent = NULL); + ~V4LThread(); + + 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; + int m_localdecimation; + + 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_V4LTHREAD_H