diff --git a/CMakeLists.txt b/CMakeLists.txt index 29e8ed02b..a7486057e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 2.8.9) -option(KERNEL "Use Linux Kernel Video4Linux Source." OFF) +option(V4L-RTL "Use Linux Kernel RTL-SDR Source." OFF) +option(V4L-MSI "Use Linux Kernel MSI2500 Source." OFF) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules) diff --git a/Readme.md b/Readme.md index adcf7d86d..7b38d957d 100644 --- a/Readme.md +++ b/Readme.md @@ -17,7 +17,7 @@ For Ubuntu: "librtlsdr-dev" is in the "universe" repo. (utopic 14.10 amd64.) -Use "cmake ../ -DKERNEL=ON" to build the Linux kernel driver (Experimental). Needs a recent kernel and libv4l2. Will need extra work to support Airspy and Hackrf. Needs "cp KERNEL_SOURCE/include/linux/compiler.h /usr/include/linux/" and "cp KERNEL_SOURCE/include/uapi/linux/videodev2.h /usr/include/uapi/linux/" and package "libv4l-dev". +Use "cmake ../ -DV4L-RTL=ON" to build the Linux kernel driver for RTL-SDR (Experimental). Needs a recent kernel and libv4l2. Will need extra work to support SDRPlay. Needs "cp KERNEL_SOURCE/include/linux/compiler.h /usr/include/linux/" and "cp KERNEL_SOURCE/include/uapi/linux/videodev2.h /usr/include/uapi/linux/" and package "libv4l-dev". The Gnuradio plugin source needs extra packages, including "liblog4cpp-dev libboost-system-dev gnuradio-dev libosmosdr-dev" diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index df3fe3fb4..a85c0a642 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -10,10 +10,15 @@ if(LIBUSB_FOUND AND LIBOSMOSDR_FOUND) add_subdirectory(osmosdr) endif(LIBUSB_FOUND AND LIBOSMOSDR_FOUND) -if(KERNEL AND UNIX) +if(V4L-RTL) FIND_LIBRARY (LIBV4L2 v4l2) FIND_PATH (LIBV4L2H libv4l2.h) - add_subdirectory(v4l) + add_subdirectory(v4l-rtl) +endif() +if(V4L-MSI) + FIND_LIBRARY (LIBV4L2 v4l2) + FIND_PATH (LIBV4L2H libv4l2.h) + add_subdirectory(v4l-msi) endif() if(LIBUSB_FOUND AND UNIX) diff --git a/plugins/samplesource/v4l/CMakeLists.txt b/plugins/samplesource/v4l-msi/CMakeLists.txt similarity index 100% rename from plugins/samplesource/v4l/CMakeLists.txt rename to plugins/samplesource/v4l-msi/CMakeLists.txt diff --git a/plugins/samplesource/v4l/v4lgui.cpp b/plugins/samplesource/v4l-msi/v4lgui.cpp similarity index 100% rename from plugins/samplesource/v4l/v4lgui.cpp rename to plugins/samplesource/v4l-msi/v4lgui.cpp diff --git a/plugins/samplesource/v4l/v4lgui.h b/plugins/samplesource/v4l-msi/v4lgui.h similarity index 100% rename from plugins/samplesource/v4l/v4lgui.h rename to plugins/samplesource/v4l-msi/v4lgui.h diff --git a/plugins/samplesource/v4l/v4lgui.ui b/plugins/samplesource/v4l-msi/v4lgui.ui similarity index 100% rename from plugins/samplesource/v4l/v4lgui.ui rename to plugins/samplesource/v4l-msi/v4lgui.ui diff --git a/plugins/samplesource/v4l/v4linput.cpp b/plugins/samplesource/v4l-msi/v4linput.cpp similarity index 100% rename from plugins/samplesource/v4l/v4linput.cpp rename to plugins/samplesource/v4l-msi/v4linput.cpp diff --git a/plugins/samplesource/v4l/v4linput.h b/plugins/samplesource/v4l-msi/v4linput.h similarity index 100% rename from plugins/samplesource/v4l/v4linput.h rename to plugins/samplesource/v4l-msi/v4linput.h diff --git a/plugins/samplesource/v4l/v4lplugin.cpp b/plugins/samplesource/v4l-msi/v4lplugin.cpp similarity index 100% rename from plugins/samplesource/v4l/v4lplugin.cpp rename to plugins/samplesource/v4l-msi/v4lplugin.cpp diff --git a/plugins/samplesource/v4l/v4lplugin.h b/plugins/samplesource/v4l-msi/v4lplugin.h similarity index 100% rename from plugins/samplesource/v4l/v4lplugin.h rename to plugins/samplesource/v4l-msi/v4lplugin.h diff --git a/plugins/samplesource/v4l/v4lsource.cpp b/plugins/samplesource/v4l-msi/v4lsource.cpp similarity index 100% rename from plugins/samplesource/v4l/v4lsource.cpp rename to plugins/samplesource/v4l-msi/v4lsource.cpp diff --git a/plugins/samplesource/v4l/v4lthread.cpp b/plugins/samplesource/v4l-msi/v4lthread.cpp similarity index 100% rename from plugins/samplesource/v4l/v4lthread.cpp rename to plugins/samplesource/v4l-msi/v4lthread.cpp diff --git a/plugins/samplesource/v4l/v4lthread.h b/plugins/samplesource/v4l-msi/v4lthread.h similarity index 100% rename from plugins/samplesource/v4l/v4lthread.h rename to plugins/samplesource/v4l-msi/v4lthread.h diff --git a/plugins/samplesource/v4l-rtl/CMakeLists.txt b/plugins/samplesource/v4l-rtl/CMakeLists.txt new file mode 100644 index 000000000..dc747b740 --- /dev/null +++ b/plugins/samplesource/v4l-rtl/CMakeLists.txt @@ -0,0 +1,50 @@ +project(v4l) + +set(v4l_SOURCES + v4lgui.cpp + v4linput.cpp + v4lplugin.cpp + v4lthread.cpp + v4lsource.cpp +) + +set(v4l_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 + ${LIBV4L2H} +) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt4_wrap_cpp(v4l_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} + ${LIBV4L2} + sdrbase +) + +qt5_use_modules(inputv4l Core Widgets OpenGL Multimedia) diff --git a/plugins/samplesource/v4l-rtl/v4lgui.cpp b/plugins/samplesource/v4l-rtl/v4lgui.cpp new file mode 100644 index 000000000..802764536 --- /dev/null +++ b/plugins/samplesource/v4l-rtl/v4lgui.cpp @@ -0,0 +1,145 @@ +#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); + 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::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-rtl/v4lgui.h b/plugins/samplesource/v4l-rtl/v4lgui.h new file mode 100644 index 000000000..448c97dee --- /dev/null +++ b/plugins/samplesource/v4l-rtl/v4lgui.h @@ -0,0 +1,51 @@ +#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 updateHardware(); +}; + +#endif // INCLUDE_V4LGUI_H diff --git a/plugins/samplesource/v4l-rtl/v4lgui.ui b/plugins/samplesource/v4l-rtl/v4lgui.ui new file mode 100644 index 000000000..9254a4a50 --- /dev/null +++ b/plugins/samplesource/v4l-rtl/v4lgui.ui @@ -0,0 +1,177 @@ + + + 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 + + + + + + + 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-rtl/v4linput.cpp b/plugins/samplesource/v4l-rtl/v4linput.cpp new file mode 100644 index 000000000..074c8f38b --- /dev/null +++ b/plugins/samplesource/v4l-rtl/v4linput.cpp @@ -0,0 +1,167 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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) +{ +} + +void V4LInput::Settings::resetToDefaults() +{ + m_gain = 0; +} + +QByteArray V4LInput::Settings::serialize() const +{ + SimpleSerializer s(1); + s.writeS32(1, m_gain); + s.writeS32(2, SAMPLERATE); + 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_samplerate, 0); + return true; + } else { + resetToDefaults(); + return false; + } +} + +V4LInput::V4LInput(MessageQueue* msgQueueToGUI) : + SampleSource(msgQueueToGUI), + m_settings(), + m_V4LThread(NULL), + m_deviceDescription() +{ +} + +V4LInput::~V4LInput() +{ + stopInput(); +} + +bool V4LInput::startInput(int device) +{ + QMutexLocker mutexLocker(&m_mutex); + double freq; + + if(m_V4LThread) + return false; + + if(!m_sampleFifo.setSize(4096*16)) { + qCritical("Could not allocate SampleFifo"); + return false; + } + + freq = (double)getCenterFrequency(); + if((m_V4LThread = new V4LThread(&m_sampleFifo, freq)) == NULL) { + qFatal("out of memory"); + return false; + } + + m_deviceDescription = QString("RTL-SDR /dev/swradio0"); + + qDebug("V4LInput: start"); + //MsgReportV4L::create(m_gains)->submit(m_guiMessageQueue); + + return true; +} + +void V4LInput::stopInput() +{ + QMutexLocker mutexLocker(&m_mutex); + + if(m_V4LThread) { + m_V4LThread->stopWork(); + // wait for thread to quit ? + delete m_V4LThread; + m_V4LThread = NULL; + } + m_deviceDescription.clear(); +} + +const QString& V4LInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int V4LInput::getSampleRate() const +{ + int result = SAMPLERATE / 4; + return result; +} + +quint64 V4LInput::getCenterFrequency() const +{ + return m_generalSettings.m_centerFrequency; +} + +bool V4LInput::handleMessage(Message* message) +{ + if(MsgConfigureV4L::match(message)) { + MsgConfigureV4L* conf = (MsgConfigureV4L*)message; + applySettings(conf->getGeneralSettings(), conf->getSettings(), false); + message->completed(); + return true; + } else { + return false; + } +} + +void V4LInput::applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force) +{ + QMutexLocker mutexLocker(&m_mutex); + + if (!m_V4LThread) { + m_generalSettings.m_centerFrequency = generalSettings.m_centerFrequency; + return; + } + + if((m_generalSettings.m_centerFrequency != generalSettings.m_centerFrequency) || force) { + m_V4LThread->set_center_freq( (double)(generalSettings.m_centerFrequency + + (SAMPLERATE / 4) )); + } + m_generalSettings.m_centerFrequency = generalSettings.m_centerFrequency; +#if 0 + if((m_settings.m_gain != settings.m_gain) || force) { + m_settings.m_gain = settings.m_gain; + m_V4LThread->set_tuner_gain((double)m_settings.m_gain); + } +#endif +} + diff --git a/plugins/samplesource/v4l-rtl/v4linput.h b/plugins/samplesource/v4l-rtl/v4linput.h new file mode 100644 index 000000000..680d65c53 --- /dev/null +++ b/plugins/samplesource/v4l-rtl/v4linput.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_V4LINPUT_H +#define INCLUDE_V4LINPUT_H + +#include "dsp/samplesource/samplesource.h" +#include + +struct v4l_buffer { + void *start; + size_t length; +}; + +class V4LThread; + +class V4LInput : public SampleSource { +public: + struct Settings { + qint32 m_gain; + + 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; + V4LThread* m_V4LThread; + QString m_deviceDescription; + std::vector m_gains; + + void applySettings(const GeneralSettings& generalSettings, const Settings& settings, bool force); +}; + +#endif // INCLUDE_V4L_H diff --git a/plugins/samplesource/v4l-rtl/v4lplugin.cpp b/plugins/samplesource/v4l-rtl/v4lplugin.cpp new file mode 100644 index 000000000..4412f19d1 --- /dev/null +++ b/plugins/samplesource/v4l-rtl/v4lplugin.cpp @@ -0,0 +1,56 @@ +#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("3.18"), + QString("(c) 2014 John Greb"), + QString("http://palosaari.fi/linux/"), + true, + QString("github.com/hexameron/rtl-sdrangelove") +}; + +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; + + QString displayedName(QString("Kernel Source #1")); + SimpleSerializer s(1); + s.writeS32(1, 0); + 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-rtl/v4lplugin.h b/plugins/samplesource/v4l-rtl/v4lplugin.h new file mode 100644 index 000000000..5352d4ab9 --- /dev/null +++ b/plugins/samplesource/v4l-rtl/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-rtl/v4lsource.cpp b/plugins/samplesource/v4l-rtl/v4lsource.cpp new file mode 100644 index 000000000..0d4a5fd08 --- /dev/null +++ b/plugins/samplesource/v4l-rtl/v4lsource.cpp @@ -0,0 +1,291 @@ +/* + * Copyright 2013 Antti Palosaari + * + * This 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; either version 3, or (at your option) + * any later version. + * + * This software 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "v4linput.h" +#include "v4lthread.h" +#include +#include +#include +#include +#include +#include +#include "libv4l2.h" +#include +#include + +/* Control classes */ +#define V4L2_CTRL_CLASS_USER 0x00980000 /* Old-style 'user' controls */ +/* User-class control IDs */ +#define V4L2_CID_BASE (V4L2_CTRL_CLASS_USER | 0x900) +#define V4L2_CID_USER_BASE V4L2_CID_BASE + +#define CID_SAMPLING_MODE ((V4L2_CID_USER_BASE | 0xf000) + 0) +#define CID_SAMPLING_RATE ((V4L2_CID_USER_BASE | 0xf000) + 1) +#define CID_SAMPLING_RESOLUTION ((V4L2_CID_USER_BASE | 0xf000) + 2) +#define CID_TUNER_RF ((V4L2_CID_USER_BASE | 0xf000) + 10) +#define CID_TUNER_BW ((V4L2_CID_USER_BASE | 0xf000) + 11) +#define CID_TUNER_IF ((V4L2_CID_USER_BASE | 0xf000) + 12) +#define CID_TUNER_GAIN ((V4L2_CID_USER_BASE | 0xf000) + 13) + +#define V4L2_PIX_FMT_SDR_U8 v4l2_fourcc('C', 'U', '0', '8') /* unsigned 8-bit Complex*/ +#define V4L2_PIX_FMT_SDR_U16LE v4l2_fourcc('C', 'U', '1', '6') /* unsigned 16-bit Complex*/ + +#define CLEAR(x) memset(&(x), 0, sizeof(x)) + +static void xioctl(int fh, unsigned long int request, void *arg) +{ + int ret; + + do { + ret = v4l2_ioctl(fh, request, arg); + } while (ret == -1 && ((errno == EINTR) || (errno == EAGAIN))); + if (ret == -1) { + qCritical("V4L2 ioctl error: %d", errno); + } +} + +void +V4LThread::OpenSource(const char *filename) + { + struct v4l2_format fmt; + struct v4l2_buffer buf; + struct v4l2_requestbuffers req; + enum v4l2_buf_type type; + unsigned int i; + + recebuf_len = 0; + + // fd = v4l2_open(filename, O_RDWR | O_NONBLOCK, 0); + fd = open(filename, O_RDWR | O_NONBLOCK, 0); + if (fd < 0) { + qCritical("Cannot open /dev/swradio0 :%d", fd); + return; + } + + pixelformat = V4L2_PIX_FMT_SDR_U8; + // RTLSDR has limited ioctls in 3.18, expect fail. + qCritical("Want Pixelformat : CU08"); + CLEAR(fmt); + fmt.type = V4L2_BUF_TYPE_SDR_CAPTURE; + fmt.fmt.sdr.pixelformat = pixelformat; + xioctl(fd, VIDIOC_S_FMT, &fmt); + qCritical("Got Pixelformat : %4.4s", (char *)&fmt.fmt.sdr.pixelformat); + + CLEAR(req); + req.count = 8; + req.type = V4L2_BUF_TYPE_SDR_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + xioctl(fd, VIDIOC_REQBUFS, &req); + + buffers = (struct v4l_buffer*) calloc(req.count, sizeof(*buffers)); + for (n_buffers = 0; n_buffers < req.count; n_buffers++) { + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_SDR_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = n_buffers; + xioctl(fd, VIDIOC_QUERYBUF, &buf); + + buffers[n_buffers].length = buf.length; + buffers[n_buffers].start = v4l2_mmap(NULL, buf.length, + PROT_READ | PROT_WRITE, MAP_SHARED, + fd, buf.m.offset); + + if (buffers[n_buffers].start == MAP_FAILED) { + qCritical("V4L2 buffer mmap failed"); + } + } + + for (i = 0; i < n_buffers; i++) { + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_SDR_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + xioctl(fd, VIDIOC_QBUF, &buf); + } + + set_sample_rate((double)SAMPLERATE); + set_center_freq( centerFreq + (SAMPLERATE / 4) ); + // start streaming + type = V4L2_BUF_TYPE_SDR_CAPTURE; + xioctl(fd, VIDIOC_STREAMON, &type); + } + +void +V4LThread::CloseSource() + { + unsigned int i; + enum v4l2_buf_type type; + + // stop streaming + type = V4L2_BUF_TYPE_SDR_CAPTURE; + xioctl(fd, VIDIOC_STREAMOFF, &type); + + for (i = 0; i < n_buffers; i++) + v4l2_munmap(buffers[i].start, buffers[i].length); + + v4l2_close(fd); + fd = -1; + } + +void +V4LThread::set_sample_rate(double samp_rate) + { + struct v4l2_frequency frequency; + + memset (&frequency, 0, sizeof(frequency)); + frequency.tuner = 0; + frequency.type = V4L2_TUNER_ADC; + frequency.frequency = samp_rate / 1; + + xioctl(fd, VIDIOC_S_FREQUENCY, &frequency); + + return; + } + +// Cannot change freq while streaming in Linux 4.0 +void +V4LThread::set_center_freq(double freq) + { + struct v4l2_frequency frequency; + + if (fd <= 0) + return; + memset (&frequency, 0, sizeof(frequency)); + frequency.tuner = 1; + frequency.type = V4L2_TUNER_RF; + frequency.frequency = freq; + + xioctl(fd, VIDIOC_S_FREQUENCY, &frequency); + + return; + } + +void +V4LThread::set_bandwidth(double bandwidth) + { + struct v4l2_ext_controls ext_ctrls; + struct v4l2_ext_control ext_ctrl; + + memset (&ext_ctrl, 0, sizeof(ext_ctrl)); + ext_ctrl.id = CID_TUNER_BW; + ext_ctrl.value = bandwidth; + + memset (&ext_ctrls, 0, sizeof(ext_ctrls)); + ext_ctrls.ctrl_class = V4L2_CTRL_CLASS_USER; + ext_ctrls.count = 1; + ext_ctrls.controls = &ext_ctrl; + + xioctl(fd, VIDIOC_S_EXT_CTRLS, &ext_ctrls); + + return; + } + +void +V4LThread::set_tuner_gain(double gain) + { + struct v4l2_ext_controls ext_ctrls; + struct v4l2_ext_control ext_ctrl; + + if (fd <= 0) + return; + memset (&ext_ctrl, 0, sizeof(ext_ctrl)); + ext_ctrl.id = CID_TUNER_GAIN; + ext_ctrl.value = gain; + + memset (&ext_ctrls, 0, sizeof(ext_ctrls)); + ext_ctrls.ctrl_class = V4L2_CTRL_CLASS_USER; + ext_ctrls.count = 1; + ext_ctrls.controls = &ext_ctrl; + + xioctl(fd, VIDIOC_S_EXT_CTRLS, &ext_ctrls); + + return; + } + +int +V4LThread::work(int noutput_items) +{ + int ret; + struct timeval tv; + struct v4l2_buffer buf; + fd_set fds; + qint16 xreal, yimag; + uint8_t* b; + SampleVector::iterator it; + + unsigned int pos = 0; + // in is 4*8bit*2(IQ), 8 bytes; out is 1*16bit*2(IQ) , 4bytes + it = m_convertBuffer.begin(); + if (recebuf_len > 0) { + b = (uint8_t *) recebuf_ptr; + unsigned int len = noutput_items * 8; + if (len > recebuf_len) + len = recebuf_len; + for (pos = 0; pos < len - 7; pos += 8) { + xreal = b[pos+0] - b[pos+3] + b[pos+7] - b[pos+4]; + yimag = b[pos+1] - b[pos+5] + b[pos+2] - b[pos+6]; + Sample s( xreal << 3, yimag << 3 ); + *it = s; + it++; + } + m_sampleFifo->write(m_convertBuffer.begin(), it); + recebuf_len -= pos; + recebuf_ptr = (void*)(b + pos); + } + // return now if there is still data in buffer, else free buffer and get another. + if (recebuf_len >= 8) + return pos / 8; + { // frre buffer, if there was one. + if (pos > 0) { + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_SDR_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = recebuf_mmap_index; + xioctl(fd, VIDIOC_QBUF, &buf); + } + + /* Read data from device */ + do { + FD_ZERO(&fds); + FD_SET(fd, &fds); + + // Timeout + tv.tv_sec = 2; + tv.tv_usec = 0; + + ret = select(fd + 1, &fds, NULL, NULL, &tv); + } while ((ret == -1 && (errno = EINTR))); + if (ret == -1) { + perror("select"); + return errno; + } + + /* dequeue mmap buf (receive data) */ + CLEAR(buf); + buf.type = V4L2_BUF_TYPE_SDR_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + xioctl(fd, VIDIOC_DQBUF, &buf); + + /* store buffer in order to handle it during next call */ + recebuf_ptr = buffers[buf.index].start; + recebuf_len = buf.bytesused; + recebuf_mmap_index = buf.index; + } + return pos / 8; +} diff --git a/plugins/samplesource/v4l-rtl/v4lthread.cpp b/plugins/samplesource/v4l-rtl/v4lthread.cpp new file mode 100644 index 000000000..57f517560 --- /dev/null +++ b/plugins/samplesource/v4l-rtl/v4lthread.cpp @@ -0,0 +1,55 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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" + +V4LThread::V4LThread(SampleFifo* sampleFifo, double frequency, QObject* parent) : + QThread(parent), + m_running(false), + m_convertBuffer(BLOCKSIZE), + m_sampleFifo(sampleFifo) +{ + centerFreq = frequency; + start(); +} + +V4LThread::~V4LThread() +{ +} + +void V4LThread::stopWork() +{ + m_running = false; + wait(); +} + +void V4LThread::run() +{ + m_running = true; + OpenSource("/dev/swradio0"); + if (fd < 0) + return; + + while(m_running) { + work(BLOCKSIZE); + } + CloseSource(); +} + diff --git a/plugins/samplesource/v4l-rtl/v4lthread.h b/plugins/samplesource/v4l-rtl/v4lthread.h new file mode 100644 index 000000000..cf4318761 --- /dev/null +++ b/plugins/samplesource/v4l-rtl/v4lthread.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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_V4LTHREAD_H +#define INCLUDE_V4LTHREAD_H + +#include +#include +#include +#include "dsp/samplefifo.h" +#include "dsp/inthalfbandfilter.h" + +#define SAMPLERATE 1024000 +#define BLOCKSIZE 4096 + +class V4LThread : public QThread { + Q_OBJECT + +public: + V4LThread(SampleFifo* sampleFifo, double frequency, QObject* parent = NULL); + ~V4LThread(); + + void stopWork(); + void OpenSource(const char *filename); + void CloseSource(); + void set_sample_rate(double samp_rate); + void set_center_freq(double freq); + void set_bandwidth(double bandwidth); + void set_tuner_gain(double gain); + int work(int n_items); +private: + int fd; + quint32 pixelformat; + struct v4l_buffer *buffers; + unsigned int n_buffers; + void *recebuf_ptr; + unsigned int recebuf_len; + unsigned int recebuf_mmap_index; + + double centerFreq; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + SampleVector m_convertBuffer; + SampleFifo* m_sampleFifo; + + void run(); + +}; +#endif // INCLUDE_V4LTHREAD_H