diff --git a/doc/img/AudioOutput_plugin.png b/doc/img/AudioOutput_plugin.png new file mode 100644 index 000000000..fdcc2b7e3 Binary files /dev/null and b/doc/img/AudioOutput_plugin.png differ diff --git a/doc/img/AudioOutput_plugin.xcf b/doc/img/AudioOutput_plugin.xcf new file mode 100644 index 000000000..371b2449d Binary files /dev/null and b/doc/img/AudioOutput_plugin.xcf differ diff --git a/plugins/samplesink/CMakeLists.txt b/plugins/samplesink/CMakeLists.txt index c8180d5eb..c86461bcf 100644 --- a/plugins/samplesink/CMakeLists.txt +++ b/plugins/samplesink/CMakeLists.txt @@ -3,6 +3,7 @@ project(samplesink) add_subdirectory(testsink) add_subdirectory(fileoutput) add_subdirectory(localoutput) +add_subdirectory(audiooutput) if(CM256CC_FOUND) add_subdirectory(remoteoutput) diff --git a/plugins/samplesink/audiooutput/CMakeLists.txt b/plugins/samplesink/audiooutput/CMakeLists.txt new file mode 100644 index 000000000..72c7d8924 --- /dev/null +++ b/plugins/samplesink/audiooutput/CMakeLists.txt @@ -0,0 +1,57 @@ +project(audiooutput) + +set(audiooutput_SOURCES + audiooutput.cpp + audiooutputplugin.cpp + audiooutputsettings.cpp + # audiooutputwebapiadapter.cpp + audiooutputworker.cpp +) + +set(audiooutput_HEADERS + audiooutput.h + audiooutputplugin.h + audiooutputsettings.h + # audiooutputwebapiadapter.h + audiooutputworker.h +) + +include_directories( + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client +) + +if(NOT SERVER_MODE) + set(audiooutput_SOURCES + ${audiooutput_SOURCES} + audiooutputgui.cpp + audiooutputgui.ui + ) + set(audiooutput_HEADERS + ${audiooutput_HEADERS} + audiooutputgui.h + ) + + set(TARGET_NAME outputaudio) + set(TARGET_LIB "Qt5::Widgets") + set(TARGET_LIB_GUI "sdrgui") + set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) +else() + set(TARGET_NAME outputaudiosrv) + set(TARGET_LIB "") + set(TARGET_LIB_GUI "") + set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR}) +endif() + +add_library(${TARGET_NAME} SHARED + ${audiooutput_SOURCES} +) + +target_link_libraries(${TARGET_NAME} + Qt5::Core + ${TARGET_LIB} + sdrbase + ${TARGET_LIB_GUI} + swagger +) + +install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER}) diff --git a/plugins/samplesink/audiooutput/audiooutput.cpp b/plugins/samplesink/audiooutput/audiooutput.cpp new file mode 100644 index 000000000..8a7be74ac --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutput.cpp @@ -0,0 +1,444 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "SWGDeviceSettings.h" +#include "SWGDeviceState.h" + +#include "device/deviceapi.h" +#include "audio/audiodevicemanager.h" +#include "dsp/dspcommands.h" +#include "dsp/dspengine.h" + +#include "audiooutputworker.h" +#include "audiooutput.h" + +MESSAGE_CLASS_DEFINITION(AudioOutput::MsgConfigureAudioOutput, Message) +MESSAGE_CLASS_DEFINITION(AudioOutput::MsgStartStop, Message) + +AudioOutput::AudioOutput(DeviceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_audioFifo(48000), + m_settings(), + m_audioDeviceIndex(-1), + m_centerFrequency(0), + m_worker(nullptr), + m_deviceDescription("AudioOutput") +{ + m_deviceAPI->setNbSinkStreams(1); + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + m_sampleRate = audioDeviceManager->getOutputSampleRate(m_audioDeviceIndex); + m_settings.m_deviceName = AudioDeviceManager::m_defaultDeviceName; + m_sampleSourceFifo.resize(SampleSourceFifo::getSizePolicy(48000)); +} + +AudioOutput::~AudioOutput() +{ + stop(); +} + +void AudioOutput::destroy() +{ + delete this; +} + +void AudioOutput::init() +{ + applySettings(m_settings, true); +} + +bool AudioOutput::start() +{ + QMutexLocker mutexLocker(&m_mutex); + qDebug("AudioOutput::start"); + + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), m_audioDeviceIndex); + + m_worker = new AudioOutputWorker(&m_sampleSourceFifo, &m_audioFifo); + m_worker->moveToThread(&m_workerThread); + m_worker->setSamplerate(m_sampleRate); + m_worker->setIQMapping(m_settings.m_iqMapping); + m_worker->connectTimer(m_deviceAPI->getMasterTimer()); + m_worker->startWork(); + m_workerThread.start(); + + mutexLocker.unlock(); + + qDebug("AudioOutput::start: started"); + + return true; +} + +void AudioOutput::stop() +{ + qDebug("AudioOutput::stop"); + if (m_worker) + { + m_worker->stopWork(); + m_workerThread.quit(); + m_workerThread.wait(); + delete m_worker; + m_worker = nullptr; + } + + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + audioDeviceManager->removeAudioSink(&m_audioFifo); + + m_running = false; + qDebug("AudioOutput::stop: stopped"); +} + +QByteArray AudioOutput::serialize() const +{ + return m_settings.serialize(); +} + +bool AudioOutput::deserialize(const QByteArray& data) +{ + bool success = true; + + if (!m_settings.deserialize(data)) + { + m_settings.resetToDefaults(); + success = false; + } + + MsgConfigureAudioOutput* message = MsgConfigureAudioOutput::create(m_settings, true); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) + { + MsgConfigureAudioOutput* messageToGUI = MsgConfigureAudioOutput::create(m_settings, true); + m_guiMessageQueue->push(messageToGUI); + } + + return success; +} + +const QString& AudioOutput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int AudioOutput::getSampleRate() const +{ + return m_sampleRate; +} + +quint64 AudioOutput::getCenterFrequency() const +{ + return m_centerFrequency; +} + +bool AudioOutput::handleMessage(const Message& message) +{ + if(MsgConfigureAudioOutput::match(message)) + { + qDebug() << "AudioOutput::handleMessage: MsgConfigureAudioOutput"; + MsgConfigureAudioOutput& conf = (MsgConfigureAudioOutput&) message; + applySettings(conf.getSettings(), conf.getForce()); + return true; + } + else if (MsgStartStop::match(message)) + { + MsgStartStop& cmd = (MsgStartStop&) message; + qDebug() << "AudioOutput::handleMessage: MsgStartStop: " << (cmd.getStartStop() ? "start" : "stop"); + + if (cmd.getStartStop()) + { + if (m_deviceAPI->initDeviceEngine()) { + m_deviceAPI->startDeviceEngine(); + } + } + else + { + m_deviceAPI->stopDeviceEngine(); + } + + if (m_settings.m_useReverseAPI) { + webapiReverseSendStartStop(cmd.getStartStop()); + } + + return true; + } + else + { + return false; + } +} + +void AudioOutput::applySettings(const AudioOutputSettings& settings, bool force) +{ + bool forwardChange = false; + QList reverseAPIKeys; + + if ((m_settings.m_deviceName != settings.m_deviceName) || force) + { + reverseAPIKeys.append("deviceName"); + AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager(); + m_audioDeviceIndex = audioDeviceManager->getOutputDeviceIndex(settings.m_deviceName); + //qDebug("AMDemod::applySettings: audioDeviceName: %s audioDeviceIndex: %d", qPrintable(settings.m_audioDeviceName), audioDeviceIndex); + audioDeviceManager->removeAudioSink(&m_audioFifo); + audioDeviceManager->addAudioSink(&m_audioFifo, getInputMessageQueue(), m_audioDeviceIndex); + m_sampleRate = audioDeviceManager->getOutputSampleRate(m_audioDeviceIndex); + forwardChange = true; + } + + if ((m_settings.m_volume != settings.m_volume) || force) + { + reverseAPIKeys.append("volume"); + m_audioOutputDevice.setVolume(settings.m_volume); + qDebug() << "AudioOutput::applySettings: set volume to " << settings.m_volume; + } + + if ((m_settings.m_iqMapping != settings.m_iqMapping) || force) + { + reverseAPIKeys.append("iqMapping"); + forwardChange = true; + + if (m_worker) { + m_worker->setIQMapping(settings.m_iqMapping); + } + } + + if (settings.m_useReverseAPI) + { + bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) || + (m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) || + (m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) || + (m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex); + webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force); + } + + m_settings = settings; + + if (forwardChange) + { + if (m_worker) { + m_worker->setSamplerate(m_sampleRate); + } + + DSPSignalNotification *notif = new DSPSignalNotification(m_sampleRate, 0); + m_centerFrequency = 0; + m_deviceAPI->getDeviceEngineInputMessageQueue()->push(notif); + } +} + +int AudioOutput::webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + return 200; +} + +int AudioOutput::webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage) +{ + (void) errorMessage; + m_deviceAPI->getDeviceEngineStateStr(*response.getState()); + MsgStartStop *message = MsgStartStop::create(run); + m_inputMessageQueue.push(message); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgStartStop *msgToGUI = MsgStartStop::create(run); + m_guiMessageQueue->push(msgToGUI); + } + + return 200; +} + +int AudioOutput::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setAudioOutputSettings(new SWGSDRangel::SWGAudioOutputSettings()); + response.getAudioOutputSettings()->init(); + webapiFormatDeviceSettings(response, m_settings); + + return 200; +} + +int AudioOutput::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage) +{ + (void) errorMessage; + AudioOutputSettings settings = m_settings; + webapiUpdateDeviceSettings(settings, deviceSettingsKeys, response); + + MsgConfigureAudioOutput *msg = MsgConfigureAudioOutput::create(settings, force); + m_inputMessageQueue.push(msg); + + if (m_guiMessageQueue) // forward to GUI if any + { + MsgConfigureAudioOutput *msgToGUI = MsgConfigureAudioOutput::create(settings, force); + m_guiMessageQueue->push(msgToGUI); + } + + webapiFormatDeviceSettings(response, settings); + return 200; +} + +void AudioOutput::webapiUpdateDeviceSettings( + AudioOutputSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response) +{ + if (deviceSettingsKeys.contains("deviceName")) { + settings.m_deviceName = *response.getAudioOutputSettings()->getDeviceName(); + } + if (deviceSettingsKeys.contains("volume")) { + settings.m_volume = response.getAudioOutputSettings()->getVolume(); + } + if (deviceSettingsKeys.contains("iqMapping")) { + settings.m_iqMapping = (AudioOutputSettings::IQMapping) response.getAudioOutputSettings()->getIqMapping(); + } + if (deviceSettingsKeys.contains("useReverseAPI")) { + settings.m_useReverseAPI = response.getAudioOutputSettings()->getUseReverseApi() != 0; + } + if (deviceSettingsKeys.contains("reverseAPIAddress")) { + settings.m_reverseAPIAddress = *response.getAudioOutputSettings()->getReverseApiAddress(); + } + if (deviceSettingsKeys.contains("reverseAPIPort")) { + settings.m_reverseAPIPort = response.getAudioOutputSettings()->getReverseApiPort(); + } + if (deviceSettingsKeys.contains("reverseAPIDeviceIndex")) { + settings.m_reverseAPIDeviceIndex = response.getAudioOutputSettings()->getReverseApiDeviceIndex(); + } +} + +void AudioOutput::webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const AudioOutputSettings& settings) +{ + response.getAudioOutputSettings()->setDeviceName(new QString(settings.m_deviceName)); + response.getAudioOutputSettings()->setVolume(settings.m_volume); + response.getAudioOutputSettings()->setIqMapping((int) settings.m_iqMapping); + + response.getAudioOutputSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0); + + if (response.getAudioOutputSettings()->getReverseApiAddress()) { + *response.getAudioOutputSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress; + } else { + response.getAudioOutputSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress)); + } + + response.getAudioOutputSettings()->setReverseApiPort(settings.m_reverseAPIPort); + response.getAudioOutputSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex); +} + +void AudioOutput::webapiReverseSendSettings(QList& deviceSettingsKeys, const AudioOutputSettings& settings, bool force) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(1); // single Tx + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("AudioOutput")); + swgDeviceSettings->setAudioOutputSettings(new SWGSDRangel::SWGAudioOutputSettings()); + SWGSDRangel::SWGAudioOutputSettings *swgAudioOutputSettings = swgDeviceSettings->getAudioOutputSettings(); + + // transfer data that has been modified. When force is on transfer all data except reverse API data + + if (deviceSettingsKeys.contains("deviceName") || force) { + swgAudioOutputSettings->setDeviceName(new QString(settings.m_deviceName)); + } + if (deviceSettingsKeys.contains("volume") || force) { + swgAudioOutputSettings->setVolume(settings.m_volume); + } + if (deviceSettingsKeys.contains("iqMapping") || force) { + swgAudioOutputSettings->setIqMapping(settings.m_iqMapping); + } + + QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/settings") + .arg(settings.m_reverseAPIAddress) + .arg(settings.m_reverseAPIPort) + .arg(settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(deviceSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + + // Always use PATCH to avoid passing reverse API settings + QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer); + buffer->setParent(reply); + + delete swgDeviceSettings; +} + +void AudioOutput::webapiReverseSendStartStop(bool start) +{ + SWGSDRangel::SWGDeviceSettings *swgDeviceSettings = new SWGSDRangel::SWGDeviceSettings(); + swgDeviceSettings->setDirection(1); // single Tx + swgDeviceSettings->setOriginatorIndex(m_deviceAPI->getDeviceSetIndex()); + swgDeviceSettings->setDeviceHwType(new QString("AudioOutput")); + + QString deviceSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/device/run") + .arg(m_settings.m_reverseAPIAddress) + .arg(m_settings.m_reverseAPIPort) + .arg(m_settings.m_reverseAPIDeviceIndex); + m_networkRequest.setUrl(QUrl(deviceSettingsURL)); + m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QBuffer *buffer = new QBuffer(); + buffer->open((QBuffer::ReadWrite)); + buffer->write(swgDeviceSettings->asJson().toUtf8()); + buffer->seek(0); + QNetworkReply *reply; + + if (start) { + reply = m_networkManager->sendCustomRequest(m_networkRequest, "POST", buffer); + } else { + reply = m_networkManager->sendCustomRequest(m_networkRequest, "DELETE", buffer); + } + + buffer->setParent(reply); + delete swgDeviceSettings; +} + +void AudioOutput::networkManagerFinished(QNetworkReply *reply) +{ + QNetworkReply::NetworkError replyError = reply->error(); + + if (replyError) + { + qWarning() << "AudioOutput::networkManagerFinished:" + << " error(" << (int) replyError + << "): " << replyError + << ": " << reply->errorString(); + } + else + { + QString answer = reply->readAll(); + answer.chop(1); // remove last \n + qDebug("AudioOutput::networkManagerFinished: reply:\n%s", answer.toStdString().c_str()); + } + + reply->deleteLater(); +} + diff --git a/plugins/samplesink/audiooutput/audiooutput.h b/plugins/samplesink/audiooutput/audiooutput.h new file mode 100644 index 000000000..01b2ff309 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutput.h @@ -0,0 +1,153 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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 _AUDIOOUTPUT_AUDIOOUTPUT_H_ +#define _AUDIOOUTPUT_AUDIOOUTPUT_H_ + +#include +#include + +#include "dsp/devicesamplesink.h" +#include "audio/audiooutputdevice.h" +#include "audio/audiofifo.h" + +#include "audiooutputsettings.h" + +class QNetworkAccessManager; +class QNetworkReply; +class AudioOutputWorker; +class DeviceAPI; + +class AudioOutput : public DeviceSampleSink { +public: + class MsgConfigureAudioOutput : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const AudioOutputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureAudioOutput* create(const AudioOutputSettings& settings, bool force) + { + return new MsgConfigureAudioOutput(settings, force); + } + + private: + AudioOutputSettings m_settings; + bool m_force; + + MsgConfigureAudioOutput(const AudioOutputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + AudioOutput(DeviceAPI *deviceAPI); + virtual ~AudioOutput(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual void setSampleRate(int sampleRate) { (void) sampleRate; } + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency) { (void) centerFrequency; } + const QString& getDeviceName() const { return m_settings.m_deviceName; } + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + static void webapiFormatDeviceSettings( + SWGSDRangel::SWGDeviceSettings& response, + const AudioOutputSettings& settings); + + static void webapiUpdateDeviceSettings( + AudioOutputSettings& settings, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response); + +private: + DeviceAPI *m_deviceAPI; + AudioOutputDevice m_audioOutputDevice; + AudioFifo m_audioFifo; + QMutex m_mutex; + AudioOutputSettings m_settings; + int m_audioDeviceIndex; + int m_sampleRate; + qint64 m_centerFrequency; + AudioOutputWorker* m_worker; + QThread m_workerThread; + QString m_deviceDescription; + bool m_running; + QNetworkAccessManager *m_networkManager; + QNetworkRequest m_networkRequest; + + void applySettings(const AudioOutputSettings& settings, bool force); + + void webapiReverseSendSettings(QList& deviceSettingsKeys, const AudioOutputSettings& settings, bool force); + void webapiReverseSendStartStop(bool start); + +private slots: + void networkManagerFinished(QNetworkReply *reply); +}; + + +#endif diff --git a/plugins/samplesink/audiooutput/audiooutputgui.cpp b/plugins/samplesink/audiooutput/audiooutputgui.cpp new file mode 100644 index 000000000..be4df2bb1 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputgui.cpp @@ -0,0 +1,239 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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 "ui_audiooutputgui.h" +#include "gui/colormapper.h" +#include "gui/glspectrum.h" +#include "gui/crightclickenabler.h" +#include "gui/basicdevicesettingsdialog.h" +#include "gui/audioselectdialog.h" +#include "dsp/dspengine.h" +#include "dsp/dspcommands.h" +#include "audiooutputgui.h" + +#include "device/deviceapi.h" +#include "device/deviceuiset.h" + +AudioOutputGui::AudioOutputGui(DeviceUISet *deviceUISet, QWidget* parent) : + DeviceGUI(parent), + ui(new Ui::AudioOutputGui), + m_deviceUISet(deviceUISet), + m_doApplySettings(true), + m_forceSettings(true), + m_settings(), + m_centerFrequency(0) +{ + m_audioOutput = (AudioOutput*) m_deviceUISet->m_deviceAPI->getSampleSink(); + + ui->setupUi(this); + + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + + CRightClickEnabler *startStopRightClickEnabler = new CRightClickEnabler(ui->startStop); + connect(startStopRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(openDeviceSettingsDialog(const QPoint &))); + + m_sampleRate = m_audioOutput->getSampleRate(); + m_centerFrequency = m_audioOutput->getCenterFrequency(); + m_settings.m_deviceName = m_audioOutput->getDeviceName(); + updateSampleRateAndFrequency(); + displaySettings(); + + connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()), Qt::QueuedConnection); + m_audioOutput->setMessageQueueToGUI(&m_inputMessageQueue); +} + +AudioOutputGui::~AudioOutputGui() +{ + delete ui; +} + +void AudioOutputGui::destroy() +{ + delete this; +} + +void AudioOutputGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +QByteArray AudioOutputGui::serialize() const +{ + return m_settings.serialize(); +} + +bool AudioOutputGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) + { + displaySettings(); + m_forceSettings = true; + sendSettings(); + return true; + } + else + { + resetToDefaults(); + return false; + } +} + +bool AudioOutputGui::handleMessage(const Message& message) +{ + if (AudioOutput::MsgConfigureAudioOutput::match(message)) + { + const AudioOutput::MsgConfigureAudioOutput& cfg = (AudioOutput::MsgConfigureAudioOutput&) message; + m_settings = cfg.getSettings(); + blockApplySettings(true); + displaySettings(); + blockApplySettings(false); + return true; + } + else if (AudioOutput::MsgStartStop::match(message)) + { + AudioOutput::MsgStartStop& notif = (AudioOutput::MsgStartStop&) message; + blockApplySettings(true); + ui->startStop->setChecked(notif.getStartStop()); + blockApplySettings(false); + return true; + } + else + { + return false; + } +} + +void AudioOutputGui::handleInputMessages() +{ + Message* message; + + while ((message = m_inputMessageQueue.pop()) != 0) + { + qDebug("AudioOutputGui::handleInputMessages: message: %s", message->getIdentifier()); + + if (DSPSignalNotification::match(*message)) + { + DSPSignalNotification* notif = (DSPSignalNotification*) message; + m_sampleRate = notif->getSampleRate(); + m_centerFrequency = notif->getCenterFrequency(); + qDebug("AudioOutputGui::handleInputMessages: DSPSignalNotification: SampleRate: %d", notif->getSampleRate()); + updateSampleRateAndFrequency(); + + delete message; + } + else + { + if (handleMessage(*message)) { + delete message; + } + } + } +} + +void AudioOutputGui::updateSampleRateAndFrequency() +{ + m_deviceUISet->getSpectrum()->setSampleRate(m_sampleRate); + m_deviceUISet->getSpectrum()->setCenterFrequency(m_centerFrequency); + m_deviceUISet->getSpectrum()->setSsbSpectrum(false); + m_deviceUISet->getSpectrum()->setLsbDisplay(false); + ui->deviceRateText->setText(tr("%1k").arg((float)m_sampleRate / 1000)); +} + +void AudioOutputGui::displaySettings() +{ + ui->deviceLabel->setText(m_settings.m_deviceName); + ui->volume->setValue((int)(m_settings.m_volume*10.0f)); + ui->volumeText->setText(QString("%1").arg(m_settings.m_volume, 3, 'f', 1)); + ui->channels->setCurrentIndex((int)m_settings.m_iqMapping); +} + +void AudioOutputGui::on_deviceSelect_clicked() +{ + AudioSelectDialog audioSelect(DSPEngine::instance()->getAudioDeviceManager(), m_settings.m_deviceName, false, this); + audioSelect.exec(); + + if (audioSelect.m_selected) + { + m_settings.m_deviceName = audioSelect.m_audioDeviceName; + ui->deviceLabel->setText(m_settings.m_deviceName); + sendSettings(); + } +} + +void AudioOutputGui::on_volume_valueChanged(int value) +{ + m_settings.m_volume = value/10.0f; + ui->volumeText->setText(QString("%1").arg(m_settings.m_volume, 3, 'f', 1)); + sendSettings(); +} + +void AudioOutputGui::on_channels_currentIndexChanged(int index) +{ + m_settings.m_iqMapping = (AudioOutputSettings::IQMapping) index; + updateSampleRateAndFrequency(); + sendSettings(); +} + +void AudioOutputGui::on_startStop_toggled(bool checked) +{ + if (m_doApplySettings) + { + AudioOutput::MsgStartStop *message = AudioOutput::MsgStartStop::create(checked); + m_audioOutput->getInputMessageQueue()->push(message); + } +} + +void AudioOutputGui::sendSettings() +{ + if(!m_updateTimer.isActive()) + m_updateTimer.start(100); +} + +void AudioOutputGui::updateHardware() +{ + if (m_doApplySettings) + { + AudioOutput::MsgConfigureAudioOutput* message = AudioOutput::MsgConfigureAudioOutput::create(m_settings, m_forceSettings); + m_audioOutput->getInputMessageQueue()->push(message); + m_forceSettings = false; + m_updateTimer.stop(); + } +} + +void AudioOutputGui::openDeviceSettingsDialog(const QPoint& p) +{ + BasicDeviceSettingsDialog dialog(this); + dialog.setUseReverseAPI(m_settings.m_useReverseAPI); + dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress); + dialog.setReverseAPIPort(m_settings.m_reverseAPIPort); + dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex); + + dialog.move(p); + dialog.exec(); + + m_settings.m_useReverseAPI = dialog.useReverseAPI(); + m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress(); + m_settings.m_reverseAPIPort = dialog.getReverseAPIPort(); + m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex(); + + sendSettings(); +} diff --git a/plugins/samplesink/audiooutput/audiooutputgui.h b/plugins/samplesink/audiooutput/audiooutputgui.h new file mode 100644 index 000000000..84e3e92a9 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputgui.h @@ -0,0 +1,79 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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_AUDIOOUTPUTGUI_H +#define INCLUDE_AUDIOOUTPUTGUI_H + +#include +#include + +#include "device/devicegui.h" +#include "util/messagequeue.h" + +#include "audiooutput.h" + +class QWidget; +class DeviceUISet; + +namespace Ui { + class AudioOutputGui; +} + +class AudioOutputGui : public DeviceGUI { + Q_OBJECT + +public: + explicit AudioOutputGui(DeviceUISet *deviceUISet, QWidget* parent = nullptr); + virtual ~AudioOutputGui(); + virtual void destroy(); + + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } + +private: + Ui::AudioOutputGui* ui; + + DeviceUISet* m_deviceUISet; + AudioOutput* m_audioOutput; + bool m_doApplySettings; + bool m_forceSettings; + AudioOutputSettings m_settings; + QTimer m_updateTimer; + int m_sampleRate; + qint64 m_centerFrequency; + + MessageQueue m_inputMessageQueue; + + void blockApplySettings(bool block) { m_doApplySettings = !block; } + void displaySettings(); + void sendSettings(); + void updateSampleRateAndFrequency(); + bool handleMessage(const Message& message); + +private slots: + void handleInputMessages(); + void on_deviceSelect_clicked(); + void on_volume_valueChanged(int value); + void on_channels_currentIndexChanged(int index); + void on_startStop_toggled(bool checked); + void updateHardware(); + void openDeviceSettingsDialog(const QPoint& p); +}; + +#endif // INCLUDE_AUDIOINPUTGUI_H diff --git a/plugins/samplesink/audiooutput/audiooutputgui.ui b/plugins/samplesink/audiooutput/audiooutputgui.ui new file mode 100644 index 000000000..291a3b123 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputgui.ui @@ -0,0 +1,290 @@ + + + AudioOutputGui + + + + 0 + 0 + 320 + 200 + + + + + 0 + 0 + + + + + 320 + 200 + + + + + Liberation Sans + 9 + + + + Audio Output + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 4 + + + + + + + + + start/stop acquisition + + + + + + + :/play.png + :/stop.png:/play.png + + + + + + + + + + + Baseband I/Q sample rate kS/s + + + 00000k + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + Qt::Horizontal + + + + + + + + + + 24 + 24 + + + + Open dialog to select output device + + + + + + + :/sound_on.png:/sound_on.png + + + + + + + + 0 + 0 + + + + Output device selected + + + ... + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + + + Volume + + + + + + + + 24 + 0 + + + + + 24 + 24 + + + + Audio volume. Not supported by all devices + + + 10 + + + 10 + + + + + + + 1.0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Channels + + + + + + + + 80 + 0 + + + + How audio channels map to IQ data + + + 0 + + + + I=L, Q=R + + + + + I=R, Q=L + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + ButtonSwitch + QToolButton +
gui/buttonswitch.h
+
+
+ + + + +
diff --git a/plugins/samplesink/audiooutput/audiooutputplugin.cpp b/plugins/samplesink/audiooutput/audiooutputplugin.cpp new file mode 100644 index 000000000..ec36a0476 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputplugin.cpp @@ -0,0 +1,148 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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 "plugin/pluginapi.h" +#include "util/simpleserializer.h" +#include "audiooutputplugin.h" +#include "audiooutputwebapiadapter.h" + +#ifdef SERVER_MODE +#include "audiooutput.h" +#else +#include "audiooutputgui.h" +#endif + +const PluginDescriptor AudioOutputPlugin::m_pluginDescriptor = { + QString("AudioOutput"), + QString("Audio output"), + QString("6.1.0"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +const QString AudioOutputPlugin::m_hardwareID = "AudioOutput"; +const QString AudioOutputPlugin::m_deviceTypeID = AUDIOOUTPUT_DEVICE_TYPE_ID; + +AudioOutputPlugin::AudioOutputPlugin(QObject* parent) : + QObject(parent) +{ +} + +const PluginDescriptor& AudioOutputPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void AudioOutputPlugin::initPlugin(PluginAPI* pluginAPI) +{ + pluginAPI->registerSampleSink(m_deviceTypeID, this); +} + +void AudioOutputPlugin::enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices) +{ + if (listedHwIds.contains(m_hardwareID)) { // check if it was done + return; + } + + originDevices.append(OriginDevice( + "AudioOutput", + m_hardwareID, + QString(), + 0, // Sequence + 0, // nb Rx + 1 // nb Tx + )); + + listedHwIds.append(m_hardwareID); +} + +PluginInterface::SamplingDevices AudioOutputPlugin::enumSampleSinks(const OriginDevices& originDevices) +{ + SamplingDevices result; + + for (OriginDevices::const_iterator it = originDevices.begin(); it != originDevices.end(); ++it) + { + if (it->hardwareId == m_hardwareID) + { + result.append(SamplingDevice( + it->displayableName, + it->hardwareId, + m_deviceTypeID, + it->serial, + it->sequence, + PluginInterface::SamplingDevice::BuiltInDevice, + PluginInterface::SamplingDevice::StreamSingleTx, + 1, + 0 + )); + } + } + + return result; +} + +#ifdef SERVER_MODE +DeviceGUI* AudioOutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + (void) sinkId; + (void) widget; + (void) deviceUISet; + return nullptr; +} +#else +DeviceGUI* AudioOutputPlugin::createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet) +{ + if (sinkId == m_deviceTypeID) + { + AudioOutputGui* gui = new AudioOutputGui(deviceUISet); + *widget = gui; + return gui; + } + else + { + return nullptr; + } +} +#endif + +DeviceSampleSink* AudioOutputPlugin::createSampleSinkPluginInstance(const QString& sinkId, DeviceAPI *deviceAPI) +{ + if(sinkId == m_deviceTypeID) + { + AudioOutput* output = new AudioOutput(deviceAPI); + return output; + } + else + { + return nullptr; + } +} + +DeviceWebAPIAdapter *AudioOutputPlugin::createDeviceWebAPIAdapter() const +{ + return new AudioOutputWebAPIAdapter(); +} diff --git a/plugins/samplesink/audiooutput/audiooutputplugin.h b/plugins/samplesink/audiooutput/audiooutputplugin.h new file mode 100644 index 000000000..030ec3000 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputplugin.h @@ -0,0 +1,56 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2016 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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_AUDIOOUTPUTPLUGIN_H +#define INCLUDE_AUDIOOUTPUTPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +#define AUDIOOUTPUT_DEVICE_TYPE_ID "sdrangel.samplesink.audiooutput" + +class PluginAPI; +class DeviceAPI; + +class AudioOutputPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID AUDIOOUTPUT_DEVICE_TYPE_ID) + +public: + explicit AudioOutputPlugin(QObject* parent = nullptr); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + virtual void enumOriginDevices(QStringList& listedHwIds, OriginDevices& originDevices); + virtual SamplingDevices enumSampleSinks(const OriginDevices& originDevices); + virtual DeviceGUI* createSampleSinkPluginInstanceGUI( + const QString& sinkId, + QWidget **widget, + DeviceUISet *deviceUISet); + virtual DeviceSampleSink* createSampleSinkPluginInstance(const QString& sinkId, DeviceAPI *deviceAPI); + virtual DeviceWebAPIAdapter* createDeviceWebAPIAdapter() const; + + static const QString m_hardwareID; + static const QString m_deviceTypeID; + +private: + static const PluginDescriptor m_pluginDescriptor; +}; + +#endif // INCLUDE_AUDIOOUTPUTPLUGIN_H diff --git a/plugins/samplesink/audiooutput/audiooutputsettings.cpp b/plugins/samplesink/audiooutput/audiooutputsettings.cpp new file mode 100644 index 000000000..e6d39f949 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputsettings.cpp @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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 "util/simpleserializer.h" +#include "audiooutputsettings.h" + +AudioOutputSettings::AudioOutputSettings() +{ + resetToDefaults(); +} + +void AudioOutputSettings::resetToDefaults() +{ + m_deviceName = ""; + m_volume = 1.0f; + m_iqMapping = LR; + m_useReverseAPI = false; + m_reverseAPIAddress = "127.0.0.1"; + m_reverseAPIPort = 8888; + m_reverseAPIDeviceIndex = 0; +} + +QByteArray AudioOutputSettings::serialize() const +{ + SimpleSerializer s(1); + + s.writeString(1, m_deviceName); + s.writeFloat(3, m_volume); + s.writeS32(5, (int)m_iqMapping); + + s.writeBool(24, m_useReverseAPI); + s.writeString(25, m_reverseAPIAddress); + s.writeU32(26, m_reverseAPIPort); + s.writeU32(27, m_reverseAPIDeviceIndex); + + return s.final(); +} + +bool AudioOutputSettings::deserialize(const QByteArray& data) +{ + SimpleDeserializer d(data); + + if (!d.isValid()) + { + resetToDefaults(); + return false; + } + + if (d.getVersion() == 1) + { + uint32_t uintval; + + d.readString(1, &m_deviceName, ""); + d.readFloat(3, &m_volume, 1.0f); + d.readS32(5, (int *)&m_iqMapping, IQMapping::LR); + + d.readBool(24, &m_useReverseAPI, false); + d.readString(25, &m_reverseAPIAddress, "127.0.0.1"); + d.readU32(26, &uintval, 0); + + if ((uintval > 1023) && (uintval < 65535)) { + m_reverseAPIPort = uintval; + } else { + m_reverseAPIPort = 8888; + } + + d.readU32(27, &uintval, 0); + m_reverseAPIDeviceIndex = uintval > 99 ? 99 : uintval; + + return true; + } + else + { + resetToDefaults(); + return false; + } +} diff --git a/plugins/samplesink/audiooutput/audiooutputsettings.h b/plugins/samplesink/audiooutput/audiooutputsettings.h new file mode 100644 index 000000000..d2f9e7634 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputsettings.h @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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 _AUDIOOUTPUT_AUDIOOUTPUTSETTINGS_H_ +#define _AUDIOOUTPUT_AUDIOOUTPUTSETTINGS_H_ + +#include +#include + +struct AudioOutputSettings { + + QString m_deviceName; // Including realm, as from getFullDeviceName below + float m_volume; + enum IQMapping { + LR, + RL + } m_iqMapping; + + bool m_useReverseAPI; + QString m_reverseAPIAddress; + uint16_t m_reverseAPIPort; + uint16_t m_reverseAPIDeviceIndex; + + AudioOutputSettings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + + // Append realm to device names, because there may be multiple devices with the same name on Windows + static QString getFullDeviceName(const QAudioDeviceInfo &deviceInfo) + { +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + return deviceInfo.deviceName(); +#else + QString realm = deviceInfo.realm(); + if (realm != "" && realm != "default" && realm != "alsa") + return deviceInfo.deviceName() + " " + realm; + else + return deviceInfo.deviceName(); +#endif + } +}; + +#endif /* _AUDIOOUTPUT_AUDIOOUTPUTSETTINGS_H_ */ diff --git a/plugins/samplesink/audiooutput/audiooutputwebapiadapter.cpp b/plugins/samplesink/audiooutput/audiooutputwebapiadapter.cpp new file mode 100644 index 000000000..5bafb0148 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputwebapiadapter.cpp @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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 "SWGDeviceSettings.h" +#include "audiooutput.h" +#include "audiooutputwebapiadapter.h" + +AudioOutputWebAPIAdapter::AudioOutputWebAPIAdapter() +{} + +AudioOutputWebAPIAdapter::~AudioOutputWebAPIAdapter() +{} + +int AudioOutputWebAPIAdapter::webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage) +{ + (void) errorMessage; + response.setAirspyHfSettings(new SWGSDRangel::SWGAirspyHFSettings()); + response.getAirspyHfSettings()->init(); + AudioOutput::webapiFormatDeviceSettings(response, m_settings); + return 200; +} + +int AudioOutputWebAPIAdapter::webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage) +{ + (void) force; // no action + (void) errorMessage; + AudioOutput::webapiUpdateDeviceSettings(m_settings, deviceSettingsKeys, response); + return 200; +} diff --git a/plugins/samplesink/audiooutput/audiooutputwebapiadapter.h b/plugins/samplesink/audiooutput/audiooutputwebapiadapter.h new file mode 100644 index 000000000..0660398ba --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputwebapiadapter.h @@ -0,0 +1,41 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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 "device/devicewebapiadapter.h" +#include "audiooutputsettings.h" + +class AudioOutputWebAPIAdapter : public DeviceWebAPIAdapter +{ +public: + AudioOutputWebAPIAdapter(); + virtual ~AudioOutputWebAPIAdapter(); + virtual QByteArray serialize() { return m_settings.serialize(); } + virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); } + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + +private: + AudioOutputSettings m_settings; +}; diff --git a/plugins/samplesink/audiooutput/audiooutputworker.cpp b/plugins/samplesink/audiooutput/audiooutputworker.cpp new file mode 100644 index 000000000..01da2559d --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputworker.cpp @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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 "dsp/samplesourcefifo.h" +#include "audio/audiofifo.h" + +#include "audiooutputworker.h" + +#define AUDIOOUTPUT_THROTTLE_MS 50 + +AudioOutputWorker::AudioOutputWorker(SampleSourceFifo* sampleFifo, AudioFifo *fifo, QObject* parent) : + QObject(parent), + m_running(false), + m_samplerate(0), + m_throttlems(AUDIOOUTPUT_THROTTLE_MS), + m_maxThrottlems(50), + m_throttleToggle(false), + m_iqMapping(AudioOutputSettings::IQMapping::LR), + m_buf(nullptr), + m_samplesChunkSize(0), + m_sampleFifo(sampleFifo), + m_audioFifo(fifo) +{ + m_audioBuffer.resize(1<<14); + m_audioBufferFill = 0; +} + +AudioOutputWorker::~AudioOutputWorker() +{ +} + +void AudioOutputWorker::startWork() +{ + qDebug("AudioOutputWorker::startWork"); + m_running = true; +} + +void AudioOutputWorker::stopWork() +{ + qDebug("AudioOutputWorker::stopWork"); + m_running = false; +} + +void AudioOutputWorker::connectTimer(const QTimer& timer) +{ + qDebug() << "AudioOutputWorker::connectTimer"; + connect(&timer, SIGNAL(timeout()), this, SLOT(tick())); +} + +void AudioOutputWorker::setSamplerate(int samplerate) +{ + if (samplerate != m_samplerate) + { + bool wasRunning = false; + + if (m_running) + { + stopWork(); + wasRunning = true; + } + + // resize sample FIFO + if (m_sampleFifo) { + m_sampleFifo->resize(SampleSourceFifo::getSizePolicy(samplerate)); // 1s buffer + } + + qDebug() << "AudioOutputWorker::setSamplerate:" + << " new:" << samplerate + << " old:" << m_samplerate + << " m_sampleFifo size:" << m_sampleFifo->size() + << " m_audioFifo size:" << m_audioFifo->size() + << " sample i/q size" << sizeof(FixReal); + + // resize output buffer + if (m_buf) { + delete[] m_buf; + } + + m_buf = new int16_t[samplerate*2]; + + m_samplerate = samplerate; + m_samplesChunkSize = (m_samplerate * m_throttlems) / 1000; + + if (wasRunning) { + startWork(); + } + } +} + +void AudioOutputWorker::tick() +{ + if (m_running) + { + qint64 throttlems = m_elapsedTimer.restart(); + + if (throttlems != m_throttlems) + { + m_throttlems = throttlems; + m_samplesChunkSize = (m_samplerate * (m_throttlems+(m_throttleToggle ? 1 : 0))) / 1000; + m_throttleToggle = !m_throttleToggle; + } + + unsigned int iPart1Begin, iPart1End, iPart2Begin, iPart2End; + SampleVector& data = m_sampleFifo->getData(); + m_sampleFifo->read(m_samplesChunkSize, iPart1Begin, iPart1End, iPart2Begin, iPart2End); + + if (iPart1Begin != iPart1End) { + callbackPart(data, iPart1Begin, iPart1End); + } + + if (iPart2Begin != iPart2End) { + callbackPart(data, iPart2Begin, iPart2End); + } + + // qDebug("AudioOutputWorker::tick: %d samples fill: %u", m_samplesChunkSize, m_audioFifo->fill()); + } +} + +void AudioOutputWorker::callbackPart(SampleVector& data, unsigned int iBegin, unsigned int iEnd) +{ + for (unsigned int i = iBegin; i < iEnd; i++) + { + m_audioBuffer[m_audioBufferFill].l = m_iqMapping == AudioOutputSettings::LR ? data[i].m_real : data[i].m_imag; + m_audioBuffer[m_audioBufferFill].r = m_iqMapping == AudioOutputSettings::LR ? data[i].m_imag : data[i].m_real; + m_audioBufferFill++; + + if (m_audioBufferFill >= m_audioBuffer.size()) + { + uint res = m_audioFifo->write((const quint8*)&m_audioBuffer[0], m_audioBufferFill); + + if (res != m_audioBufferFill) + { + qDebug("AudioOutputWorker::callbackPart: %u/%u audio samples written", res, m_audioBufferFill); + m_audioFifo->clear(); + } + + m_audioBufferFill = 0; + } + } +} diff --git a/plugins/samplesink/audiooutput/audiooutputworker.h b/plugins/samplesink/audiooutput/audiooutputworker.h new file mode 100644 index 000000000..5c1ca93a3 --- /dev/null +++ b/plugins/samplesink/audiooutput/audiooutputworker.h @@ -0,0 +1,66 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2020 Edouard Griffiths, F4EXB // +// // +// This program is free software; you can redistribute it and/or modify // +// it under the terms of the GNU General Public License as published by // +// the Free Software Foundation as version 3 of the License, or // +// (at your option) any later version. // +// // +// 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_AUDIOOUTPUTWORKER_H +#define INCLUDE_AUDIOOUTPUTWORKER_H + +#include +#include + +#include "dsp/interpolators.h" +#include "audiooutputsettings.h" + +class QTimer; +class SampleSourceFifo; +class AudioFifo; + +class AudioOutputWorker : public QObject { + Q_OBJECT + +public: + AudioOutputWorker(SampleSourceFifo* sampleFifo, AudioFifo *fifo, QObject* parent = nullptr); + ~AudioOutputWorker(); + + void startWork(); + void stopWork(); + void setSamplerate(int samplerate); + void setIQMapping(AudioOutputSettings::IQMapping iqMapping) {m_iqMapping = iqMapping;} + void connectTimer(const QTimer& timer); + +private: + bool m_running; + int m_samplerate; + int m_throttlems; + int m_maxThrottlems; + QElapsedTimer m_elapsedTimer; + bool m_throttleToggle; + AudioOutputSettings::IQMapping m_iqMapping; + AudioVector m_audioBuffer; + uint32_t m_audioBufferFill; + qint16 *m_buf; // stereo (I, Q) + unsigned int m_samplesChunkSize; + SampleSourceFifo* m_sampleFifo; + AudioFifo* m_audioFifo; + Interpolators m_interpolators; + + void callbackPart(SampleVector& data, unsigned int iBegin, unsigned int iEnd); + +private slots: + void tick(); +}; + +#endif // INCLUDE_AUDIOOUTPUTWORKER_H diff --git a/plugins/samplesink/audiooutput/readme.md b/plugins/samplesink/audiooutput/readme.md new file mode 100644 index 000000000..7275c48a9 --- /dev/null +++ b/plugins/samplesink/audiooutput/readme.md @@ -0,0 +1,36 @@ +

Audio output plugin

+ +

Introduction

+ +This output plugin sends its samples to an audio device. + +

Interface

+ +![Audio output plugin GUI](../../../doc/img/AudioOutput_plugin.png) + +

1: Start/Stop

+ +Device start / stop button. Use this switch button to play or stop audio playback + +

2: Audio sample rate

+ +Audio sample rate in Hz (Sa/s) with multiplier indicator (k). + +

3: Select audio device

+ +Use this push button to open a dialog that lets you choose the audio playback device. See [audio management documentation](../../../sdrgui/audio.md) for details. + +

4: Audio device

+ +The name of the audio device in use. + +

5: Volume

+ +A control to set the output volume. This is not supported by all output audio devices. + +

6: Channel Map

+ +This controls how the left and right audio channels map on to the IQ channels. + +* I=L, Q=R - The left audio channel is driven to the I channel. The right audio channel is driven to the Q channel for a complex (analytic signal)input. +* I=R, Q=L - The right audio channel is driven to the I channel. The left audio channel is driven to the Q channel for a complex (analytic signal)input. diff --git a/sdrbase/audio/audiofifo.cpp b/sdrbase/audio/audiofifo.cpp index 85347717f..01a780648 100644 --- a/sdrbase/audio/audiofifo.cpp +++ b/sdrbase/audio/audiofifo.cpp @@ -25,7 +25,7 @@ #define MIN(x, y) ((x) < (y) ? (x) : (y)) AudioFifo::AudioFifo() : - m_fifo(0), + m_fifo(nullptr), m_sampleSize(sizeof(AudioSample)) { m_size = 0; @@ -35,7 +35,7 @@ AudioFifo::AudioFifo() : } AudioFifo::AudioFifo(uint32_t numSamples) : - m_fifo(0), + m_fifo(nullptr), m_sampleSize(sizeof(AudioSample)) { QMutexLocker mutexLocker(&m_mutex); @@ -47,10 +47,10 @@ AudioFifo::~AudioFifo() { QMutexLocker mutexLocker(&m_mutex); - if (m_fifo != 0) + if (m_fifo) { delete[] m_fifo; - m_fifo = 0; + m_fifo = nullptr; } m_size = 0; @@ -69,7 +69,7 @@ uint AudioFifo::write(const quint8* data, uint32_t numSamples) uint32_t remaining; uint32_t copyLen; - if (m_fifo == 0) { + if (!m_fifo) { return 0; } @@ -83,6 +83,11 @@ uint AudioFifo::write(const quint8* data, uint32_t numSamples) if (isFull()) { m_mutex.unlock(); + + if (total - remaining > 0) { + emit dataReady(); + } + return total - remaining; // written so far } @@ -107,7 +112,7 @@ uint AudioFifo::read(quint8* data, uint32_t numSamples) uint32_t remaining; uint32_t copyLen; - if (m_fifo == 0) { + if (!m_fifo) { return 0; } @@ -165,10 +170,10 @@ void AudioFifo::clear() bool AudioFifo::create(uint32_t numSamples) { - if(m_fifo != 0) + if (m_fifo) { delete[] m_fifo; - m_fifo = 0; + m_fifo = nullptr; } m_fill = 0; diff --git a/sdrbase/resources/webapi.qrc b/sdrbase/resources/webapi.qrc index f6d580f0c..79f167f6d 100644 --- a/sdrbase/resources/webapi.qrc +++ b/sdrbase/resources/webapi.qrc @@ -11,6 +11,7 @@ webapi/doc/swagger/include/ATVDemod.yaml webapi/doc/swagger/include/ATVMod.yaml webapi/doc/swagger/include/AudioInput.yaml + webapi/doc/swagger/include/AudioOutput.yaml webapi/doc/swagger/include/BeamSteeringCWMod.yaml webapi/doc/swagger/include/BFMDemod.yaml webapi/doc/swagger/include/BladeRF1.yaml diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index e13223fa7..78b316629 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -1665,6 +1665,36 @@ margin-bottom: 20px; } }, "description" : "Audio output device" +}; + defs.AudioOutputSettings = { + "properties" : { + "deviceName" : { + "type" : "string", + "description" : "The name of the audio device" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "iqMapping" : { + "type" : "integer", + "description" : "Audio channel to IQ mapping\n * 0 - I=L, Q=R\n * 1 - I=R, Q=L\n" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + } + }, + "description" : "AudioOutput" }; defs.BFMDemodReport = { "properties" : { @@ -3600,6 +3630,9 @@ margin-bottom: 20px; "audioInputSettings" : { "$ref" : "#/definitions/AudioInputSettings" }, + "audioOutputSettings" : { + "$ref" : "#/definitions/AudioOutputSettings" + }, "bladeRF1InputSettings" : { "$ref" : "#/definitions/BladeRF1InputSettings" }, @@ -44596,7 +44629,7 @@ except ApiException as e:
- Generated 2020-11-11T13:32:52.276+01:00 + Generated 2020-11-21T10:29:19.215+01:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/AudioOutput.yaml b/sdrbase/resources/webapi/doc/swagger/include/AudioOutput.yaml new file mode 100644 index 000000000..15f2f46db --- /dev/null +++ b/sdrbase/resources/webapi/doc/swagger/include/AudioOutput.yaml @@ -0,0 +1,24 @@ +AudioOutputSettings: + description: AudioOutput + properties: + deviceName: + description: The name of the audio device + type: string + volume: + type: number + format: float + iqMapping: + type: integer + description: > + Audio channel to IQ mapping + * 0 - I=L, Q=R + * 1 - I=R, Q=L + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer diff --git a/sdrbase/resources/webapi/doc/swagger/include/DeviceSettings.yaml b/sdrbase/resources/webapi/doc/swagger/include/DeviceSettings.yaml index 4a75ffc72..35f0af122 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/DeviceSettings.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/DeviceSettings.yaml @@ -20,6 +20,8 @@ DeviceSettings: $ref: "/doc/swagger/include/AirspyHF.yaml#/AirspyHFSettings" audioInputSettings: $ref: "/doc/swagger/include/AudioInput.yaml#/AudioInputSettings" + audioOutputSettings: + $ref: "/doc/swagger/include/AudioOutput.yaml#/AudioOutputSettings" bladeRF1InputSettings: $ref: "/doc/swagger/include/BladeRF1.yaml#/BladeRF1InputSettings" bladeRF2InputSettings: diff --git a/sdrbase/webapi/webapirequestmapper.cpp b/sdrbase/webapi/webapirequestmapper.cpp index 9b8a85513..2556a07e4 100644 --- a/sdrbase/webapi/webapirequestmapper.cpp +++ b/sdrbase/webapi/webapirequestmapper.cpp @@ -4096,6 +4096,11 @@ bool WebAPIRequestMapper::getDeviceSettings( deviceSettings->setAudioInputSettings(new SWGSDRangel::SWGAudioInputSettings()); deviceSettings->getAudioInputSettings()->fromJsonObject(settingsJsonObject); } + else if (deviceSettingsKey == "audioOutputSettings") + { + deviceSettings->setAudioOutputSettings(new SWGSDRangel::SWGAudioOutputSettings()); + deviceSettings->getAudioOutputSettings()->fromJsonObject(settingsJsonObject); + } else if (deviceSettingsKey == "bladeRF1InputSettings") { deviceSettings->setBladeRf1InputSettings(new SWGSDRangel::SWGBladeRF1InputSettings()); diff --git a/sdrbase/webapi/webapiutils.cpp b/sdrbase/webapi/webapiutils.cpp index e4265d02a..86abdc6c8 100644 --- a/sdrbase/webapi/webapiutils.cpp +++ b/sdrbase/webapi/webapiutils.cpp @@ -68,7 +68,8 @@ const QMap WebAPIUtils::m_channelURIToSettingsKey = { const QMap WebAPIUtils::m_deviceIdToSettingsKey = { {"sdrangel.samplesource.airspy", "airspySettings"}, {"sdrangel.samplesource.airspyhf", "airspyHFSettings"}, - {"sdrangel.samplesource.audio", "audioInputSettings"}, + {"sdrangel.samplesource.audioinput", "audioInputSettings"}, + {"sdrangel.samplesink.audiooutput", "audioOutputSettings"}, {"sdrangel.samplesource.bladerf1input", "bladeRF1InputSettings"}, {"sdrangel.samplesource.bladerf", "bladeRF1InputSettings"}, // remap {"sdrangel.samplesink.bladerf1output", "bladeRF1OutputSettings"}, @@ -204,6 +205,7 @@ const QMap WebAPIUtils::m_sourceDeviceHwIdToActionsKey = { }; const QMap WebAPIUtils::m_sinkDeviceHwIdToSettingsKey = { + {"AudioOutput", "AudioOutputSettings"}, {"BladeRF1", "bladeRF1OutputSettings"}, {"BladeRF2", "bladeRF2OutputSettings"}, {"HackRF", "hackRFOutputSettings"}, diff --git a/swagger/sdrangel/api/swagger/include/AudioOutput.yaml b/swagger/sdrangel/api/swagger/include/AudioOutput.yaml new file mode 100644 index 000000000..15f2f46db --- /dev/null +++ b/swagger/sdrangel/api/swagger/include/AudioOutput.yaml @@ -0,0 +1,24 @@ +AudioOutputSettings: + description: AudioOutput + properties: + deviceName: + description: The name of the audio device + type: string + volume: + type: number + format: float + iqMapping: + type: integer + description: > + Audio channel to IQ mapping + * 0 - I=L, Q=R + * 1 - I=R, Q=L + useReverseAPI: + description: Synchronize with reverse API (1 for yes, 0 for no) + type: integer + reverseAPIAddress: + type: string + reverseAPIPort: + type: integer + reverseAPIDeviceIndex: + type: integer diff --git a/swagger/sdrangel/api/swagger/include/DeviceSettings.yaml b/swagger/sdrangel/api/swagger/include/DeviceSettings.yaml index 74cec9986..03a6922ee 100644 --- a/swagger/sdrangel/api/swagger/include/DeviceSettings.yaml +++ b/swagger/sdrangel/api/swagger/include/DeviceSettings.yaml @@ -20,6 +20,8 @@ DeviceSettings: $ref: "http://swgserver:8081/api/swagger/include/AirspyHF.yaml#/AirspyHFSettings" audioInputSettings: $ref: "http://swgserver:8081/api/swagger/include/AudioInput.yaml#/AudioInputSettings" + audioOutputSettings: + $ref: "http://swgserver:8081/api/swagger/include/AudioOutput.yaml#/AudioOutputSettings" bladeRF1InputSettings: $ref: "http://swgserver:8081/api/swagger/include/BladeRF1.yaml#/BladeRF1InputSettings" bladeRF2InputSettings: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index e13223fa7..78b316629 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -1665,6 +1665,36 @@ margin-bottom: 20px; } }, "description" : "Audio output device" +}; + defs.AudioOutputSettings = { + "properties" : { + "deviceName" : { + "type" : "string", + "description" : "The name of the audio device" + }, + "volume" : { + "type" : "number", + "format" : "float" + }, + "iqMapping" : { + "type" : "integer", + "description" : "Audio channel to IQ mapping\n * 0 - I=L, Q=R\n * 1 - I=R, Q=L\n" + }, + "useReverseAPI" : { + "type" : "integer", + "description" : "Synchronize with reverse API (1 for yes, 0 for no)" + }, + "reverseAPIAddress" : { + "type" : "string" + }, + "reverseAPIPort" : { + "type" : "integer" + }, + "reverseAPIDeviceIndex" : { + "type" : "integer" + } + }, + "description" : "AudioOutput" }; defs.BFMDemodReport = { "properties" : { @@ -3600,6 +3630,9 @@ margin-bottom: 20px; "audioInputSettings" : { "$ref" : "#/definitions/AudioInputSettings" }, + "audioOutputSettings" : { + "$ref" : "#/definitions/AudioOutputSettings" + }, "bladeRF1InputSettings" : { "$ref" : "#/definitions/BladeRF1InputSettings" }, @@ -44596,7 +44629,7 @@ except ApiException as e:
- Generated 2020-11-11T13:32:52.276+01:00 + Generated 2020-11-21T10:29:19.215+01:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioOutputSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGAudioOutputSettings.cpp new file mode 100644 index 000000000..8404fb3a9 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAudioOutputSettings.cpp @@ -0,0 +1,250 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + + +#include "SWGAudioOutputSettings.h" + +#include "SWGHelpers.h" + +#include +#include +#include +#include + +namespace SWGSDRangel { + +SWGAudioOutputSettings::SWGAudioOutputSettings(QString* json) { + init(); + this->fromJson(*json); +} + +SWGAudioOutputSettings::SWGAudioOutputSettings() { + device_name = nullptr; + m_device_name_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + iq_mapping = 0; + m_iq_mapping_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = nullptr; + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; +} + +SWGAudioOutputSettings::~SWGAudioOutputSettings() { + this->cleanup(); +} + +void +SWGAudioOutputSettings::init() { + device_name = new QString(""); + m_device_name_isSet = false; + volume = 0.0f; + m_volume_isSet = false; + iq_mapping = 0; + m_iq_mapping_isSet = false; + use_reverse_api = 0; + m_use_reverse_api_isSet = false; + reverse_api_address = new QString(""); + m_reverse_api_address_isSet = false; + reverse_api_port = 0; + m_reverse_api_port_isSet = false; + reverse_api_device_index = 0; + m_reverse_api_device_index_isSet = false; +} + +void +SWGAudioOutputSettings::cleanup() { + if(device_name != nullptr) { + delete device_name; + } + + + + if(reverse_api_address != nullptr) { + delete reverse_api_address; + } + + +} + +SWGAudioOutputSettings* +SWGAudioOutputSettings::fromJson(QString &json) { + QByteArray array (json.toStdString().c_str()); + QJsonDocument doc = QJsonDocument::fromJson(array); + QJsonObject jsonObject = doc.object(); + this->fromJsonObject(jsonObject); + return this; +} + +void +SWGAudioOutputSettings::fromJsonObject(QJsonObject &pJson) { + ::SWGSDRangel::setValue(&device_name, pJson["deviceName"], "QString", "QString"); + + ::SWGSDRangel::setValue(&volume, pJson["volume"], "float", ""); + + ::SWGSDRangel::setValue(&iq_mapping, pJson["iqMapping"], "qint32", ""); + + ::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString"); + + ::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", ""); + + ::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", ""); + +} + +QString +SWGAudioOutputSettings::asJson () +{ + QJsonObject* obj = this->asJsonObject(); + + QJsonDocument doc(*obj); + QByteArray bytes = doc.toJson(); + delete obj; + return QString(bytes); +} + +QJsonObject* +SWGAudioOutputSettings::asJsonObject() { + QJsonObject* obj = new QJsonObject(); + if(device_name != nullptr && *device_name != QString("")){ + toJsonValue(QString("deviceName"), device_name, obj, QString("QString")); + } + if(m_volume_isSet){ + obj->insert("volume", QJsonValue(volume)); + } + if(m_iq_mapping_isSet){ + obj->insert("iqMapping", QJsonValue(iq_mapping)); + } + if(m_use_reverse_api_isSet){ + obj->insert("useReverseAPI", QJsonValue(use_reverse_api)); + } + if(reverse_api_address != nullptr && *reverse_api_address != QString("")){ + toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString")); + } + if(m_reverse_api_port_isSet){ + obj->insert("reverseAPIPort", QJsonValue(reverse_api_port)); + } + if(m_reverse_api_device_index_isSet){ + obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index)); + } + + return obj; +} + +QString* +SWGAudioOutputSettings::getDeviceName() { + return device_name; +} +void +SWGAudioOutputSettings::setDeviceName(QString* device_name) { + this->device_name = device_name; + this->m_device_name_isSet = true; +} + +float +SWGAudioOutputSettings::getVolume() { + return volume; +} +void +SWGAudioOutputSettings::setVolume(float volume) { + this->volume = volume; + this->m_volume_isSet = true; +} + +qint32 +SWGAudioOutputSettings::getIqMapping() { + return iq_mapping; +} +void +SWGAudioOutputSettings::setIqMapping(qint32 iq_mapping) { + this->iq_mapping = iq_mapping; + this->m_iq_mapping_isSet = true; +} + +qint32 +SWGAudioOutputSettings::getUseReverseApi() { + return use_reverse_api; +} +void +SWGAudioOutputSettings::setUseReverseApi(qint32 use_reverse_api) { + this->use_reverse_api = use_reverse_api; + this->m_use_reverse_api_isSet = true; +} + +QString* +SWGAudioOutputSettings::getReverseApiAddress() { + return reverse_api_address; +} +void +SWGAudioOutputSettings::setReverseApiAddress(QString* reverse_api_address) { + this->reverse_api_address = reverse_api_address; + this->m_reverse_api_address_isSet = true; +} + +qint32 +SWGAudioOutputSettings::getReverseApiPort() { + return reverse_api_port; +} +void +SWGAudioOutputSettings::setReverseApiPort(qint32 reverse_api_port) { + this->reverse_api_port = reverse_api_port; + this->m_reverse_api_port_isSet = true; +} + +qint32 +SWGAudioOutputSettings::getReverseApiDeviceIndex() { + return reverse_api_device_index; +} +void +SWGAudioOutputSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) { + this->reverse_api_device_index = reverse_api_device_index; + this->m_reverse_api_device_index_isSet = true; +} + + +bool +SWGAudioOutputSettings::isSet(){ + bool isObjectUpdated = false; + do{ + if(device_name && *device_name != QString("")){ + isObjectUpdated = true; break; + } + if(m_volume_isSet){ + isObjectUpdated = true; break; + } + if(m_iq_mapping_isSet){ + isObjectUpdated = true; break; + } + if(m_use_reverse_api_isSet){ + isObjectUpdated = true; break; + } + if(reverse_api_address && *reverse_api_address != QString("")){ + isObjectUpdated = true; break; + } + if(m_reverse_api_port_isSet){ + isObjectUpdated = true; break; + } + if(m_reverse_api_device_index_isSet){ + isObjectUpdated = true; break; + } + }while(false); + return isObjectUpdated; +} +} + diff --git a/swagger/sdrangel/code/qt5/client/SWGAudioOutputSettings.h b/swagger/sdrangel/code/qt5/client/SWGAudioOutputSettings.h new file mode 100644 index 000000000..c594d47b1 --- /dev/null +++ b/swagger/sdrangel/code/qt5/client/SWGAudioOutputSettings.h @@ -0,0 +1,95 @@ +/** + * SDRangel + * This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time --- + * + * OpenAPI spec version: 6.0.0 + * Contact: f4exb06@gmail.com + * + * NOTE: This class is auto generated by the swagger code generator program. + * https://github.com/swagger-api/swagger-codegen.git + * Do not edit the class manually. + */ + +/* + * SWGAudioOutputSettings.h + * + * AudioOutput + */ + +#ifndef SWGAudioOutputSettings_H_ +#define SWGAudioOutputSettings_H_ + +#include + + +#include + +#include "SWGObject.h" +#include "export.h" + +namespace SWGSDRangel { + +class SWG_API SWGAudioOutputSettings: public SWGObject { +public: + SWGAudioOutputSettings(); + SWGAudioOutputSettings(QString* json); + virtual ~SWGAudioOutputSettings(); + void init(); + void cleanup(); + + virtual QString asJson () override; + virtual QJsonObject* asJsonObject() override; + virtual void fromJsonObject(QJsonObject &json) override; + virtual SWGAudioOutputSettings* fromJson(QString &jsonString) override; + + QString* getDeviceName(); + void setDeviceName(QString* device_name); + + float getVolume(); + void setVolume(float volume); + + qint32 getIqMapping(); + void setIqMapping(qint32 iq_mapping); + + qint32 getUseReverseApi(); + void setUseReverseApi(qint32 use_reverse_api); + + QString* getReverseApiAddress(); + void setReverseApiAddress(QString* reverse_api_address); + + qint32 getReverseApiPort(); + void setReverseApiPort(qint32 reverse_api_port); + + qint32 getReverseApiDeviceIndex(); + void setReverseApiDeviceIndex(qint32 reverse_api_device_index); + + + virtual bool isSet() override; + +private: + QString* device_name; + bool m_device_name_isSet; + + float volume; + bool m_volume_isSet; + + qint32 iq_mapping; + bool m_iq_mapping_isSet; + + qint32 use_reverse_api; + bool m_use_reverse_api_isSet; + + QString* reverse_api_address; + bool m_reverse_api_address_isSet; + + qint32 reverse_api_port; + bool m_reverse_api_port_isSet; + + qint32 reverse_api_device_index; + bool m_reverse_api_device_index_isSet; + +}; + +} + +#endif /* SWGAudioOutputSettings_H_ */ diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp index 02795a32c..a9d12fc7e 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.cpp @@ -40,6 +40,8 @@ SWGDeviceSettings::SWGDeviceSettings() { m_airspy_hf_settings_isSet = false; audio_input_settings = nullptr; m_audio_input_settings_isSet = false; + audio_output_settings = nullptr; + m_audio_output_settings_isSet = false; blade_rf1_input_settings = nullptr; m_blade_rf1_input_settings_isSet = false; blade_rf2_input_settings = nullptr; @@ -130,6 +132,8 @@ SWGDeviceSettings::init() { m_airspy_hf_settings_isSet = false; audio_input_settings = new SWGAudioInputSettings(); m_audio_input_settings_isSet = false; + audio_output_settings = new SWGAudioOutputSettings(); + m_audio_output_settings_isSet = false; blade_rf1_input_settings = new SWGBladeRF1InputSettings(); m_blade_rf1_input_settings_isSet = false; blade_rf2_input_settings = new SWGBladeRF2InputSettings(); @@ -218,6 +222,9 @@ SWGDeviceSettings::cleanup() { if(audio_input_settings != nullptr) { delete audio_input_settings; } + if(audio_output_settings != nullptr) { + delete audio_output_settings; + } if(blade_rf1_input_settings != nullptr) { delete blade_rf1_input_settings; } @@ -348,6 +355,8 @@ SWGDeviceSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&audio_input_settings, pJson["audioInputSettings"], "SWGAudioInputSettings", "SWGAudioInputSettings"); + ::SWGSDRangel::setValue(&audio_output_settings, pJson["audioOutputSettings"], "SWGAudioOutputSettings", "SWGAudioOutputSettings"); + ::SWGSDRangel::setValue(&blade_rf1_input_settings, pJson["bladeRF1InputSettings"], "SWGBladeRF1InputSettings", "SWGBladeRF1InputSettings"); ::SWGSDRangel::setValue(&blade_rf2_input_settings, pJson["bladeRF2InputSettings"], "SWGBladeRF2InputSettings", "SWGBladeRF2InputSettings"); @@ -452,6 +461,9 @@ SWGDeviceSettings::asJsonObject() { if((audio_input_settings != nullptr) && (audio_input_settings->isSet())){ toJsonValue(QString("audioInputSettings"), audio_input_settings, obj, QString("SWGAudioInputSettings")); } + if((audio_output_settings != nullptr) && (audio_output_settings->isSet())){ + toJsonValue(QString("audioOutputSettings"), audio_output_settings, obj, QString("SWGAudioOutputSettings")); + } if((blade_rf1_input_settings != nullptr) && (blade_rf1_input_settings->isSet())){ toJsonValue(QString("bladeRF1InputSettings"), blade_rf1_input_settings, obj, QString("SWGBladeRF1InputSettings")); } @@ -621,6 +633,16 @@ SWGDeviceSettings::setAudioInputSettings(SWGAudioInputSettings* audio_input_sett this->m_audio_input_settings_isSet = true; } +SWGAudioOutputSettings* +SWGDeviceSettings::getAudioOutputSettings() { + return audio_output_settings; +} +void +SWGDeviceSettings::setAudioOutputSettings(SWGAudioOutputSettings* audio_output_settings) { + this->audio_output_settings = audio_output_settings; + this->m_audio_output_settings_isSet = true; +} + SWGBladeRF1InputSettings* SWGDeviceSettings::getBladeRf1InputSettings() { return blade_rf1_input_settings; @@ -994,6 +1016,9 @@ SWGDeviceSettings::isSet(){ if(audio_input_settings && audio_input_settings->isSet()){ isObjectUpdated = true; break; } + if(audio_output_settings && audio_output_settings->isSet()){ + isObjectUpdated = true; break; + } if(blade_rf1_input_settings && blade_rf1_input_settings->isSet()){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h index b77b7cbf1..ef74f5bc6 100644 --- a/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGDeviceSettings.h @@ -25,6 +25,7 @@ #include "SWGAirspyHFSettings.h" #include "SWGAirspySettings.h" #include "SWGAudioInputSettings.h" +#include "SWGAudioOutputSettings.h" #include "SWGBladeRF1InputSettings.h" #include "SWGBladeRF1OutputSettings.h" #include "SWGBladeRF2InputSettings.h" @@ -98,6 +99,9 @@ public: SWGAudioInputSettings* getAudioInputSettings(); void setAudioInputSettings(SWGAudioInputSettings* audio_input_settings); + SWGAudioOutputSettings* getAudioOutputSettings(); + void setAudioOutputSettings(SWGAudioOutputSettings* audio_output_settings); + SWGBladeRF1InputSettings* getBladeRf1InputSettings(); void setBladeRf1InputSettings(SWGBladeRF1InputSettings* blade_rf1_input_settings); @@ -225,6 +229,9 @@ private: SWGAudioInputSettings* audio_input_settings; bool m_audio_input_settings_isSet; + SWGAudioOutputSettings* audio_output_settings; + bool m_audio_output_settings_isSet; + SWGBladeRF1InputSettings* blade_rf1_input_settings; bool m_blade_rf1_input_settings_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h index f4f960a9d..8c1faa313 100644 --- a/swagger/sdrangel/code/qt5/client/SWGModelFactory.h +++ b/swagger/sdrangel/code/qt5/client/SWGModelFactory.h @@ -38,6 +38,7 @@ #include "SWGAudioInputDevice.h" #include "SWGAudioInputSettings.h" #include "SWGAudioOutputDevice.h" +#include "SWGAudioOutputSettings.h" #include "SWGBFMDemodReport.h" #include "SWGBFMDemodSettings.h" #include "SWGBandwidth.h" @@ -306,6 +307,9 @@ namespace SWGSDRangel { if(QString("SWGAudioOutputDevice").compare(type) == 0) { return new SWGAudioOutputDevice(); } + if(QString("SWGAudioOutputSettings").compare(type) == 0) { + return new SWGAudioOutputSettings(); + } if(QString("SWGBFMDemodReport").compare(type) == 0) { return new SWGBFMDemodReport(); }