diff --git a/src/blackcore/afv/clients/afvclient.cpp b/src/blackcore/afv/clients/afvclient.cpp index 678aaea03..d1707522f 100644 --- a/src/blackcore/afv/clients/afvclient.cpp +++ b/src/blackcore/afv/clients/afvclient.cpp @@ -8,6 +8,7 @@ #include "afvclient.h" #include "blackcore/context/contextownaircraft.h" +#include "blackcore/context/contextnetwork.h" #include "blackcore/application.h" #include "blacksound/audioutilities.h" #include "blackmisc/audio/audiodeviceinfolist.h" @@ -47,7 +48,7 @@ namespace BlackCore m_connection(new CClientConnection(apiServer, this)), m_input(new CInput(SampleRate, this)), m_output(new COutput(this)), - m_voiceServerPositionTimer(new QTimer(this)) + m_voiceServerTimer(new QTimer(this)) { this->setObjectName("AFV client"); m_connection->setReceiveAudio(false); @@ -57,7 +58,7 @@ namespace BlackCore connect(m_output, &COutput::outputVolumeStream, this, &CAfvClient::outputVolumeStream); connect(m_connection, &CClientConnection::audioReceived, this, &CAfvClient::audioOutDataAvailable); - connect(m_voiceServerPositionTimer, &QTimer::timeout, this, &CAfvClient::onPositionUpdateTimer); + connect(m_voiceServerTimer, &QTimer::timeout, this, &CAfvClient::onTimerUpdate); m_updateTimer.stop(); // not used @@ -80,6 +81,12 @@ namespace BlackCore m_callsign = callsign; } + bool CAfvClient::isConnected() const + { + QMutexLocker lock(&m_mutexConnection); + return m_connection->isConnected(); + } + void CAfvClient::initTransceivers() { { @@ -98,7 +105,7 @@ namespace BlackCore this->connectWithContexts(); // update from context - this->onPositionUpdateTimer(); + this->onTimerUpdate(); } void CAfvClient::connectWithContexts() @@ -125,15 +132,25 @@ namespace BlackCore this->connectWithContexts(); this->setCallsign(callsign); - m_connection->connectTo(cid, password, callsign); - m_aliasedStations = m_connection->getAllAliasedStations(); + QVector aliasedStations; + // thread safe connect + { + // QMutexLocker lock(&m_mutexConnection); + m_connection->connectTo(cid, password, callsign); + aliasedStations = m_connection->getAllAliasedStations(); + } + this->setAliasedStations(aliasedStations); // threadsafe + this->onTimerUpdate(); - this->onPositionUpdateTimer(); - - if (m_connection->isConnected()) + const bool isConnected = this->isConnected(); // threadsafe + if (isConnected) { // restart timer, normally it should be started already, paranoia - if (m_voiceServerPositionTimer) { m_voiceServerPositionTimer->start(PositionUpdatesMs); } + // as I run in "my thread" starting timer should be OK + { + QMutexLocker lock(&m_mutex); + if (m_voiceServerTimer) { m_voiceServerTimer->start(PositionUpdatesMs); } + } emit this->connectionStatusChanged(Connected); } else @@ -153,7 +170,11 @@ namespace BlackCore } // we intentionally DO NOT STOP the timer here, but keep it for preset (own aircraft pos.) - m_connection->disconnectFrom(); + // threadsafe + { + QMutexLocker lock(&m_mutexConnection); + m_connection->disconnectFrom(); + } emit connectionStatusChanged(Disconnected); } @@ -208,30 +229,37 @@ namespace BlackCore this->initTransceivers(); - if (m_soundcardSampleProvider) + // threadsafe block { - m_soundcardSampleProvider->disconnect(); - m_soundcardSampleProvider->deleteLater(); + QMutexLocker lock{&m_mutex}; + if (m_soundcardSampleProvider) + { + m_soundcardSampleProvider->disconnect(); + m_soundcardSampleProvider->deleteLater(); + } + m_soundcardSampleProvider = new CSoundcardSampleProvider(SampleRate, allTransceiverIds(), this); + connect(m_soundcardSampleProvider, &CSoundcardSampleProvider::receivingCallsignsChanged, this, &CAfvClient::receivingCallsignsChanged); + + if (m_outputSampleProvider) { m_outputSampleProvider->deleteLater(); } + m_outputSampleProvider = new CVolumeSampleProvider(m_soundcardSampleProvider, this); + m_outputSampleProvider->setVolume(m_outputVolume); + + m_output->start(outputDevice, m_outputSampleProvider); + m_input->start(inputDevice); + + m_startDateTimeUtc = QDateTime::currentDateTimeUtc(); + + // runs in correct thread + m_voiceServerTimer->start(PositionUpdatesMs); // start for preset values } - m_soundcardSampleProvider = new CSoundcardSampleProvider(SampleRate, allTransceiverIds(), this); - connect(m_soundcardSampleProvider, &CSoundcardSampleProvider::receivingCallsignsChanged, this, &CAfvClient::receivingCallsignsChanged); - if (m_outputSampleProvider) { m_outputSampleProvider->deleteLater(); } - m_outputSampleProvider = new CVolumeSampleProvider(m_soundcardSampleProvider, this); - m_outputSampleProvider->setVolume(m_outputVolume); - m_output->start(outputDevice, m_outputSampleProvider); - - m_input->start(inputDevice); - - m_startDateTimeUtc = QDateTime::currentDateTimeUtc(); - m_connection->setReceiveAudio(true); - m_voiceServerPositionTimer->start(PositionUpdatesMs); // start for preset values + this->setReceiveAudio(true); // threadsafe this->onSettingsChanged(); // make sure all settings are applied m_isStarted = true; CLogMessage(this).info(u"Started [Input: %1] [Output: %2]") << inputDevice.getName() << outputDevice.getName(); - this->onPositionUpdateTimer(); // update values + this->onTimerUpdate(); // update values emit this->startedAudio(inputDevice, outputDevice); } @@ -260,14 +288,25 @@ namespace BlackCore } m_isStarted = false; - m_connection->setReceiveAudio(false); + this->setReceiveAudio(false); // threadsafe // stop input/output - m_input->stop(); - m_output->stop(); + + { + QMutexLocker lock{&m_mutex}; + m_input->stop(); + m_output->stop(); + } CLogMessage(this).info(u"Client stopped"); } + void CAfvClient::setReceiveAudio(bool receive) + { + QMutexLocker lock(&m_mutexConnection); + if (!m_connection) { return; } + m_connection->setReceiveAudio(receive); + } + void CAfvClient::enableTransceiver(quint16 id, bool enable) { { @@ -387,10 +426,15 @@ namespace BlackCore } } - // in connection and soundcard only use the enabled tarnsceivers - QMutexLocker lock(&m_mutex); - if (m_connection) { m_connection->updateTransceivers(callsign, newEnabledTransceivers); } - if (m_soundcardSampleProvider) { m_soundcardSampleProvider->updateRadioTransceivers(newEnabledTransceivers); } + // in connection and soundcard only use the enabled transceivers + { + QMutexLocker lock(&m_mutexConnection); + if (m_connection) { m_connection->updateTransceivers(callsign, newEnabledTransceivers); } + } + { + QMutexLocker lock(&m_mutex); + if (m_soundcardSampleProvider) { m_soundcardSampleProvider->updateRadioTransceivers(newEnabledTransceivers); } + } } void CAfvClient::setTransmittingTransceiver(quint16 transceiverID) @@ -461,6 +505,7 @@ namespace BlackCore if (m_transmit == active) { return; } m_transmit = active; + // thread safe block { QMutexLocker lock(&m_mutex); if (m_soundcardSampleProvider) @@ -468,6 +513,8 @@ namespace BlackCore m_soundcardSampleProvider->pttUpdate(active, m_transmittingTransceivers); } + /** TODO: RR 2019-10 as discussed https://discordapp.com/channels/539048679160676382/623947987822837779/633320595978846208 + * disabled for the moment as not needed if (!active) { // AGC @@ -475,6 +522,7 @@ namespace BlackCore // if (maxDbReadingInPTTInterval < -4) InputVolumeDb = InputVolumeDb + 1; m_maxDbReadingInPTTInterval = -100; } + **/ } emit this->ptt(active, com, this->identifier()); @@ -579,14 +627,14 @@ namespace BlackCore { const bool transmit = m_transmit; const bool loopback = m_loopbackOn; - const bool transmitHistory = m_transmitHistory; + const bool transmitHistory = m_transmitHistory; // threadsafe const auto transceivers = this->getTransceivers(); if (loopback && transmit) { IAudioDto audioData; audioData.audio = QByteArray(args.audio.data(), args.audio.size()); - audioData.callsign = "loopback"; + audioData.callsign = QStringLiteral("loopback"); audioData.lastPacket = false; audioData.sequenceCounter = 0; @@ -598,10 +646,7 @@ namespace BlackCore return; } - { - QMutexLocker lock(&m_mutex); - if (!m_connection->isConnected()) { return; } - } + if (!this->isConnected()) { return; } // threadsafe const QString callsign = this->getCallsign(); // threadsafe const auto transmittingTransceivers = this->getTransmittingTransceivers(); // threadsafe @@ -615,7 +660,7 @@ namespace BlackCore dto.audio = std::vector(args.audio.begin(), args.audio.end()); dto.lastPacket = false; dto.transceivers = transmittingTransceivers.toStdVector(); - QMutexLocker lock(&m_mutex); + QMutexLocker lock(&m_mutexConnection); m_connection->sendToVoiceServer(dto); } @@ -627,7 +672,7 @@ namespace BlackCore dto.audio = std::vector(args.audio.begin(), args.audio.end()); dto.lastPacket = true; dto.transceivers = transmittingTransceivers.toStdVector(); - QMutexLocker lock(&m_mutex); + QMutexLocker lock(&m_mutexConnection); m_connection->sendToVoiceServer(dto); } m_transmitHistory = transmit; // threadsafe @@ -641,11 +686,14 @@ namespace BlackCore audioData.callsign = QString::fromStdString(dto.callsign); audioData.lastPacket = dto.lastPacket; audioData.sequenceCounter = dto.sequenceCounter; + + QMutexLocker lock(&m_mutex); m_soundcardSampleProvider->addOpusSamples(audioData, QVector::fromStdVector(dto.transceivers)); } void CAfvClient::inputVolumeStream(const InputVolumeStreamArgs &args) { + // thread safe block { QMutexLocker lock(&m_mutexInputStream); m_inputVolumeStream = args; @@ -655,6 +703,7 @@ namespace BlackCore void CAfvClient::outputVolumeStream(const OutputVolumeStreamArgs &args) { + // thread safe block { QMutexLocker lock(&m_mutexOutputStream); m_outputVolumeStream = args; @@ -678,7 +727,7 @@ namespace BlackCore bool CAfvClient::updateVoiceServerUrl(const QString &url) { - QMutexLocker lock(&m_mutex); + QMutexLocker lock(&m_mutexConnection); if (!m_connection) { return false; } return m_connection->updateVoiceServerUrl(url); } @@ -724,12 +773,15 @@ namespace BlackCore #endif } - void CAfvClient::onPositionUpdateTimer() + void CAfvClient::onTimerUpdate() { if (hasContext()) { // for pilot client this->updateFromOwnAircraft(sApp->getIContextOwnAircraft()->getOwnAircraft(), false); + + // disconnect if NOT connected + this->autoLogoffWithoutFsdNetwork(); } else { @@ -746,6 +798,19 @@ namespace BlackCore this->setBypassEffects(!audioSettings.isAudioEffectsEnabled()); } + void CAfvClient::autoLogoffWithoutFsdNetwork() + { + if (!hasContext()) { return; } + if (!this->isConnected()) { m_connectMismatches = 0; return; } + + // AFV is connected + if (sApp->getIContextNetwork()->isConnected()) { m_connectMismatches = 0; return; } + if (++m_connectMismatches < 2) { return; } // avoid a single issue causing logoff + + CLogMessage(this).warning(u"Auto logoff AFV client because FSD no longer connected"); + this->disconnectFrom(); + } + void CAfvClient::updateFromOwnAircraft(const CSimulatedAircraft &aircraft, bool withSignals) { if (!sApp || sApp->isShuttingDown()) { return; } @@ -804,12 +869,19 @@ namespace BlackCore m_enabledTransceivers = newEnabledTransceiverIds; m_transmittingTransceivers = newTransmittingTransceivers; } + // in connection and soundcard only use the enabled tarnsceivers const QString callsign = this->getCallsign(); { - QMutexLocker lock(&m_mutex); - if (m_connection) { m_connection->updateTransceivers(callsign, newEnabledTransceivers); } - if (m_soundcardSampleProvider) { m_soundcardSampleProvider->updateRadioTransceivers(newEnabledTransceivers); } + { + QMutexLocker lock(&m_mutexConnection); + if (m_connection) { m_connection->updateTransceivers(callsign, newEnabledTransceivers); } + } + + { + QMutexLocker lock(&m_mutex); + if (m_soundcardSampleProvider) { m_soundcardSampleProvider->updateRadioTransceivers(newEnabledTransceivers); } + } } if (withSignals) { emit this->updatedFromOwnAircraftCockpit(); } @@ -821,6 +893,18 @@ namespace BlackCore this->updateFromOwnAircraft(aircraft); } + QVector CAfvClient::getAliasedStations() const + { + QMutexLocker lock(&m_mutex); + return m_aliasedStations; + } + + void CAfvClient::setAliasedStations(const QVector &stations) + { + QMutexLocker lock(&m_mutex); + m_aliasedStations = stations; + } + quint32 CAfvClient::getAliasFrequencyHz(quint32 frequencyHz) const { // void rounding issues from float/double @@ -875,7 +959,7 @@ namespace BlackCore bool CAfvClient::hasContext() { - return sApp && !sApp->isShuttingDown() && sApp->getIContextOwnAircraft(); + return sApp && !sApp->isShuttingDown() && sApp->getIContextOwnAircraft() && sApp->getIContextNetwork(); } bool CAfvClient::setOutputVolumeDb(double valueDb) @@ -928,7 +1012,7 @@ namespace BlackCore CAfvClient::ConnectionStatus CAfvClient::getConnectionStatus() const { - return m_connection->isConnected() ? Connected : Disconnected; + return this->isConnected() ? Connected : Disconnected; } } // ns } // ns diff --git a/src/blackcore/afv/clients/afvclient.h b/src/blackcore/afv/clients/afvclient.h index a679495e6..80eb2735c 100644 --- a/src/blackcore/afv/clients/afvclient.h +++ b/src/blackcore/afv/clients/afvclient.h @@ -76,15 +76,21 @@ namespace BlackCore //! @} //! Is connected to network? - bool isConnected() const { return m_connection->isConnected(); } + //! \threadsafe + bool isConnected() const; //! Connection status + //! \threadsafe ConnectionStatus getConnectionStatus() const; //! Connect to network + //! \threadsafe + //! \remark runs in thread of CAfvClient object and is ASYNC when called from another thread Q_INVOKABLE void connectTo(const QString &cid, const QString &password, const QString &getCallsign); //! Disconnect from network + //! \threadsafe + //! \remark runs in thread of CAfvClient object and is ASYNC when called from another thread Q_INVOKABLE void disconnectFrom(); //! Audio devices @{ @@ -101,7 +107,9 @@ namespace BlackCore //! When started const QDateTime &getStartDateTimeUtc() const { return m_startDateTimeUtc; } - //! Muted @{ + //! Muted + //! \threadsafe + //! @{ bool isMuted() const; void setMuted(bool mute); //! @} @@ -112,6 +120,10 @@ namespace BlackCore void stopAudio(); //! @} + //! Receive audio + //! \threadsafe + void setReceiveAudio(bool receive); + //! Enable COM unit/transceiver //! \threadsafe //! @{ @@ -161,7 +173,9 @@ namespace BlackCore //! \threadsafe void updateFromOwnAircraft(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, bool withSignals = true); - //! Push to talk @{ + //! Push to talk + //! \threadsafe + //! @{ Q_INVOKABLE void setPtt(bool active); void setPttForCom(bool active, BlackMisc::Audio::PTTCOM com); //! @} @@ -254,18 +268,26 @@ namespace BlackCore virtual void cleanup() override; private: - void opusDataAvailable(const Audio::OpusDataAvailableArgs &args); - void audioOutDataAvailable(const AudioRxOnTransceiversDto &dto); + void opusDataAvailable(const Audio::OpusDataAvailableArgs &args); // threadsafe + void audioOutDataAvailable(const AudioRxOnTransceiversDto &dto); // threadsafe void inputVolumeStream(const Audio::InputVolumeStreamArgs &args); void outputVolumeStream(const Audio::OutputVolumeStreamArgs &args); void inputOpusDataAvailable(); - void onPositionUpdateTimer(); + void onTimerUpdate(); void onSettingsChanged(); + void autoLogoffWithoutFsdNetwork(); void updateTransceivers(bool updateFrequencies = true); void onUpdateTransceiversFromContext(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const BlackMisc::CIdentifier &originator); + //! All aliased stations + //! \threadsafe + //! @{ + QVector getAliasedStations() const; + void setAliasedStations(const QVector &stations); + //! @} + //! Frequency from aliased stations //! \threadsafe quint32 getAliasFrequencyHz(quint32 frequencyHz) const; @@ -286,7 +308,7 @@ namespace BlackCore BlackMisc::CSetting m_audioSettings { this, &CAfvClient::onSettingsChanged }; QString m_callsign; - Audio::CInput *m_input = nullptr; + Audio::CInput *m_input = nullptr; Audio::COutput *m_output = nullptr; Audio::CSoundcardSampleProvider *m_soundcardSampleProvider = nullptr; @@ -299,6 +321,7 @@ namespace BlackCore QSet m_enabledTransceivers; static const QVector &allTransceiverIds() { static const QVector transceiverIds{0, 1}; return transceiverIds; } + std::atomic_int m_connectMismatches { 0 }; std::atomic_bool m_isStarted { false }; std::atomic_bool m_loopbackOn { false }; std::atomic_bool m_winCoInitialized { false }; //!< Windows only CoInitializeEx @@ -309,7 +332,7 @@ namespace BlackCore double m_outputVolume = 1.0; double m_maxDbReadingInPTTInterval = -100; - QTimer *m_voiceServerPositionTimer = nullptr; + QTimer *m_voiceServerTimer = nullptr; QVector m_aliasedStations; Audio::InputVolumeStreamArgs m_inputVolumeStream; @@ -327,6 +350,7 @@ namespace BlackCore mutable QMutex m_mutexOutputStream; mutable QMutex m_mutexTransceivers; mutable QMutex m_mutexCallsign; + mutable QMutex m_mutexConnection; }; } // ns } // ns diff --git a/src/blackcore/afv/connection/clientconnection.h b/src/blackcore/afv/connection/clientconnection.h index 9335a734b..b1ec8fb7f 100644 --- a/src/blackcore/afv/connection/clientconnection.h +++ b/src/blackcore/afv/connection/clientconnection.h @@ -78,6 +78,9 @@ namespace BlackCore //! Update the voice server URL bool updateVoiceServerUrl(const QString &url); + //! Authenticated since when + qint64 secondsSinceAuthentication() const { return m_connection.secondsSinceAuthentication(); } + signals: //! Audio has been received void audioReceived(const AudioRxOnTransceiversDto &dto);