diff --git a/samples/afvclient/afvclient.pro b/samples/afvclient/afvclient.pro index 83eaeab98..e098d75ed 100644 --- a/samples/afvclient/afvclient.pro +++ b/samples/afvclient/afvclient.pro @@ -4,7 +4,7 @@ QT += dbus network multimedia gui quick CONFIG += c++14 CONFIG -= app_bundle -CONFIG += blackmisc blackcore blackconfig +CONFIG += blackmisc blackcore blackconfig DEPENDPATH += . $$SourceRoot/src/blackmisc INCLUDEPATH += . $$SourceRoot/src @@ -23,19 +23,13 @@ DEFINES += QT_DEPRECATED_WARNINGS # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 -SOURCES += \ - afvmapreader.cpp \ - models/atcstationmodel.cpp \ - main.cpp \ - -HEADERS += \ - models/atcstationmodel.h \ - afvmapreader.h \ +HEADERS += *.h +HEADERS += $$PWD/models/*.h +SOURCES += *.cpp +SOURCES += $$PWD/models/*.cpp DEFINES += _USE_MATH_DEFINES - -RESOURCES += \ - qml/qml.qrc +RESOURCES += qml/qml.qrc DESTDIR = $$DestRoot/bin diff --git a/samples/afvclient/afvmapreader.cpp b/samples/afvclient/afvmapreader.cpp index 480dee1a4..5b8d501f8 100644 --- a/samples/afvclient/afvmapreader.cpp +++ b/samples/afvclient/afvmapreader.cpp @@ -7,12 +7,14 @@ #include #include +using namespace BlackCore::Afv; + AFVMapReader::AFVMapReader(QObject *parent) : QObject(parent) { - model = new AtcStationModel(this); - timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &AFVMapReader::updateFromMap); - timer->start(3000); + m_model = new CSampleAtcStationModel(this); + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &AFVMapReader::updateFromMap); + m_timer->start(3000); } void AFVMapReader::updateFromMap() @@ -32,7 +34,7 @@ void AFVMapReader::updateFromMap() if (jsonDoc.isObject()) { QJsonObject rootObject = jsonDoc.object(); - QVector transceivers; + QVector transceivers; if (rootObject.contains("controllers")) { @@ -79,11 +81,11 @@ void AFVMapReader::updateFromMap() } if (transceivers.isEmpty()) { return; } - transceivers.erase(std::remove_if(transceivers.begin(), transceivers.end(), [this](const AtcStation &s) + transceivers.erase(std::remove_if(transceivers.begin(), transceivers.end(), [this](const CSampleAtcStation &s) { return s.callsign() == m_callsign; }), transceivers.end()); - model->updateAtcStations(transceivers); + m_model->updateAtcStations(transceivers); } } diff --git a/samples/afvclient/afvmapreader.h b/samples/afvclient/afvmapreader.h index 57acb8900..457510dfc 100644 --- a/samples/afvclient/afvmapreader.h +++ b/samples/afvclient/afvmapreader.h @@ -1,28 +1,39 @@ -#ifndef AFVMAPREADER_H -#define AFVMAPREADER_H +/* Copyright (C) 2019 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#ifndef BLACKSAMPLE_AFVMAPREADER_H +#define BLACKSAMPLE_AFVMAPREADER_H #include "models/atcstationmodel.h" #include #include +//! Map reader class AFVMapReader : public QObject { Q_OBJECT - Q_PROPERTY(AtcStationModel* atcStationModel READ getAtcStationModel CONSTANT) + Q_PROPERTY(CSampleAtcStationModel* atcStationModel READ getAtcStationModel CONSTANT) + public: + //! Ctor AFVMapReader(QObject *parent = nullptr); Q_INVOKABLE void setOwnCallsign(const QString &callsign) { m_callsign = callsign; } void updateFromMap(); - AtcStationModel *getAtcStationModel() { return model; } + CSampleAtcStationModel *getAtcStationModel() { return m_model; } private: - AtcStationModel *model = nullptr; - QTimer *timer = nullptr; + CSampleAtcStationModel *m_model = nullptr; + QTimer *m_timer = nullptr; QString m_callsign; }; -#endif // AFVMAPREADER_H +#endif // guard diff --git a/samples/afvclient/main.cpp b/samples/afvclient/main.cpp index 2a4bb3cf8..e18fbdedd 100644 --- a/samples/afvclient/main.cpp +++ b/samples/afvclient/main.cpp @@ -1,4 +1,5 @@ // #include "voiceclientui.h" + #include "models/atcstationmodel.h" #include "clients/afvclient.h" #include "afvmapreader.h" @@ -10,6 +11,8 @@ #include #include +using namespace BlackCore::Afv::Clients; + int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); diff --git a/samples/afvclient/models/atcstationmodel.cpp b/samples/afvclient/models/atcstationmodel.cpp index e97d0d038..a35ae6f6c 100644 --- a/samples/afvclient/models/atcstationmodel.cpp +++ b/samples/afvclient/models/atcstationmodel.cpp @@ -1,34 +1,43 @@ +/* Copyright (C) 2019 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + #include "atcstationmodel.h" #include "dto.h" #include -AtcStation::AtcStation(const QString &callsign, const TransceiverDto &transceiver) : +using namespace BlackCore::Afv; + +CSampleAtcStation::CSampleAtcStation(const QString &callsign, const TransceiverDto &transceiver) : m_callsign(callsign), m_transceiver(transceiver) { } -double AtcStation::latitude() const +double CSampleAtcStation::latitude() const { return m_transceiver.LatDeg; } -double AtcStation::longitude() const +double CSampleAtcStation::longitude() const { return m_transceiver.LonDeg; } -quint32 AtcStation::frequency() const +quint32 CSampleAtcStation::frequency() const { return m_transceiver.frequency; } - -QString AtcStation::formattedFrequency() const +QString CSampleAtcStation::formattedFrequency() const { return QString::number(m_transceiver.frequency / 1000000.0, 'f', 3); } -double AtcStation::radioDistanceM() const +double CSampleAtcStation::radioDistanceM() const { double sqrtAltM = qSqrt(m_transceiver.HeightMslM); const double radioFactor = 4193.18014745372; @@ -36,20 +45,13 @@ double AtcStation::radioDistanceM() const return radioFactor * sqrtAltM; } -QString AtcStation::callsign() const -{ - return m_callsign; -} - -AtcStationModel::AtcStationModel(QObject *parent) : +CSampleAtcStationModel::CSampleAtcStationModel(QObject *parent) : QAbstractListModel(parent) -{ +{ } -} +CSampleAtcStationModel::~CSampleAtcStationModel() {} -AtcStationModel::~AtcStationModel() {} - -void AtcStationModel::updateAtcStations(const QVector &atcStations) +void CSampleAtcStationModel::updateAtcStations(const QVector &atcStations) { // Add stations which didn't exist yet for (const auto &station : atcStations) @@ -60,7 +62,7 @@ void AtcStationModel::updateAtcStations(const QVector &atcStations) // Remove all stations which are no longer there for (int i = m_atcStations.size() - 1; i >= 0; i--) { - AtcStation &station = m_atcStations[i]; + CSampleAtcStation &station = m_atcStations[i]; if (! m_atcStations.contains(station)) { removeStationAtPosition(i); @@ -68,48 +70,42 @@ void AtcStationModel::updateAtcStations(const QVector &atcStations) } } -void AtcStationModel::addStation(const AtcStation &atcStation) +void CSampleAtcStationModel::addStation(const CSampleAtcStation &atcStation) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_atcStations << atcStation; endInsertRows(); } -void AtcStationModel::removeStationAtPosition(int i) +void CSampleAtcStationModel::removeStationAtPosition(int i) { beginRemoveRows(QModelIndex(), i, i); m_atcStations.removeAt(i); endRemoveRows(); } -int AtcStationModel::rowCount(const QModelIndex &parent) const +int CSampleAtcStationModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_atcStations.count(); } -QVariant AtcStationModel::data(const QModelIndex &index, int role) const +QVariant CSampleAtcStationModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= m_atcStations.count()) return QVariant(); - const AtcStation &atcStation = m_atcStations[index.row()]; - if (role == CallsignRole) - return atcStation.callsign(); - else if (role == LatitudeRole) - return atcStation.latitude(); - else if (role == LongitudeRole) - return atcStation.longitude(); - else if (role == RadioDistanceRole) - return atcStation.radioDistanceM(); - else if (role == FrequencyRole) - return atcStation.formattedFrequency(); - else if (role == FrequencyKhzRole) - return atcStation.frequency() / 1000; + const CSampleAtcStation &atcStation = m_atcStations[index.row()]; + if (role == CallsignRole) return atcStation.callsign(); + if (role == LatitudeRole) return atcStation.latitude(); + if (role == LongitudeRole) return atcStation.longitude(); + if (role == RadioDistanceRole) return atcStation.radioDistanceM(); + if (role == FrequencyRole) return atcStation.formattedFrequency(); + if (role == FrequencyKhzRole) return atcStation.frequency() / 1000; return QVariant(); } -QHash AtcStationModel::roleNames() const +QHash CSampleAtcStationModel::roleNames() const { QHash roles; roles[CallsignRole] = "callsign"; diff --git a/samples/afvclient/models/atcstationmodel.h b/samples/afvclient/models/atcstationmodel.h index baab921e6..b0039730c 100644 --- a/samples/afvclient/models/atcstationmodel.h +++ b/samples/afvclient/models/atcstationmodel.h @@ -1,5 +1,13 @@ -#ifndef ATCSTATIONMODEL_H -#define ATCSTATIONMODEL_H +/* Copyright (C) 2019 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#ifndef BLACKSAMPLE_MODELS_ATCSTATIONMODEL_H +#define BLACKSAMPLE_MODELS_ATCSTATIONMODEL_H #include "dto.h" #include @@ -7,40 +15,48 @@ #include #include #include +#include -class AtcStation +//! Sample ATC station +class CSampleAtcStation { public: - AtcStation() {} - AtcStation(const QString &callsign, const TransceiverDto &transceiver); + //! Ctor + CSampleAtcStation() {} - QString callsign() const; + //! Ctor + CSampleAtcStation(const QString &callsign, const BlackCore::Afv::TransceiverDto &transceiver); + + //! Getter @{ + const QString &callsign() const { return m_callsign; } + QString formattedFrequency() const; double latitude() const; double longitude() const; - quint32 frequency() const; - - QString formattedFrequency() const; - double radioDistanceM() const; + quint32 frequency() const; + //! @} private: QString m_callsign; - TransceiverDto m_transceiver; + BlackCore::Afv::TransceiverDto m_transceiver; }; -inline bool operator==(const AtcStation& lhs, const AtcStation& rhs) +inline bool operator==(const CSampleAtcStation &lhs, const CSampleAtcStation &rhs) { return lhs.callsign() == rhs.callsign() && - qFuzzyCompare(lhs.latitude(), rhs.latitude()) && - qFuzzyCompare(lhs.longitude(), rhs.longitude()); + qFuzzyCompare(lhs.latitude(), rhs.latitude()) && + qFuzzyCompare(lhs.longitude(), rhs.longitude()); } - -class AtcStationModel : public QAbstractListModel +//! Sample list model +class CSampleAtcStationModel : public QAbstractListModel { Q_OBJECT + public: - enum AtcStationRoles { + //! Roles for model + enum AtcStationRoles + { CallsignRole = Qt::UserRole + 1, LatitudeRole, LongitudeRole, @@ -49,22 +65,30 @@ public: FrequencyKhzRole }; - AtcStationModel(QObject *parent = nullptr); - virtual ~AtcStationModel(); + //! Ctor + CSampleAtcStationModel(QObject *parent = nullptr); - void updateAtcStations(const QVector &atcStations); + //! Dtor + virtual ~CSampleAtcStationModel() override; - int rowCount(const QModelIndex & parent = QModelIndex()) const override; + //! Update the stations + void updateAtcStations(const QVector &atcStations); - QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; + //! copydoc QAbstractListModel::rowCount + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + //! copydoc QAbstractListModel::data + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; protected: + //! copydoc QAbstractListModel::roleNames QHash roleNames() const override; + private: - void addStation(const AtcStation &atcStation); + void addStation(const CSampleAtcStation &atcStation); void removeStationAtPosition(int i); - QList m_atcStations; + QList m_atcStations; }; -#endif // ATCSTATIONMODEL_H +#endif // guard diff --git a/samples/samples.pro b/samples/samples.pro index 5e75c7528..38764d720 100644 --- a/samples/samples.pro +++ b/samples/samples.pro @@ -21,5 +21,6 @@ sampleblackmiscsim.file = blackmiscsim/sampleblackmiscsim.pro samplehotkey.file = hotkey/samplehotkey.pro sampleweatherdata.file = weatherdata/sampleweatherdata.pro samplefsd.file = fsd/samplefsd.pro +afvclient.file = afvclient/afvclient.pro load(common_post) diff --git a/src/blackcore/afv/afv.pri b/src/blackcore/afv/afv.pri deleted file mode 100644 index 3ab2ab756..000000000 --- a/src/blackcore/afv/afv.pri +++ /dev/null @@ -1,31 +0,0 @@ -SOURCES += \ - $$PWD/audio/callsigndelaycache.cpp \ - $$PWD/audio/callsignsampleprovider.cpp \ - $$PWD/audio/input.cpp \ - $$PWD/audio/output.cpp \ - $$PWD/audio/receiversampleprovider.cpp \ - $$PWD/audio/soundcardsampleprovider.cpp \ - $$PWD/clients/afvclient.cpp \ - $$PWD/connection/clientconnection.cpp \ - $$PWD/connection/apiserverconnection.cpp \ - $$PWD/connection/clientconnectiondata.cpp \ - $$PWD/crypto/cryptodtoserializer.cpp \ - $$PWD/crypto/cryptodtochannel.cpp \ - -HEADERS += \ - $$PWD/audio/callsigndelaycache.h \ - $$PWD/audio/callsignsampleprovider.h \ - $$PWD/audio/receiversampleprovider.h \ - $$PWD/audio/input.h \ - $$PWD/audio/output.h \ - $$PWD/audio/soundcardsampleprovider.h \ - $$PWD/clients/afvclient.h \ - $$PWD/connection/apiserverconnection.h \ - $$PWD/connection/clientconnection.h \ - $$PWD/connection/clientconnectiondata.h \ - $$PWD/crypto/cryptodtoserializer.h \ - $$PWD/crypto/cryptodtochannel.h \ - $$PWD/crypto/cryptodtomode.h \ - $$PWD/crypto/cryptodtoheaderdto.h \ - $$PWD/dto.h \ - $$PWD/constants.h \ diff --git a/src/blackcore/afv/audio/callsigndelaycache.cpp b/src/blackcore/afv/audio/callsigndelaycache.cpp index 87c7810f0..0f7851828 100644 --- a/src/blackcore/afv/audio/callsigndelaycache.cpp +++ b/src/blackcore/afv/audio/callsigndelaycache.cpp @@ -1,67 +1,82 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + #include "callsigndelaycache.h" -void CallsignDelayCache::initialise(const QString &callsign) +namespace BlackCore { - if (!m_delayCache.contains(callsign)) - m_delayCache[callsign] = delayDefault; - - if (!successfulTransmissionsCache.contains(callsign)) - successfulTransmissionsCache[callsign] = 0; -} - -int CallsignDelayCache::get(const QString &callsign) -{ - return m_delayCache[callsign]; -} - -void CallsignDelayCache::underflow(const QString &callsign) -{ - if (!successfulTransmissionsCache.contains(callsign)) - return; - - successfulTransmissionsCache[callsign] = 0; - increaseDelayMs(callsign); -} - -void CallsignDelayCache::success(const QString &callsign) -{ - if (!successfulTransmissionsCache.contains(callsign)) - return; - - successfulTransmissionsCache[callsign]++; - if (successfulTransmissionsCache[callsign] > 5) + namespace Afv { - decreaseDelayMs(callsign); - successfulTransmissionsCache[callsign] = 0; - } -} + namespace Audio + { + void CallsignDelayCache::initialise(const QString &callsign) + { + if (!m_delayCache.contains(callsign)) { m_delayCache[callsign] = delayDefault; } + if (!successfulTransmissionsCache.contains(callsign)) { successfulTransmissionsCache[callsign] = 0; } + } -void CallsignDelayCache::increaseDelayMs(const QString &callsign) -{ - if (!m_delayCache.contains(callsign)) - return; + int CallsignDelayCache::get(const QString &callsign) + { + return m_delayCache[callsign]; + } - m_delayCache[callsign] += delayIncrement; - if (m_delayCache[callsign] > delayMax) - { - m_delayCache[callsign] = delayMax; - } -} + void CallsignDelayCache::underflow(const QString &callsign) + { + if (!successfulTransmissionsCache.contains(callsign)) + return; -void CallsignDelayCache::decreaseDelayMs(const QString &callsign) -{ - if (!m_delayCache.contains(callsign)) - return; + successfulTransmissionsCache[callsign] = 0; + increaseDelayMs(callsign); + } - m_delayCache[callsign] -= delayIncrement; - if (m_delayCache[callsign] < delayMin) - { - m_delayCache[callsign] = delayMin; - } -} + void CallsignDelayCache::success(const QString &callsign) + { + if (!successfulTransmissionsCache.contains(callsign)) + return; -CallsignDelayCache &CallsignDelayCache::instance() -{ - static CallsignDelayCache cache; - return cache; -} + successfulTransmissionsCache[callsign]++; + if (successfulTransmissionsCache[callsign] > 5) + { + decreaseDelayMs(callsign); + successfulTransmissionsCache[callsign] = 0; + } + } + + void CallsignDelayCache::increaseDelayMs(const QString &callsign) + { + if (!m_delayCache.contains(callsign)) + return; + + m_delayCache[callsign] += delayIncrement; + if (m_delayCache[callsign] > delayMax) + { + m_delayCache[callsign] = delayMax; + } + } + + void CallsignDelayCache::decreaseDelayMs(const QString &callsign) + { + if (!m_delayCache.contains(callsign)) + return; + + m_delayCache[callsign] -= delayIncrement; + if (m_delayCache[callsign] < delayMin) + { + m_delayCache[callsign] = delayMin; + } + } + + CallsignDelayCache &CallsignDelayCache::instance() + { + static CallsignDelayCache cache; + return cache; + } + + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/audio/callsigndelaycache.h b/src/blackcore/afv/audio/callsigndelaycache.h index 3f9ef412e..a0bdd9ccb 100644 --- a/src/blackcore/afv/audio/callsigndelaycache.h +++ b/src/blackcore/afv/audio/callsigndelaycache.h @@ -1,32 +1,53 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + #ifndef CALLSIGNDELAYCACHE_H #define CALLSIGNDELAYCACHE_H #include #include - -class CallsignDelayCache +namespace BlackCore { -public: - void initialise(const QString &callsign); - int get(const QString &callsign); - void underflow(const QString &callsign); - void success(const QString &callsign); - void increaseDelayMs(const QString &callsign); - void decreaseDelayMs(const QString &callsign); + namespace Afv + { + namespace Audio + { + //! Callsign delay cache + class CallsignDelayCache + { + public: + void initialise(const QString &callsign); + int get(const QString &callsign); + void underflow(const QString &callsign); + void success(const QString &callsign); + void increaseDelayMs(const QString &callsign); + void decreaseDelayMs(const QString &callsign); - static CallsignDelayCache &instance(); + static CallsignDelayCache &instance(); -private: - CallsignDelayCache() = default; + private: + //! Ctor + CallsignDelayCache() = default; - static constexpr int delayDefault = 60; - static constexpr int delayMin = 40; - static constexpr int delayIncrement = 20; - static constexpr int delayMax = 300; + static constexpr int delayDefault = 60; + static constexpr int delayMin = 40; + static constexpr int delayIncrement = 20; + static constexpr int delayMax = 300; - QHash m_delayCache; - QHash successfulTransmissionsCache; -}; + QHash m_delayCache; + QHash successfulTransmissionsCache; + }; -#endif // CALLSIGNDELAYCACHE_H + } // ns + } // ns +} // ns + +#endif // guard diff --git a/src/blackcore/afv/audio/callsignsampleprovider.cpp b/src/blackcore/afv/audio/callsignsampleprovider.cpp index f1072acad..7468f56d4 100644 --- a/src/blackcore/afv/audio/callsignsampleprovider.cpp +++ b/src/blackcore/afv/audio/callsignsampleprovider.cpp @@ -1,191 +1,205 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + #include "callsignsampleprovider.h" #include "callsigndelaycache.h" #include "blacksound/sampleprovider/samples.h" #include #include -CallsignSampleProvider::CallsignSampleProvider(const QAudioFormat &audioFormat, QObject *parent) : - ISampleProvider(parent), - m_audioFormat(audioFormat), - m_decoder(audioFormat.sampleRate(), 1) +namespace BlackCore { - Q_ASSERT(audioFormat.channelCount() == 1); - - mixer = new MixingSampleProvider(this); - crackleSoundProvider = new ResourceSoundSampleProvider(Samples::instance().crackle(), mixer); - crackleSoundProvider->setLooping(true); - crackleSoundProvider->setGain(0.0); - whiteNoise = new ResourceSoundSampleProvider(Samples::instance().whiteNoise(), mixer); - whiteNoise->setLooping(true); - whiteNoise->setGain(0.0); - acBusNoise = new SawToothGenerator(400, mixer); - audioInput = new BufferedWaveProvider(audioFormat, mixer); - - // Create the compressor - simpleCompressorEffect = new SimpleCompressorEffect(audioInput, mixer); - simpleCompressorEffect->setMakeUpGain(-5.5); - - // Create the voice EQ - voiceEq = new EqualizerSampleProvider(simpleCompressorEffect, EqualizerPresets::VHFEmulation, mixer); - - mixer->addMixerInput(whiteNoise); - mixer->addMixerInput(acBusNoise); - mixer->addMixerInput(voiceEq); - - m_timer.setInterval(100); - connect(&m_timer, &QTimer::timeout, this, &CallsignSampleProvider::timerElapsed); -} - -int CallsignSampleProvider::readSamples(QVector &samples, qint64 count) -{ - int noOfSamples = mixer->readSamples(samples, count); - - if (m_inUse && m_lastPacketLatch && audioInput->getBufferedBytes() == 0) + namespace Afv { - idle(); - m_lastPacketLatch = false; - } + namespace Audio + { + CallsignSampleProvider::CallsignSampleProvider(const QAudioFormat &audioFormat, QObject *parent) : + ISampleProvider(parent), + m_audioFormat(audioFormat), + m_decoder(audioFormat.sampleRate(), 1) + { + Q_ASSERT(audioFormat.channelCount() == 1); - if (m_inUse && !m_underflow && audioInput->getBufferedBytes() == 0) - { - qDebug() << "[" << m_callsign << "] [Delay++]"; - CallsignDelayCache::instance().underflow(m_callsign); - m_underflow = true; - } + mixer = new MixingSampleProvider(this); + crackleSoundProvider = new ResourceSoundSampleProvider(Samples::instance().crackle(), mixer); + crackleSoundProvider->setLooping(true); + crackleSoundProvider->setGain(0.0); + whiteNoise = new ResourceSoundSampleProvider(Samples::instance().whiteNoise(), mixer); + whiteNoise->setLooping(true); + whiteNoise->setGain(0.0); + acBusNoise = new SawToothGenerator(400, mixer); + audioInput = new BufferedWaveProvider(audioFormat, mixer); - return noOfSamples; -} + // Create the compressor + simpleCompressorEffect = new SimpleCompressorEffect(audioInput, mixer); + simpleCompressorEffect->setMakeUpGain(-5.5); -void CallsignSampleProvider::timerElapsed() -{ - if (m_inUse && audioInput->getBufferedBytes() == 0 && m_lastSamplesAddedUtc.msecsTo(QDateTime::currentDateTimeUtc()) > idleTimeoutMs) - { - idle(); - } -} + // Create the voice EQ + voiceEq = new EqualizerSampleProvider(simpleCompressorEffect, EqualizerPresets::VHFEmulation, mixer); -QString CallsignSampleProvider::type() const -{ - return m_type; -} + mixer->addMixerInput(whiteNoise); + mixer->addMixerInput(acBusNoise); + mixer->addMixerInput(voiceEq); -void CallsignSampleProvider::active(const QString &callsign, const QString &aircraftType) -{ - m_callsign = callsign; - CallsignDelayCache::instance().initialise(callsign); - m_type = aircraftType; - m_decoder.resetState(); - m_inUse = true; - setEffects(); - m_underflow = false; + m_timer.setInterval(100); + connect(&m_timer, &QTimer::timeout, this, &CallsignSampleProvider::timerElapsed); + } - int delayMs = CallsignDelayCache::instance().get(callsign); - qDebug() << "[" << m_callsign << "] [Delay " << delayMs << "ms]"; - if (delayMs > 0) - { - int phaseDelayLength = (m_audioFormat.sampleRate() / 1000) * delayMs; - QVector phaseDelay(phaseDelayLength * 2, 0); - audioInput->addSamples(phaseDelay); - } -} + int CallsignSampleProvider::readSamples(QVector &samples, qint64 count) + { + int noOfSamples = mixer->readSamples(samples, count); -void CallsignSampleProvider::activeSilent(const QString &callsign, const QString &aircraftType) -{ - m_callsign = callsign; - CallsignDelayCache::instance().initialise(callsign); - m_type = aircraftType; - m_decoder.resetState(); - m_inUse = true; - setEffects(true); - m_underflow = true; -} + if (m_inUse && m_lastPacketLatch && audioInput->getBufferedBytes() == 0) + { + idle(); + m_lastPacketLatch = false; + } -void CallsignSampleProvider::clear() -{ - idle(); - audioInput->clearBuffer(); -} + if (m_inUse && !m_underflow && audioInput->getBufferedBytes() == 0) + { + qDebug() << "[" << m_callsign << "] [Delay++]"; + CallsignDelayCache::instance().underflow(m_callsign); + m_underflow = true; + } -void CallsignSampleProvider::addOpusSamples(const IAudioDto &audioDto, float distanceRatio) -{ - m_distanceRatio = distanceRatio; + return noOfSamples; + } - QVector audio = decodeOpus(audioDto.audio); - audioInput->addSamples(audio); - m_lastPacketLatch = audioDto.lastPacket; - if (audioDto.lastPacket && !m_underflow) - CallsignDelayCache::instance().success(m_callsign); + void CallsignSampleProvider::timerElapsed() + { + if (m_inUse && audioInput->getBufferedBytes() == 0 && m_lastSamplesAddedUtc.msecsTo(QDateTime::currentDateTimeUtc()) > idleTimeoutMs) + { + idle(); + } + } - m_lastSamplesAddedUtc = QDateTime::currentDateTimeUtc(); - if (!m_timer.isActive()) { m_timer.start(); } -} + QString CallsignSampleProvider::type() const + { + return m_type; + } -void CallsignSampleProvider::addSilentSamples(const IAudioDto &audioDto) -{ - // Disable all audio effects - setEffects(true); + void CallsignSampleProvider::active(const QString &callsign, const QString &aircraftType) + { + m_callsign = callsign; + CallsignDelayCache::instance().initialise(callsign); + m_type = aircraftType; + m_decoder.resetState(); + m_inUse = true; + setEffects(); + m_underflow = false; - // TODO audioInput->addSamples(decoderByteBuffer, 0, frameCount * 2); - m_lastPacketLatch = audioDto.lastPacket; + int delayMs = CallsignDelayCache::instance().get(callsign); + qDebug() << "[" << m_callsign << "] [Delay " << delayMs << "ms]"; + if (delayMs > 0) + { + int phaseDelayLength = (m_audioFormat.sampleRate() / 1000) * delayMs; + QVector phaseDelay(phaseDelayLength * 2, 0); + audioInput->addSamples(phaseDelay); + } + } - m_lastSamplesAddedUtc = QDateTime::currentDateTimeUtc(); - if (!m_timer.isActive()) { m_timer.start(); } -} + void CallsignSampleProvider::activeSilent(const QString &callsign, const QString &aircraftType) + { + m_callsign = callsign; + CallsignDelayCache::instance().initialise(callsign); + m_type = aircraftType; + m_decoder.resetState(); + m_inUse = true; + setEffects(true); + m_underflow = true; + } -QString CallsignSampleProvider::callsign() const -{ - return m_callsign; -} + void CallsignSampleProvider::clear() + { + idle(); + audioInput->clearBuffer(); + } -void CallsignSampleProvider::idle() -{ - m_timer.stop(); - m_inUse = false; - setEffects(); - m_callsign = QString(); - m_type = QString(); -} + void CallsignSampleProvider::addOpusSamples(const IAudioDto &audioDto, float distanceRatio) + { + m_distanceRatio = distanceRatio; -QVector CallsignSampleProvider::decodeOpus(const QByteArray &opusData) -{ - int decodedLength = 0; - QVector decoded = m_decoder.decode(opusData, opusData.size(), &decodedLength); - return decoded; -} + QVector audio = decodeOpus(audioDto.audio); + audioInput->addSamples(audio); + m_lastPacketLatch = audioDto.lastPacket; + if (audioDto.lastPacket && !m_underflow) + CallsignDelayCache::instance().success(m_callsign); -void CallsignSampleProvider::setEffects(bool noEffects) -{ - if (noEffects || m_bypassEffects || !m_inUse) - { - crackleSoundProvider->setGain(0.0); - whiteNoise->setGain(0.0); - acBusNoise->setGain(0.0); - simpleCompressorEffect->setEnabled(false); - voiceEq->setBypassEffects(true); - } - else - { - float crackleFactor = (float)(((qExp(m_distanceRatio) * qPow(m_distanceRatio, -4.0)) / 350) - 0.00776652); + m_lastSamplesAddedUtc = QDateTime::currentDateTimeUtc(); + if (!m_timer.isActive()) { m_timer.start(); } + } - if (crackleFactor < 0.0f) { crackleFactor = 0.0f; } - if (crackleFactor > 0.20f) { crackleFactor = 0.20f; } + void CallsignSampleProvider::addSilentSamples(const IAudioDto &audioDto) + { + // Disable all audio effects + setEffects(true); - crackleSoundProvider->setGain(crackleFactor * 2); - whiteNoise->setGain(whiteNoiseGainMin); - acBusNoise->setGain(acBusGainMin); - simpleCompressorEffect->setEnabled(true); - voiceEq->setBypassEffects(false); - voiceEq->setOutputGain(1.0 - crackleFactor * 3.7); - } -} + // TODO audioInput->addSamples(decoderByteBuffer, 0, frameCount * 2); + m_lastPacketLatch = audioDto.lastPacket; -void CallsignSampleProvider::setBypassEffects(bool bypassEffects) -{ - m_bypassEffects = bypassEffects; - setEffects(); -} + m_lastSamplesAddedUtc = QDateTime::currentDateTimeUtc(); + if (!m_timer.isActive()) { m_timer.start(); } + } -bool CallsignSampleProvider::inUse() const -{ - return m_inUse; -} + void CallsignSampleProvider::idle() + { + m_timer.stop(); + m_inUse = false; + setEffects(); + m_callsign = QString(); + m_type = QString(); + } + + QVector CallsignSampleProvider::decodeOpus(const QByteArray &opusData) + { + int decodedLength = 0; + QVector decoded = m_decoder.decode(opusData, opusData.size(), &decodedLength); + return decoded; + } + + void CallsignSampleProvider::setEffects(bool noEffects) + { + if (noEffects || m_bypassEffects || !m_inUse) + { + crackleSoundProvider->setGain(0.0); + whiteNoise->setGain(0.0); + acBusNoise->setGain(0.0); + simpleCompressorEffect->setEnabled(false); + voiceEq->setBypassEffects(true); + } + else + { + float crackleFactor = (float)(((qExp(m_distanceRatio) * qPow(m_distanceRatio, -4.0)) / 350) - 0.00776652); + + if (crackleFactor < 0.0f) { crackleFactor = 0.0f; } + if (crackleFactor > 0.20f) { crackleFactor = 0.20f; } + + crackleSoundProvider->setGain(crackleFactor * 2); + whiteNoise->setGain(whiteNoiseGainMin); + acBusNoise->setGain(acBusGainMin); + simpleCompressorEffect->setEnabled(true); + voiceEq->setBypassEffects(false); + voiceEq->setOutputGain(1.0 - crackleFactor * 3.7); + } + } + + void CallsignSampleProvider::setBypassEffects(bool bypassEffects) + { + m_bypassEffects = bypassEffects; + setEffects(); + } + + bool CallsignSampleProvider::inUse() const + { + return m_inUse; + } + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/audio/callsignsampleprovider.h b/src/blackcore/afv/audio/callsignsampleprovider.h index 1c5fb97c6..029fd3344 100644 --- a/src/blackcore/afv/audio/callsignsampleprovider.h +++ b/src/blackcore/afv/audio/callsignsampleprovider.h @@ -1,3 +1,13 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + #ifndef CALLSIGNSAMPLEPROVIDER_H #define CALLSIGNSAMPLEPROVIDER_H @@ -17,64 +27,77 @@ #include #include -class CallsignSampleProvider : public ISampleProvider + +namespace BlackCore { - Q_OBJECT + namespace Afv + { + namespace Audio + { + //! Callsign provide + class CallsignSampleProvider : public ISampleProvider + { + Q_OBJECT -public: - CallsignSampleProvider(const QAudioFormat &audioFormat, QObject *parent = nullptr); + public: + CallsignSampleProvider(const QAudioFormat &audioFormat, QObject *parent = nullptr); - int readSamples(QVector &samples, qint64 count) override; + int readSamples(QVector &samples, qint64 count) override; - QString callsign() const; - QString type() const; + //! The callsign + const QString callsign() const { return m_callsign; } - void active(const QString &callsign, const QString &aircraftType); - void activeSilent(const QString &callsign, const QString &aircraftType); + QString type() const; - void clear(); + void active(const QString &callsign, const QString &aircraftType); + void activeSilent(const QString &callsign, const QString &aircraftType); - void addOpusSamples(const IAudioDto &audioDto, float distanceRatio); - void addSilentSamples(const IAudioDto &audioDto); + void clear(); - bool inUse() const; + void addOpusSamples(const IAudioDto &audioDto, float distanceRatio); + void addSilentSamples(const IAudioDto &audioDto); - void setBypassEffects(bool bypassEffects); + bool inUse() const; -private: - void timerElapsed(); - void idle(); - QVector decodeOpus(const QByteArray &opusData); - void setEffects(bool noEffects = false); + void setBypassEffects(bool bypassEffects); - QAudioFormat m_audioFormat; + private: + void timerElapsed(); + void idle(); + QVector decodeOpus(const QByteArray &opusData); + void setEffects(bool noEffects = false); - const double whiteNoiseGainMin = 0.15; //0.01; - const double acBusGainMin = 0.003; //0.002; - const int frameCount = 960; - const int idleTimeoutMs = 500; + QAudioFormat m_audioFormat; - QString m_callsign; - QString m_type; - bool m_inUse = false; + const double whiteNoiseGainMin = 0.15; //0.01; + const double acBusGainMin = 0.003; //0.002; + const int frameCount = 960; + const int idleTimeoutMs = 500; - bool m_bypassEffects = false; + QString m_callsign; + QString m_type; + bool m_inUse = false; - float m_distanceRatio = 1.0; + bool m_bypassEffects = false; - MixingSampleProvider *mixer; - ResourceSoundSampleProvider *crackleSoundProvider; - ResourceSoundSampleProvider *whiteNoise; - SawToothGenerator *acBusNoise; - SimpleCompressorEffect *simpleCompressorEffect; - EqualizerSampleProvider *voiceEq; - BufferedWaveProvider *audioInput; - QTimer m_timer; + float m_distanceRatio = 1.0; - COpusDecoder m_decoder; - bool m_lastPacketLatch = false; - QDateTime m_lastSamplesAddedUtc; - bool m_underflow = false; -}; + MixingSampleProvider *mixer; + ResourceSoundSampleProvider *crackleSoundProvider; + ResourceSoundSampleProvider *whiteNoise; + SawToothGenerator *acBusNoise; + SimpleCompressorEffect *simpleCompressorEffect; + EqualizerSampleProvider *voiceEq; + BufferedWaveProvider *audioInput; + QTimer m_timer; -#endif // CALLSIGNSAMPLEPROVIDER_H + COpusDecoder m_decoder; + bool m_lastPacketLatch = false; + QDateTime m_lastSamplesAddedUtc; + bool m_underflow = false; + }; + } // ns + } // ns +} // ns + +#endif // guard diff --git a/src/blackcore/afv/audio/input.cpp b/src/blackcore/afv/audio/input.cpp index 5ed94df3a..28785dd91 100644 --- a/src/blackcore/afv/audio/input.cpp +++ b/src/blackcore/afv/audio/input.cpp @@ -1,3 +1,13 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + #include "input.h" #include "blacksound/audioutilities.h" @@ -5,148 +15,158 @@ #include #include -void AudioInputBuffer::start() +namespace BlackCore { - open(QIODevice::WriteOnly); -} - -void AudioInputBuffer::stop() -{ - close(); -} - -qint64 AudioInputBuffer::readData(char *data, qint64 maxlen) -{ - Q_UNUSED(data) - Q_UNUSED(maxlen) - - return 0; -} - -qint64 AudioInputBuffer::writeData(const char *data, qint64 len) -{ - QByteArray buffer(data, static_cast(len)); - m_buffer.append(buffer); - // 20 ms = 960 samples * 2 bytes = 1920 Bytes - if (m_buffer.size() >= 1920) + namespace Afv { - emit frameAvailable(m_buffer.left(1920)); - m_buffer.remove(0, 1920); - } + namespace Audio + { + void AudioInputBuffer::start() + { + open(QIODevice::WriteOnly); + } - return len; -} + void AudioInputBuffer::stop() + { + close(); + } -Input::Input(int sampleRate, QObject *parent) : - QObject(parent), - m_sampleRate(sampleRate), - m_encoder(sampleRate, 1, OPUS_APPLICATION_VOIP) -{ - m_encoder.setBitRate(16 * 1024); -} + qint64 AudioInputBuffer::readData(char *data, qint64 maxlen) + { + Q_UNUSED(data) + Q_UNUSED(maxlen) -bool Input::started() const -{ - return m_started; -} + return 0; + } -int Input::opusBytesEncoded() const -{ - return m_opusBytesEncoded; -} + qint64 AudioInputBuffer::writeData(const char *data, qint64 len) + { + QByteArray buffer(data, static_cast(len)); + m_buffer.append(buffer); + // 20 ms = 960 samples * 2 bytes = 1920 Bytes + if (m_buffer.size() >= 1920) + { + emit frameAvailable(m_buffer.left(1920)); + m_buffer.remove(0, 1920); + } -void Input::setOpusBytesEncoded(int opusBytesEncoded) -{ - m_opusBytesEncoded = opusBytesEncoded; -} + return len; + } -float Input::volume() const -{ - return m_volume; -} + Input::Input(int sampleRate, QObject *parent) : + QObject(parent), + m_sampleRate(sampleRate), + m_encoder(sampleRate, 1, OPUS_APPLICATION_VOIP) + { + m_encoder.setBitRate(16 * 1024); + } -void Input::setVolume(float volume) -{ - m_volume = volume; -} + bool Input::started() const + { + return m_started; + } -void Input::start(const QAudioDeviceInfo &inputDevice) -{ - if (m_started) { return; } + int Input::opusBytesEncoded() const + { + return m_opusBytesEncoded; + } - QAudioFormat waveFormat; + void Input::setOpusBytesEncoded(int opusBytesEncoded) + { + m_opusBytesEncoded = opusBytesEncoded; + } - waveFormat.setSampleRate(m_sampleRate); - waveFormat.setChannelCount(1); - waveFormat.setSampleSize(16); - waveFormat.setSampleType(QAudioFormat::SignedInt); - waveFormat.setByteOrder(QAudioFormat::LittleEndian); - waveFormat.setCodec("audio/pcm"); + float Input::volume() const + { + return m_volume; + } - QAudioFormat inputFormat = waveFormat; - if (!inputDevice.isFormatSupported(inputFormat)) - { - qWarning() << "Default format not supported - trying to use nearest"; - inputFormat = inputDevice.nearestFormat(inputFormat); - } + void Input::setVolume(float volume) + { + m_volume = volume; + } - m_audioInput.reset(new QAudioInput(inputDevice, inputFormat)); - // We want 20 ms of buffer size - // 20 ms * nSamplesPerSec × nChannels × wBitsPerSample / 8 x 1000 - int bufferSize = 20 * inputFormat.sampleRate() * inputFormat.channelCount() * inputFormat.sampleSize() / ( 8 * 1000 ); - m_audioInput->setBufferSize(bufferSize); - m_audioInputBuffer.start(); - m_audioInput->start(&m_audioInputBuffer); - connect(&m_audioInputBuffer, &AudioInputBuffer::frameAvailable, this, &Input::audioInDataAvailable); + void Input::start(const QAudioDeviceInfo &inputDevice) + { + if (m_started) { return; } - m_started = true; -} + QAudioFormat waveFormat; -void Input::stop() -{ - if (! m_started) { return; } + waveFormat.setSampleRate(m_sampleRate); + waveFormat.setChannelCount(1); + waveFormat.setSampleSize(16); + waveFormat.setSampleType(QAudioFormat::SignedInt); + waveFormat.setByteOrder(QAudioFormat::LittleEndian); + waveFormat.setCodec("audio/pcm"); - m_started = false; + QAudioFormat inputFormat = waveFormat; + if (!inputDevice.isFormatSupported(inputFormat)) + { + qWarning() << "Default format not supported - trying to use nearest"; + inputFormat = inputDevice.nearestFormat(inputFormat); + } - m_audioInput->stop(); - m_audioInput.reset(); -} + m_audioInput.reset(new QAudioInput(inputDevice, inputFormat)); + // We want 20 ms of buffer size + // 20 ms * nSamplesPerSec × nChannels × wBitsPerSample / 8 x 1000 + int bufferSize = 20 * inputFormat.sampleRate() * inputFormat.channelCount() * inputFormat.sampleSize() / (8 * 1000); + m_audioInput->setBufferSize(bufferSize); + m_audioInputBuffer.start(); + m_audioInput->start(&m_audioInputBuffer); + connect(&m_audioInputBuffer, &AudioInputBuffer::frameAvailable, this, &Input::audioInDataAvailable); -void Input::audioInDataAvailable(const QByteArray &frame) -{ - const QVector samples = convertBytesTo16BitPCM(frame); + m_started = true; + } - int length; - QByteArray encodedBuffer = m_encoder.encode(samples, samples.size(), &length); - m_opusBytesEncoded += length; + void Input::stop() + { + if (! m_started) { return; } - for (const qint16 sample : samples) - { - qint16 sampleInput = sample; - sampleInput = qAbs(sampleInput); - if (sampleInput > m_maxSampleInput) - m_maxSampleInput = sampleInput; - } + m_started = false; - m_sampleCount += samples.size(); - if (m_sampleCount >= c_sampleCountPerEvent) - { - InputVolumeStreamArgs inputVolumeStreamArgs; - qint16 maxInt = std::numeric_limits::max(); - inputVolumeStreamArgs.PeakRaw = m_maxSampleInput / maxInt; - inputVolumeStreamArgs.PeakDB = (float)(20 * std::log10(inputVolumeStreamArgs.PeakRaw)); - float db = qBound(minDb, inputVolumeStreamArgs.PeakDB, maxDb); - float ratio = (db - minDb) / (maxDb - minDb); - if (ratio < 0.30) - ratio = 0; - if (ratio > 1.0) - ratio = 1; - inputVolumeStreamArgs.PeakVU = ratio; - emit inputVolumeStream(inputVolumeStreamArgs); - m_sampleCount = 0; - m_maxSampleInput = 0; - } + m_audioInput->stop(); + m_audioInput.reset(); + } - OpusDataAvailableArgs opusDataAvailableArgs = { m_audioSequenceCounter++, encodedBuffer }; - emit opusDataAvailable(opusDataAvailableArgs); -} + void Input::audioInDataAvailable(const QByteArray &frame) + { + const QVector samples = convertBytesTo16BitPCM(frame); + + int length; + QByteArray encodedBuffer = m_encoder.encode(samples, samples.size(), &length); + m_opusBytesEncoded += length; + + for (const qint16 sample : samples) + { + qint16 sampleInput = sample; + sampleInput = qAbs(sampleInput); + if (sampleInput > m_maxSampleInput) + m_maxSampleInput = sampleInput; + } + + m_sampleCount += samples.size(); + if (m_sampleCount >= c_sampleCountPerEvent) + { + InputVolumeStreamArgs inputVolumeStreamArgs; + qint16 maxInt = std::numeric_limits::max(); + inputVolumeStreamArgs.PeakRaw = m_maxSampleInput / maxInt; + inputVolumeStreamArgs.PeakDB = (float)(20 * std::log10(inputVolumeStreamArgs.PeakRaw)); + float db = qBound(minDb, inputVolumeStreamArgs.PeakDB, maxDb); + float ratio = (db - minDb) / (maxDb - minDb); + if (ratio < 0.30) + ratio = 0; + if (ratio > 1.0) + ratio = 1; + inputVolumeStreamArgs.PeakVU = ratio; + emit inputVolumeStream(inputVolumeStreamArgs); + m_sampleCount = 0; + m_maxSampleInput = 0; + } + + OpusDataAvailableArgs opusDataAvailableArgs = { m_audioSequenceCounter++, encodedBuffer }; + emit opusDataAvailable(opusDataAvailableArgs); + } + + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/audio/input.h b/src/blackcore/afv/audio/input.h index c61fbec3d..5d4f8e30f 100644 --- a/src/blackcore/afv/audio/input.h +++ b/src/blackcore/afv/audio/input.h @@ -1,5 +1,15 @@ -#ifndef AUDIO_INPUT_H -#define AUDIO_INPUT_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_AUDIO_AUDIO_INPUT_H +#define BLACKCORE_AFV_AUDIO_AUDIO_INPUT_H #include "blacksound/sampleprovider/bufferedwaveprovider.h" #include "blacksound/codecs/opusencoder.h" @@ -10,85 +20,95 @@ #include #include -class AudioInputBuffer : public QIODevice +namespace BlackCore { - Q_OBJECT + namespace Afv + { + namespace Audio + { + //! Input buffer + class AudioInputBuffer : public QIODevice + { + Q_OBJECT -public: - AudioInputBuffer() {} + public: + AudioInputBuffer() {} - void start(); - void stop(); + void start(); + void stop(); - qint64 readData(char *data, qint64 maxlen) override; - qint64 writeData(const char *data, qint64 len) override; + qint64 readData(char *data, qint64 maxlen) override; + qint64 writeData(const char *data, qint64 len) override; -signals: - void frameAvailable(const QByteArray &frame); + signals: + void frameAvailable(const QByteArray &frame); -private: - static constexpr qint64 frameSize = 960; - QByteArray m_buffer; -}; + private: + static constexpr qint64 frameSize = 960; + QByteArray m_buffer; + }; -struct OpusDataAvailableArgs -{ - uint sequenceCounter = 0; - QByteArray audio; -}; + struct OpusDataAvailableArgs + { + uint sequenceCounter = 0; + QByteArray audio; + }; -struct InputVolumeStreamArgs -{ - QAudioDeviceInfo DeviceNumber; - float PeakRaw = 0.0; - float PeakDB = -1 * std::numeric_limits::infinity(); - float PeakVU = 0.0; -}; + struct InputVolumeStreamArgs + { + QAudioDeviceInfo DeviceNumber; + float PeakRaw = 0.0; + float PeakDB = -1 * std::numeric_limits::infinity(); + float PeakVU = 0.0; + }; -class Input : public QObject -{ - Q_OBJECT + class Input : public QObject + { + Q_OBJECT -public: - Input(int sampleRate, QObject *parent = nullptr); + public: + Input(int sampleRate, QObject *parent = nullptr); - bool started() const; + bool started() const; - int opusBytesEncoded() const; - void setOpusBytesEncoded(int opusBytesEncoded); + int opusBytesEncoded() const; + void setOpusBytesEncoded(int opusBytesEncoded); - float volume() const; - void setVolume(float volume); + float volume() const; + void setVolume(float volume); - void start(const QAudioDeviceInfo &inputDevice); - void stop(); + void start(const QAudioDeviceInfo &inputDevice); + void stop(); -signals: - void inputVolumeStream(const InputVolumeStreamArgs &args); - void opusDataAvailable(const OpusDataAvailableArgs &args); + signals: + void inputVolumeStream(const InputVolumeStreamArgs &args); + void opusDataAvailable(const OpusDataAvailableArgs &args); -private: - void audioInDataAvailable(const QByteArray &frame); + private: + void audioInDataAvailable(const QByteArray &frame); - static constexpr qint64 c_frameSize = 960; - int m_sampleRate = 0; + static constexpr qint64 c_frameSize = 960; + int m_sampleRate = 0; - COpusEncoder m_encoder; - QScopedPointer m_audioInput; + COpusEncoder m_encoder; + QScopedPointer m_audioInput; - bool m_started = false; - int m_opusBytesEncoded = 0; - float m_volume = 1.0f; - int m_sampleCount = 0; - float m_maxSampleInput = 0; + bool m_started = false; + int m_opusBytesEncoded = 0; + float m_volume = 1.0f; + int m_sampleCount = 0; + float m_maxSampleInput = 0; - const int c_sampleCountPerEvent = 4800; - const float maxDb = 0; - const float minDb = -40; + const int c_sampleCountPerEvent = 4800; + const float maxDb = 0; + const float minDb = -40; - uint m_audioSequenceCounter = 0; + uint m_audioSequenceCounter = 0; - AudioInputBuffer m_audioInputBuffer; -}; + AudioInputBuffer m_audioInputBuffer; + }; + } // ns + } // ns +} // ns -#endif // AIRCRAFTVHFINPUT_H +#endif // guard diff --git a/src/blackcore/afv/audio/output.cpp b/src/blackcore/afv/audio/output.cpp index e1f728b18..ecd2a58c7 100644 --- a/src/blackcore/afv/audio/output.cpp +++ b/src/blackcore/afv/audio/output.cpp @@ -1,92 +1,111 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + #include "output.h" #include #include -AudioOutputBuffer::AudioOutputBuffer(ISampleProvider *sampleProvider, QObject *parent) : - QIODevice(parent), - m_sampleProvider(sampleProvider) -{ } - -qint64 AudioOutputBuffer::readData(char *data, qint64 maxlen) +namespace BlackCore { - int sampleBytes = m_outputFormat.sampleSize() / 8; - int channelCount = m_outputFormat.channelCount(); - int count = maxlen / ( sampleBytes * channelCount); - QVector buffer; - m_sampleProvider->readSamples(buffer, count); - - for (const qint16 sample : buffer) + namespace Afv { - qint16 sampleInput = sample; - sampleInput = qAbs(sampleInput); - if (sampleInput > m_maxSampleOutput) - m_maxSampleOutput = sampleInput; - } + namespace Audio + { + AudioOutputBuffer::AudioOutputBuffer(ISampleProvider *sampleProvider, QObject *parent) : + QIODevice(parent), + m_sampleProvider(sampleProvider) + { } - m_sampleCount += buffer.size(); - if (m_sampleCount >= c_sampleCountPerEvent) - { - OutputVolumeStreamArgs outputVolumeStreamArgs; - qint16 maxInt = std::numeric_limits::max(); - outputVolumeStreamArgs.PeakRaw = m_maxSampleOutput / maxInt; - outputVolumeStreamArgs.PeakDB = (float)(20 * std::log10(outputVolumeStreamArgs.PeakRaw)); - float db = qBound(minDb, outputVolumeStreamArgs.PeakDB, maxDb); - float ratio = (db - minDb) / (maxDb - minDb); - if (ratio < 0.30) - ratio = 0; - if (ratio > 1.0) - ratio = 1; - outputVolumeStreamArgs.PeakVU = ratio; - emit outputVolumeStream(outputVolumeStreamArgs); - m_sampleCount = 0; - m_maxSampleOutput = 0; - } + qint64 AudioOutputBuffer::readData(char *data, qint64 maxlen) + { + int sampleBytes = m_outputFormat.sampleSize() / 8; + int channelCount = m_outputFormat.channelCount(); + int count = maxlen / (sampleBytes * channelCount); + QVector buffer; + m_sampleProvider->readSamples(buffer, count); - memcpy(data, buffer.constData(), maxlen); - return maxlen; -} + for (const qint16 sample : buffer) + { + qint16 sampleInput = sample; + sampleInput = qAbs(sampleInput); + if (sampleInput > m_maxSampleOutput) + m_maxSampleOutput = sampleInput; + } -qint64 AudioOutputBuffer::writeData(const char *data, qint64 len) -{ - Q_UNUSED(data); - Q_UNUSED(len); - return -1; -} + m_sampleCount += buffer.size(); + if (m_sampleCount >= c_sampleCountPerEvent) + { + OutputVolumeStreamArgs outputVolumeStreamArgs; + qint16 maxInt = std::numeric_limits::max(); + outputVolumeStreamArgs.PeakRaw = m_maxSampleOutput / maxInt; + outputVolumeStreamArgs.PeakDB = (float)(20 * std::log10(outputVolumeStreamArgs.PeakRaw)); + float db = qBound(minDb, outputVolumeStreamArgs.PeakDB, maxDb); + float ratio = (db - minDb) / (maxDb - minDb); + if (ratio < 0.30) + ratio = 0; + if (ratio > 1.0) + ratio = 1; + outputVolumeStreamArgs.PeakVU = ratio; + emit outputVolumeStream(outputVolumeStreamArgs); + m_sampleCount = 0; + m_maxSampleOutput = 0; + } -Output::Output(QObject *parent) : QObject(parent) -{ } + memcpy(data, buffer.constData(), maxlen); + return maxlen; + } -void Output::start(const QAudioDeviceInfo &device, ISampleProvider *sampleProvider) -{ - m_audioOutputBuffer = new AudioOutputBuffer(sampleProvider, this); - connect(m_audioOutputBuffer, &AudioOutputBuffer::outputVolumeStream, this, &Output::outputVolumeStream); + qint64 AudioOutputBuffer::writeData(const char *data, qint64 len) + { + Q_UNUSED(data); + Q_UNUSED(len); + return -1; + } - QAudioFormat outputFormat; - outputFormat.setSampleRate(48000); - outputFormat.setChannelCount(1); - outputFormat.setSampleSize(16); - outputFormat.setSampleType(QAudioFormat::SignedInt); - outputFormat.setByteOrder(QAudioFormat::LittleEndian); - outputFormat.setCodec("audio/pcm"); + Output::Output(QObject *parent) : QObject(parent) + { } - if (!device.isFormatSupported(outputFormat)) - { - qWarning() << "Default format not supported - trying to use nearest"; - outputFormat = device.nearestFormat(outputFormat); - } + void Output::start(const QAudioDeviceInfo &device, ISampleProvider *sampleProvider) + { + m_audioOutputBuffer = new AudioOutputBuffer(sampleProvider, this); + connect(m_audioOutputBuffer, &AudioOutputBuffer::outputVolumeStream, this, &Output::outputVolumeStream); - m_audioOutputCom1.reset(new QAudioOutput(device, outputFormat)); - // m_audioOutput->setBufferSize(bufferSize); - m_audioOutputBuffer->open(QIODevice::ReadWrite | QIODevice::Unbuffered); - m_audioOutputBuffer->setAudioFormat(outputFormat); - m_audioOutputCom1->start(m_audioOutputBuffer); + QAudioFormat outputFormat; + outputFormat.setSampleRate(48000); + outputFormat.setChannelCount(1); + outputFormat.setSampleSize(16); + outputFormat.setSampleType(QAudioFormat::SignedInt); + outputFormat.setByteOrder(QAudioFormat::LittleEndian); + outputFormat.setCodec("audio/pcm"); - m_started = true; -} + if (!device.isFormatSupported(outputFormat)) + { + qWarning() << "Default format not supported - trying to use nearest"; + outputFormat = device.nearestFormat(outputFormat); + } -void Output::stop() -{ - if (!m_started) { return; } - m_started = false; -} + m_audioOutputCom1.reset(new QAudioOutput(device, outputFormat)); + // m_audioOutput->setBufferSize(bufferSize); + m_audioOutputBuffer->open(QIODevice::ReadWrite | QIODevice::Unbuffered); + m_audioOutputBuffer->setAudioFormat(outputFormat); + m_audioOutputCom1->start(m_audioOutputBuffer); + + m_started = true; + } + + void Output::stop() + { + if (!m_started) { return; } + m_started = false; + } + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/audio/output.h b/src/blackcore/afv/audio/output.h index 2e6c6b882..8c160f2a3 100644 --- a/src/blackcore/afv/audio/output.h +++ b/src/blackcore/afv/audio/output.h @@ -1,5 +1,15 @@ -#ifndef OUTPUT_H -#define OUTPUT_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_AUDIO_OUTPUT_H +#define BLACKCORE_AFV_AUDIO_OUTPUT_H #include "blacksound/sampleprovider/sampleprovider.h" @@ -7,59 +17,69 @@ #include #include -struct OutputVolumeStreamArgs +namespace BlackCore { - QAudioDeviceInfo DeviceNumber; - float PeakRaw = 0.0; - float PeakDB = -1 * std::numeric_limits::infinity(); - float PeakVU = 0.0; -}; + namespace Afv + { + namespace Audio + { + //! Stream args + struct OutputVolumeStreamArgs + { + QAudioDeviceInfo DeviceNumber; + float PeakRaw = 0.0; + float PeakDB = -1 * std::numeric_limits::infinity(); + float PeakVU = 0.0; + }; -class AudioOutputBuffer : public QIODevice -{ - Q_OBJECT + class AudioOutputBuffer : public QIODevice + { + Q_OBJECT -public: - AudioOutputBuffer(ISampleProvider *sampleProvider, QObject *parent = nullptr); + public: + AudioOutputBuffer(ISampleProvider *sampleProvider, QObject *parent = nullptr); - ISampleProvider *m_sampleProvider = nullptr; + ISampleProvider *m_sampleProvider = nullptr; - void setAudioFormat(const QAudioFormat &format) { m_outputFormat = format; } + void setAudioFormat(const QAudioFormat &format) { m_outputFormat = format; } -signals: - void outputVolumeStream(const OutputVolumeStreamArgs &args); + signals: + void outputVolumeStream(const OutputVolumeStreamArgs &args); -protected: - virtual qint64 readData(char *data, qint64 maxlen) override; - virtual qint64 writeData(const char *data, qint64 len) override; + protected: + virtual qint64 readData(char *data, qint64 maxlen) override; + virtual qint64 writeData(const char *data, qint64 len) override; -private: - QAudioFormat m_outputFormat; + private: + QAudioFormat m_outputFormat; - float m_maxSampleOutput = 0; - int m_sampleCount = 0; - const int c_sampleCountPerEvent = 4800; - const float maxDb = 0; - const float minDb = -40; -}; + float m_maxSampleOutput = 0; + int m_sampleCount = 0; + const int c_sampleCountPerEvent = 4800; + const float maxDb = 0; + const float minDb = -40; + }; -class Output : public QObject -{ - Q_OBJECT -public: - Output(QObject *parent = nullptr); + class Output : public QObject + { + Q_OBJECT + public: + Output(QObject *parent = nullptr); - void start(const QAudioDeviceInfo &device, ISampleProvider *sampleProvider); - void stop(); + void start(const QAudioDeviceInfo &device, ISampleProvider *sampleProvider); + void stop(); -signals: - void outputVolumeStream(const OutputVolumeStreamArgs &args); + signals: + void outputVolumeStream(const OutputVolumeStreamArgs &args); -private: - bool m_started = false; + private: + bool m_started = false; - QScopedPointer m_audioOutputCom1; - AudioOutputBuffer *m_audioOutputBuffer; -}; + QScopedPointer m_audioOutputCom1; + AudioOutputBuffer *m_audioOutputBuffer; + }; + } // ns + } // ns +} // ns -#endif // OUTPUT_H +#endif // guard diff --git a/src/blackcore/afv/audio/receiversampleprovider.cpp b/src/blackcore/afv/audio/receiversampleprovider.cpp index fd4eb81e3..26ed87c8d 100644 --- a/src/blackcore/afv/audio/receiversampleprovider.cpp +++ b/src/blackcore/afv/audio/receiversampleprovider.cpp @@ -1,189 +1,205 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + #include "receiversampleprovider.h" #include "blacksound/sampleprovider/resourcesoundsampleprovider.h" #include "blacksound/sampleprovider/samples.h" #include -ReceiverSampleProvider::ReceiverSampleProvider(const QAudioFormat &audioFormat, quint16 id, int voiceInputNumber, QObject *parent) : - ISampleProvider(parent), - m_id(id) +namespace BlackCore { - m_mixer = new MixingSampleProvider(this); - - for (int i = 0; i < voiceInputNumber; i++) - { - auto voiceInput = new CallsignSampleProvider(audioFormat, m_mixer); - m_voiceInputs.push_back(voiceInput); - m_mixer->addMixerInput(voiceInput); - }; - - // TODO blockTone = new SignalGenerator(WaveFormat.SampleRate, 1) { Gain = 0, Type = SignalGeneratorType.Sin, Frequency = 180 }; - // TODO mixer.AddMixerInput(blockTone.ToMono()); - // TODO volume = new VolumeSampleProvider(mixer); -} - -void ReceiverSampleProvider::setBypassEffects(bool value) -{ - for (CallsignSampleProvider *voiceInput : m_voiceInputs) + namespace Afv { - voiceInput->setBypassEffects(value); - } -} - -void ReceiverSampleProvider::setFrequency(const uint &frequency) -{ - if (frequency != m_frequency) - { - for (CallsignSampleProvider *voiceInput : m_voiceInputs) + namespace Audio { - voiceInput->clear(); - } - } - m_frequency = frequency; -} + ReceiverSampleProvider::ReceiverSampleProvider(const QAudioFormat &audioFormat, quint16 id, int voiceInputNumber, QObject *parent) : + ISampleProvider(parent), + m_id(id) + { + m_mixer = new MixingSampleProvider(this); -int ReceiverSampleProvider::activeCallsigns() const -{ - int numberOfCallsigns = std::count_if(m_voiceInputs.begin(), m_voiceInputs.end(), [] (const CallsignSampleProvider *p) - { - return p->inUse() == true; - }); - return numberOfCallsigns; -} + for (int i = 0; i < voiceInputNumber; i++) + { + auto voiceInput = new CallsignSampleProvider(audioFormat, m_mixer); + m_voiceInputs.push_back(voiceInput); + m_mixer->addMixerInput(voiceInput); + }; -float ReceiverSampleProvider::volume() const -{ - return 1.0; -} + // TODO blockTone = new SignalGenerator(WaveFormat.SampleRate, 1) { Gain = 0, Type = SignalGeneratorType.Sin, Frequency = 180 }; + // TODO mixer.AddMixerInput(blockTone.ToMono()); + // TODO volume = new VolumeSampleProvider(mixer); + } -bool ReceiverSampleProvider::getMute() const -{ - return m_mute; -} + void ReceiverSampleProvider::setBypassEffects(bool value) + { + for (CallsignSampleProvider *voiceInput : m_voiceInputs) + { + voiceInput->setBypassEffects(value); + } + } -void ReceiverSampleProvider::setMute(bool value) -{ - m_mute = value; - if (value) - { - for (CallsignSampleProvider *voiceInput : m_voiceInputs) - { - voiceInput->clear(); - } - } -} + void ReceiverSampleProvider::setFrequency(const uint &frequency) + { + if (frequency != m_frequency) + { + for (CallsignSampleProvider *voiceInput : m_voiceInputs) + { + voiceInput->clear(); + } + } + m_frequency = frequency; + } -int ReceiverSampleProvider::readSamples(QVector &samples, qint64 count) -{ - int numberOfInUseInputs = activeCallsigns(); + int ReceiverSampleProvider::activeCallsigns() const + { + int numberOfCallsigns = std::count_if(m_voiceInputs.begin(), m_voiceInputs.end(), [](const CallsignSampleProvider * p) + { + return p->inUse() == true; + }); + return numberOfCallsigns; + } - if (numberOfInUseInputs > 1) - { + float ReceiverSampleProvider::volume() const + { + return 1.0; + } + + bool ReceiverSampleProvider::getMute() const + { + return m_mute; + } + + void ReceiverSampleProvider::setMute(bool value) + { + m_mute = value; + if (value) + { + for (CallsignSampleProvider *voiceInput : m_voiceInputs) + { + voiceInput->clear(); + } + } + } + + int ReceiverSampleProvider::readSamples(QVector &samples, qint64 count) + { + int numberOfInUseInputs = activeCallsigns(); + + if (numberOfInUseInputs > 1) + { // blockTone.Frequency = 180; // blockTone.Gain = blockToneGain; - } - else - { + } + else + { // blockTone.Gain = 0; - } + } - if (m_doClickWhenAppropriate && numberOfInUseInputs == 0) - { - ResourceSoundSampleProvider *resourceSound = new ResourceSoundSampleProvider(Samples::instance().click(), m_mixer); - m_mixer->addMixerInput(resourceSound); - qDebug() << "Click..."; - m_doClickWhenAppropriate = false; - } + if (m_doClickWhenAppropriate && numberOfInUseInputs == 0) + { + ResourceSoundSampleProvider *resourceSound = new ResourceSoundSampleProvider(Samples::instance().click(), m_mixer); + m_mixer->addMixerInput(resourceSound); + qDebug() << "Click..."; + m_doClickWhenAppropriate = false; + } - if (numberOfInUseInputs != lastNumberOfInUseInputs) - { - QStringList receivingCallsigns; - for (const CallsignSampleProvider *voiceInput : m_voiceInputs) - { - QString callsign = voiceInput->callsign(); - if (! callsign.isEmpty()) - { - receivingCallsigns.push_back(callsign); - } - } + if (numberOfInUseInputs != lastNumberOfInUseInputs) + { + QStringList receivingCallsigns; + for (const CallsignSampleProvider *voiceInput : m_voiceInputs) + { + QString callsign = voiceInput->callsign(); + if (! callsign.isEmpty()) + { + receivingCallsigns.push_back(callsign); + } + } - TransceiverReceivingCallsignsChangedArgs args = { m_id, receivingCallsigns }; - emit receivingCallsignsChanged(args); - } - lastNumberOfInUseInputs = numberOfInUseInputs; + TransceiverReceivingCallsignsChangedArgs args = { m_id, receivingCallsigns }; + emit receivingCallsignsChanged(args); + } + lastNumberOfInUseInputs = numberOfInUseInputs; // return volume.Read(buffer, offset, count); - return m_mixer->readSamples(samples, count); -} + return m_mixer->readSamples(samples, count); + } -void ReceiverSampleProvider::addOpusSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio) -{ - if (m_frequency != frequency) //Lag in the backend means we get the tail end of a transmission - return; + void ReceiverSampleProvider::addOpusSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio) + { + if (m_frequency != frequency) //Lag in the backend means we get the tail end of a transmission + return; - CallsignSampleProvider *voiceInput = nullptr; + CallsignSampleProvider *voiceInput = nullptr; - auto it = std::find_if(m_voiceInputs.begin(), m_voiceInputs.end(), [audioDto] (const CallsignSampleProvider *p) - { - return p->callsign() == audioDto.callsign; - }); - if (it != m_voiceInputs.end()) - { - voiceInput = *it; - } + auto it = std::find_if(m_voiceInputs.begin(), m_voiceInputs.end(), [audioDto](const CallsignSampleProvider * p) + { + return p->callsign() == audioDto.callsign; + }); + if (it != m_voiceInputs.end()) + { + voiceInput = *it; + } - if (! voiceInput) - { - it = std::find_if(m_voiceInputs.begin(), m_voiceInputs.end(), [] (const CallsignSampleProvider *p) { return p->inUse() == false; }); - if (it != m_voiceInputs.end()) - { - voiceInput = *it; - voiceInput->active(audioDto.callsign, ""); - } - } + if (! voiceInput) + { + it = std::find_if(m_voiceInputs.begin(), m_voiceInputs.end(), [](const CallsignSampleProvider * p) { return p->inUse() == false; }); + if (it != m_voiceInputs.end()) + { + voiceInput = *it; + voiceInput->active(audioDto.callsign, ""); + } + } - if (voiceInput) - { - voiceInput->addOpusSamples(audioDto, distanceRatio); - } + if (voiceInput) + { + voiceInput->addOpusSamples(audioDto, distanceRatio); + } - m_doClickWhenAppropriate = true; -} + m_doClickWhenAppropriate = true; + } -void ReceiverSampleProvider::addSilentSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio) -{ - Q_UNUSED(distanceRatio); - if (m_frequency != frequency) //Lag in the backend means we get the tail end of a transmission - return; + void ReceiverSampleProvider::addSilentSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio) + { + Q_UNUSED(distanceRatio); + if (m_frequency != frequency) //Lag in the backend means we get the tail end of a transmission + return; - CallsignSampleProvider *voiceInput = nullptr; + CallsignSampleProvider *voiceInput = nullptr; - auto it = std::find_if(m_voiceInputs.begin(), m_voiceInputs.end(), [audioDto] (const CallsignSampleProvider *p) - { - return p->callsign() == audioDto.callsign; - }); - if (it != m_voiceInputs.end()) - { - voiceInput = *it; - } + auto it = std::find_if(m_voiceInputs.begin(), m_voiceInputs.end(), [audioDto](const CallsignSampleProvider * p) + { + return p->callsign() == audioDto.callsign; + }); + if (it != m_voiceInputs.end()) + { + voiceInput = *it; + } - if (! voiceInput) - { - it = std::find_if(m_voiceInputs.begin(), m_voiceInputs.end(), [] (const CallsignSampleProvider *p) { return p->inUse() == false; }); - if (it != m_voiceInputs.end()) - { - voiceInput = *it; - voiceInput->active(audioDto.callsign, ""); - } - } + if (! voiceInput) + { + it = std::find_if(m_voiceInputs.begin(), m_voiceInputs.end(), [](const CallsignSampleProvider * p) { return p->inUse() == false; }); + if (it != m_voiceInputs.end()) + { + voiceInput = *it; + voiceInput->active(audioDto.callsign, ""); + } + } - if (voiceInput) - { - voiceInput->addSilentSamples(audioDto); - } -} + if (voiceInput) + { + voiceInput->addSilentSamples(audioDto); + } + } -quint16 ReceiverSampleProvider::getId() const -{ - return m_id; -} + + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/audio/receiversampleprovider.h b/src/blackcore/afv/audio/receiversampleprovider.h index 566f5586f..f2d251a61 100644 --- a/src/blackcore/afv/audio/receiversampleprovider.h +++ b/src/blackcore/afv/audio/receiversampleprovider.h @@ -1,5 +1,15 @@ -#ifndef RECEIVERSAMPLEPROVIDER_H -#define RECEIVERSAMPLEPROVIDER_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_AUDIO_RECEIVERSAMPLEPROVIDER_H +#define BLACKCORE_AFV_AUDIO_RECEIVERSAMPLEPROVIDER_H #include "callsignsampleprovider.h" #include "blacksound/sampleprovider/sampleprovider.h" @@ -7,53 +17,64 @@ #include -struct TransceiverReceivingCallsignsChangedArgs +namespace BlackCore { - quint16 transceiverID; - QStringList receivingCallsigns; -}; + namespace Afv + { + namespace Audio + { + //! Arguments + struct TransceiverReceivingCallsignsChangedArgs + { + quint16 transceiverID; + QStringList receivingCallsigns; + }; -class ReceiverSampleProvider : public ISampleProvider -{ - Q_OBJECT + //! A sample provider + class ReceiverSampleProvider : public ISampleProvider + { + Q_OBJECT -public: - ReceiverSampleProvider(const QAudioFormat &audioFormat, quint16 id, int voiceInputNumber, QObject *parent = nullptr); + public: + ReceiverSampleProvider(const QAudioFormat &audioFormat, quint16 id, int voiceInputNumber, QObject *parent = nullptr); - void setBypassEffects(bool value); - void setFrequency(const uint &frequency); - int activeCallsigns() const; - float volume() const; + void setBypassEffects(bool value); + void setFrequency(const uint &frequency); + int activeCallsigns() const; + float volume() const; - bool getMute() const; - void setMute(bool value); + bool getMute() const; + void setMute(bool value); - virtual int readSamples(QVector &samples, qint64 count) override; + virtual int readSamples(QVector &samples, qint64 count) override; - void addOpusSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio); - void addSilentSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio); + void addOpusSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio); + void addSilentSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio); - quint16 getId() const; + quint16 getId() const { return m_id; } -signals: - void receivingCallsignsChanged(const TransceiverReceivingCallsignsChangedArgs &args); + signals: + void receivingCallsignsChanged(const TransceiverReceivingCallsignsChangedArgs &args); -private: - uint m_frequency = 122800; - bool m_mute = false; + private: + uint m_frequency = 122800; + bool m_mute = false; - const float m_clickGain = 1.0f; - const double m_blockToneGain = 0.10f; + const float m_clickGain = 1.0f; + const double m_blockToneGain = 0.10f; - quint16 m_id; + quint16 m_id; - // TODO VolumeSampleProvider volume; - MixingSampleProvider *m_mixer; - // TODO SignalGenerator blockTone; - QVector m_voiceInputs; + // TODO VolumeSampleProvider volume; + MixingSampleProvider *m_mixer; + // TODO SignalGenerator blockTone; + QVector m_voiceInputs; - bool m_doClickWhenAppropriate = false; - int lastNumberOfInUseInputs = 0; -}; + bool m_doClickWhenAppropriate = false; + int lastNumberOfInUseInputs = 0; + }; + } // ns + } // ns +} // ns -#endif // RECEIVERSAMPLEPROVIDER_H +#endif // guard diff --git a/src/blackcore/afv/audio/soundcardsampleprovider.cpp b/src/blackcore/afv/audio/soundcardsampleprovider.cpp index 8b0b44c2e..19d2b9c72 100644 --- a/src/blackcore/afv/audio/soundcardsampleprovider.cpp +++ b/src/blackcore/afv/audio/soundcardsampleprovider.cpp @@ -1,158 +1,178 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + #include "soundcardsampleprovider.h" -SoundcardSampleProvider::SoundcardSampleProvider(int sampleRate, const QVector &transceiverIDs, QObject *parent) : - ISampleProvider(parent), - m_mixer(new MixingSampleProvider()) +namespace BlackCore { - m_waveFormat.setSampleRate(sampleRate); - m_waveFormat.setChannelCount(1); - m_waveFormat.setSampleSize(16); - m_waveFormat.setSampleType(QAudioFormat::SignedInt); - m_waveFormat.setByteOrder(QAudioFormat::LittleEndian); - m_waveFormat.setCodec("audio/pcm"); - - m_mixer = new MixingSampleProvider(this); - - m_receiverIDs = transceiverIDs; - - for (quint16 transceiverID : transceiverIDs) + namespace Afv { - ReceiverSampleProvider *transceiverInput = new ReceiverSampleProvider(m_waveFormat, transceiverID, 4, m_mixer); - connect(transceiverInput, &ReceiverSampleProvider::receivingCallsignsChanged, this, &SoundcardSampleProvider::receivingCallsignsChanged); - m_receiverInputs.push_back(transceiverInput); - m_receiverIDs.push_back(transceiverID); - m_mixer->addMixerInput(transceiverInput); - } -} - -QAudioFormat SoundcardSampleProvider::waveFormat() const -{ - return m_waveFormat; -} - -void SoundcardSampleProvider::setBypassEffects(bool value) -{ - for (ReceiverSampleProvider *receiverInput : m_receiverInputs) - { - receiverInput->setBypassEffects(value); - } -} - -void SoundcardSampleProvider::pttUpdate(bool active, const QVector &txTransceivers) -{ - if (active) - { - if (txTransceivers.size() > 0) + namespace Audio { - QVector txTransceiversFiltered = txTransceivers; - - txTransceiversFiltered.erase(std::remove_if(txTransceiversFiltered.begin(), txTransceiversFiltered.end(), [this] (const TxTransceiverDto &d) + SoundcardSampleProvider::SoundcardSampleProvider(int sampleRate, const QVector &transceiverIDs, QObject *parent) : + ISampleProvider(parent), + m_mixer(new MixingSampleProvider()) { - return ! m_receiverIDs.contains(d.id); - }), - txTransceiversFiltered.end()); + m_waveFormat.setSampleRate(sampleRate); + m_waveFormat.setChannelCount(1); + m_waveFormat.setSampleSize(16); + m_waveFormat.setSampleType(QAudioFormat::SignedInt); + m_waveFormat.setByteOrder(QAudioFormat::LittleEndian); + m_waveFormat.setCodec("audio/pcm"); + m_mixer = new MixingSampleProvider(this); - for (const TxTransceiverDto &txTransceiver : txTransceiversFiltered) - { - auto it = std::find_if(m_receiverInputs.begin(), m_receiverInputs.end(), [txTransceiver] (const ReceiverSampleProvider *p) + m_receiverIDs = transceiverIDs; + + for (quint16 transceiverID : transceiverIDs) { - return p->getId() == txTransceiver.id; - }); - - if (it != m_receiverInputs.end()) { (*it)->setMute(true); } - } - } - } - else - { - for (ReceiverSampleProvider *receiverInput : m_receiverInputs) - { - receiverInput->setMute(false); - } - } -} - -int SoundcardSampleProvider::readSamples(QVector &samples, qint64 count) -{ - return m_mixer->readSamples(samples, count); -} - -void SoundcardSampleProvider::addOpusSamples(const IAudioDto &audioDto, const QVector &rxTransceivers) -{ - QVector rxTransceiversFilteredAndSorted = rxTransceivers; - - rxTransceiversFilteredAndSorted.erase(std::remove_if(rxTransceiversFilteredAndSorted.begin(), rxTransceiversFilteredAndSorted.end(), [this] (const RxTransceiverDto &r) - { - return !m_receiverIDs.contains(r.id); - }), - rxTransceiversFilteredAndSorted.end()); - - std::sort(rxTransceiversFilteredAndSorted.begin(), rxTransceiversFilteredAndSorted.end(), [](const RxTransceiverDto & a, const RxTransceiverDto & b) -> bool - { - return a.distanceRatio > b.distanceRatio; - }); - - if (rxTransceiversFilteredAndSorted.size() > 0) - { - bool audioPlayed = false; - QVector handledTransceiverIDs; - for (int i = 0; i < rxTransceiversFilteredAndSorted.size(); i++) - { - RxTransceiverDto rxTransceiver = rxTransceiversFilteredAndSorted[i]; - if (!handledTransceiverIDs.contains(rxTransceiver.id)) - { - handledTransceiverIDs.push_back(rxTransceiver.id); - - ReceiverSampleProvider *receiverInput = nullptr; - auto it = std::find_if(m_receiverInputs.begin(), m_receiverInputs.end(), [rxTransceiver] (const ReceiverSampleProvider *p) - { - return p->getId() == rxTransceiver.id; - }); - if (it != m_receiverInputs.end()) - { - receiverInput = *it; + ReceiverSampleProvider *transceiverInput = new ReceiverSampleProvider(m_waveFormat, transceiverID, 4, m_mixer); + connect(transceiverInput, &ReceiverSampleProvider::receivingCallsignsChanged, this, &SoundcardSampleProvider::receivingCallsignsChanged); + m_receiverInputs.push_back(transceiverInput); + m_receiverIDs.push_back(transceiverID); + m_mixer->addMixerInput(transceiverInput); } + } - if (! receiverInput) { continue; } - if (receiverInput->getMute()) { continue; } + QAudioFormat SoundcardSampleProvider::waveFormat() const + { + return m_waveFormat; + } - if (!audioPlayed) + void SoundcardSampleProvider::setBypassEffects(bool value) + { + for (ReceiverSampleProvider *receiverInput : m_receiverInputs) { - receiverInput->addOpusSamples(audioDto, rxTransceiver.frequency, rxTransceiver.distanceRatio); - audioPlayed = true; + receiverInput->setBypassEffects(value); + } + } + + void SoundcardSampleProvider::pttUpdate(bool active, const QVector &txTransceivers) + { + if (active) + { + if (txTransceivers.size() > 0) + { + QVector txTransceiversFiltered = txTransceivers; + + txTransceiversFiltered.erase(std::remove_if(txTransceiversFiltered.begin(), txTransceiversFiltered.end(), [this](const TxTransceiverDto & d) + { + return ! m_receiverIDs.contains(d.id); + }), + txTransceiversFiltered.end()); + + + for (const TxTransceiverDto &txTransceiver : txTransceiversFiltered) + { + auto it = std::find_if(m_receiverInputs.begin(), m_receiverInputs.end(), [txTransceiver](const ReceiverSampleProvider * p) + { + return p->getId() == txTransceiver.id; + }); + + if (it != m_receiverInputs.end()) { (*it)->setMute(true); } + } + } } else { - receiverInput->addSilentSamples(audioDto, rxTransceiver.frequency, rxTransceiver.distanceRatio); + for (ReceiverSampleProvider *receiverInput : m_receiverInputs) + { + receiverInput->setMute(false); + } } } - } - } -} -void SoundcardSampleProvider::updateRadioTransceivers(const QVector &radioTransceivers) -{ - for (const TransceiverDto &radioTransceiver : radioTransceivers) - { - auto it = std::find_if(m_receiverInputs.begin(), m_receiverInputs.end(), [radioTransceiver] (const ReceiverSampleProvider *p) - { - return p->getId() == radioTransceiver.id; - }); + int SoundcardSampleProvider::readSamples(QVector &samples, qint64 count) + { + return m_mixer->readSamples(samples, count); + } - if (it) - { - (*it)->setFrequency(radioTransceiver.frequency); - } - } + void SoundcardSampleProvider::addOpusSamples(const IAudioDto &audioDto, const QVector &rxTransceivers) + { + QVector rxTransceiversFilteredAndSorted = rxTransceivers; - for (ReceiverSampleProvider *receiverInput : m_receiverInputs) - { - quint16 transceiverID = receiverInput->getId(); - bool contains = std::any_of(radioTransceivers.begin(), radioTransceivers.end(), [&] (const auto &tx) { return transceiverID == tx.id; }); - if (! contains) - { - receiverInput->setFrequency(0); - } - } -} + rxTransceiversFilteredAndSorted.erase(std::remove_if(rxTransceiversFilteredAndSorted.begin(), rxTransceiversFilteredAndSorted.end(), [this](const RxTransceiverDto & r) + { + return !m_receiverIDs.contains(r.id); + }), + rxTransceiversFilteredAndSorted.end()); + + std::sort(rxTransceiversFilteredAndSorted.begin(), rxTransceiversFilteredAndSorted.end(), [](const RxTransceiverDto & a, const RxTransceiverDto & b) -> bool + { + return a.distanceRatio > b.distanceRatio; + }); + + if (rxTransceiversFilteredAndSorted.size() > 0) + { + bool audioPlayed = false; + QVector handledTransceiverIDs; + for (int i = 0; i < rxTransceiversFilteredAndSorted.size(); i++) + { + RxTransceiverDto rxTransceiver = rxTransceiversFilteredAndSorted[i]; + if (!handledTransceiverIDs.contains(rxTransceiver.id)) + { + handledTransceiverIDs.push_back(rxTransceiver.id); + + ReceiverSampleProvider *receiverInput = nullptr; + auto it = std::find_if(m_receiverInputs.begin(), m_receiverInputs.end(), [rxTransceiver](const ReceiverSampleProvider * p) + { + return p->getId() == rxTransceiver.id; + }); + if (it != m_receiverInputs.end()) + { + receiverInput = *it; + } + + if (! receiverInput) { continue; } + if (receiverInput->getMute()) { continue; } + + if (!audioPlayed) + { + receiverInput->addOpusSamples(audioDto, rxTransceiver.frequency, rxTransceiver.distanceRatio); + audioPlayed = true; + } + else + { + receiverInput->addSilentSamples(audioDto, rxTransceiver.frequency, rxTransceiver.distanceRatio); + } + } + } + } + } + + void SoundcardSampleProvider::updateRadioTransceivers(const QVector &radioTransceivers) + { + for (const TransceiverDto &radioTransceiver : radioTransceivers) + { + auto it = std::find_if(m_receiverInputs.begin(), m_receiverInputs.end(), [radioTransceiver](const ReceiverSampleProvider * p) + { + return p->getId() == radioTransceiver.id; + }); + + if (it) + { + (*it)->setFrequency(radioTransceiver.frequency); + } + } + + for (ReceiverSampleProvider *receiverInput : m_receiverInputs) + { + quint16 transceiverID = receiverInput->getId(); + bool contains = std::any_of(radioTransceivers.begin(), radioTransceivers.end(), [&](const auto & tx) { return transceiverID == tx.id; }); + if (! contains) + { + receiverInput->setFrequency(0); + } + } + } + + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/audio/soundcardsampleprovider.h b/src/blackcore/afv/audio/soundcardsampleprovider.h index c13d8c969..2a31c1a64 100644 --- a/src/blackcore/afv/audio/soundcardsampleprovider.h +++ b/src/blackcore/afv/audio/soundcardsampleprovider.h @@ -1,35 +1,58 @@ -#ifndef SOUNDCARDSAMPLEPROVIDER_H -#define SOUNDCARDSAMPLEPROVIDER_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_AUDIO_SOUNDCARDSAMPLEPROVIDER_H +#define BLACKCORE_AFV_AUDIO_SOUNDCARDSAMPLEPROVIDER_H #include "blacksound/sampleprovider/sampleprovider.h" #include "blacksound/sampleprovider/mixingsampleprovider.h" #include "receiversampleprovider.h" #include +#include -class SoundcardSampleProvider : public ISampleProvider +namespace BlackCore { - Q_OBJECT + namespace Afv + { + namespace Audio + { + //! Soundcard sample + class SoundcardSampleProvider : public ISampleProvider + { + Q_OBJECT -public: - SoundcardSampleProvider(int sampleRate, const QVector &transceiverIDs, QObject *parent = nullptr); + public: + //! Ctor + SoundcardSampleProvider(int sampleRate, const QVector &transceiverIDs, QObject *parent = nullptr); - QAudioFormat waveFormat() const; + QAudioFormat waveFormat() const; - void setBypassEffects(bool value); - void pttUpdate(bool active, const QVector &txTransceivers); - virtual int readSamples(QVector &samples, qint64 count) override; - void addOpusSamples(const IAudioDto &audioDto, const QVector &rxTransceivers); - void updateRadioTransceivers(const QVector &radioTransceivers); + void setBypassEffects(bool value); + void pttUpdate(bool active, const QVector &txTransceivers); + virtual int readSamples(QVector &samples, qint64 count) override; + void addOpusSamples(const IAudioDto &audioDto, const QVector &rxTransceivers); + void updateRadioTransceivers(const QVector &radioTransceivers); -signals: - void receivingCallsignsChanged(const TransceiverReceivingCallsignsChangedArgs &args); + signals: + void receivingCallsignsChanged(const TransceiverReceivingCallsignsChangedArgs &args); -private: - QAudioFormat m_waveFormat; - MixingSampleProvider *m_mixer; - QVector m_receiverInputs; - QVector m_receiverIDs; -}; + private: + QAudioFormat m_waveFormat; + MixingSampleProvider *m_mixer; + QVector m_receiverInputs; + QVector m_receiverIDs; + }; -#endif // SOUNDCARDSAMPLEPROVIDER_H + } // ns + } // ns +} // ns + +#endif // guard diff --git a/src/blackcore/afv/clients/afvclient.cpp b/src/blackcore/afv/clients/afvclient.cpp index 646301a9a..369c04d72 100644 --- a/src/blackcore/afv/clients/afvclient.cpp +++ b/src/blackcore/afv/clients/afvclient.cpp @@ -1,356 +1,375 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + #include "afvclient.h" #include "blacksound/audioutilities.h" #include using namespace BlackMisc::PhysicalQuantities; using namespace BlackCore::Context; +using namespace BlackCore::Afv::Audio; +using namespace BlackCore::Afv::Connection; -AFVClient::AFVClient(const QString &apiServer, QObject *parent) : - QObject(parent) +namespace BlackCore { - m_connection = new ClientConnection(apiServer, this); - m_connection->setReceiveAudio(false); - - m_input = new Input(c_sampleRate, this); - connect(m_input, &Input::opusDataAvailable, this, &AFVClient::opusDataAvailable); - connect(m_input, &Input::inputVolumeStream, this, &AFVClient::inputVolumeStream); - - m_output = new Output(this); - connect(m_output, &Output::outputVolumeStream, this, &AFVClient::outputVolumeStream); - - connect(m_connection, &ClientConnection::audioReceived, this, &AFVClient::audioOutDataAvailable); - - connect(&m_voiceServerPositionTimer, &QTimer::timeout, this, qOverload<>(&AFVClient::updateTransceivers)); - - m_transceivers = + namespace Afv { - { 0, 122800000, 48.5, 11.5, 1000.0, 1000.0 }, - { 1, 122800000, 48.5, 11.5, 1000.0, 1000.0 } - }; - - qDebug() << "UserClient instantiated"; -} - -void AFVClient::setContextOwnAircraft(const IContextOwnAircraft *contextOwnAircraft) -{ - m_contextOwnAircraft = contextOwnAircraft; - if (m_contextOwnAircraft) - { - connect(m_contextOwnAircraft, &IContextOwnAircraft::changedAircraftCockpit, this, &AFVClient::updateTransceiversFromContext); - } -} - -void AFVClient::connectTo(const QString &cid, const QString &password, const QString &callsign) -{ - m_callsign = callsign; - m_connection->connectTo(cid, password, callsign); - updateTransceivers(); -} - -void AFVClient::disconnectFrom() -{ - m_connection->disconnectFrom(); -} - -QStringList AFVClient::availableInputDevices() const -{ - const QList inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); - - QStringList deviceNames; - for (const QAudioDeviceInfo &inputDevice : inputDevices) - { - deviceNames << inputDevice.deviceName(); - } - return deviceNames; -} - -QStringList AFVClient::availableOutputDevices() const -{ - const QList outputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); - - QStringList deviceNames; - for (const QAudioDeviceInfo &outputDevice : outputDevices) - { - deviceNames << outputDevice.deviceName(); - } - return deviceNames; -} - -void AFVClient::setBypassEffects(bool value) -{ - if (soundcardSampleProvider) - { - soundcardSampleProvider->setBypassEffects(value); - } -} - -void AFVClient::start(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice, const QVector &transceiverIDs) -{ - if (m_isStarted) - { - qDebug() << "Client already started"; - return; - } - - soundcardSampleProvider = new SoundcardSampleProvider(c_sampleRate, transceiverIDs, this); - connect(soundcardSampleProvider, &SoundcardSampleProvider::receivingCallsignsChanged, this, &AFVClient::receivingCallsignsChanged); - // TODO outputSampleProvider = new VolumeSampleProvider(soundcardSampleProvider); - - m_output->start(outputDevice, soundcardSampleProvider); - m_input->start(inputDevice); - - m_startDateTimeUtc = QDateTime::currentDateTimeUtc(); - m_connection->setReceiveAudio(true); - m_voiceServerPositionTimer.start(5000); - m_isStarted = true; - qDebug() << ("Started [Input: " + inputDevice.deviceName() + "] [Output: " + outputDevice.deviceName() + "]"); -} - -void AFVClient::start(const QString &inputDeviceName, const QString &outputDeviceName) -{ - if (m_isStarted) { return; } - - soundcardSampleProvider = new SoundcardSampleProvider(c_sampleRate, { 0, 1 }, this); - connect(soundcardSampleProvider, &SoundcardSampleProvider::receivingCallsignsChanged, this, &AFVClient::receivingCallsignsChanged); - // TODO outputSampleProvider = new VolumeSampleProvider(soundcardSampleProvider); - - QAudioDeviceInfo inputDevice = QAudioDeviceInfo::defaultInputDevice(); - for (const auto &device : QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) - { - if (device.deviceName().startsWith(inputDeviceName)) + namespace Clients { - inputDevice = device; - break; - } - } - - QAudioDeviceInfo outputDevice = QAudioDeviceInfo::defaultOutputDevice(); - for (const auto &device : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) - { - if (device.deviceName().startsWith(outputDeviceName)) - { - outputDevice = device; - break; - } - } - - m_output->start(outputDevice, soundcardSampleProvider); - m_input->start(inputDevice); - - m_startDateTimeUtc = QDateTime::currentDateTimeUtc(); - m_connection->setReceiveAudio(true); - m_voiceServerPositionTimer.start(5000); - m_isStarted = true; -} - -void AFVClient::stop() -{ - if (! m_isStarted) - { - qDebug() << "Client not started"; - return; - } - - m_isStarted = false; - m_connection->setReceiveAudio(false); - - m_transceivers.clear(); - updateTransceivers(); - - m_input->stop(); - m_output->stop(); -} - -void AFVClient::updateComFrequency(quint16 id, quint32 frequency) -{ - if (id != 0 && id != 1) { return; } - - // Fix rounding issues like 128074999 Hz -> 128075000 Hz - quint32 roundedFrequency = qRound(frequency / 1000.0) * 1000; - - if (m_transceivers.size() >= id + 1) - { - if (m_transceivers[id].frequency != roundedFrequency) - { - m_transceivers[id].frequency = roundedFrequency; - updateTransceivers(); - } - } -} - -void AFVClient::updatePosition(double latitude, double longitude, double height) -{ - for (TransceiverDto &transceiver : m_transceivers) - { - transceiver.LatDeg = latitude; - transceiver.LonDeg = longitude; - transceiver.HeightAglM = height; - transceiver.HeightMslM = height; - } -} - -void AFVClient::updateTransceivers() -{ - if (! m_connection->isConnected()) { return; } - - if (m_contextOwnAircraft) - { - BlackMisc::Simulation::CSimulatedAircraft ownAircraft = m_contextOwnAircraft->getOwnAircraft(); - updatePosition(ownAircraft.latitude().value(CAngleUnit::deg()), - ownAircraft.longitude().value(CAngleUnit::deg()), - ownAircraft.getAltitude().value(CLengthUnit::ft())); - updateComFrequency(0, ownAircraft.getCom1System().getFrequencyActive().value(CFrequencyUnit::Hz())); - updateComFrequency(1, ownAircraft.getCom2System().getFrequencyActive().value(CFrequencyUnit::Hz())); - } - - m_connection->updateTransceivers(m_callsign, m_transceivers); - - if (soundcardSampleProvider) - { - soundcardSampleProvider->updateRadioTransceivers(m_transceivers); - } -} - -void AFVClient::setTransmittingTransceivers(quint16 transceiverID) -{ - TxTransceiverDto tx = { transceiverID }; - setTransmittingTransceivers( { tx } ); -} - -void AFVClient::setTransmittingTransceivers(const QVector &transceivers) -{ - m_transmittingTransceivers = transceivers; -} - -void AFVClient::setPtt(bool active) -{ - if (! m_isStarted) - { - qDebug() << "Client not started"; - return; - } - - if (m_transmit == active) { return; } - - m_transmit = active; - - if (soundcardSampleProvider) - { - soundcardSampleProvider->pttUpdate(active, m_transmittingTransceivers); - } - - if (!active) - { - //AGC - //if (maxDbReadingInPTTInterval > -1) - // InputVolumeDb = InputVolumeDb - 1; - //if(maxDbReadingInPTTInterval < -4) - // InputVolumeDb = InputVolumeDb + 1; - m_maxDbReadingInPTTInterval = -100; - } - - qDebug() << "PTT:" << active; -} - -void AFVClient::setInputVolumeDb(float value) -{ - if (value > 18) { value = 18; } - if (value < -18) { value = -18; } - m_inputVolumeDb = value; - // TODO input.Volume = (float)System.Math.Pow(10, value / 20); -} - -void AFVClient::opusDataAvailable(const OpusDataAvailableArgs &args) -{ - if (m_loopbackOn && m_transmit) - { - IAudioDto audioData; - audioData.audio = QByteArray(args.audio.data(), args.audio.size()); - audioData.callsign = "loopback"; - audioData.lastPacket = false; - audioData.sequenceCounter = 0; - RxTransceiverDto com1 = { 0, m_transceivers[0].frequency, 0.0 }; - RxTransceiverDto com2 = { 1, m_transceivers[1].frequency, 0.0 }; - - soundcardSampleProvider->addOpusSamples(audioData, { com1, com2 }); - return; - } - - if (m_transmittingTransceivers.size() > 0) - { - if (m_transmit) - { - if (m_connection->isConnected()) + AFVClient::AFVClient(const QString &apiServer, QObject *parent) : + QObject(parent) { - AudioTxOnTransceiversDto dto; - dto.callsign = m_callsign.toStdString(); - dto.sequenceCounter = args.sequenceCounter; - dto.audio = std::vector(args.audio.begin(), args.audio.end()); - dto.lastPacket = false; - dto.transceivers = m_transmittingTransceivers.toStdVector(); - m_connection->sendToVoiceServer(dto); - } - } + m_connection = new ClientConnection(apiServer, this); + m_connection->setReceiveAudio(false); - if (!m_transmit && m_transmitHistory) - { - if (m_connection->isConnected()) + m_input = new Input(c_sampleRate, this); + connect(m_input, &Input::opusDataAvailable, this, &AFVClient::opusDataAvailable); + connect(m_input, &Input::inputVolumeStream, this, &AFVClient::inputVolumeStream); + + m_output = new Output(this); + connect(m_output, &Output::outputVolumeStream, this, &AFVClient::outputVolumeStream); + + connect(m_connection, &ClientConnection::audioReceived, this, &AFVClient::audioOutDataAvailable); + + connect(&m_voiceServerPositionTimer, &QTimer::timeout, this, qOverload<>(&AFVClient::updateTransceivers)); + + m_transceivers = + { + { 0, 122800000, 48.5, 11.5, 1000.0, 1000.0 }, + { 1, 122800000, 48.5, 11.5, 1000.0, 1000.0 } + }; + + qDebug() << "UserClient instantiated"; + } + + void AFVClient::setContextOwnAircraft(const IContextOwnAircraft *contextOwnAircraft) { - AudioTxOnTransceiversDto dto; - dto.callsign = m_callsign.toStdString(); - dto.sequenceCounter = args.sequenceCounter; - dto.audio = std::vector(args.audio.begin(), args.audio.end()); - dto.lastPacket = true; - dto.transceivers = m_transmittingTransceivers.toStdVector(); - m_connection->sendToVoiceServer(dto); + m_contextOwnAircraft = contextOwnAircraft; + if (m_contextOwnAircraft) + { + connect(m_contextOwnAircraft, &IContextOwnAircraft::changedAircraftCockpit, this, &AFVClient::updateTransceiversFromContext); + } } - } - m_transmitHistory = m_transmit; - } -} -void AFVClient::audioOutDataAvailable(const AudioRxOnTransceiversDto &dto) -{ - IAudioDto audioData; - audioData.audio = QByteArray(dto.audio.data(), dto.audio.size()); - audioData.callsign = QString::fromStdString(dto.callsign); - audioData.lastPacket = dto.lastPacket; - audioData.sequenceCounter = dto.sequenceCounter; - soundcardSampleProvider->addOpusSamples(audioData, QVector::fromStdVector(dto.transceivers)); -} + void AFVClient::connectTo(const QString &cid, const QString &password, const QString &callsign) + { + m_callsign = callsign; + m_connection->connectTo(cid, password, callsign); + updateTransceivers(); + } -void AFVClient::inputVolumeStream(const InputVolumeStreamArgs &args) -{ - m_inputVolumeStream = args; - emit inputVolumePeakVU(m_inputVolumeStream.PeakVU); -} + void AFVClient::disconnectFrom() + { + m_connection->disconnectFrom(); + } -void AFVClient::outputVolumeStream(const OutputVolumeStreamArgs &args) -{ - m_outputVolumeStream = args; - emit outputVolumePeakVU(m_outputVolumeStream.PeakVU); -} + QStringList AFVClient::availableInputDevices() const + { + const QList inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); -void AFVClient::updateTransceiversFromContext(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const BlackMisc::CIdentifier &originator) -{ - Q_UNUSED(originator); - updatePosition(aircraft.latitude().value(CAngleUnit::deg()), - aircraft.longitude().value(CAngleUnit::deg()), - aircraft.getAltitude().value(CLengthUnit::ft())); - updateComFrequency(0, aircraft.getCom1System().getFrequencyActive().value(CFrequencyUnit::Hz())); - updateComFrequency(1, aircraft.getCom2System().getFrequencyActive().value(CFrequencyUnit::Hz())); - updateTransceivers(); -} + QStringList deviceNames; + for (const QAudioDeviceInfo &inputDevice : inputDevices) + { + deviceNames << inputDevice.deviceName(); + } + return deviceNames; + } -float AFVClient::getOutputVolume() const -{ - return m_outputVolume; -} + QStringList AFVClient::availableOutputDevices() const + { + const QList outputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); -void AFVClient::setOutputVolume(float outputVolume) -{ - if (outputVolume > 18) { m_outputVolume = 18; } - if (outputVolume < -60) { m_outputVolume = -60; } - // m_outputVolume = (float)System.Math.Pow(10, value / 20); - // TODO outputSampleProvider.Volume = outputVolume; -} + QStringList deviceNames; + for (const QAudioDeviceInfo &outputDevice : outputDevices) + { + deviceNames << outputDevice.deviceName(); + } + return deviceNames; + } + + void AFVClient::setBypassEffects(bool value) + { + if (soundcardSampleProvider) + { + soundcardSampleProvider->setBypassEffects(value); + } + } + + void AFVClient::start(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice, const QVector &transceiverIDs) + { + if (m_isStarted) + { + qDebug() << "Client already started"; + return; + } + + soundcardSampleProvider = new SoundcardSampleProvider(c_sampleRate, transceiverIDs, this); + connect(soundcardSampleProvider, &SoundcardSampleProvider::receivingCallsignsChanged, this, &AFVClient::receivingCallsignsChanged); + // TODO outputSampleProvider = new VolumeSampleProvider(soundcardSampleProvider); + + m_output->start(outputDevice, soundcardSampleProvider); + m_input->start(inputDevice); + + m_startDateTimeUtc = QDateTime::currentDateTimeUtc(); + m_connection->setReceiveAudio(true); + m_voiceServerPositionTimer.start(5000); + m_isStarted = true; + qDebug() << ("Started [Input: " + inputDevice.deviceName() + "] [Output: " + outputDevice.deviceName() + "]"); + } + + void AFVClient::start(const QString &inputDeviceName, const QString &outputDeviceName) + { + if (m_isStarted) { return; } + + soundcardSampleProvider = new SoundcardSampleProvider(c_sampleRate, { 0, 1 }, this); + connect(soundcardSampleProvider, &SoundcardSampleProvider::receivingCallsignsChanged, this, &AFVClient::receivingCallsignsChanged); + // TODO outputSampleProvider = new VolumeSampleProvider(soundcardSampleProvider); + + QAudioDeviceInfo inputDevice = QAudioDeviceInfo::defaultInputDevice(); + for (const auto &device : QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) + { + if (device.deviceName().startsWith(inputDeviceName)) + { + inputDevice = device; + break; + } + } + + QAudioDeviceInfo outputDevice = QAudioDeviceInfo::defaultOutputDevice(); + for (const auto &device : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) + { + if (device.deviceName().startsWith(outputDeviceName)) + { + outputDevice = device; + break; + } + } + + m_output->start(outputDevice, soundcardSampleProvider); + m_input->start(inputDevice); + + m_startDateTimeUtc = QDateTime::currentDateTimeUtc(); + m_connection->setReceiveAudio(true); + m_voiceServerPositionTimer.start(5000); + m_isStarted = true; + } + + void AFVClient::stop() + { + if (! m_isStarted) + { + qDebug() << "Client not started"; + return; + } + + m_isStarted = false; + m_connection->setReceiveAudio(false); + + m_transceivers.clear(); + updateTransceivers(); + + m_input->stop(); + m_output->stop(); + } + + void AFVClient::updateComFrequency(quint16 id, quint32 frequency) + { + if (id != 0 && id != 1) { return; } + + // Fix rounding issues like 128074999 Hz -> 128075000 Hz + quint32 roundedFrequency = qRound(frequency / 1000.0) * 1000; + + if (m_transceivers.size() >= id + 1) + { + if (m_transceivers[id].frequency != roundedFrequency) + { + m_transceivers[id].frequency = roundedFrequency; + updateTransceivers(); + } + } + } + + void AFVClient::updatePosition(double latitude, double longitude, double height) + { + for (TransceiverDto &transceiver : m_transceivers) + { + transceiver.LatDeg = latitude; + transceiver.LonDeg = longitude; + transceiver.HeightAglM = height; + transceiver.HeightMslM = height; + } + } + + void AFVClient::updateTransceivers() + { + if (! m_connection->isConnected()) { return; } + + if (m_contextOwnAircraft) + { + BlackMisc::Simulation::CSimulatedAircraft ownAircraft = m_contextOwnAircraft->getOwnAircraft(); + updatePosition(ownAircraft.latitude().value(CAngleUnit::deg()), + ownAircraft.longitude().value(CAngleUnit::deg()), + ownAircraft.getAltitude().value(CLengthUnit::ft())); + updateComFrequency(0, ownAircraft.getCom1System().getFrequencyActive().value(CFrequencyUnit::Hz())); + updateComFrequency(1, ownAircraft.getCom2System().getFrequencyActive().value(CFrequencyUnit::Hz())); + } + + m_connection->updateTransceivers(m_callsign, m_transceivers); + + if (soundcardSampleProvider) + { + soundcardSampleProvider->updateRadioTransceivers(m_transceivers); + } + } + + void AFVClient::setTransmittingTransceivers(quint16 transceiverID) + { + TxTransceiverDto tx = { transceiverID }; + setTransmittingTransceivers({ tx }); + } + + void AFVClient::setTransmittingTransceivers(const QVector &transceivers) + { + m_transmittingTransceivers = transceivers; + } + + void AFVClient::setPtt(bool active) + { + if (! m_isStarted) + { + qDebug() << "Client not started"; + return; + } + + if (m_transmit == active) { return; } + + m_transmit = active; + + if (soundcardSampleProvider) + { + soundcardSampleProvider->pttUpdate(active, m_transmittingTransceivers); + } + + if (!active) + { + //AGC + //if (maxDbReadingInPTTInterval > -1) + // InputVolumeDb = InputVolumeDb - 1; + //if(maxDbReadingInPTTInterval < -4) + // InputVolumeDb = InputVolumeDb + 1; + m_maxDbReadingInPTTInterval = -100; + } + + qDebug() << "PTT:" << active; + } + + void AFVClient::setInputVolumeDb(float value) + { + if (value > 18) { value = 18; } + if (value < -18) { value = -18; } + m_inputVolumeDb = value; + // TODO input.Volume = (float)System.Math.Pow(10, value / 20); + } + + void AFVClient::opusDataAvailable(const OpusDataAvailableArgs &args) + { + if (m_loopbackOn && m_transmit) + { + IAudioDto audioData; + audioData.audio = QByteArray(args.audio.data(), args.audio.size()); + audioData.callsign = "loopback"; + audioData.lastPacket = false; + audioData.sequenceCounter = 0; + RxTransceiverDto com1 = { 0, m_transceivers[0].frequency, 0.0 }; + RxTransceiverDto com2 = { 1, m_transceivers[1].frequency, 0.0 }; + + soundcardSampleProvider->addOpusSamples(audioData, { com1, com2 }); + return; + } + + if (m_transmittingTransceivers.size() > 0) + { + if (m_transmit) + { + if (m_connection->isConnected()) + { + AudioTxOnTransceiversDto dto; + dto.callsign = m_callsign.toStdString(); + dto.sequenceCounter = args.sequenceCounter; + dto.audio = std::vector(args.audio.begin(), args.audio.end()); + dto.lastPacket = false; + dto.transceivers = m_transmittingTransceivers.toStdVector(); + m_connection->sendToVoiceServer(dto); + } + } + + if (!m_transmit && m_transmitHistory) + { + if (m_connection->isConnected()) + { + AudioTxOnTransceiversDto dto; + dto.callsign = m_callsign.toStdString(); + dto.sequenceCounter = args.sequenceCounter; + dto.audio = std::vector(args.audio.begin(), args.audio.end()); + dto.lastPacket = true; + dto.transceivers = m_transmittingTransceivers.toStdVector(); + m_connection->sendToVoiceServer(dto); + } + } + m_transmitHistory = m_transmit; + } + } + + void AFVClient::audioOutDataAvailable(const AudioRxOnTransceiversDto &dto) + { + IAudioDto audioData; + audioData.audio = QByteArray(dto.audio.data(), dto.audio.size()); + audioData.callsign = QString::fromStdString(dto.callsign); + audioData.lastPacket = dto.lastPacket; + audioData.sequenceCounter = dto.sequenceCounter; + soundcardSampleProvider->addOpusSamples(audioData, QVector::fromStdVector(dto.transceivers)); + } + + void AFVClient::inputVolumeStream(const InputVolumeStreamArgs &args) + { + m_inputVolumeStream = args; + emit inputVolumePeakVU(m_inputVolumeStream.PeakVU); + } + + void AFVClient::outputVolumeStream(const OutputVolumeStreamArgs &args) + { + m_outputVolumeStream = args; + emit outputVolumePeakVU(m_outputVolumeStream.PeakVU); + } + + void AFVClient::updateTransceiversFromContext(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const BlackMisc::CIdentifier &originator) + { + Q_UNUSED(originator); + updatePosition(aircraft.latitude().value(CAngleUnit::deg()), + aircraft.longitude().value(CAngleUnit::deg()), + aircraft.getAltitude().value(CLengthUnit::ft())); + updateComFrequency(0, aircraft.getCom1System().getFrequencyActive().value(CFrequencyUnit::Hz())); + updateComFrequency(1, aircraft.getCom2System().getFrequencyActive().value(CFrequencyUnit::Hz())); + updateTransceivers(); + } + + float AFVClient::getOutputVolume() const + { + return m_outputVolume; + } + + void AFVClient::setOutputVolume(float outputVolume) + { + if (outputVolume > 18) { m_outputVolume = 18; } + if (outputVolume < -60) { m_outputVolume = -60; } + // m_outputVolume = (float)System.Math.Pow(10, value / 20); + // TODO outputSampleProvider.Volume = outputVolume; + } + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/clients/afvclient.h b/src/blackcore/afv/clients/afvclient.h index fdc04cf41..df197bb45 100644 --- a/src/blackcore/afv/clients/afvclient.h +++ b/src/blackcore/afv/clients/afvclient.h @@ -1,12 +1,22 @@ -#ifndef AFVCLIENT_H -#define AFVCLIENT_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_CLIENT_AFVCLIENT_H +#define BLACKCORE_AFV_CLIENT_AFVCLIENT_H -#include "blackcore/blackcoreexport.h" #include "blackcore/afv/connection/clientconnection.h" -#include "blackcore/afv/dto.h" #include "blackcore/afv/audio/input.h" #include "blackcore/afv/audio/output.h" #include "blackcore/afv/audio/soundcardsampleprovider.h" +#include "blackcore/afv/dto.h" +#include "blackcore/blackcoreexport.h" #include "blackcore/context/contextownaircraft.h" @@ -18,115 +28,128 @@ #include #include -class BLACKCORE_EXPORT AFVClient final : public QObject +namespace BlackCore { - Q_OBJECT - Q_PROPERTY(float inputVolumePeakVU READ getInputVolumePeakVU NOTIFY inputVolumePeakVU) - Q_PROPERTY(float outputVolumePeakVU READ getOutputVolumePeakVU NOTIFY outputVolumePeakVU) -public: - AFVClient(const QString &apiServer, QObject *parent = nullptr); - - virtual ~AFVClient() + namespace Afv { - stop(); - } + namespace Clients + { + //! AFV client + class BLACKCORE_EXPORT AFVClient final : public QObject + { + Q_OBJECT + Q_PROPERTY(float inputVolumePeakVU READ getInputVolumePeakVU NOTIFY inputVolumePeakVU) + Q_PROPERTY(float outputVolumePeakVU READ getOutputVolumePeakVU NOTIFY outputVolumePeakVU) - void setContextOwnAircraft(const BlackCore::Context::IContextOwnAircraft *contextOwnAircraft); + public: + //! Ctor + AFVClient(const QString &apiServer, QObject *parent = nullptr); - QString callsign() const { return m_callsign; } + //! Dtor + virtual ~AFVClient() + { + stop(); + } - bool isConnected() const { return m_connection->isConnected(); } + void setContextOwnAircraft(const BlackCore::Context::IContextOwnAircraft *contextOwnAircraft); - Q_INVOKABLE void connectTo(const QString &cid, const QString &password, const QString &callsign); - Q_INVOKABLE void disconnectFrom(); + QString callsign() const { return m_callsign; } - Q_INVOKABLE QStringList availableInputDevices() const; - Q_INVOKABLE QStringList availableOutputDevices() const; + bool isConnected() const { return m_connection->isConnected(); } - void setBypassEffects(bool value); + Q_INVOKABLE void connectTo(const QString &cid, const QString &password, const QString &callsign); + Q_INVOKABLE void disconnectFrom(); - bool isStarted() const { return m_isStarted; } - QDateTime getStartDateTimeUt() const { return m_startDateTimeUtc; } + Q_INVOKABLE QStringList availableInputDevices() const; + Q_INVOKABLE QStringList availableOutputDevices() const; - void start(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice, const QVector &transceiverIDs); - Q_INVOKABLE void start(const QString &inputDeviceName, const QString &outputDeviceName); - void stop(); + void setBypassEffects(bool value); - Q_INVOKABLE void updateComFrequency(quint16 id, quint32 frequency); - Q_INVOKABLE void updatePosition(double latitude, double longitude, double height); + bool isStarted() const { return m_isStarted; } + QDateTime getStartDateTimeUt() const { return m_startDateTimeUtc; } - void setTransmittingTransceivers(quint16 transceiverID); - void setTransmittingTransceivers(const QVector &transceivers); + void start(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice, const QVector &transceiverIDs); + Q_INVOKABLE void start(const QString &inputDeviceName, const QString &outputDeviceName); + void stop(); - void setPtt(bool active); + Q_INVOKABLE void updateComFrequency(quint16 id, quint32 frequency); + Q_INVOKABLE void updatePosition(double latitude, double longitude, double height); - void setLoopBack(bool on) { m_loopbackOn = on; } + void setTransmittingTransceivers(quint16 transceiverID); + void setTransmittingTransceivers(const QVector &transceivers); - float inputVolumeDb() const - { - return m_inputVolumeDb; - } + void setPtt(bool active); - void setInputVolumeDb(float value); + void setLoopBack(bool on) { m_loopbackOn = on; } - float getOutputVolume() const; - void setOutputVolume(float outputVolume); + float inputVolumeDb() const + { + return m_inputVolumeDb; + } - float getInputVolumePeakVU() const { return m_inputVolumeStream.PeakVU; } - float getOutputVolumePeakVU() const { return m_outputVolumeStream.PeakVU; } + void setInputVolumeDb(float value); -signals: - void receivingCallsignsChanged(const TransceiverReceivingCallsignsChangedArgs &args); - void inputVolumePeakVU(float value); - void outputVolumePeakVU(float value); + float getOutputVolume() const; + void setOutputVolume(float outputVolume); -private: - void opusDataAvailable(const OpusDataAvailableArgs &args); - void audioOutDataAvailable(const AudioRxOnTransceiversDto &dto); - void inputVolumeStream(const InputVolumeStreamArgs &args); - void outputVolumeStream(const OutputVolumeStreamArgs &args); + float getInputVolumePeakVU() const { return m_inputVolumeStream.PeakVU; } + float getOutputVolumePeakVU() const { return m_outputVolumeStream.PeakVU; } - void input_OpusDataAvailable(); + signals: + void receivingCallsignsChanged(const Audio::TransceiverReceivingCallsignsChangedArgs &args); + void inputVolumePeakVU(float value); + void outputVolumePeakVU(float value); - void updateTransceivers(); - void updateTransceiversFromContext(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const BlackMisc::CIdentifier &originator); + private: + void opusDataAvailable(const Audio::OpusDataAvailableArgs &args); + void audioOutDataAvailable(const AudioRxOnTransceiversDto &dto); + void inputVolumeStream(const Audio::InputVolumeStreamArgs &args); + void outputVolumeStream(const Audio::OutputVolumeStreamArgs &args); - static constexpr int c_sampleRate = 48000; - static constexpr int frameSize = 960; //20ms + void input_OpusDataAvailable(); - // Connection - ClientConnection *m_connection = nullptr; + void updateTransceivers(); + void updateTransceiversFromContext(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const BlackMisc::CIdentifier &originator); - // Properties - QString m_callsign; + static constexpr int c_sampleRate = 48000; + static constexpr int frameSize = 960; //20ms - Input *m_input = nullptr; - Output *m_output = nullptr; + // Connection + Connection::ClientConnection *m_connection = nullptr; - SoundcardSampleProvider *soundcardSampleProvider = nullptr; - // TODO VolumeSampleProvider outputSampleProvider; + // Properties + QString m_callsign; - bool m_transmit = false; - bool m_transmitHistory = false; - QVector m_transmittingTransceivers; + Audio::Input *m_input = nullptr; + Audio::Output *m_output = nullptr; - bool m_isStarted = false; - QDateTime m_startDateTimeUtc; + Audio::SoundcardSampleProvider *soundcardSampleProvider = nullptr; + // TODO VolumeSampleProvider outputSampleProvider; - float m_inputVolumeDb; - float m_outputVolume = 1; + bool m_transmit = false; + bool m_transmitHistory = false; + QVector m_transmittingTransceivers; - double m_maxDbReadingInPTTInterval = -100; + bool m_isStarted = false; + QDateTime m_startDateTimeUtc; - bool m_loopbackOn = false; + float m_inputVolumeDb; + float m_outputVolume = 1; - QTimer m_voiceServerPositionTimer; - QVector m_transceivers; + double m_maxDbReadingInPTTInterval = -100; - InputVolumeStreamArgs m_inputVolumeStream; - OutputVolumeStreamArgs m_outputVolumeStream; + bool m_loopbackOn = false; - BlackCore::Context::IContextOwnAircraft const *m_contextOwnAircraft = nullptr; -}; + QTimer m_voiceServerPositionTimer; + QVector m_transceivers; + + Audio::InputVolumeStreamArgs m_inputVolumeStream; + Audio::OutputVolumeStreamArgs m_outputVolumeStream; + + BlackCore::Context::IContextOwnAircraft const *m_contextOwnAircraft = nullptr; + }; + } // ns + } // ns +} // ns #endif diff --git a/src/blackcore/afv/connection/apiserverconnection.cpp b/src/blackcore/afv/connection/apiserverconnection.cpp index 5984cff40..cfebb446c 100644 --- a/src/blackcore/afv/connection/apiserverconnection.cpp +++ b/src/blackcore/afv/connection/apiserverconnection.cpp @@ -1,3 +1,11 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + #include "apiserverconnection.h" #include #include @@ -5,146 +13,157 @@ #include #include -ApiServerConnection::ApiServerConnection(const QString &address, QObject *parent) : - QObject(parent), - m_address(address) +namespace BlackCore { - qDebug() << "ApiServerConnection instantiated"; -} - -void ApiServerConnection::connectTo(const QString &username, const QString &password, const QUuid &networkVersion) -{ - m_username = username; - m_password = password; - m_networkVersion = networkVersion; - m_watch.start(); - - QUrl url(m_address); - url.setPath("/api/v1/auth"); - - QJsonObject obj + namespace Afv { - {"username", username}, - {"password", password}, - {"networkversion", networkVersion.toString()}, - }; + namespace Connection + { + ApiServerConnection::ApiServerConnection(const QString &address, QObject *parent) : + QObject(parent), + m_address(address) + { + qDebug() << "ApiServerConnection instantiated"; + } - QNetworkAccessManager *nam = sApp->getNetworkAccessManager(); - QEventLoop loop; - connect(nam, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QScopedPointer reply(nam->post(request, QJsonDocument(obj).toJson())); - while(! reply->isFinished() ) { loop.exec(); } + void ApiServerConnection::connectTo(const QString &username, const QString &password, const QUuid &networkVersion) + { + m_username = username; + m_password = password; + m_networkVersion = networkVersion; + m_watch.start(); - qDebug() << "POST api/v1/auth (" << m_watch.elapsed() << "ms)"; - if (reply->error() != QNetworkReply::NoError) - { - qWarning() << reply->errorString(); - return; - } + QUrl url(m_address); + url.setPath("/api/v1/auth"); - m_jwt = reply->readAll().trimmed(); - // TODO JwtSecurityToken. Now we assume its 6 hours - m_serverToUserOffset = 0; - m_expiryLocalUtc = QDateTime::currentDateTimeUtc().addSecs( 6 * 60 * 60); - m_isAuthenticated = true; -} + QJsonObject obj + { + {"username", username}, + {"password", password}, + {"networkversion", networkVersion.toString()}, + }; -PostCallsignResponseDto ApiServerConnection::addCallsign(const QString &callsign) -{ - return postNoRequest("/api/v1/users/" + m_username + "/callsigns/" + callsign); -} + QNetworkAccessManager *nam = sApp->getNetworkAccessManager(); + QEventLoop loop; + connect(nam, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QScopedPointer reply(nam->post(request, QJsonDocument(obj).toJson())); + while (! reply->isFinished()) { loop.exec(); } -void ApiServerConnection::removeCallsign(const QString &callsign) -{ - deleteResource("/api/v1/users/" + m_username + "/callsigns/" + callsign); -} - -void ApiServerConnection::updateTransceivers(const QString &callsign, const QVector &transceivers) -{ - QJsonArray array; - for (const TransceiverDto &tx : transceivers) - { - array.append(tx.toJson()); - } - - postNoResponse("/api/v1/users/" + m_username + "/callsigns/" + callsign + "/transceivers", QJsonDocument(array)); -} - -void ApiServerConnection::forceDisconnect() -{ - m_isAuthenticated = false; - m_jwt.clear(); -} - -void ApiServerConnection::postNoResponse(const QString &resource, const QJsonDocument &json) -{ - if (isShuttingDown()) { return; } // avoid crash - if (! m_isAuthenticated) - { - qDebug() << "Not authenticated"; - return; - } - - checkExpiry(); - - m_watch.start(); - QUrl url(m_address); - url.setPath(resource); - QNetworkAccessManager *nam = sApp->getNetworkAccessManager(); - QEventLoop loop; - connect(nam, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); - - QNetworkRequest request(url); - request.setRawHeader("Authorization", "Bearer " + m_jwt); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QScopedPointer reply(nam->deleteResource(request)); - while(! reply->isFinished() ) { loop.exec(); } - qDebug() << "POST" << resource << "(" << m_watch.elapsed() << "ms)"; - - if (reply->error() != QNetworkReply::NoError) - { - qWarning() << "POST" << resource << "failed:" << reply->errorString(); - return; - } -} - -void ApiServerConnection::deleteResource(const QString &resource) -{ - if (! m_isAuthenticated) { return; } - - m_watch.start(); - QUrl url(m_address); - url.setPath(resource); - - QNetworkAccessManager *nam = sApp->getNetworkAccessManager(); - QEventLoop loop; - connect(nam, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); - - QNetworkRequest request(url); - request.setRawHeader("Authorization", "Bearer " + m_jwt); - QScopedPointer reply(nam->deleteResource(request)); - while(! reply->isFinished() ) { loop.exec(); } - qDebug() << "DELETE" << resource << "(" << m_watch.elapsed() << "ms)"; - - if (reply->error() != QNetworkReply::NoError) - { - qWarning() << "DELETE" << resource << "failed:" << reply->errorString(); - } -} - -void ApiServerConnection::checkExpiry() -{ - if (QDateTime::currentDateTimeUtc() > m_expiryLocalUtc.addSecs(-5 * 60)) - { - connectTo(m_username, m_password, m_networkVersion); - } -} - -bool ApiServerConnection::isShuttingDown() -{ - return !sApp || sApp->isShuttingDown(); -} + qDebug() << "POST api/v1/auth (" << m_watch.elapsed() << "ms)"; + if (reply->error() != QNetworkReply::NoError) + { + qWarning() << reply->errorString(); + return; + } + + m_jwt = reply->readAll().trimmed(); + // TODO JwtSecurityToken. Now we assume its 6 hours + m_serverToUserOffset = 0; + m_expiryLocalUtc = QDateTime::currentDateTimeUtc().addSecs(6 * 60 * 60); + m_isAuthenticated = true; + } + + PostCallsignResponseDto ApiServerConnection::addCallsign(const QString &callsign) + { + return postNoRequest("/api/v1/users/" + m_username + "/callsigns/" + callsign); + } + + void ApiServerConnection::removeCallsign(const QString &callsign) + { + deleteResource("/api/v1/users/" + m_username + "/callsigns/" + callsign); + } + + void ApiServerConnection::updateTransceivers(const QString &callsign, const QVector &transceivers) + { + QJsonArray array; + for (const TransceiverDto &tx : transceivers) + { + array.append(tx.toJson()); + } + + postNoResponse("/api/v1/users/" + m_username + "/callsigns/" + callsign + "/transceivers", QJsonDocument(array)); + } + + void ApiServerConnection::forceDisconnect() + { + m_isAuthenticated = false; + m_jwt.clear(); + } + + void ApiServerConnection::postNoResponse(const QString &resource, const QJsonDocument &json) + { + if (isShuttingDown()) { return; } // avoid crash + if (! m_isAuthenticated) + { + qDebug() << "Not authenticated"; + return; + } + + checkExpiry(); + + m_watch.start(); + QUrl url(m_address); + url.setPath(resource); + QNetworkAccessManager *nam = sApp->getNetworkAccessManager(); + QEventLoop loop; + connect(nam, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); + + QNetworkRequest request(url); + request.setRawHeader("Authorization", "Bearer " + m_jwt); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QScopedPointer reply(nam->deleteResource(request)); + while (! reply->isFinished()) { loop.exec(); } + qDebug() << "POST" << resource << "(" << m_watch.elapsed() << "ms)"; + + if (reply->error() != QNetworkReply::NoError) + { + qWarning() << "POST" << resource << "failed:" << reply->errorString(); + return; + } + } + + void ApiServerConnection::deleteResource(const QString &resource) + { + if (! m_isAuthenticated) { return; } + + m_watch.start(); + QUrl url(m_address); + url.setPath(resource); + + QNetworkAccessManager *nam = sApp->getNetworkAccessManager(); + QEventLoop loop; + connect(nam, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); + + QNetworkRequest request(url); + request.setRawHeader("Authorization", "Bearer " + m_jwt); + QScopedPointer reply(nam->deleteResource(request)); + while (! reply->isFinished()) { loop.exec(); } + qDebug() << "DELETE" << resource << "(" << m_watch.elapsed() << "ms)"; + + if (reply->error() != QNetworkReply::NoError) + { + qWarning() << "DELETE" << resource << "failed:" << reply->errorString(); + } + } + + void ApiServerConnection::checkExpiry() + { + if (QDateTime::currentDateTimeUtc() > m_expiryLocalUtc.addSecs(-5 * 60)) + { + connectTo(m_username, m_password, m_networkVersion); + } + } + + bool ApiServerConnection::isShuttingDown() + { + return !sApp || sApp->isShuttingDown(); + } + + } // ns + } // ns +}// ns + diff --git a/src/blackcore/afv/connection/apiserverconnection.h b/src/blackcore/afv/connection/apiserverconnection.h index e36ed3659..845699cdc 100644 --- a/src/blackcore/afv/connection/apiserverconnection.h +++ b/src/blackcore/afv/connection/apiserverconnection.h @@ -1,5 +1,15 @@ -#ifndef APISERVERCONNECTION_H -#define APISERVERCONNECTION_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_CONNECTION_APISERVERCONNECTION_H +#define BLACKCORE_AFV_CONNECTION_APISERVERCONNECTION_H #include "blackcore/afv/dto.h" #include "blackcore/application.h" @@ -18,83 +28,92 @@ // TODO: // - JWT refresh -class ApiServerConnection : public QObject +namespace BlackCore { - Q_OBJECT - -public: - enum ServerError + namespace Afv { - NoError - }; - - ApiServerConnection(const QString &address, QObject *parent = nullptr); - - bool isAuthenticated() const { return m_isAuthenticated; } - void connectTo(const QString &username, const QString &password, const QUuid &networkVersion); - - PostCallsignResponseDto addCallsign(const QString &callsign); - void removeCallsign(const QString &callsign); - - void updateTransceivers(const QString &callsign, const QVector &transceivers); - - void forceDisconnect(); - -private: - template - TResponse postNoRequest(const QString &resource) - { - if (! m_isAuthenticated) + namespace Connection { - qDebug() << "Not authenticated"; - return {}; - } + class ApiServerConnection : public QObject + { + Q_OBJECT - checkExpiry(); + public: + enum ServerError + { + NoError + }; - QNetworkAccessManager *nam = sApp->getNetworkAccessManager(); + ApiServerConnection(const QString &address, QObject *parent = nullptr); - m_watch.start(); - QUrl url(m_address); - url.setPath(resource); - QEventLoop loop; - connect(nam, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); + bool isAuthenticated() const { return m_isAuthenticated; } + void connectTo(const QString &username, const QString &password, const QUuid &networkVersion); - QNetworkRequest request(url); - request.setRawHeader("Authorization", "Bearer " + m_jwt); - QNetworkReply *reply = nam->post(request, QByteArray()); - while(! reply->isFinished() ) { loop.exec(); } - qDebug() << "POST" << resource << "(" << m_watch.elapsed() << "ms)"; + PostCallsignResponseDto addCallsign(const QString &callsign); + void removeCallsign(const QString &callsign); - if (reply->error() != QNetworkReply::NoError) - { - qWarning() << "POST" << resource << "failed:" << reply->errorString(); - return {}; - } + void updateTransceivers(const QString &callsign, const QVector &transceivers); - const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); - TResponse response = TResponse::fromJson(doc.object()); + void forceDisconnect(); - reply->deleteLater(); - return response; - } + private: + template + TResponse postNoRequest(const QString &resource) + { + if (! m_isAuthenticated) + { + qDebug() << "Not authenticated"; + return {}; + } - void postNoResponse(const QString &resource, const QJsonDocument &json); - void deleteResource(const QString &resource); - void checkExpiry(); - static bool isShuttingDown(); + checkExpiry(); - const QString m_address; - QByteArray m_jwt; - QString m_username; - QString m_password; - QUuid m_networkVersion; - QDateTime m_expiryLocalUtc; - qint64 m_serverToUserOffset; + QNetworkAccessManager *nam = sApp->getNetworkAccessManager(); - bool m_isAuthenticated = false; + m_watch.start(); + QUrl url(m_address); + url.setPath(resource); + QEventLoop loop; + connect(nam, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); - QElapsedTimer m_watch; -}; + QNetworkRequest request(url); + request.setRawHeader("Authorization", "Bearer " + m_jwt); + QNetworkReply *reply = nam->post(request, QByteArray()); + while (! reply->isFinished()) { loop.exec(); } + qDebug() << "POST" << resource << "(" << m_watch.elapsed() << "ms)"; -#endif // APISERVERCONNECTION_H + if (reply->error() != QNetworkReply::NoError) + { + qWarning() << "POST" << resource << "failed:" << reply->errorString(); + return {}; + } + + const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); + TResponse response = TResponse::fromJson(doc.object()); + + reply->deleteLater(); + return response; + } + + void postNoResponse(const QString &resource, const QJsonDocument &json); + void deleteResource(const QString &resource); + void checkExpiry(); + static bool isShuttingDown(); + + const QString m_address; + QByteArray m_jwt; + QString m_username; + QString m_password; + QUuid m_networkVersion; + QDateTime m_expiryLocalUtc; + qint64 m_serverToUserOffset; + + bool m_isAuthenticated = false; + + QElapsedTimer m_watch; + }; + } // ns + } // ns +} // ns + +#endif // guard diff --git a/src/blackcore/afv/connection/clientconnection.cpp b/src/blackcore/afv/connection/clientconnection.cpp index ae04a7bcf..09a9a9a9d 100644 --- a/src/blackcore/afv/connection/clientconnection.cpp +++ b/src/blackcore/afv/connection/clientconnection.cpp @@ -1,146 +1,165 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + #include "clientconnection.h" #include -ClientConnection::ClientConnection(const QString &apiServer, QObject *parent) : - QObject(parent), - m_apiServerConnection(apiServer, this) +using namespace BlackCore::Afv::Crypto; + +namespace BlackCore { - qDebug() << "ClientConnection instantiated"; - -// connect(&m_apiServerConnection, &ApiServerConnection::authenticationFinished, this, &ClientConnection::apiConnectionFinished); -// connect(&m_apiServerConnection, &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished); -// connect(&m_apiServerConnection, &ApiServerConnection::removeCallsignFinished, this, &ClientConnection::removeCallsignFinished); - - connect(&m_voiceServerTimer, &QTimer::timeout, this, &ClientConnection::voiceServerHeartbeat); - - connect(&m_udpSocket, &QUdpSocket::readyRead, this, &ClientConnection::readPendingDatagrams); - connect(&m_udpSocket, qOverload(&QUdpSocket::error), this, &ClientConnection::handleSocketError); -} - -void ClientConnection::connectTo(const QString &userName, const QString &password, const QString &callsign) -{ - if (m_connection.m_connected) + namespace Afv { - qDebug() << "Client already connected"; - return; - } - - m_connection.m_userName = userName; - m_connection.m_callsign = callsign; - m_apiServerConnection.connectTo(userName, password, m_networkVersion); - m_connection.m_tokens = m_apiServerConnection.addCallsign(m_connection.m_callsign); - m_connection.m_authenticatedDateTimeUtc = QDateTime::currentDateTimeUtc(); - m_connection.createCryptoChannels(); - - connectToVoiceServer(); - - // taskServerConnectionCheck.Start(); - - m_connection.m_connected = true; - qDebug() << "Connected:" << callsign; -} - -void ClientConnection::disconnectFrom(const QString &reason) -{ - if (! m_connection.m_connected) - { - qDebug() << "Client not connected"; - return; - } - - m_connection.m_connected = false; - // TODO emit disconnected(reason) - qDebug() << "Disconnected:" << reason; - - if (! m_connection.m_callsign.isEmpty()) - { - m_apiServerConnection.removeCallsign(m_connection.m_callsign); - } - - // TODO connectionCheckCancelTokenSource.Cancel(); //Stops connection check loop - disconnectFromVoiceServer(); - m_apiServerConnection.forceDisconnect(); - m_connection.m_tokens = {}; - - qDebug() << "Disconnection complete"; -} - -bool ClientConnection::receiveAudioDto() const -{ - return m_receiveAudioDto; -} - -void ClientConnection::setReceiveAudioDto(bool receiveAudioDto) -{ - m_receiveAudioDto = receiveAudioDto; -} - -void ClientConnection::updateTransceivers(const QString &callsign, const QVector &transceivers) -{ - m_apiServerConnection.updateTransceivers(callsign, transceivers); -} - -void ClientConnection::connectToVoiceServer() -{ - QHostAddress localAddress(QHostAddress::AnyIPv4); - m_udpSocket.bind(localAddress); - m_voiceServerTimer.start(3000); - - qDebug() << "Connected to voice server (" + m_connection.m_tokens.VoiceServer.addressIpV4 << ")"; -} - -void ClientConnection::disconnectFromVoiceServer() -{ - m_voiceServerTimer.stop(); - m_udpSocket.disconnectFromHost(); - qDebug() << "All TaskVoiceServer tasks stopped"; -} - -void ClientConnection::readPendingDatagrams() -{ - while (m_udpSocket.hasPendingDatagrams()) - { - QNetworkDatagram datagram = m_udpSocket.receiveDatagram(); - processMessage(datagram.data()); - } -} - -void ClientConnection::processMessage(const QByteArray &messageDdata, bool loopback) -{ - CryptoDtoSerializer::Deserializer deserializer = CryptoDtoSerializer::deserialize(*m_connection.voiceCryptoChannel, messageDdata, loopback); - - if(deserializer.dtoNameBuffer == AudioRxOnTransceiversDto::getShortDtoName()) - { - // qDebug() << "Received audio data"; - AudioRxOnTransceiversDto audioOnTransceiverDto = deserializer.getDto(); - if (m_connection.m_receiveAudio && m_connection.m_connected) + namespace Connection { - emit audioReceived(audioOnTransceiverDto); - } - } - else if(deserializer.dtoNameBuffer == HeartbeatAckDto::getShortDtoName()) - { - m_connection.m_lastVoiceServerHeartbeatAckUtc = QDateTime::currentDateTimeUtc(); - qDebug() << "Received voice server heartbeat"; - } - else - { - qWarning() << "Received unknown data:" << deserializer.dtoNameBuffer << deserializer.dataLength; - } -} + ClientConnection::ClientConnection(const QString &apiServer, QObject *parent) : + QObject(parent), + m_apiServerConnection(apiServer, this) + { + qDebug() << "ClientConnection instantiated"; -void ClientConnection::handleSocketError(QAbstractSocket::SocketError error) -{ - Q_UNUSED(error); - qDebug() << "UDP socket error" << m_udpSocket.errorString(); -} + // connect(&m_apiServerConnection, &ApiServerConnection::authenticationFinished, this, &ClientConnection::apiConnectionFinished); + // connect(&m_apiServerConnection, &ApiServerConnection::addCallsignFinished, this, &ClientConnection::addCallsignFinished); + // connect(&m_apiServerConnection, &ApiServerConnection::removeCallsignFinished, this, &ClientConnection::removeCallsignFinished); -void ClientConnection::voiceServerHeartbeat() -{ - QUrl voiceServerUrl("udp://" + m_connection.m_tokens.VoiceServer.addressIpV4); - qDebug() << "Sending voice server heartbeat to" << voiceServerUrl.host(); - HeartbeatDto keepAlive; - keepAlive.callsign = m_connection.m_callsign.toStdString(); - QByteArray dataBytes = CryptoDtoSerializer::Serialize(*m_connection.voiceCryptoChannel, CryptoDtoMode::AEAD_ChaCha20Poly1305, keepAlive); - m_udpSocket.writeDatagram(dataBytes, QHostAddress(voiceServerUrl.host()), voiceServerUrl.port()); -} + connect(&m_voiceServerTimer, &QTimer::timeout, this, &ClientConnection::voiceServerHeartbeat); + + connect(&m_udpSocket, &QUdpSocket::readyRead, this, &ClientConnection::readPendingDatagrams); + connect(&m_udpSocket, qOverload(&QUdpSocket::error), this, &ClientConnection::handleSocketError); + } + + void ClientConnection::connectTo(const QString &userName, const QString &password, const QString &callsign) + { + if (m_connection.m_connected) + { + qDebug() << "Client already connected"; + return; + } + + m_connection.m_userName = userName; + m_connection.m_callsign = callsign; + m_apiServerConnection.connectTo(userName, password, m_networkVersion); + m_connection.m_tokens = m_apiServerConnection.addCallsign(m_connection.m_callsign); + m_connection.m_authenticatedDateTimeUtc = QDateTime::currentDateTimeUtc(); + m_connection.createCryptoChannels(); + + connectToVoiceServer(); + + // taskServerConnectionCheck.Start(); + + m_connection.m_connected = true; + qDebug() << "Connected:" << callsign; + } + + void ClientConnection::disconnectFrom(const QString &reason) + { + if (! m_connection.m_connected) + { + qDebug() << "Client not connected"; + return; + } + + m_connection.m_connected = false; + // TODO emit disconnected(reason) + qDebug() << "Disconnected:" << reason; + + if (! m_connection.m_callsign.isEmpty()) + { + m_apiServerConnection.removeCallsign(m_connection.m_callsign); + } + + // TODO connectionCheckCancelTokenSource.Cancel(); //Stops connection check loop + disconnectFromVoiceServer(); + m_apiServerConnection.forceDisconnect(); + m_connection.m_tokens = {}; + + qDebug() << "Disconnection complete"; + } + + bool ClientConnection::receiveAudioDto() const + { + return m_receiveAudioDto; + } + + void ClientConnection::setReceiveAudioDto(bool receiveAudioDto) + { + m_receiveAudioDto = receiveAudioDto; + } + + void ClientConnection::updateTransceivers(const QString &callsign, const QVector &transceivers) + { + m_apiServerConnection.updateTransceivers(callsign, transceivers); + } + + void ClientConnection::connectToVoiceServer() + { + QHostAddress localAddress(QHostAddress::AnyIPv4); + m_udpSocket.bind(localAddress); + m_voiceServerTimer.start(3000); + + qDebug() << "Connected to voice server (" + m_connection.m_tokens.VoiceServer.addressIpV4 << ")"; + } + + void ClientConnection::disconnectFromVoiceServer() + { + m_voiceServerTimer.stop(); + m_udpSocket.disconnectFromHost(); + qDebug() << "All TaskVoiceServer tasks stopped"; + } + + void ClientConnection::readPendingDatagrams() + { + while (m_udpSocket.hasPendingDatagrams()) + { + QNetworkDatagram datagram = m_udpSocket.receiveDatagram(); + processMessage(datagram.data()); + } + } + + void ClientConnection::processMessage(const QByteArray &messageDdata, bool loopback) + { + CryptoDtoSerializer::Deserializer deserializer = CryptoDtoSerializer::deserialize(*m_connection.voiceCryptoChannel, messageDdata, loopback); + + if (deserializer.dtoNameBuffer == AudioRxOnTransceiversDto::getShortDtoName()) + { + // qDebug() << "Received audio data"; + AudioRxOnTransceiversDto audioOnTransceiverDto = deserializer.getDto(); + if (m_connection.m_receiveAudio && m_connection.m_connected) + { + emit audioReceived(audioOnTransceiverDto); + } + } + else if (deserializer.dtoNameBuffer == HeartbeatAckDto::getShortDtoName()) + { + m_connection.m_lastVoiceServerHeartbeatAckUtc = QDateTime::currentDateTimeUtc(); + qDebug() << "Received voice server heartbeat"; + } + else + { + qWarning() << "Received unknown data:" << deserializer.dtoNameBuffer << deserializer.dataLength; + } + } + + void ClientConnection::handleSocketError(QAbstractSocket::SocketError error) + { + Q_UNUSED(error); + qDebug() << "UDP socket error" << m_udpSocket.errorString(); + } + + void ClientConnection::voiceServerHeartbeat() + { + QUrl voiceServerUrl("udp://" + m_connection.m_tokens.VoiceServer.addressIpV4); + qDebug() << "Sending voice server heartbeat to" << voiceServerUrl.host(); + HeartbeatDto keepAlive; + keepAlive.callsign = m_connection.m_callsign.toStdString(); + QByteArray dataBytes = CryptoDtoSerializer::Serialize(*m_connection.voiceCryptoChannel, CryptoDtoMode::AEAD_ChaCha20Poly1305, keepAlive); + m_udpSocket.writeDatagram(dataBytes, QHostAddress(voiceServerUrl.host()), voiceServerUrl.port()); + } + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/connection/clientconnection.h b/src/blackcore/afv/connection/clientconnection.h index 79707f70c..9472d3fef 100644 --- a/src/blackcore/afv/connection/clientconnection.h +++ b/src/blackcore/afv/connection/clientconnection.h @@ -1,5 +1,15 @@ -#ifndef CLIENTCONNECTION_H -#define CLIENTCONNECTION_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_CONNECTION_CLIENTCONNECTION_H +#define BLACKCORE_AFV_CONNECTION_CLIENTCONNECTION_H #include "blackcore/afv/crypto/cryptodtoserializer.h" #include "blackcore/afv/connection/clientconnectiondata.h" @@ -11,69 +21,79 @@ #include #include -class ClientConnection : public QObject +namespace BlackCore { - Q_OBJECT - -public: - //! Com status - enum ConnectionStatus + namespace Afv { - Disconnected, //!< Not connected - Connected, //!< Connection established - }; - Q_ENUM(ConnectionStatus) + namespace Connection + { + //! Client connection + class ClientConnection : public QObject + { + Q_OBJECT - ClientConnection(const QString &apiServer, QObject *parent = nullptr); + public: + //! Com status + enum ConnectionStatus + { + Disconnected, //!< Not connected + Connected, //!< Connection established + }; + Q_ENUM(ConnectionStatus) - void connectTo(const QString &userName, const QString &password, const QString &callsign); - void disconnectFrom(const QString &reason = {}); + ClientConnection(const QString &apiServer, QObject *parent = nullptr); - bool isConnected() const { return m_connection.m_connected; } + void connectTo(const QString &userName, const QString &password, const QString &callsign); + void disconnectFrom(const QString &reason = {}); - void setReceiveAudio(bool value) { m_connection.m_receiveAudio = value; } - bool receiveAudio() const { return m_connection.m_receiveAudio; } + bool isConnected() const { return m_connection.m_connected; } - template - void sendToVoiceServer(T dto) - { - QUrl voiceServerUrl("udp://" + m_connection.m_tokens.VoiceServer.addressIpV4); - QByteArray dataBytes = CryptoDtoSerializer::Serialize(*m_connection.voiceCryptoChannel, CryptoDtoMode::AEAD_ChaCha20Poly1305, dto); - m_udpSocket.writeDatagram(dataBytes, QHostAddress(voiceServerUrl.host()), voiceServerUrl.port()); - } + void setReceiveAudio(bool value) { m_connection.m_receiveAudio = value; } + bool receiveAudio() const { return m_connection.m_receiveAudio; } - bool receiveAudioDto() const; - void setReceiveAudioDto(bool receiveAudioDto); + template + void sendToVoiceServer(T dto) + { + QUrl voiceServerUrl("udp://" + m_connection.m_tokens.VoiceServer.addressIpV4); + QByteArray dataBytes = Crypto::CryptoDtoSerializer::Serialize(*m_connection.voiceCryptoChannel, CryptoDtoMode::AEAD_ChaCha20Poly1305, dto); + m_udpSocket.writeDatagram(dataBytes, QHostAddress(voiceServerUrl.host()), voiceServerUrl.port()); + } - void updateTransceivers(const QString &callsign, const QVector &transceivers); + bool receiveAudioDto() const; + void setReceiveAudioDto(bool receiveAudioDto); -signals: - void audioReceived(const AudioRxOnTransceiversDto &dto); + void updateTransceivers(const QString &callsign, const QVector &transceivers); -private: - void connectToVoiceServer(); - void disconnectFromVoiceServer(); + signals: + void audioReceived(const AudioRxOnTransceiversDto &dto); - void readPendingDatagrams(); - void processMessage(const QByteArray &messageDdata, bool loopback = false); - void handleSocketError(QAbstractSocket::SocketError error); + private: + void connectToVoiceServer(); + void disconnectFromVoiceServer(); - void voiceServerHeartbeat(); + void readPendingDatagrams(); + void processMessage(const QByteArray &messageDdata, bool loopback = false); + void handleSocketError(QAbstractSocket::SocketError error); - const QUuid m_networkVersion = QUuid("3a5ddc6d-cf5d-4319-bd0e-d184f772db80"); + void voiceServerHeartbeat(); - //Data - ClientConnectionData m_connection; + const QUuid m_networkVersion = QUuid("3a5ddc6d-cf5d-4319-bd0e-d184f772db80"); - // Voice server - QUdpSocket m_udpSocket; - QTimer m_voiceServerTimer; + //Data + ClientConnectionData m_connection; - // API server - ApiServerConnection m_apiServerConnection; + // Voice server + QUdpSocket m_udpSocket; + QTimer m_voiceServerTimer; - // Properties - bool m_receiveAudioDto = true; -}; + // API server + ApiServerConnection m_apiServerConnection; -#endif + // Properties + bool m_receiveAudioDto = true; + }; + } // ns + } // ns +} // ns + +#endif // guard diff --git a/src/blackcore/afv/connection/clientconnectiondata.cpp b/src/blackcore/afv/connection/clientconnectiondata.cpp index d38a02d58..878ed84ee 100644 --- a/src/blackcore/afv/connection/clientconnectiondata.cpp +++ b/src/blackcore/afv/connection/clientconnectiondata.cpp @@ -1,28 +1,47 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + #include "clientconnectiondata.h" #include -qint64 ClientConnectionData::secondsSinceAuthentication() const -{ - return m_authenticatedDateTimeUtc.secsTo(QDateTime::currentDateTimeUtc()); -} +using namespace BlackCore::Afv::Crypto; -bool ClientConnectionData::isVoiceServerAlive() const +namespace BlackCore { - return m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > serverTimeout; -} - -void ClientConnectionData::createCryptoChannels() -{ - if (! m_tokens.isValid) + namespace Afv { - qWarning() << "Tokens not set"; - } - voiceCryptoChannel.reset(new CryptoDtoChannel(m_tokens.VoiceServer.channelConfig)); - // dataCryptoChannel.reset(new CryptoDtoChannel(m_tokens.DataServer.channelConfig)); -} + namespace Connection + { + qint64 ClientConnectionData::secondsSinceAuthentication() const + { + return m_authenticatedDateTimeUtc.secsTo(QDateTime::currentDateTimeUtc()); + } -bool ClientConnectionData::voiceServerAlive() const -{ - return timeSinceAuthentication() < serverTimeout || - m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) < serverTimeout; -} + bool ClientConnectionData::isVoiceServerAlive() const + { + return m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) > serverTimeout; + } + + void ClientConnectionData::createCryptoChannels() + { + if (! m_tokens.isValid) + { + qWarning() << "Tokens not set"; + } + voiceCryptoChannel.reset(new CryptoDtoChannel(m_tokens.VoiceServer.channelConfig)); + // dataCryptoChannel.reset(new CryptoDtoChannel(m_tokens.DataServer.channelConfig)); + } + + bool ClientConnectionData::voiceServerAlive() const + { + return timeSinceAuthentication() < serverTimeout || + m_lastVoiceServerHeartbeatAckUtc.secsTo(QDateTime::currentDateTimeUtc()) < serverTimeout; + } + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/connection/clientconnectiondata.h b/src/blackcore/afv/connection/clientconnectiondata.h index 5812b413f..26717053b 100644 --- a/src/blackcore/afv/connection/clientconnectiondata.h +++ b/src/blackcore/afv/connection/clientconnectiondata.h @@ -1,5 +1,15 @@ -#ifndef CLIENTCONNECTIONDATA_H -#define CLIENTCONNECTIONDATA_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_CONNECTION_CLIENTCONNECTIONDATA_H +#define BLACKCORE_AFV_CONNECTION_CLIENTCONNECTIONDATA_H #include "blackcore/afv/dto.h" #include "apiserverconnection.h" @@ -10,41 +20,51 @@ #include #include -struct ClientConnectionData +namespace BlackCore { - ClientConnectionData() = default; + namespace Afv + { + namespace Connection + { + //! Client connection data + struct ClientConnectionData + { + ClientConnectionData() = default; - qint64 secondsSinceAuthentication() const; + qint64 secondsSinceAuthentication() const; - bool isVoiceServerAlive() const; - bool isDataServerAlive() const; + bool isVoiceServerAlive() const; + bool isDataServerAlive() const; - /* TODO - public long VoiceServerBytesSent { get; set; } - public long VoiceServerBytesReceived { get; set; } - public long DataServerBytesSent { get; set; } - public long DataServerBytesReceived { get; set; } - */ + /* TODO + public long VoiceServerBytesSent { get; set; } + public long VoiceServerBytesReceived { get; set; } + public long DataServerBytesSent { get; set; } + public long DataServerBytesReceived { get; set; } + */ - void createCryptoChannels(); + void createCryptoChannels(); - qint64 timeSinceAuthentication() const { return m_authenticatedDateTimeUtc.secsTo(QDateTime::currentDateTimeUtc()); } - bool voiceServerAlive() const; + qint64 timeSinceAuthentication() const { return m_authenticatedDateTimeUtc.secsTo(QDateTime::currentDateTimeUtc()); } + bool voiceServerAlive() const; - QString m_userName; - QString m_callsign; + QString m_userName; + QString m_callsign; - PostCallsignResponseDto m_tokens; + PostCallsignResponseDto m_tokens; - QScopedPointer voiceCryptoChannel; + QScopedPointer voiceCryptoChannel; - QDateTime m_authenticatedDateTimeUtc; - QDateTime m_lastVoiceServerHeartbeatAckUtc; + QDateTime m_authenticatedDateTimeUtc; + QDateTime m_lastVoiceServerHeartbeatAckUtc; - bool m_receiveAudio = true; - bool m_connected = false; + bool m_receiveAudio = true; + bool m_connected = false; - static constexpr qint64 serverTimeout = 10; -}; + static constexpr qint64 serverTimeout = 10; + }; + } // ns + } // ns +} // ns -#endif // CLIENTCONNECTIONDATA_H +#endif // guard diff --git a/src/blackcore/afv/constants.h b/src/blackcore/afv/constants.h index 7d9985aac..044fe5916 100644 --- a/src/blackcore/afv/constants.h +++ b/src/blackcore/afv/constants.h @@ -1,15 +1,31 @@ -#ifndef CONSTANTS_H -#define CONSTANTS_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ -constexpr double MilesToMeters = 1609.34; -constexpr double MetersToFeet = 3.28084; -constexpr double FeetToMeters = 0.3048; -constexpr double NauticalMilesToMeters = 1852; -constexpr double MetersToNauticalMiles = 0.000539957; -constexpr double RadToDeg = 57.295779513082320876798154814105; -constexpr double DegToRad = 0.01745329251994329576923690768489; +//! \file -constexpr int c_channelCount = 1; -constexpr int c_sampleRate = 48000; +#ifndef BLACKCORE_AFV_CONSTANTS_H +#define BLACKCORE_AFV_CONSTANTS_H -#endif // CONSTANTS_H +namespace BlackCore +{ + namespace Afv + { + constexpr double MilesToMeters = 1609.34; + constexpr double MetersToFeet = 3.28084; + constexpr double FeetToMeters = 0.3048; + constexpr double NauticalMilesToMeters = 1852; + constexpr double MetersToNauticalMiles = 0.000539957; + constexpr double RadToDeg = 57.295779513082320876798154814105; + constexpr double DegToRad = 0.01745329251994329576923690768489; + + constexpr int c_channelCount = 1; + constexpr int c_sampleRate = 48000; + } // ns +} // ns + +#endif // guard diff --git a/src/blackcore/afv/crypto/cryptodtochannel.cpp b/src/blackcore/afv/crypto/cryptodtochannel.cpp index 537ed8d79..c05bc2984 100644 --- a/src/blackcore/afv/crypto/cryptodtochannel.cpp +++ b/src/blackcore/afv/crypto/cryptodtochannel.cpp @@ -1,128 +1,146 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + #include "cryptodtochannel.h" -CryptoDtoChannel::CryptoDtoChannel(QString channelTag, const QByteArray &aeadReceiveKey, const QByteArray &aeadTransmitKey, int receiveSequenceHistorySize) +namespace BlackCore { - ChannelTag = channelTag; - m_aeadReceiveKey = aeadReceiveKey; - m_aeadTransmitKey = aeadTransmitKey; - - receiveSequenceSizeMaxSize = receiveSequenceHistorySize; - if (receiveSequenceSizeMaxSize < 1) - receiveSequenceSizeMaxSize = 1; - receiveSequenceHistory = new uint[receiveSequenceSizeMaxSize]; - receiveSequenceHistoryDepth = 0; -} - -CryptoDtoChannel::CryptoDtoChannel(CryptoDtoChannelConfigDto channelConfig, int receiveSequenceHistorySize) -{ - ChannelTag = channelConfig.channelTag; - m_aeadReceiveKey = channelConfig.aeadReceiveKey; - m_aeadTransmitKey = channelConfig.aeadTransmitKey; - hmacKey = channelConfig.hmacKey; - - receiveSequenceSizeMaxSize = receiveSequenceHistorySize; - if (receiveSequenceSizeMaxSize < 1) - receiveSequenceSizeMaxSize = 1; - receiveSequenceHistory = new uint[receiveSequenceSizeMaxSize]; - receiveSequenceHistoryDepth = 0; -} - -QByteArray CryptoDtoChannel::getTransmitKey(CryptoDtoMode mode) -{ - switch (mode) + namespace Afv { - case CryptoDtoMode::AEAD_ChaCha20Poly1305: return m_aeadTransmitKey; - case CryptoDtoMode::Undefined: - case CryptoDtoMode::None: - qFatal("GetTransmitKey called with wrong argument."); - } - - return {}; -} - -QByteArray CryptoDtoChannel::getTransmitKey(CryptoDtoMode mode, uint &sequenceToSend) -{ - sequenceToSend = transmitSequence; - transmitSequence++; - LastTransmitUtc = QDateTime::currentDateTimeUtc(); - - switch (mode) - { - case CryptoDtoMode::AEAD_ChaCha20Poly1305: return m_aeadTransmitKey; - case CryptoDtoMode::Undefined: - case CryptoDtoMode::None: - qFatal("GetTransmitKey called with wrong argument."); - } - - return {}; -} - -QString CryptoDtoChannel::getChannelTag() const -{ - return ChannelTag; -} - -QByteArray CryptoDtoChannel::getReceiveKey(CryptoDtoMode mode) -{ - switch (mode) - { - case CryptoDtoMode::AEAD_ChaCha20Poly1305: return m_aeadReceiveKey; - case CryptoDtoMode::Undefined: - case CryptoDtoMode::None: - qFatal("getReceiveKey called with wrong argument."); - } - - return {}; -} - -bool CryptoDtoChannel::checkReceivedSequence(uint sequenceReceived) -{ - if (contains(sequenceReceived)) - { - // Duplication or replay attack - return false; - } - - if (receiveSequenceHistoryDepth < receiveSequenceSizeMaxSize) //If the buffer has been filled... - { - receiveSequenceHistory[receiveSequenceHistoryDepth++] = sequenceReceived; - } - else - { - int minIndex; - uint minValue = getMin(minIndex); - if (sequenceReceived < minValue) { return false; } // Possible replay attack - receiveSequenceHistory[minIndex] = sequenceReceived; - } - - LastReceiveUtc = QDateTime::currentDateTimeUtc(); - return true; -} - -bool CryptoDtoChannel::contains(uint sequence) -{ - for (int i = 0; i < receiveSequenceHistoryDepth; i++) - { - if (receiveSequenceHistory[i] == sequence) - return true; - } - return false; -} - -uint CryptoDtoChannel::getMin(int &minIndex) -{ - uint minValue = std::numeric_limits::max(); - minIndex = -1; - int index = -1; - - for (int i = 0; i < receiveSequenceHistoryDepth; i++) - { - index++; - if (receiveSequenceHistory[i] <= minValue) + namespace Crypto { - minValue = receiveSequenceHistory[i]; - minIndex = index; - } - } - return minValue; -} + CryptoDtoChannel::CryptoDtoChannel(QString channelTag, const QByteArray &aeadReceiveKey, const QByteArray &aeadTransmitKey, int receiveSequenceHistorySize) + { + ChannelTag = channelTag; + m_aeadReceiveKey = aeadReceiveKey; + m_aeadTransmitKey = aeadTransmitKey; + + receiveSequenceSizeMaxSize = receiveSequenceHistorySize; + if (receiveSequenceSizeMaxSize < 1) + receiveSequenceSizeMaxSize = 1; + receiveSequenceHistory = new uint[receiveSequenceSizeMaxSize]; + receiveSequenceHistoryDepth = 0; + } + + CryptoDtoChannel::CryptoDtoChannel(CryptoDtoChannelConfigDto channelConfig, int receiveSequenceHistorySize) + { + ChannelTag = channelConfig.channelTag; + m_aeadReceiveKey = channelConfig.aeadReceiveKey; + m_aeadTransmitKey = channelConfig.aeadTransmitKey; + hmacKey = channelConfig.hmacKey; + + receiveSequenceSizeMaxSize = receiveSequenceHistorySize; + if (receiveSequenceSizeMaxSize < 1) + receiveSequenceSizeMaxSize = 1; + receiveSequenceHistory = new uint[receiveSequenceSizeMaxSize]; + receiveSequenceHistoryDepth = 0; + } + + QByteArray CryptoDtoChannel::getTransmitKey(CryptoDtoMode mode) + { + switch (mode) + { + case CryptoDtoMode::AEAD_ChaCha20Poly1305: return m_aeadTransmitKey; + case CryptoDtoMode::Undefined: + case CryptoDtoMode::None: + qFatal("GetTransmitKey called with wrong argument."); + } + + return {}; + } + + QByteArray CryptoDtoChannel::getTransmitKey(CryptoDtoMode mode, uint &sequenceToSend) + { + sequenceToSend = transmitSequence; + transmitSequence++; + LastTransmitUtc = QDateTime::currentDateTimeUtc(); + + switch (mode) + { + case CryptoDtoMode::AEAD_ChaCha20Poly1305: return m_aeadTransmitKey; + case CryptoDtoMode::Undefined: + case CryptoDtoMode::None: + qFatal("GetTransmitKey called with wrong argument."); + } + + return {}; + } + + QString CryptoDtoChannel::getChannelTag() const + { + return ChannelTag; + } + + QByteArray CryptoDtoChannel::getReceiveKey(CryptoDtoMode mode) + { + switch (mode) + { + case CryptoDtoMode::AEAD_ChaCha20Poly1305: return m_aeadReceiveKey; + case CryptoDtoMode::Undefined: + case CryptoDtoMode::None: + qFatal("getReceiveKey called with wrong argument."); + } + + return {}; + } + + bool CryptoDtoChannel::checkReceivedSequence(uint sequenceReceived) + { + if (contains(sequenceReceived)) + { + // Duplication or replay attack + return false; + } + + if (receiveSequenceHistoryDepth < receiveSequenceSizeMaxSize) //If the buffer has been filled... + { + receiveSequenceHistory[receiveSequenceHistoryDepth++] = sequenceReceived; + } + else + { + int minIndex; + uint minValue = getMin(minIndex); + if (sequenceReceived < minValue) { return false; } // Possible replay attack + receiveSequenceHistory[minIndex] = sequenceReceived; + } + + LastReceiveUtc = QDateTime::currentDateTimeUtc(); + return true; + } + + bool CryptoDtoChannel::contains(uint sequence) + { + for (int i = 0; i < receiveSequenceHistoryDepth; i++) + { + if (receiveSequenceHistory[i] == sequence) + return true; + } + return false; + } + + uint CryptoDtoChannel::getMin(int &minIndex) + { + uint minValue = std::numeric_limits::max(); + minIndex = -1; + int index = -1; + + for (int i = 0; i < receiveSequenceHistoryDepth; i++) + { + index++; + if (receiveSequenceHistory[i] <= minValue) + { + minValue = receiveSequenceHistory[i]; + minIndex = index; + } + } + return minValue; + } + + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/crypto/cryptodtochannel.h b/src/blackcore/afv/crypto/cryptodtochannel.h index eed1778bd..6c5d36b1a 100644 --- a/src/blackcore/afv/crypto/cryptodtochannel.h +++ b/src/blackcore/afv/crypto/cryptodtochannel.h @@ -1,5 +1,15 @@ -#ifndef CRYPTODTOCHANNEL_H -#define CRYPTODTOCHANNEL_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_CRYPTO_CRYPTODTOCHANNEL_H +#define BLACKCORE_AFV_CRYPTO_CRYPTODTOCHANNEL_H #include "blackcore/afv/dto.h" #include "cryptodtomode.h" @@ -9,39 +19,51 @@ #include -class CryptoDtoChannel +namespace BlackCore { -public: + namespace Afv + { + namespace Crypto + { + //! Crypto channel + class CryptoDtoChannel + { + public: + //! Ctor + CryptoDtoChannel(QString channelTag, const QByteArray &aeadReceiveKey, const QByteArray &aeadTransmitKey, int receiveSequenceHistorySize = 10); - CryptoDtoChannel(QString channelTag, const QByteArray &aeadReceiveKey, const QByteArray &aeadTransmitKey, int receiveSequenceHistorySize = 10); - CryptoDtoChannel(CryptoDtoChannelConfigDto channelConfig, int receiveSequenceHistorySize = 10); + //! Ctor + CryptoDtoChannel(CryptoDtoChannelConfigDto channelConfig, int receiveSequenceHistorySize = 10); - QByteArray getTransmitKey(CryptoDtoMode mode); - QByteArray getTransmitKey(CryptoDtoMode mode, uint &sequenceToSend); - QString getChannelTag() const; - QByteArray getReceiveKey(CryptoDtoMode mode); + QByteArray getTransmitKey(CryptoDtoMode mode); + QByteArray getTransmitKey(CryptoDtoMode mode, uint &sequenceToSend); + QString getChannelTag() const; + QByteArray getReceiveKey(CryptoDtoMode mode); - bool checkReceivedSequence(uint sequenceReceived); + bool checkReceivedSequence(uint sequenceReceived); -private: - bool contains(uint sequence); - uint getMin(int &minIndex); + private: + bool contains(uint sequence); + uint getMin(int &minIndex); - QByteArray m_aeadTransmitKey; - uint transmitSequence = 0; + QByteArray m_aeadTransmitKey; + uint transmitSequence = 0; - QByteArray m_aeadReceiveKey; + QByteArray m_aeadReceiveKey; - uint *receiveSequenceHistory; - int receiveSequenceHistoryDepth; - int receiveSequenceSizeMaxSize; + uint *receiveSequenceHistory; + int receiveSequenceHistoryDepth; + int receiveSequenceSizeMaxSize; - QByteArray hmacKey; + QByteArray hmacKey; - QString ChannelTag; - QDateTime LastTransmitUtc; - QDateTime LastReceiveUtc; -}; + QString ChannelTag; + QDateTime LastTransmitUtc; + QDateTime LastReceiveUtc; + }; + } // ns + } // ns +} // ns -#endif // CRYPTODTOCHANNEL_H +#endif // guard diff --git a/src/blackcore/afv/crypto/cryptodtoheaderdto.h b/src/blackcore/afv/crypto/cryptodtoheaderdto.h index 231cb9616..843e4f8c4 100644 --- a/src/blackcore/afv/crypto/cryptodtoheaderdto.h +++ b/src/blackcore/afv/crypto/cryptodtoheaderdto.h @@ -1,17 +1,37 @@ -#ifndef CRYPTODTOHEADERDTO_H -#define CRYPTODTOHEADERDTO_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_CRYPTO_CRYPTODTOHEADERDTO_H +#define BLACKCORE_AFV_CRYPTO_CRYPTODTOHEADERDTO_H #include "cryptodtomode.h" #include "msgpack.hpp" #include #include -struct CryptoDtoHeaderDto +namespace BlackCore { - std::string ChannelTag; - uint64_t Sequence; - CryptoDtoMode Mode; - MSGPACK_DEFINE(ChannelTag, Sequence, Mode) -}; + namespace Afv + { + namespace Crypto + { + //! DTO header + struct CryptoDtoHeaderDto + { + std::string ChannelTag; + uint64_t Sequence; + CryptoDtoMode Mode; + MSGPACK_DEFINE(ChannelTag, Sequence, Mode) + }; + } // ns + } // ns +} // ns -#endif // CRYPTODTOHEADERDTO_H +#endif // gaurd diff --git a/src/blackcore/afv/crypto/cryptodtomode.h b/src/blackcore/afv/crypto/cryptodtomode.h index e0dfe4447..c87c5147d 100644 --- a/src/blackcore/afv/crypto/cryptodtomode.h +++ b/src/blackcore/afv/crypto/cryptodtomode.h @@ -1,8 +1,19 @@ -#ifndef CRYPTODTOMODE_H -#define CRYPTODTOMODE_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_CRYPTO_CRYPTODTOMODE_H +#define BLACKCORE_AFV_CRYPTO_CRYPTODTOMODE_H #include "msgpack.hpp" +//! DTO mode enum class CryptoDtoMode { Undefined = 0, @@ -12,4 +23,4 @@ enum class CryptoDtoMode MSGPACK_ADD_ENUM(CryptoDtoMode); -#endif // CRYPTODTOMODE_H +#endif // guard diff --git a/src/blackcore/afv/crypto/cryptodtoserializer.cpp b/src/blackcore/afv/crypto/cryptodtoserializer.cpp index 58ad6f160..c8ae8fdd8 100644 --- a/src/blackcore/afv/crypto/cryptodtoserializer.cpp +++ b/src/blackcore/afv/crypto/cryptodtoserializer.cpp @@ -1,72 +1,87 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + #include "cryptodtoserializer.h" -CryptoDtoSerializer::CryptoDtoSerializer() + +namespace BlackCore { - -} - -CryptoDtoSerializer::Deserializer CryptoDtoSerializer::deserialize(CryptoDtoChannel &channel, const QByteArray &bytes, bool loopback) -{ - return Deserializer(channel, bytes, loopback); -} - -CryptoDtoSerializer::Deserializer::Deserializer(CryptoDtoChannel &channel, const QByteArray &bytes, bool loopback) -{ - QByteArray data(bytes); - QBuffer buffer(&data); - buffer.open(QIODevice::ReadOnly); - - buffer.read((char *)&headerLength, sizeof(headerLength)); - - QByteArray headerBuffer = buffer.read(headerLength); - - msgpack::object_handle oh = msgpack::unpack(headerBuffer.data(), headerBuffer.size()); - header = oh.get().as(); - - if(header.Mode == CryptoDtoMode::AEAD_ChaCha20Poly1305) + namespace Afv { - int aeLength = buffer.size() - (2 + headerLength); - const QByteArray aePayloadBuffer = buffer.read(aeLength); - - const QByteArray adBuffer = data.left(2 + headerLength); - - QByteArray nonce; - nonce.fill(0, crypto_aead_chacha20poly1305_IETF_NPUBBYTES); - QBuffer nonceBuffer(&nonce); - nonceBuffer.open(QIODevice::WriteOnly); - uint32_t id = 0; - nonceBuffer.write(reinterpret_cast(&id), sizeof(id)); - nonceBuffer.write(reinterpret_cast(&header.Sequence), sizeof(header.Sequence)); - nonceBuffer.close(); - - QByteArray decryptedPayload; - unsigned long long mlen = 500; - decryptedPayload.fill(0, mlen); - - QByteArray key; - if (loopback) { key = channel.getTransmitKey(CryptoDtoMode::AEAD_ChaCha20Poly1305); } - else { key = channel.getReceiveKey(CryptoDtoMode::AEAD_ChaCha20Poly1305); } - int result = crypto_aead_chacha20poly1305_ietf_decrypt(reinterpret_cast(decryptedPayload.data()), &mlen, nullptr, - reinterpret_cast(aePayloadBuffer.constData()), aePayloadBuffer.size(), - reinterpret_cast(adBuffer.constData()), adBuffer.size(), - reinterpret_cast(nonce.constData()), - reinterpret_cast(key.constData())); - - if (result == 0) + namespace Crypto { - decryptedPayload.resize(mlen); + CryptoDtoSerializer::CryptoDtoSerializer() { } - // Fix this: - // if (! channel.checkReceivedSequence(header.Sequence)) { } + CryptoDtoSerializer::Deserializer CryptoDtoSerializer::deserialize(CryptoDtoChannel &channel, const QByteArray &bytes, bool loopback) + { + return Deserializer(channel, bytes, loopback); + } - QBuffer decryptedPayloadBuffer(&decryptedPayload); - decryptedPayloadBuffer.open(QIODevice::ReadOnly); - decryptedPayloadBuffer.read((char *)&dtoNameLength, sizeof(dtoNameLength)); - dtoNameBuffer = decryptedPayloadBuffer.read(dtoNameLength); + CryptoDtoSerializer::Deserializer::Deserializer(CryptoDtoChannel &channel, const QByteArray &bytes, bool loopback) + { + QByteArray data(bytes); + QBuffer buffer(&data); + buffer.open(QIODevice::ReadOnly); - decryptedPayloadBuffer.read((char *)&dataLength, sizeof(dataLength)); - dataBuffer = decryptedPayloadBuffer.read(dataLength); - verified = true; - } - } -} + buffer.read((char *)&headerLength, sizeof(headerLength)); + + QByteArray headerBuffer = buffer.read(headerLength); + + msgpack::object_handle oh = msgpack::unpack(headerBuffer.data(), headerBuffer.size()); + header = oh.get().as(); + + if (header.Mode == CryptoDtoMode::AEAD_ChaCha20Poly1305) + { + int aeLength = buffer.size() - (2 + headerLength); + const QByteArray aePayloadBuffer = buffer.read(aeLength); + + const QByteArray adBuffer = data.left(2 + headerLength); + + QByteArray nonce; + nonce.fill(0, crypto_aead_chacha20poly1305_IETF_NPUBBYTES); + QBuffer nonceBuffer(&nonce); + nonceBuffer.open(QIODevice::WriteOnly); + uint32_t id = 0; + nonceBuffer.write(reinterpret_cast(&id), sizeof(id)); + nonceBuffer.write(reinterpret_cast(&header.Sequence), sizeof(header.Sequence)); + nonceBuffer.close(); + + QByteArray decryptedPayload; + unsigned long long mlen = 500; + decryptedPayload.fill(0, mlen); + + QByteArray key; + if (loopback) { key = channel.getTransmitKey(CryptoDtoMode::AEAD_ChaCha20Poly1305); } + else { key = channel.getReceiveKey(CryptoDtoMode::AEAD_ChaCha20Poly1305); } + int result = crypto_aead_chacha20poly1305_ietf_decrypt(reinterpret_cast(decryptedPayload.data()), &mlen, nullptr, + reinterpret_cast(aePayloadBuffer.constData()), aePayloadBuffer.size(), + reinterpret_cast(adBuffer.constData()), adBuffer.size(), + reinterpret_cast(nonce.constData()), + reinterpret_cast(key.constData())); + + if (result == 0) + { + decryptedPayload.resize(mlen); + + // Fix this: + // if (! channel.checkReceivedSequence(header.Sequence)) { } + + QBuffer decryptedPayloadBuffer(&decryptedPayload); + decryptedPayloadBuffer.open(QIODevice::ReadOnly); + decryptedPayloadBuffer.read((char *)&dtoNameLength, sizeof(dtoNameLength)); + dtoNameBuffer = decryptedPayloadBuffer.read(dtoNameLength); + + decryptedPayloadBuffer.read((char *)&dataLength, sizeof(dataLength)); + dataBuffer = decryptedPayloadBuffer.read(dataLength); + verified = true; + } + } + } + } // ns + } // ns +} // ns diff --git a/src/blackcore/afv/crypto/cryptodtoserializer.h b/src/blackcore/afv/crypto/cryptodtoserializer.h index 5d6e79593..87ee1b35c 100644 --- a/src/blackcore/afv/crypto/cryptodtoserializer.h +++ b/src/blackcore/afv/crypto/cryptodtoserializer.h @@ -1,5 +1,15 @@ -#ifndef CRYPTODTOSERIALIZER_H -#define CRYPTODTOSERIALIZER_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_CRYPTO_CRYPTODTO_SERIALIZER_H +#define BLACKCORE_AFV_CRYPTO_CRYPTODTO_SERIALIZER_H #include "cryptodtochannel.h" #include "cryptodtomode.h" @@ -10,123 +20,133 @@ #include #include -extern QHash gShortDtoNames; - -class CryptoDtoSerializer +namespace BlackCore { -public: - CryptoDtoSerializer(); - - template - static QByteArray Serialize(const QString &channelTag, CryptoDtoMode mode, const QByteArray &transmitKey, uint sequenceToBeSent, T dto) + namespace Afv { - const CryptoDtoHeaderDto header = { channelTag.toStdString(), sequenceToBeSent, mode }; - - QBuffer headerBuffer; - headerBuffer.open(QIODevice::WriteOnly); - msgpack::pack(headerBuffer, header); - headerBuffer.close(); - const quint16 headerLength = static_cast(headerBuffer.buffer().size()); - - const QByteArray dtoNameBuffer = T::getDtoName(); - const QByteArray dtoShortName = T::getShortDtoName(); - const quint16 dtoNameLength = static_cast(dtoShortName.size()); - - QBuffer dtoBuffer; - dtoBuffer.open(QIODevice::WriteOnly); - msgpack::pack(dtoBuffer, dto); - dtoBuffer.close(); - const quint16 dtoLength = static_cast(dtoBuffer.buffer().size()); - - if(header.Mode == CryptoDtoMode::AEAD_ChaCha20Poly1305) + namespace Crypto { - QBuffer aePayloadBuffer; - aePayloadBuffer.open(QIODevice::WriteOnly); - aePayloadBuffer.write(reinterpret_cast(&dtoNameLength), sizeof(dtoNameLength)); - aePayloadBuffer.write(dtoShortName); - aePayloadBuffer.write(reinterpret_cast(&dtoLength), sizeof(dtoLength)); - aePayloadBuffer.write(dtoBuffer.buffer()); - aePayloadBuffer.close(); + extern QHash gShortDtoNames; - QBuffer adPayloadBuffer; - adPayloadBuffer.open(QIODevice::WriteOnly); - adPayloadBuffer.write(reinterpret_cast(&headerLength), sizeof(headerLength)); - adPayloadBuffer.write(headerBuffer.buffer()); - adPayloadBuffer.close(); - - QByteArray nonce; - nonce.fill(0, crypto_aead_chacha20poly1305_IETF_NPUBBYTES); - QBuffer nonceBuffer(&nonce); - nonceBuffer.open(QIODevice::WriteOnly); - uint32_t id = 0; - nonceBuffer.write(reinterpret_cast(&id), sizeof(id)); - nonceBuffer.write(reinterpret_cast(&header.Sequence), sizeof(header.Sequence)); - nonceBuffer.close(); - - unsigned long long clen; - QByteArray aeadPayload; - aeadPayload.fill(0, static_cast(aePayloadBuffer.size() + crypto_aead_chacha20poly1305_IETF_ABYTES)); - int result = crypto_aead_chacha20poly1305_ietf_encrypt(reinterpret_cast(aeadPayload.data()), - &clen, - reinterpret_cast(aePayloadBuffer.buffer().constData()), aePayloadBuffer.size(), - reinterpret_cast(adPayloadBuffer.buffer().constData()), adPayloadBuffer.size(), - nullptr, - reinterpret_cast(nonce.constData()), - reinterpret_cast(transmitKey.constData())); - if (result != 0) { return {}; } - - QBuffer packetBuffer; - packetBuffer.open(QIODevice::WriteOnly); - packetBuffer.write(reinterpret_cast(&headerLength), sizeof(headerLength)); - packetBuffer.write(headerBuffer.buffer()); - packetBuffer.write(aeadPayload); - packetBuffer.close(); - - return packetBuffer.buffer(); - } - - return {}; - } - - template - static QByteArray Serialize(CryptoDtoChannel &channel, CryptoDtoMode mode, T dto) - { - uint sequenceToSend = 0; - QByteArray transmitKey = channel.getTransmitKey(mode, sequenceToSend); - return Serialize(channel.getChannelTag(), mode, transmitKey, sequenceToSend++, dto); - } - - struct Deserializer - { - Deserializer(CryptoDtoChannel &channel, const QByteArray &bytes, bool loopback); - - template - T getDto() - { - if (! verified) return {}; - if (dtoNameBuffer == T::getDtoName() || dtoNameBuffer == T::getShortDtoName()) + //! Crypto serializer + class CryptoDtoSerializer { - msgpack::object_handle oh2 = msgpack::unpack(dataBuffer.data(), dataBuffer.size()); - msgpack::object obj = oh2.get(); - T dto = obj.as(); - return dto; - } - return {}; - } + public: + CryptoDtoSerializer(); - quint16 headerLength; - CryptoDtoHeaderDto header; + template + static QByteArray Serialize(const QString &channelTag, CryptoDtoMode mode, const QByteArray &transmitKey, uint sequenceToBeSent, T dto) + { + const CryptoDtoHeaderDto header = { channelTag.toStdString(), sequenceToBeSent, mode }; - quint16 dtoNameLength; - QByteArray dtoNameBuffer; + QBuffer headerBuffer; + headerBuffer.open(QIODevice::WriteOnly); + msgpack::pack(headerBuffer, header); + headerBuffer.close(); + const quint16 headerLength = static_cast(headerBuffer.buffer().size()); - quint16 dataLength; - QByteArray dataBuffer; + const QByteArray dtoNameBuffer = T::getDtoName(); + const QByteArray dtoShortName = T::getShortDtoName(); + const quint16 dtoNameLength = static_cast(dtoShortName.size()); - bool verified = false; - }; + QBuffer dtoBuffer; + dtoBuffer.open(QIODevice::WriteOnly); + msgpack::pack(dtoBuffer, dto); + dtoBuffer.close(); + const quint16 dtoLength = static_cast(dtoBuffer.buffer().size()); - static Deserializer deserialize(CryptoDtoChannel &channel, const QByteArray &bytes, bool loopback); -}; + if (header.Mode == CryptoDtoMode::AEAD_ChaCha20Poly1305) + { + QBuffer aePayloadBuffer; + aePayloadBuffer.open(QIODevice::WriteOnly); + aePayloadBuffer.write(reinterpret_cast(&dtoNameLength), sizeof(dtoNameLength)); + aePayloadBuffer.write(dtoShortName); + aePayloadBuffer.write(reinterpret_cast(&dtoLength), sizeof(dtoLength)); + aePayloadBuffer.write(dtoBuffer.buffer()); + aePayloadBuffer.close(); -#endif // CRYPTODTOSERIALIZER_H + QBuffer adPayloadBuffer; + adPayloadBuffer.open(QIODevice::WriteOnly); + adPayloadBuffer.write(reinterpret_cast(&headerLength), sizeof(headerLength)); + adPayloadBuffer.write(headerBuffer.buffer()); + adPayloadBuffer.close(); + + QByteArray nonce; + nonce.fill(0, crypto_aead_chacha20poly1305_IETF_NPUBBYTES); + QBuffer nonceBuffer(&nonce); + nonceBuffer.open(QIODevice::WriteOnly); + uint32_t id = 0; + nonceBuffer.write(reinterpret_cast(&id), sizeof(id)); + nonceBuffer.write(reinterpret_cast(&header.Sequence), sizeof(header.Sequence)); + nonceBuffer.close(); + + unsigned long long clen; + QByteArray aeadPayload; + aeadPayload.fill(0, static_cast(aePayloadBuffer.size() + crypto_aead_chacha20poly1305_IETF_ABYTES)); + int result = crypto_aead_chacha20poly1305_ietf_encrypt(reinterpret_cast(aeadPayload.data()), + &clen, + reinterpret_cast(aePayloadBuffer.buffer().constData()), aePayloadBuffer.size(), + reinterpret_cast(adPayloadBuffer.buffer().constData()), adPayloadBuffer.size(), + nullptr, + reinterpret_cast(nonce.constData()), + reinterpret_cast(transmitKey.constData())); + if (result != 0) { return {}; } + + QBuffer packetBuffer; + packetBuffer.open(QIODevice::WriteOnly); + packetBuffer.write(reinterpret_cast(&headerLength), sizeof(headerLength)); + packetBuffer.write(headerBuffer.buffer()); + packetBuffer.write(aeadPayload); + packetBuffer.close(); + + return packetBuffer.buffer(); + } + + return {}; + } + + template + static QByteArray Serialize(CryptoDtoChannel &channel, CryptoDtoMode mode, T dto) + { + uint sequenceToSend = 0; + QByteArray transmitKey = channel.getTransmitKey(mode, sequenceToSend); + return Serialize(channel.getChannelTag(), mode, transmitKey, sequenceToSend++, dto); + } + + struct Deserializer + { + Deserializer(CryptoDtoChannel &channel, const QByteArray &bytes, bool loopback); + + template + T getDto() + { + if (! verified) return {}; + if (dtoNameBuffer == T::getDtoName() || dtoNameBuffer == T::getShortDtoName()) + { + msgpack::object_handle oh2 = msgpack::unpack(dataBuffer.data(), dataBuffer.size()); + msgpack::object obj = oh2.get(); + T dto = obj.as(); + return dto; + } + return {}; + } + + quint16 headerLength; + CryptoDtoHeaderDto header; + + quint16 dtoNameLength; + QByteArray dtoNameBuffer; + + quint16 dataLength; + QByteArray dataBuffer; + + bool verified = false; + }; + + static Deserializer deserialize(CryptoDtoChannel &channel, const QByteArray &bytes, bool loopback); + }; + } // ns + } // ns +} // ns + +#endif // guard diff --git a/src/blackcore/afv/dto.h b/src/blackcore/afv/dto.h index a3692aae5..0e2a7a1f1 100644 --- a/src/blackcore/afv/dto.h +++ b/src/blackcore/afv/dto.h @@ -1,5 +1,15 @@ -#ifndef DTO_H -#define DTO_H +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKCORE_AFV_DTO_H +#define BLACKCORE_AFV_DTO_H #include "msgpack.hpp" @@ -7,186 +17,192 @@ #include #include -struct IMsgPack -{ }; - -struct CryptoDtoChannelConfigDto +namespace BlackCore { - QString channelTag; - QByteArray aeadReceiveKey; - QByteArray aeadTransmitKey; - QByteArray hmacKey; - - QJsonObject toJson() const + namespace Afv { - QJsonObject json; - json["channelTag"] = channelTag; - json["aeadReceiveKey"] = QString(aeadReceiveKey); - json["aeadTransmitKey"] = QString(aeadTransmitKey); - json["hmacKey"] = QString(hmacKey); - return json; - } + struct IMsgPack + { }; - static CryptoDtoChannelConfigDto fromJson(const QJsonObject &json) - { - CryptoDtoChannelConfigDto dto; + struct CryptoDtoChannelConfigDto + { + QString channelTag; + QByteArray aeadReceiveKey; + QByteArray aeadTransmitKey; + QByteArray hmacKey; - dto.channelTag = json.value("channelTag").toString(); - dto.aeadReceiveKey = QByteArray::fromBase64(json.value("aeadReceiveKey").toString().toLocal8Bit()); - dto.aeadTransmitKey = QByteArray::fromBase64(json.value("aeadTransmitKey").toString().toLocal8Bit()); - dto.hmacKey = QByteArray::fromBase64(json.value("hmacKey").toString().toLocal8Bit()); - return dto; - } -}; + QJsonObject toJson() const + { + QJsonObject json; + json["channelTag"] = channelTag; + json["aeadReceiveKey"] = QString(aeadReceiveKey); + json["aeadTransmitKey"] = QString(aeadTransmitKey); + json["hmacKey"] = QString(hmacKey); + return json; + } -struct VoiceServerConnectionDataDto -{ - QString addressIpV4; // Example: 123.123.123.123:50000 - QString addressIpV6; // Example: 123.123.123.123:50000 - CryptoDtoChannelConfigDto channelConfig; + static CryptoDtoChannelConfigDto fromJson(const QJsonObject &json) + { + CryptoDtoChannelConfigDto dto; - QJsonObject toJson() const - { - QJsonObject json; - json["addressIpV4"] = addressIpV4; - json["addressIpV6"] = addressIpV6; - json["channelConfig"] = channelConfig.toJson(); - return json; - } + dto.channelTag = json.value("channelTag").toString(); + dto.aeadReceiveKey = QByteArray::fromBase64(json.value("aeadReceiveKey").toString().toLocal8Bit()); + dto.aeadTransmitKey = QByteArray::fromBase64(json.value("aeadTransmitKey").toString().toLocal8Bit()); + dto.hmacKey = QByteArray::fromBase64(json.value("hmacKey").toString().toLocal8Bit()); + return dto; + } + }; - static VoiceServerConnectionDataDto fromJson(const QJsonObject &json) - { - VoiceServerConnectionDataDto dto; - dto.addressIpV4 = json.value("addressIpV4").toString(); - dto.addressIpV6 = json.value("addressIpV6").toString(); - dto.channelConfig = CryptoDtoChannelConfigDto::fromJson(json.value("channelConfig").toObject()); - return dto; - } -}; + struct VoiceServerConnectionDataDto + { + QString addressIpV4; // Example: 123.123.123.123:50000 + QString addressIpV6; // Example: 123.123.123.123:50000 + CryptoDtoChannelConfigDto channelConfig; -struct PostCallsignResponseDto -{ - VoiceServerConnectionDataDto VoiceServer; - // DataServerConnectionDataDto DataServer; - bool isValid = false; + QJsonObject toJson() const + { + QJsonObject json; + json["addressIpV4"] = addressIpV4; + json["addressIpV6"] = addressIpV6; + json["channelConfig"] = channelConfig.toJson(); + return json; + } - QJsonObject toJson() const - { - QJsonObject json; - json["voiceserverauthdatadto"] = VoiceServer.toJson(); - // json["dataserverauthdatadto"] = DataServer.toJson(); - return json; - } + static VoiceServerConnectionDataDto fromJson(const QJsonObject &json) + { + VoiceServerConnectionDataDto dto; + dto.addressIpV4 = json.value("addressIpV4").toString(); + dto.addressIpV6 = json.value("addressIpV6").toString(); + dto.channelConfig = CryptoDtoChannelConfigDto::fromJson(json.value("channelConfig").toObject()); + return dto; + } + }; - static PostCallsignResponseDto fromJson(const QJsonObject &json) - { - PostCallsignResponseDto dto; - dto.VoiceServer = VoiceServerConnectionDataDto::fromJson(json.value("voiceServer").toObject()); - // dto.DataServer = DataServerConnectionDataDto::fromJson(json.value("dataServer").toObject()); - dto.isValid = true; - return dto; - } -}; + struct PostCallsignResponseDto + { + VoiceServerConnectionDataDto VoiceServer; + // DataServerConnectionDataDto DataServer; + bool isValid = false; -struct TransceiverDto -{ - quint16 id; - quint32 frequency; - double LatDeg = 0.0; - double LonDeg = 0.0; - double HeightMslM = 0.0; - double HeightAglM = 0.0; - MSGPACK_DEFINE(id, frequency, LatDeg, LonDeg, HeightMslM, HeightAglM) + QJsonObject toJson() const + { + QJsonObject json; + json["voiceserverauthdatadto"] = VoiceServer.toJson(); + // json["dataserverauthdatadto"] = DataServer.toJson(); + return json; + } - QJsonObject toJson() const - { - QJsonObject json; - json["ID"] = id; - json["Frequency"] = static_cast(frequency); - json["LatDeg"] = LatDeg; - json["LonDeg"] = LonDeg; - json["AltMslM"] = HeightMslM; - return json; - } + static PostCallsignResponseDto fromJson(const QJsonObject &json) + { + PostCallsignResponseDto dto; + dto.VoiceServer = VoiceServerConnectionDataDto::fromJson(json.value("voiceServer").toObject()); + // dto.DataServer = DataServerConnectionDataDto::fromJson(json.value("dataServer").toObject()); + dto.isValid = true; + return dto; + } + }; - static TransceiverDto fromJson(const QJsonObject &json) - { - TransceiverDto dto; - dto.id = json.value("id").toInt(); - dto.frequency = json.value("frequency").toInt(); - dto.LatDeg = json.value("latDeg").toDouble(); - dto.LonDeg = json.value("lonDeg").toDouble(); - dto.HeightMslM = json.value("heightMslM").toDouble(); - dto.HeightAglM = json.value("heightAglM").toDouble(); - return dto; - } -}; + struct TransceiverDto + { + quint16 id; + quint32 frequency; + double LatDeg = 0.0; + double LonDeg = 0.0; + double HeightMslM = 0.0; + double HeightAglM = 0.0; + MSGPACK_DEFINE(id, frequency, LatDeg, LonDeg, HeightMslM, HeightAglM) -struct HeartbeatDto -{ - static QByteArray getDtoName() { return "HeartbeatDto"; } - static QByteArray getShortDtoName() { return "H"; } + QJsonObject toJson() const + { + QJsonObject json; + json["ID"] = id; + json["Frequency"] = static_cast(frequency); + json["LatDeg"] = LatDeg; + json["LonDeg"] = LonDeg; + json["AltMslM"] = HeightMslM; + return json; + } - std::string callsign; - MSGPACK_DEFINE(callsign) -}; + static TransceiverDto fromJson(const QJsonObject &json) + { + TransceiverDto dto; + dto.id = json.value("id").toInt(); + dto.frequency = json.value("frequency").toInt(); + dto.LatDeg = json.value("latDeg").toDouble(); + dto.LonDeg = json.value("lonDeg").toDouble(); + dto.HeightMslM = json.value("heightMslM").toDouble(); + dto.HeightAglM = json.value("heightAglM").toDouble(); + return dto; + } + }; -struct HeartbeatAckDto -{ - static QByteArray getDtoName() { return "HeartbeatAckDto"; } - static QByteArray getShortDtoName() { return "HA"; } - MSGPACK_DEFINE() -}; + struct HeartbeatDto + { + static QByteArray getDtoName() { return "HeartbeatDto"; } + static QByteArray getShortDtoName() { return "H"; } -struct RxTransceiverDto -{ - uint16_t id; - uint32_t frequency; - float distanceRatio; - // std::string RelayCallsign; + std::string callsign; + MSGPACK_DEFINE(callsign) + }; - MSGPACK_DEFINE(id, frequency, distanceRatio/*, RelayCallsign*/) -}; + struct HeartbeatAckDto + { + static QByteArray getDtoName() { return "HeartbeatAckDto"; } + static QByteArray getShortDtoName() { return "HA"; } + MSGPACK_DEFINE() + }; -struct TxTransceiverDto -{ - uint16_t id; + struct RxTransceiverDto + { + uint16_t id; + uint32_t frequency; + float distanceRatio; + // std::string RelayCallsign; - MSGPACK_DEFINE(id) -}; + MSGPACK_DEFINE(id, frequency, distanceRatio/*, RelayCallsign*/) + }; -struct AudioTxOnTransceiversDto -{ - static QByteArray getDtoName() { return "AudioTxOnTransceiversDto"; } - static QByteArray getShortDtoName() { return "AT"; } + struct TxTransceiverDto + { + uint16_t id; - std::string callsign; - uint sequenceCounter; - std::vector audio; - bool lastPacket; - std::vector transceivers; - MSGPACK_DEFINE(callsign, sequenceCounter, audio, lastPacket, transceivers) -}; + MSGPACK_DEFINE(id) + }; -struct AudioRxOnTransceiversDto -{ - static QByteArray getDtoName() { return "AudioRxOnTransceiversDto"; } - static QByteArray getShortDtoName() { return "AR"; } + struct AudioTxOnTransceiversDto + { + static QByteArray getDtoName() { return "AudioTxOnTransceiversDto"; } + static QByteArray getShortDtoName() { return "AT"; } - std::string callsign; - uint sequenceCounter; - std::vector audio; - bool lastPacket; - std::vector transceivers; - MSGPACK_DEFINE(callsign, sequenceCounter, audio, lastPacket, transceivers) -}; + std::string callsign; + uint sequenceCounter; + std::vector audio; + bool lastPacket; + std::vector transceivers; + MSGPACK_DEFINE(callsign, sequenceCounter, audio, lastPacket, transceivers) + }; -struct IAudioDto -{ - QString callsign; // Callsign that audio originates from - uint sequenceCounter; // Receiver optionally uses this in reordering algorithm/gap detection - QByteArray audio; // Opus compressed audio - bool lastPacket; // Used to indicate to receiver that the sender has stopped sending -}; + struct AudioRxOnTransceiversDto + { + static QByteArray getDtoName() { return "AudioRxOnTransceiversDto"; } + static QByteArray getShortDtoName() { return "AR"; } -#endif // DTO_H + std::string callsign; + uint sequenceCounter; + std::vector audio; + bool lastPacket; + std::vector transceivers; + MSGPACK_DEFINE(callsign, sequenceCounter, audio, lastPacket, transceivers) + }; + + struct IAudioDto + { + QString callsign; // Callsign that audio originates from + uint sequenceCounter; // Receiver optionally uses this in reordering algorithm/gap detection + QByteArray audio; // Opus compressed audio + bool lastPacket; // Used to indicate to receiver that the sender has stopped sending + }; + } // ns +} // ns + +#endif // guard diff --git a/src/blackcore/afv/voiceclient.cpp b/src/blackcore/afv/voiceclient.cpp deleted file mode 100644 index 5858d8644..000000000 --- a/src/blackcore/afv/voiceclient.cpp +++ /dev/null @@ -1,340 +0,0 @@ -#include "voiceclient.h" -#include "cryptodtoserializer.h" -#include "dto.h" -#include "constants.h" - -#include -#include -#include -#include - -void AudioInputBuffer::start() -{ - open(QIODevice::WriteOnly); -} - -void AudioInputBuffer::stop() -{ - close(); -} - -qint64 AudioInputBuffer::readData(char *data, qint64 maxlen) -{ - Q_UNUSED(data) - Q_UNUSED(maxlen) - - return 0; -} - -qint64 AudioInputBuffer::writeData(const char *data, qint64 len) -{ - QByteArray buffer(data, static_cast(len)); - m_buffer.append(buffer); - // 20 ms = 960 samples * 2 bytes = 1920 Bytes - if (m_buffer.size() >= 1920) - { - emit frameAvailable(m_buffer.left(1920)); - m_buffer.remove(0, 1920); - } - - return len; -} - -VoiceClient::VoiceClient(QObject *parent) : - QObject(parent), - m_context(), - m_dealerSocket(m_context, zmq::socket_type::dealer), - decoder(c_sampleRate, c_channelCount), - encoder(c_sampleRate, c_channelCount) -{ - //m_dealerSocket.setsockopt(ZMQ_IDENTITY, "test"); -} - -VoiceClient::~VoiceClient() -{ -} - -void VoiceClient::authenticate(const QString &apiServer, const QString &cid, const QString &password, const QString &callsign) -{ - m_callsign = callsign; - QUrl url(apiServer); - url.setPath("/api/v1/auth"); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - QJsonObject obj - { - {"cID", cid}, - {"password", password}, - {"callsign", callsign} - }; - - connect(&m_nam, &QNetworkAccessManager::finished, this, &VoiceClient::authRequestFinished); - m_reply = m_nam.post(request, QJsonDocument(obj).toJson()); -} - -void VoiceClient::updateTransceivers(const QVector &radioTransceivers) -{ - if (!clientAuthenticated) { return; } - - m_radioTransceivers = radioTransceivers; - - RadioTransceiverUpdateDto updateDto; - updateDto.callsign = m_callsign.toStdString(); - updateDto.radioTransceivers = m_radioTransceivers.toStdVector(); - QByteArray cryptoBytes = CryptoDtoSerializer::Serialize(*dataCryptoChannel, CryptoDtoMode::HMAC_SHA256, updateDto); - - zmq::multipart_t sendMessages; - sendMessages.addstr(""); - sendMessages.addmem(cryptoBytes.data(), cryptoBytes.size()); - sendMessages.send(m_dealerSocket); - - zmq::multipart_t multipart; - multipart.recv(m_dealerSocket); - - if (multipart.size() == 2) - { - zmq::message_t &msg = multipart.at(1); - QByteArray data(msg.data(), msg.size()); - CryptoDtoSerializer::Deserializer deserializer = CryptoDtoSerializer::deserialize(*dataCryptoChannel, data, false); - RadioTransceiverUpdateAckDto ack = deserializer.getDto(); - - if(ack.success) - { - clientAuthenticated = true; - } - } -} - -void VoiceClient::start(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice) -{ - if (!clientAuthenticated) { return; } - connect(&m_udpSocket, &QUdpSocket::readyRead, this, &VoiceClient::readPendingDatagrams); - connect(&m_udpSocket, qOverload(&QUdpSocket::error), this, &VoiceClient::handleSocketError); - - initializeAudio(inputDevice, outputDevice); - - QHostAddress localAddress(QHostAddress::AnyIPv4); - m_udpSocket.bind(localAddress); - sendVoiceServerKeepAlive(); - connect(&timerVoiceServerKeepAlive, &QTimer::timeout, this, &VoiceClient::sendVoiceServerKeepAlive); - timerVoiceServerKeepAlive.start(5000); - - m_isStarted = true; -} - -void VoiceClient::setTransmittingTransceivers(const QStringList &transceiverNames) -{ - m_transmittingTransceiverNames.clear(); - for (const QString &transceiverName : transceiverNames) - { - auto it = std::find_if(m_radioTransceivers.begin(), m_radioTransceivers.end(), [&transceiverName](const RadioTransceiverDto &receiver) - { - return receiver.name == transceiverName.toStdString(); - }); - - if (it != m_radioTransceivers.end()) - { - m_transmittingTransceiverNames.push_back(transceiverName); - } - } -} - -void VoiceClient::authRequestFinished(QNetworkReply *reply) -{ - if(reply->error() != QNetworkReply::NoError) - { - qDebug() << m_reply->errorString(); - return; - } - - const QJsonDocument doc = QJsonDocument::fromJson(m_reply->readAll()); - clientAuthenticationData = AuthResponseDto::fromJson(doc.object()); - m_reply->deleteLater(); - - voiceCryptoChannel = new CryptoDtoChannel(clientAuthenticationData.VoiceServer.channelConfig); - dataCryptoChannel = new CryptoDtoChannel(clientAuthenticationData.DataServer.channelConfig); - - dataServerAddress = QString("tcp://" + clientAuthenticationData.DataServer.addressIpV4); - m_dealerSocket.connect(dataServerAddress.toStdString()); - - ClientDataHeartbeatDto heartBeatDto; - heartBeatDto.callsign = m_callsign.toStdString(); - QByteArray cryptoBytes = CryptoDtoSerializer::Serialize(*dataCryptoChannel, CryptoDtoMode::AEAD_ChaCha20Poly1305, heartBeatDto); - - zmq::multipart_t sendMessages; - sendMessages.addstr(""); - sendMessages.addmem(cryptoBytes.data(), cryptoBytes.size()); - sendMessages.send(m_dealerSocket); - - zmq::multipart_t multipart; - multipart.recv(m_dealerSocket); - - if (multipart.size() == 2) - { - zmq::message_t &msg = multipart.at(1); - QByteArray data(msg.data(), msg.size()); - CryptoDtoSerializer::Deserializer deserializer = CryptoDtoSerializer::deserialize(*dataCryptoChannel, data, false); - ClientDataHeartbeatAckDto ack = deserializer.getDto(); - - if(ack.success) - { - clientAuthenticated = true; - emit isAuthenticated(); - } - } -} - -void VoiceClient::readPendingDatagrams() -{ - while (m_udpSocket.hasPendingDatagrams()) - { - QNetworkDatagram datagram = m_udpSocket.receiveDatagram(); - processMessage(datagram.data()); - } -} - -void VoiceClient::processMessage(const QByteArray &messageDdata, bool loopback) -{ - CryptoDtoSerializer::Deserializer deserializer = CryptoDtoSerializer::deserialize(*voiceCryptoChannel, messageDdata, loopback); - if(deserializer.dtoNameBuffer == "VHA") - { - ClientVoiceHeartbeatAckDto ack = deserializer.getDto(); - Q_UNUSED(ack); - } - else if(deserializer.dtoNameBuffer == "AT") - { - AudioOnTransceiversDto at = deserializer.getDto(); - QStringList transeiverNames; - for (const auto &transeiverName : at.transceiverNames) - { - transeiverNames.append(QString::fromStdString(transeiverName)); - } - - QByteArray audio(at.audio.data(), at.audio.size()); - int decodedLength = 0; - QVector decoded = decoder.decode(audio, audio.size(), &decodedLength); - m_audioSampleProvider->addSamples(decoded, QString::fromStdString(at.callsign)); - } - else - { - qDebug() << "Received unknown data:" << deserializer.dtoNameBuffer << deserializer.dataLength; - } -} - -void VoiceClient::sendVoiceServerKeepAlive() -{ - ClientVoiceHeartbeatDto keepAlive; - QUrl voiceServerUrl("udp://" + clientAuthenticationData.VoiceServer.addressIpV4); - QByteArray dataBytes = CryptoDtoSerializer::Serialize(*voiceCryptoChannel, CryptoDtoMode::AEAD_ChaCha20Poly1305, keepAlive); - m_udpSocket.writeDatagram(dataBytes, QHostAddress(voiceServerUrl.host()), voiceServerUrl.port()); -} - -void VoiceClient::handleSocketError(QAbstractSocket::SocketError error) -{ - Q_UNUSED(error); - qDebug() << "UDP socket error" << m_udpSocket.errorString(); -} - -void VoiceClient::initializeAudio(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice) -{ - QAudioFormat format; - format.setSampleRate(c_sampleRate); - format.setChannelCount(1); - format.setSampleSize(16); - format.setSampleType(QAudioFormat::SignedInt); - format.setByteOrder(QAudioFormat::LittleEndian); - format.setCodec("audio/pcm"); - - if (!inputDevice.isFormatSupported(format)) - { - qWarning() << "Default format not supported - trying to use nearest"; - format = inputDevice.nearestFormat(format); - } - - // m_recorder.start(); - m_audioInput.reset(new QAudioInput(inputDevice, format)); - // We want 20 ms of buffer size - // 20 ms * nSamplesPerSec × nChannels × wBitsPerSample / 8 x 1000 - int bufferSize = 20 * format.sampleRate() * format.channelCount() * format.sampleSize() / ( 8 * 1000 ); - m_audioInput->setBufferSize(bufferSize); - m_audioInputBuffer.start(); - m_audioInput->start(&m_audioInputBuffer); - connect(&m_audioInputBuffer, &AudioInputBuffer::frameAvailable, this, &VoiceClient::audioInDataAvailable); - connect(m_audioInput.data(), &QAudioInput::stateChanged, [&] (QAudio::State state) { qDebug() << "QAudioInput changed state to" << state; }); - - if (!outputDevice.isFormatSupported(format)) - { - qWarning() << "Default format not supported - trying to use nearest"; - format = outputDevice.nearestFormat(format); - } - - m_audioOutput.reset(new QAudioOutput(outputDevice, format)); - // m_audioOutput->setBufferSize(bufferSize); - m_audioSampleProvider.reset(new AircraftVHFSampleProvider(format, 4, 0.1)); - m_audioSampleProvider->open(QIODevice::ReadWrite | QIODevice::Unbuffered); - - m_audioOutput->start(m_audioSampleProvider.data()); -} - -void VoiceClient::audioInDataAvailable(const QByteArray &frame) -{ - QVector samples = convertBytesTo16BitPCM(frame); - - int length; - QByteArray encodedBuffer = encoder.encode(samples, samples.size(), &length); - - if (m_transmittingTransceiverNames.size() > 0 && m_isStarted) - { - if (m_transmit) - { - AudioOnTransceiversDto dto; - dto.callsign = m_callsign.toStdString(); - dto.sequenceCounter = audioSequenceCounter++; - dto.audio = std::vector(encodedBuffer.begin(), encodedBuffer.end()); - dto.lastPacket = false; - for (const QString &transceiverName : m_transmittingTransceiverNames) - { - dto.transceiverNames.push_back(transceiverName.toStdString()); - } - - QUrl voiceServerUrl("udp://" + clientAuthenticationData.VoiceServer.addressIpV4); - QByteArray dataBytes = CryptoDtoSerializer::Serialize(*voiceCryptoChannel, CryptoDtoMode::AEAD_ChaCha20Poly1305, dto); - if (m_loopbackOn) { processMessage(dataBytes, true); } - else { m_udpSocket.writeDatagram(dataBytes, QHostAddress(voiceServerUrl.host()), voiceServerUrl.port()); } - } - - if (!m_transmit && m_transmitHistory) - { - AudioOnTransceiversDto dto; - dto.callsign = m_callsign.toStdString(); - dto.sequenceCounter = audioSequenceCounter++; - dto.audio = std::vector(encodedBuffer.begin(), encodedBuffer.end()); - dto.lastPacket = true; - for (const QString &transceiverName : m_transmittingTransceiverNames) - { - dto.transceiverNames.push_back(transceiverName.toStdString()); - } - - QUrl voiceServerUrl("udp://" + clientAuthenticationData.VoiceServer.addressIpV4); - QByteArray dataBytes = CryptoDtoSerializer::Serialize(*voiceCryptoChannel, CryptoDtoMode::AEAD_ChaCha20Poly1305, dto); - if (m_loopbackOn) { processMessage(dataBytes, true); } - else { m_udpSocket.writeDatagram(dataBytes, QHostAddress(voiceServerUrl.host()), voiceServerUrl.port()); } - } - - m_transmitHistory = m_transmit; - } -} - -QVector VoiceClient::convertBytesTo16BitPCM(const QByteArray input) -{ - int inputSamples = input.size() / 2; // 16 bit input, so 2 bytes per sample - QVector output; - output.fill(0, inputSamples); - - for (int n = 0; n < inputSamples; n++) - { - output[n] = *reinterpret_cast(input.data() + n * 2); - } - return output; -} diff --git a/src/blackcore/afv/voiceclient.h b/src/blackcore/afv/voiceclient.h deleted file mode 100644 index 1bf8a773b..000000000 --- a/src/blackcore/afv/voiceclient.h +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef VOICECLIENT_H -#define VOICECLIENT_H - -#include "zmq.hpp" -#include "zmq_addon.hpp" - -#include "dto.h" -#include "cryptodtochannel.h" -#include "opusdecoder.h" -#include "opusencoder.h" -#include "audio/aircraftvhfsampleprovider.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class AudioInputBuffer : public QIODevice -{ - Q_OBJECT - -public: - AudioInputBuffer() {} - - void start(); - void stop(); - - qint64 readData(char *data, qint64 maxlen) override; - qint64 writeData(const char *data, qint64 len) override; - -signals: - void frameAvailable(const QByteArray &frame); - -private: - static constexpr qint64 frameSize = 960; - QByteArray m_buffer; -}; - - -class VoiceClient : public QObject -{ - Q_OBJECT -public: - VoiceClient(QObject *parent = nullptr); - ~VoiceClient(); - - void authenticate(const QString &apiServer, const QString &cid, const QString &password, const QString &callsign); - void updateTransceivers(const QVector &radioTransceivers); - void start(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice); - bool isStarted() const { return m_isStarted; } - void setBypassEffects(bool value) { if(m_audioSampleProvider) { m_audioSampleProvider->setBypassEffects(value); } } - void setPtt(bool transmit) - { - qDebug() << "setPtt(" << transmit << ")"; - m_transmit = transmit; - } - void setLoopBack(bool on) { m_loopbackOn = on; } - - void setTransmittingTransceivers(const QStringList &transceiverNames); - -signals: - void isAuthenticated(); - -private: - void authRequestFinished(QNetworkReply *reply); - void readPendingDatagrams(); - void processMessage(const QByteArray &messageDdata, bool loopback = false); - - void sendVoiceServerKeepAlive(); - void handleSocketError(QAbstractSocket::SocketError error); - - void initializeAudio(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice); - - void audioInDataAvailable(const QByteArray &frame); - - QVector convertBytesTo16BitPCM(const QByteArray input); - - // Tasks - QTimer timerVoiceServerKeepAlive; - QTimer timerVoiceServerTransmit; - - bool clientAuthenticated = false; - AuthResponseDto clientAuthenticationData; - CryptoDtoChannel *voiceCryptoChannel; - CryptoDtoChannel *dataCryptoChannel; - - QString dataServerAddress; - - zmq::context_t m_context; - zmq::socket_t m_dealerSocket; - - QNetworkAccessManager m_nam; - QNetworkReply *m_reply = nullptr; - - QString m_callsign; - - QVector m_radioTransceivers; - - QUdpSocket m_udpSocket; - - COpusDecoder decoder; - COpusEncoder encoder; - - QScopedPointer m_audioInput; - AudioInputBuffer m_audioInputBuffer; - QScopedPointer m_audioOutput; - QScopedPointer m_audioSampleProvider; - - bool m_isStarted = false; - - QByteArray m_notEncodedBuffer; - - int frameSize = 960; - - QStringList m_transmittingTransceiverNames; - bool m_transmit = false; - bool m_transmitHistory = false; - uint audioSequenceCounter = 0; - - bool m_loopbackOn = false; -}; - -#endif // VOICECLIENT_H diff --git a/src/blackcore/blackcore.pro b/src/blackcore/blackcore.pro index 913fabc39..a49265313 100644 --- a/src/blackcore/blackcore.pro +++ b/src/blackcore/blackcore.pro @@ -28,6 +28,11 @@ HEADERS += $$PWD/data/*.h HEADERS += $$PWD/db/*.h HEADERS += $$PWD/vatsim/*.h HEADERS += $$PWD/fsd/*.h +HEADERS += $$PWD/afv/*.h +HEADERS += $$PWD/afv/audio/*.h +HEADERS += $$PWD/afv/clients/*.h +HEADERS += $$PWD/afv/crypto/*.h +HEADERS += $$PWD/afv/connection/*.h SOURCES += *.cpp SOURCES += $$PWD/context/*.cpp @@ -35,8 +40,10 @@ SOURCES += $$PWD/data/*.cpp SOURCES += $$PWD/db/*.cpp SOURCES += $$PWD/vatsim/*.cpp SOURCES += $$PWD/fsd/*.cpp - -include($$PWD/afv/afv.pri) +SOURCES += $$PWD/afv/audio/*.cpp +SOURCES += $$PWD/afv/clients/*.cpp +SOURCES += $$PWD/afv/crypto/*.cpp +SOURCES += $$PWD/afv/connection/*.cpp LIBS *= \ -lvatlib \ diff --git a/src/blackcore/context/contextaudioimpl.h b/src/blackcore/context/contextaudioimpl.h index b4dcf82ec..b397c6310 100644 --- a/src/blackcore/context/contextaudioimpl.h +++ b/src/blackcore/context/contextaudioimpl.h @@ -223,7 +223,7 @@ namespace BlackCore BlackMisc::CSetting m_outputDeviceSetting { this, &CContextAudio::changeDeviceSettings }; // AFV - AFVClient m_voiceClient; + Afv::Clients::AFVClient m_voiceClient; }; } // namespace } // namespace diff --git a/src/blacksound/blacksound.pro b/src/blacksound/blacksound.pro index a2be4c30c..d19cdd45a 100644 --- a/src/blacksound/blacksound.pro +++ b/src/blacksound/blacksound.pro @@ -15,37 +15,26 @@ DEPENDPATH += . .. DEFINES += LOG_IN_FILE BUILD_BLACKSOUND_LIB -HEADERS += \ - blacksoundexport.h \ - notificationplayer.h \ - audioutilities.h \ - selcalplayer.h \ - soundgenerator.h \ - threadedtonepairplayer.h \ - tonepair.h \ - wav/wavfile.h \ +HEADERS += *.h +HEADERS += wav/wavfile.h +HEADERS += dsp/*.h +HEADERS += codecs/*.h +HEADERS += samplesprovider/*.h -SOURCES += \ - notificationplayer.cpp \ - audioutilities.cpp \ - selcalplayer.cpp \ - soundgenerator.cpp \ - threadedtonepairplayer.cpp \ - tonepair.cpp \ - wav/wavfile.cpp \ +SOURCES += *.cpp +SOURCES += wav/wavfile.cpp +SOURCES += dsp/*.cpp +SOURCES += codecs/*.cpp +SOURCES += samplesprovider/*.cpp -include ($$PWD/codecs/codecs.pri) -include ($$PWD/dsp/dsp.pri) -include ($$PWD/sampleprovider/sampleprovider.pri) - -LIBS *= -lopus \ +LIBS *= -lopus DESTDIR = $$DestRoot/lib DLLDESTDIR = $$DestRoot/bin OTHER_FILES += ./share/sounds/*.wav ./share/sounds/readme.txt COPY_FILES += $$PWD/share/sounds/* -RESOURCES += +# RESOURCES += win32 { dlltarget.path = $$PREFIX/bin diff --git a/src/blacksound/codecs/opusencoder.h b/src/blacksound/codecs/opusencoder.h index a7a81cf66..379a1fd64 100644 --- a/src/blacksound/codecs/opusencoder.h +++ b/src/blacksound/codecs/opusencoder.h @@ -31,4 +31,4 @@ private: static constexpr int maxDataBytes = 4000; }; -#endif // OPUSENCODER_H +#endif // guard diff --git a/src/blacksound/dsp/dsp.pri b/src/blacksound/dsp/dsp.pri deleted file mode 100644 index 3a1296026..000000000 --- a/src/blacksound/dsp/dsp.pri +++ /dev/null @@ -1,9 +0,0 @@ -SOURCES += \ - $$PWD/biquadfilter.cpp \ - $$PWD/SimpleComp.cpp \ - $$PWD/SimpleEnvelope.cpp \ - -HEADERS += \ - $$PWD/SimpleComp.h \ - $$PWD/SimpleEnvelope.h \ - $$PWD/biquadfilter.h \ diff --git a/src/blacksound/sampleprovider/pinknoisegenerator.cpp b/src/blacksound/sampleprovider/pinknoisegenerator.cpp index 65521bfdf..4242b337c 100644 --- a/src/blacksound/sampleprovider/pinknoisegenerator.cpp +++ b/src/blacksound/sampleprovider/pinknoisegenerator.cpp @@ -1,3 +1,13 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + #include "pinknoisegenerator.h" int PinkNoiseGenerator::readSamples(QVector &samples, qint64 count) diff --git a/src/blacksound/sampleprovider/pinknoisegenerator.h b/src/blacksound/sampleprovider/pinknoisegenerator.h index 8177a86b3..78e54a261 100644 --- a/src/blacksound/sampleprovider/pinknoisegenerator.h +++ b/src/blacksound/sampleprovider/pinknoisegenerator.h @@ -1,3 +1,13 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + #ifndef PINKNOISEGENERATOR_H #define PINKNOISEGENERATOR_H @@ -9,11 +19,13 @@ #include +//! Pink noise generator class BLACKSOUND_EXPORT PinkNoiseGenerator : public ISampleProvider { Q_OBJECT public: + //! Noise generator PinkNoiseGenerator(QObject *parent = nullptr) : ISampleProvider(parent) {} virtual int readSamples(QVector &samples, qint64 count) override; @@ -26,4 +38,4 @@ private: double m_gain = 0.0; }; -#endif // PINKNOISEGENERATOR_H +#endif // guard diff --git a/src/blacksound/sampleprovider/resourcesoundsampleprovider.h b/src/blacksound/sampleprovider/resourcesoundsampleprovider.h index 12f829d90..b6dcfb47e 100644 --- a/src/blacksound/sampleprovider/resourcesoundsampleprovider.h +++ b/src/blacksound/sampleprovider/resourcesoundsampleprovider.h @@ -5,11 +5,13 @@ #include "sampleprovider.h" #include "resourcesound.h" +//! A sample provider class BLACKSOUND_EXPORT ResourceSoundSampleProvider : public ISampleProvider { Q_OBJECT public: + //! Ctor ResourceSoundSampleProvider(const ResourceSound &resourceSound, QObject *parent = nullptr); virtual int readSamples(QVector &samples, qint64 count) override;