diff --git a/src/blackcore/blackcorefreefunctions.cpp b/src/blackcore/blackcorefreefunctions.cpp index 88e280a23..4ed7c0969 100644 --- a/src/blackcore/blackcorefreefunctions.cpp +++ b/src/blackcore/blackcorefreefunctions.cpp @@ -4,7 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "blackcorefreefunctions.h" -#include "voice.h" +#include "voice_channel.h" #include "simulator.h" #include @@ -15,10 +15,8 @@ namespace BlackCore // for some reasons (ask RW) these are registered twice qRegisterMetaType(); qRegisterMetaType("Status"); - qRegisterMetaType(); - qRegisterMetaType("ComUnit"); - qRegisterMetaType(); - qRegisterMetaType("ConnectionStatus"); + qRegisterMetaType(); + qRegisterMetaType("ConnectionStatus"); } bool isCurrentThreadCreatingThread(QObject *toBeTested) diff --git a/src/blackcore/voice.h b/src/blackcore/voice.h index b4c1a8e5e..03c3f54ca 100644 --- a/src/blackcore/voice.h +++ b/src/blackcore/voice.h @@ -21,6 +21,8 @@ namespace BlackCore { + class IVoiceChannel; + /*! * Interface to a connection to a ATC voice server for use in flight simulation. * @@ -49,35 +51,9 @@ namespace BlackCore public: - /*! - * \brief IVoice currently supports two different com units - */ - enum ComUnit - { - COM1 = 0, /*!< ComUnit 1 */ - COM2 /*!< ComUnit 2 */ - }; - - //! Com status - enum ConnectionStatus - { - Disconnected = 0, //!< Not connected - Disconnecting, //!< In transition to disconnected - DisconnectedError, //!< Disconnected due to socket error - Connecting, //!< Connection initiated but not established - Connected, //!< Connection established - ConnectingFailed, //!< Failed to connect - }; - //! Virtual destructor. virtual ~IVoice() {} - /*! - * \brief Own aircraft's callsign - * \param callsign - */ - virtual void setMyAircraftCallsign(const BlackMisc::Aviation::CCallsign &callsign) = 0; - /*! * \brief Audio devices * \return @@ -128,6 +104,9 @@ namespace BlackCore */ virtual QString micTestResultAsString() const = 0; + //! Get voice channel object + virtual IVoiceChannel *getVoiceChannel(qint32 channelIndex) const = 0; + public slots: /*! @@ -150,68 +129,6 @@ namespace BlackCore */ virtual void setInputDevice(const BlackMisc::Audio::CAudioDevice &device) = 0; - /*! - * Get COM1/2 voice rooms, which then allows to retrieve information - * such as audio status etc. - */ - virtual BlackMisc::Audio::CVoiceRoomList getComVoiceRoomsWithAudioStatus() const = 0; - - /*! - * Get COM1/2 voice rooms, const and with no status update - */ - virtual BlackMisc::Audio::CVoiceRoomList getComVoiceRooms() const = 0; - - /*! - * \brief Join voice room - * \param comUnit COM1/2 - * \param voiceRoom - */ - virtual void joinVoiceRoom(const ComUnit comUnit, const BlackMisc::Audio::CVoiceRoom &voiceRoom) = 0; - - /*! - * \brief Leave voice room - * \param comUnit COM1/2 - */ - virtual void leaveVoiceRoom(const ComUnit comUnit) = 0; - - /*! - * \brief Leave all voice rooms - */ - virtual void leaveAllVoiceRooms() = 0; - - /*! - * \brief Set room output volume for COM unit - */ - virtual void setRoomOutputVolume(const ComUnit comUnit, const qint32 volumne) = 0; - - /*! - * \brief Start transmitting - */ - virtual void startTransmitting(const ComUnit comUnit) = 0; - - /*! - * \brief Stop transmitting - */ - virtual void stopTransmitting(const ComUnit comUnit) = 0; - - /*! - * \brief Get voice room callsings - * \return - */ - virtual BlackMisc::Aviation::CCallsignList getVoiceRoomCallsigns(const ComUnit comUnit) const = 0; - - /*! - * \brief Is muted? - */ - virtual bool isMuted() const = 0; - - /*! - * \brief Switch audio output, enable or disable given COM unit. - * \param comUnit - * \param enable enable or disable output - */ - virtual void switchAudioOutput(const ComUnit comUnit, bool enable) = 0; - /*! * \brief Enable audio loopback to route recorded voice from microphone to speakers * \param enable (default true) @@ -220,40 +137,6 @@ namespace BlackCore signals: - //! The status of a room has changed. - void connectionStatusChanged(ComUnit comUnit, ConnectionStatus oldStatus, ConnectionStatus newStatus); - - // Signals about users joining and leaving - /*! - * \brief User with callsign joined room - */ - void userJoinedRoom(const BlackMisc::Aviation::CCallsign &callsign); - /*! - * \brief User with callsign left room - */ - void userLeftRoom(const BlackMisc::Aviation::CCallsign &callsign); - - // Audio signals - /*! - * \brief Audio for given unit started - */ - void audioStarted(const ComUnit comUnit); - - /*! - * \brief Audio for given unit stopped - */ - void audioStopped(const ComUnit comUnit); - - /*! - * \brief Audio started - */ - void globalAudioStarted(); - - /*! - * \brief Audio stopped - */ - void globalAudioStopped(); - // Test signals /*! * \brief Squelch test completed @@ -275,7 +158,4 @@ namespace BlackCore } // namespace BlackCore -Q_DECLARE_METATYPE(BlackCore::IVoice::ComUnit) -Q_DECLARE_METATYPE(BlackCore::IVoice::ConnectionStatus) - #endif // guard diff --git a/src/blackcore/voice_channel.h b/src/blackcore/voice_channel.h new file mode 100644 index 000000000..8118f9647 --- /dev/null +++ b/src/blackcore/voice_channel.h @@ -0,0 +1,121 @@ +/* Copyright (C) 2014 + * 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 and at http://www.swift-project.org/license.html. 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 BLACKCORE_VOICE_CHANNEL_H +#define BLACKCORE_VOICE_CHANNEL_H + +#include "voice_vatlib.h" +#include "blackmisc/statusmessage.h" +#include "blackmisc/voiceroomlist.h" +#include "blackmisc/avcallsignlist.h" +#include +#include + +//! \file + +namespace BlackCore +{ + //! Interface to a voice channel + class IVoiceChannel : public QObject + { + Q_OBJECT + + public: + + //! Com status + enum ConnectionStatus + { + Disconnected = 0, //!< Not connected + Disconnecting, //!< In transition to disconnected + DisconnectedError, //!< Disconnected due to socket error + Connecting, //!< Connection initiated but not established + Connected, //!< Connection established + ConnectingFailed, //!< Failed to connect + }; + + //! Join voice room + virtual void joinVoiceRoom(const BlackMisc::Audio::CVoiceRoom &voiceRoom) = 0; + + //! Leave voice room + virtual void leaveVoiceRoom() = 0; + + //! Set room output volume + virtual void setRoomOutputVolume(const qint32 volume) = 0; + + //! Start transmitting + virtual void startTransmitting() = 0; + + //! Stop transmitting + virtual void stopTransmitting() = 0; + + //! Get voice room callsings + virtual BlackMisc::Aviation::CCallsignList getVoiceRoomCallsigns() const = 0; + + //! Switch audio output, enable or disable + virtual void switchAudioOutput(bool enable) = 0; + + //! Set own aircraft's callsign + virtual void setMyAircraftCallsign(const BlackMisc::Aviation::CCallsign &callsign) = 0; + + //! Get voice room + virtual BlackMisc::Audio::CVoiceRoom getVoiceRoom() const = 0; + + //! Get assigned room index + virtual qint32 getRoomIndex() const = 0; + + //! Is channel muted? + virtual bool isMuted() const = 0; + + //! Set channel volume + virtual void setVolume(quint32 volume) = 0; + + //! Get channel volume + virtual quint32 getVolume() const = 0; + + //! Update room status + virtual void updateRoomStatus(Cvatlib_Voice_Simple::roomStatusUpdate roomStatus) = 0; + + signals: + + //! We sent a message about the status of the network connection, for the attention of the user. + void statusMessage(const BlackMisc::CStatusMessage &message); + + //! The status of a room has changed. + void connectionStatusChanged(ConnectionStatus oldStatus, ConnectionStatus newStatus); + + // Signals about users joining and leaving + + //! User with callsign joined room + void userJoinedRoom(const BlackMisc::Aviation::CCallsign &callsign); + + //! User with callsign left room + void userLeftRoom(const BlackMisc::Aviation::CCallsign &callsign); + + // Audio signals + + //! Audio for given unit started + void audioStarted(); + + //! Audio for given unit stopped + void audioStopped(); + + protected: + + //! Constructor + IVoiceChannel(QObject *parent = nullptr) : QObject(parent) {} + + //! Destructor + virtual ~IVoiceChannel() {} + + }; +} + +Q_DECLARE_METATYPE(BlackCore::IVoiceChannel::ConnectionStatus) + +#endif // guard diff --git a/src/blackcore/voice_channel_vatlib.cpp b/src/blackcore/voice_channel_vatlib.cpp new file mode 100644 index 000000000..d36d74e90 --- /dev/null +++ b/src/blackcore/voice_channel_vatlib.cpp @@ -0,0 +1,490 @@ +/* Copyright (C) 2014 + * 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 and at http://www.swift-project.org/license.html. 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 "voice_channel_vatlib.h" +#include "voice_channel_vatlib_p.h" + +#include + +using namespace BlackMisc; +using namespace BlackMisc::Audio; +using namespace BlackMisc::Aviation; + +namespace BlackCore +{ + // Room data hash shared between all CVoiceChannel objects + QHash> CVoiceChannelVatlibPrivate::m_sharedRoomData; + + // Static list of available rooms + QList CVoiceChannelVatlibPrivate::m_availableRooms = {0, 1}; + + // Constructor + // Don't set the QObject parent. It will conflict with @QSharedPointer@ memory management + CVoiceChannelVatlibPrivate::CVoiceChannelVatlibPrivate(TVatlibPointer vatlib, CVoiceChannelVatlib *parent) + : m_vatlib(vatlib), + m_mutexSharedRoomData(QMutex::Recursive), + m_mutexCallSign(QMutex::Recursive), + m_mutexVoiceRoom(QMutex::Recursive), + m_mutexCallsignList(QMutex::Recursive), + q_ptr(parent) + { + m_roomIndex.store(InvalidRoomIndex); + m_volume.store(100); + m_connectionRefCount.store(0); + m_outputEnabled.store(true); + m_roomStatus.store(IVoiceChannel::Disconnected); + + connect(this, &CVoiceChannelVatlibPrivate::userJoinedLeft, this, &CVoiceChannelVatlibPrivate::processUserJoinedLeft, Qt::QueuedConnection); + } + + CVoiceChannelVatlibPrivate::~CVoiceChannelVatlibPrivate() + { + } + + void CVoiceChannelVatlibPrivate::changeConnectionStatus(IVoiceChannel::ConnectionStatus newStatus) + { + Q_Q(CVoiceChannelVatlib); + + m_roomStatus = newStatus; + emit q->connectionStatusChanged(m_roomStatus, newStatus); + } + + void CVoiceChannelVatlibPrivate::switchAudioOutput(bool enable) + { + std::lock_guard locker(m_vatlib); + Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceChannelVatlibPrivate", "Cvatlib_Voice_Simple invalid or not setup!"); + Q_ASSERT_X(m_vatlib->IsRoomValid(m_roomIndex), "CVoiceChannelVatlibPrivate", "Room index out of bounds!"); + + m_outputEnabled = enable; + + try + { + m_vatlib->SetOutputState(m_roomIndex, 0, enable); + } + catch (...) + { + exceptionDispatcher(Q_FUNC_INFO); + } + } + + void CVoiceChannelVatlibPrivate::startTransmitting() + { + std::lock_guard locker(m_vatlib); + Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceChannelVatlibPrivate", "Cvatlib_Voice_Simple invalid or not setup!"); + Q_ASSERT_X(m_vatlib->IsRoomValid(m_roomIndex), "CVoiceChannelVatlibPrivate", "Room index out of bounds!"); + + if (m_roomStatus != IVoiceChannel::Connected) return; + + try + { + m_vatlib->SetMicState(m_roomIndex, true); + } + catch (...) + { + this->exceptionDispatcher(Q_FUNC_INFO); + } + } + + void CVoiceChannelVatlibPrivate::stopTransmitting() + { + std::lock_guard locker(m_vatlib); + Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceChannelVatlibPrivate", "Cvatlib_Voice_Simple invalid or not setup!"); + Q_ASSERT_X(m_vatlib->IsRoomValid(m_roomIndex), "CVoiceChannelVatlibPrivate", "Room index out of bounds!"); + + if (m_roomStatus != IVoiceChannel::Connected) return; + + try + { + m_vatlib->SetMicState(m_roomIndex, false); + } + catch (...) + { + this->exceptionDispatcher(Q_FUNC_INFO); + } + } + + void CVoiceChannelVatlibPrivate::setRoomOutputVolume(const qint32 volume) + { + std::lock_guard locker(m_vatlib); + Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceChannelVatlibPrivate", "Cvatlib_Voice_Simple invalid or not setup!"); + Q_ASSERT_X(m_vatlib->IsRoomValid(m_roomIndex), "CVoiceChannelVatlibPrivate", "Room index out of bounds!"); + + try + { + m_vatlib->SetRoomVolume(m_roomIndex, volume); + } + catch (...) + { + this->exceptionDispatcher(Q_FUNC_INFO); + } + } + + // Forward exception as signal + void CVoiceChannelVatlibPrivate::exceptionDispatcher(const char *caller) + { + Q_Q(CVoiceChannelVatlib); + + QString msg("Caller: "); + msg.append(caller).append(" ").append("Exception: "); + try + { + throw; + } + catch (const NetworkNotConnectedException &e) + { + // this could be caused by a race condition during normal operation, so not an error + msg.append("NetworkNotConnectedException").append(" ").append(e.what()); + emit q->statusMessage(CStatusMessage::getErrorMessage(msg, CStatusMessage::TypeAudio)); + qDebug() << "NetworkNotConnectedException caught in " << caller << "\n" << e.what(); + } + catch (const VatlibException &e) + { + msg.append("VatlibException").append(" ").append(e.what()); + emit q->statusMessage(CStatusMessage::getErrorMessage(msg, CStatusMessage::TypeAudio)); + qFatal("VatlibException caught in %s\n%s", caller, e.what()); + } + catch (const std::exception &e) + { + msg.append("std::exception").append(" ").append(e.what()); + emit q->statusMessage(CStatusMessage::getErrorMessage(msg, CStatusMessage::TypeAudio)); + qFatal("std::exception caught in %s\n%s", caller, e.what()); + } + catch (...) + { + msg.append("unknown exception"); + emit q->statusMessage(CStatusMessage::getErrorMessage(msg, CStatusMessage::TypeAudio)); + qFatal("Unknown exception caught in %s", caller); + } + } + + void CVoiceChannelVatlibPrivate::processUserJoinedLeft() + { + Q_Q(CVoiceChannelVatlib); + + try + { + std::lock_guard locker(m_vatlib); + // Paranoia... clear list completely + if (!m_vatlib->IsRoomConnected(m_roomIndex)) + { + QMutexLocker lockCallsignList(&m_mutexCallsignList); + m_listCallsigns.clear(); + return; + } + + // Callbacks already completed when function GetRoomUserList returns, + // thereafter m_voiceRoomCallsignsUpdate is filled with the latest callsigns + + m_vatlib->GetRoomUserList(m_roomIndex, updateRoomUsers, this); + + QMutexLocker lockCallsignList(&m_mutexCallsignList); + // we have all current users in m_temporaryVoiceRoomCallsigns + foreach(CCallsign callsign, m_listCallsigns) + { + if (!m_temporaryVoiceRoomCallsigns.contains(callsign)) + { + // User has left + emit q->userLeftRoom(callsign); + } + } + + foreach(CCallsign callsign, m_temporaryVoiceRoomCallsigns) + { + if (!m_listCallsigns.contains(callsign)) + { + // he joined + emit q->userJoinedRoom(callsign); + } + } + + // Finally we update it with our new list + m_listCallsigns = m_temporaryVoiceRoomCallsigns; + m_temporaryVoiceRoomCallsigns.clear(); + } + catch (...) + { + this->exceptionDispatcher(Q_FUNC_INFO); + } + } + + CVoiceChannelVatlibPrivate *cbvar_cast_voiceChannelPrivate(void *cbvar) + { + return static_cast(cbvar); + } + + /* + * Room user received + */ + void CVoiceChannelVatlibPrivate::updateRoomUsers(Cvatlib_Voice_Simple *obj, const char *name, void *cbVar) + { + Q_UNUSED(obj) + + // sanity check + QString callsign = QString(name); + if (callsign.isEmpty()) return; + + // add callsign + CVoiceChannelVatlibPrivate *voiceChannelPrivate = cbvar_cast_voiceChannelPrivate(cbVar); + + // add user + // callsign might contain: VATSIM id, user name + if (callsign.contains(" ")) + { + QStringList parts = callsign.split(" "); + callsign = parts[0]; + // I throw away VATSIM id here, maybe we could use it + } + + voiceChannelPrivate->addTemporaryCallsignForRoom(CCallsign(callsign)); + } + + /* + * Add temp.callsign for room + */ + void CVoiceChannelVatlibPrivate::addTemporaryCallsignForRoom(const CCallsign &callsign) + { + if (m_temporaryVoiceRoomCallsigns.contains(callsign)) return; + m_temporaryVoiceRoomCallsigns.push_back(callsign); + } + + // Get shared room data + QHash> &CVoiceChannelVatlibPrivate::getSharedRoomData() + { + return m_sharedRoomData; + } + + // Allocate a new room + qint32 CVoiceChannelVatlibPrivate::allocateRoom() + { + Q_ASSERT(!m_availableRooms.isEmpty()); + return m_availableRooms.takeFirst(); + } + + // Constructor + CVoiceChannelVatlib::CVoiceChannelVatlib(TVatlibPointer vatlib, QObject *parent) + : IVoiceChannel(parent), + d_ptr(new CVoiceChannelVatlibPrivate(vatlib, this)) + { + } + + // Destructor + CVoiceChannelVatlib::~CVoiceChannelVatlib() + { + } + + // Join room + void CVoiceChannelVatlib::joinVoiceRoom(const CVoiceRoom &voiceRoom) + { + // Find if a different channel is connected already to this voice room + auto roomDataList = CVoiceChannelVatlibPrivate::getSharedRoomData().values(); + auto iterator = std::find_if(roomDataList.begin(), roomDataList.end(), [&](const QSharedPointer roomData) + { + return roomData->m_voiceRoom.getVoiceRoomUrl() == voiceRoom.getVoiceRoomUrl(); + }); + + try + { + // If we found an another channel + if (iterator != roomDataList.end()) + { + // Increase the connection reference counter + (*iterator)->m_connectionRefCount++; + + d_ptr = (*iterator); + + // Assign shared room data to this channel index + CVoiceChannelVatlibPrivate::getSharedRoomData().insert(this, *iterator); + + // Since the room is used already, we have to simulate the state changes + emit connectionStatusChanged(IVoiceChannel::Disconnected, IVoiceChannel::Connecting); + emit connectionStatusChanged(IVoiceChannel::Connecting, IVoiceChannel::Connected); + emit d_ptr->userJoinedLeft(); + } + else + { + QMutexLocker lockVoiceRoom(&d_ptr->m_mutexVoiceRoom); + // No one else is using this voice room, so prepare to join + d_ptr->m_voiceRoom = voiceRoom; + d_ptr->m_roomIndex = d_ptr->allocateRoom(); + d_ptr->m_roomStatus = IVoiceChannel::Disconnected; + + std::lock_guard locker(d_ptr->m_vatlib); + QMutexLocker lockerCallsign(&d_ptr->m_mutexCallSign); + bool jr = d_ptr->m_vatlib->JoinRoom(d_ptr->m_roomIndex, d_ptr->m_callsign.toQString().toLatin1().constData(), + d_ptr->m_voiceRoom.getVoiceRoomUrl().toLatin1().constData()); + if (!jr) qWarning() << "Could not join voice room"; + CVoiceChannelVatlibPrivate::m_sharedRoomData.insert(this, d_ptr); + ++d_ptr->m_connectionRefCount; + } + + } + catch (...) + { + d_ptr->exceptionDispatcher(Q_FUNC_INFO); + } + } + + // Leave room + void CVoiceChannelVatlib::leaveVoiceRoom() + { + // If this room is not connected, there is nothing to do + if (d_ptr->m_roomStatus == IVoiceChannel::Disconnecting || d_ptr->m_roomStatus == IVoiceChannel::Disconnected) return; + + // Decrease the connection reference counter + --d_ptr->m_connectionRefCount; + + // If this was the last channel, connected to the room, leave it. + if (d_ptr->m_connectionRefCount == 0) + { + try + { + qDebug() << "Leaving voice room!"; + if(d_ptr->m_vatlib->IsRoomConnected(d_ptr->m_roomIndex)) + { + std::lock_guard locker(d_ptr->m_vatlib); + d_ptr->m_vatlib->LeaveRoom(d_ptr->m_roomIndex); + d_ptr->m_availableRooms.append(d_ptr->m_roomIndex); + } + } + catch (...) + { + d_ptr->exceptionDispatcher(Q_FUNC_INFO); + } + } + else + { + // We need to assign a private class + // This automatically clears callsign list etc. + TVatlibPointer vatlib = d_ptr->m_vatlib; + d_ptr.reset(new CVoiceChannelVatlibPrivate(vatlib, this)); + CVoiceChannelVatlibPrivate::getSharedRoomData().insert(this, d_ptr); + + // Simulate the state change + d_ptr->changeConnectionStatus(IVoiceChannel::Disconnecting); + d_ptr->changeConnectionStatus(IVoiceChannel::Disconnected); + } + } + + // Set room volume + void CVoiceChannelVatlib::setRoomOutputVolume(const qint32 volume) + { + d_ptr->setRoomOutputVolume(volume); + } + + void CVoiceChannelVatlib::startTransmitting() + { + d_ptr->startTransmitting(); + } + + void CVoiceChannelVatlib::stopTransmitting() + { + d_ptr->stopTransmitting(); + } + + CCallsignList CVoiceChannelVatlib::getVoiceRoomCallsigns() const + { + QMutexLocker lockCallsignList(&d_ptr->m_mutexCallsignList); + return d_ptr->m_listCallsigns; + } + + void CVoiceChannelVatlib::switchAudioOutput(bool enable) + { + d_ptr->switchAudioOutput(enable); + } + + void CVoiceChannelVatlib::setMyAircraftCallsign(const CCallsign &callsign) + { + QMutexLocker lockerCallsign(&d_ptr->m_mutexCallSign); + d_ptr->m_callsign = callsign; + } + + BlackMisc::Audio::CVoiceRoom CVoiceChannelVatlib::getVoiceRoom() const + { + QMutexLocker lockVoiceRoom(&d_ptr->m_mutexVoiceRoom); + return d_ptr->m_voiceRoom; + } + + qint32 CVoiceChannelVatlib::getRoomIndex() const + { + return d_ptr->m_roomIndex; + } + + void CVoiceChannelVatlib::updateRoomStatus(Cvatlib_Voice_Simple::roomStatusUpdate roomStatus) + { + switch (roomStatus) + { + case Cvatlib_Voice_Simple::roomStatusUpdate_JoinSuccess: + { + QMutexLocker lockVoiceRoom(&d_ptr->m_mutexVoiceRoom); + d_ptr->m_voiceRoom.setConnected(true); + d_ptr->changeConnectionStatus(IVoiceChannel::Connected); + bool isOutputEnabled = d_ptr->m_outputEnabled; + switchAudioOutput(isOutputEnabled); + emit d_ptr->userJoinedLeft(); + break; + } + case Cvatlib_Voice_Simple::roomStatusUpdate_JoinFail: + { + QMutexLocker lockVoiceRoom(&d_ptr->m_mutexVoiceRoom); + d_ptr->m_voiceRoom.setConnected(false); + d_ptr->changeConnectionStatus(IVoiceChannel::ConnectingFailed); + break; + } + case Cvatlib_Voice_Simple::roomStatusUpdate_UnexpectedDisconnectOrKicked: + d_ptr->m_voiceRoom.setConnected(false); + d_ptr->changeConnectionStatus(IVoiceChannel::DisconnectedError); + break; + case Cvatlib_Voice_Simple::roomStatusUpdate_LeaveComplete: + { + // Instead of clearing and resetting all internals, we just assign a new default room data + // The former one will be deallocated automatically. + TVatlibPointer vatlib = d_ptr->m_vatlib; + d_ptr.reset(new CVoiceChannelVatlibPrivate(vatlib, this)); + CVoiceChannelVatlibPrivate::getSharedRoomData().insert(this, d_ptr); + d_ptr->changeConnectionStatus(IVoiceChannel::Disconnected); + break; + } + case Cvatlib_Voice_Simple::roomStatusUpdate_UserJoinsLeaves: + // FIXME: We cannot call GetRoomUserList because vatlib is not reentrent safe. + emit d_ptr->userJoinedLeft(); + break; + case Cvatlib_Voice_Simple::roomStatusUpdate_AudioStarted: + break; + case Cvatlib_Voice_Simple::roomStatusUpdate_AudioStopped: + break; + case Cvatlib_Voice_Simple::roomStatusUpdate_RoomAudioStarted: + emit audioStarted(); + break; + case Cvatlib_Voice_Simple::roomStatusUpdate_RoomAudioStopped: + emit audioStopped(); + break; + default: + break; + } + } + + bool CVoiceChannelVatlib::isMuted() const + { + return !d_ptr->m_outputEnabled; + } + + void CVoiceChannelVatlib::setVolume(quint32 volume) + { + d_ptr->m_volume.store(volume); + Q_ASSERT_X(d_ptr->m_vatlib->IsValid() && d_ptr->m_vatlib->IsSetup(), "CVoiceChannelVatlibPrivate", "Cvatlib_Voice_Simple invalid or not setup!"); + Q_ASSERT_X(d_ptr->m_vatlib->IsRoomValid(d_ptr->m_roomIndex.load()), "CVoiceChannelVatlibPrivate", "Room index out of bounds!"); + + d_ptr->m_vatlib->SetOutputVolume(d_ptr->m_roomIndex.load(), volume); + } + + quint32 CVoiceChannelVatlib::getVolume() const + { + return d_ptr->m_volume.load(); + } +} diff --git a/src/blackcore/voice_channel_vatlib.h b/src/blackcore/voice_channel_vatlib.h new file mode 100644 index 000000000..307e9b03c --- /dev/null +++ b/src/blackcore/voice_channel_vatlib.h @@ -0,0 +1,87 @@ +/* Copyright (C) 2014 + * 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 and at http://www.swift-project.org/license.html. 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 BLACKCORE_VOICE_CHANNEL_VATLIB_H +#define BLACKCORE_VOICE_CHANNEL_VATLIB_H + +#include "voice_channel.h" +#include "voice_vatlib.h" +#include "blackmisc/statusmessage.h" +#include "blackmisc/voiceroomlist.h" +#include "blackmisc/avcallsignlist.h" +#include +#include + +//! \file + +namespace BlackCore +{ + class CVoiceChannelVatlibPrivate; + + //! Class implementing the voice channel interface + class CVoiceChannelVatlib : public IVoiceChannel + { + Q_OBJECT + + public: + + //! Default constructor + CVoiceChannelVatlib(TVatlibPointer vatlib, QObject *parent = nullptr); + + //! Destructor + virtual ~CVoiceChannelVatlib(); + + //! \copydoc IVoiceChannel::joinVoiceRoom + virtual void joinVoiceRoom(const BlackMisc::Audio::CVoiceRoom &voiceRoom) override; + + //! \copydoc IVoiceChannel::leaveVoiceRoom + virtual void leaveVoiceRoom() override; + + //! \copydoc IVoiceChannel::setRoomOutputVolume + virtual void setRoomOutputVolume(const qint32 volume) override; + + //! \copydoc IVoiceChannel::startTransmitting + virtual void startTransmitting() override; + + //! \copydoc IVoiceChannel::stopTransmitting + virtual void stopTransmitting() override; + + //! \copydoc IVoiceChannel::getVoiceRoomCallsigns + virtual BlackMisc::Aviation::CCallsignList getVoiceRoomCallsigns() const override; + + //! \copydoc IVoiceChannel::switchAudioOutput + virtual void switchAudioOutput(bool enable) override; + + //! \copydoc IVoiceChannel::setMyAircraftCallsign + virtual void setMyAircraftCallsign(const BlackMisc::Aviation::CCallsign &callsign) override; + + //! \copydoc IVoiceChannel::getVoiceRoom + virtual BlackMisc::Audio::CVoiceRoom getVoiceRoom() const override; + + //! \copydoc IVoiceChannel::getRoomIndex + virtual qint32 getRoomIndex() const override; + + //! \copydoc IVoiceChannel::isMuted + virtual bool isMuted() const override; + + //! Set channel volume + virtual void setVolume(quint32 volume) override; + + //! Get channel volume + virtual quint32 getVolume() const override; + + //! \copydoc IVoiceChannel::updateRoomStatus + virtual void updateRoomStatus(Cvatlib_Voice_Simple::roomStatusUpdate roomStatus) override; + + private: + QSharedPointer d_ptr; + }; +} + +#endif // guard diff --git a/src/blackcore/voice_channel_vatlib_p.h b/src/blackcore/voice_channel_vatlib_p.h new file mode 100644 index 000000000..df2dedb1b --- /dev/null +++ b/src/blackcore/voice_channel_vatlib_p.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2014 + * 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 and at http://www.swift-project.org/license.html. 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 BLACKCORE_VOICE_CHANNEL_P_H +#define BLACKCORE_VOICE_CHANNEL_P_H + +#include "voice_channel_vatlib.h" +#include "blackmisc/voiceroom.h" +#include "blackmisc/avcallsign.h" +#include +#include +#include + +//! \file + +namespace BlackCore +{ + // Inhibit doxygen warnings about missing documentation + //! \cond PRIVATE + + // Private Implementation of CVoiceChannelVatlib + class CVoiceChannelVatlibPrivate : public QObject + { + Q_OBJECT + + public: + + // Default constructor + CVoiceChannelVatlibPrivate(TVatlibPointer vatlib, CVoiceChannelVatlib *parent); + + // Destructor + ~CVoiceChannelVatlibPrivate(); + + // Enable or disable channel audio output + void switchAudioOutput(bool enable); + + // Start transmitting + void startTransmitting(); + + // Stop transmitting + void stopTransmitting(); + + // Set room output volume + void setRoomOutputVolume(const qint32 volume); + + // FIXME Move into free function + void exceptionDispatcher(const char *caller); + + // Update connected room users + static void updateRoomUsers(Cvatlib_Voice_Simple *obj, const char *name, void *cbVar); + + public slots: + + // Process user joined/left signals + void processUserJoinedLeft(); + + signals: + + // Signal when user has joined or left + void userJoinedLeft(); + + public: + + // Add a callsign temporarily. This is used for the getUserList callback + void addTemporaryCallsignForRoom(const BlackMisc::Aviation::CCallsign &callsign); + + // Get shared room data for this channel + static QHash> &getSharedRoomData(); + + TVatlibPointer m_vatlib; // Shared pointer to vatlib object + BlackMisc::Aviation::CCallsign m_callsign; // Own callsign + std::atomic m_roomIndex; // Room index + BlackMisc::Audio::CVoiceRoom m_voiceRoom; // Voice Room + std::atomic m_volume; // Room volume + std::atomic m_connectionRefCount; // Connection reference couting + std::atomic m_outputEnabled; // Is room output enabled? + BlackMisc::Aviation::CCallsignList m_listCallsigns; // Callsigns connected to room + std::atomic m_roomStatus; // Room connection status + + // Mutexes + QMutex m_mutexSharedRoomData; + QMutex m_mutexCallSign; + QMutex m_mutexVoiceRoom; + QMutex m_mutexCallsignList; + + BlackMisc::Aviation::CCallsignList m_temporaryVoiceRoomCallsigns; // temp. storage of voice rooms during update + + static QList m_availableRooms; // Static list of not used room indexes + static QHash> m_sharedRoomData; + const static qint32 InvalidRoomIndex = -1; // Invalid room index + + CVoiceChannelVatlib * const q_ptr = nullptr; + Q_DECLARE_PUBLIC(CVoiceChannelVatlib) + + private: + + // Change room connection status + void changeConnectionStatus(IVoiceChannel::ConnectionStatus newStatus); + + // Allocate a not used room identified by its index + qint32 allocateRoom(); + }; + //! \endcond +} + +#endif // guard diff --git a/src/blackcore/voice_vatlib.cpp b/src/blackcore/voice_vatlib.cpp index 50a9e39ac..82893ebac 100644 --- a/src/blackcore/voice_vatlib.cpp +++ b/src/blackcore/voice_vatlib.cpp @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "voice_vatlib.h" +#include "voice_channel_vatlib.h" #include #include @@ -24,8 +25,6 @@ namespace BlackCore // m_audioOutput(nullptr), // removed #227 m_inputSquelch(-1), m_micTestResult(Cvatlib_Voice_Simple::agc_Ok), - m_isAudioLoopbackEnabled(false), - m_temporaryUserRoomIndex(CVoiceVatlib::InvalidRoomIndex), m_lockCurrentOutputDevice(QMutex::Recursive), m_lockCurrentInputDevice(QMutex::Recursive), m_lockDeviceList(QMutex::Recursive) @@ -39,15 +38,15 @@ namespace BlackCore m_vatlib->GetInputDevices(onInputHardwareDeviceReceived, this); m_vatlib->GetOutputDevices(onOutputHardwareDeviceReceived, this); - connect(this, &CVoiceVatlib::userJoinedLeft, this, &CVoiceVatlib::onUserJoinedLeft, Qt::QueuedConnection); - - this->m_vatlibRooms.push_back(CVoiceRoom()); // COM1 - this->m_vatlibRooms.push_back(CVoiceRoom()); // COM2 - this->m_outputEnabled.insert(COM1, true); - this->m_outputEnabled.insert(COM2, true); this->m_currentInputDevice = this->defaultAudioInputDevice(); this->m_currentOutputDevice = this->defaultAudioOutputDevice(); - // this->m_audioOutput->setVolume(1.0); // make sure the overall sound is not muted + + for (int ii = 0; ii < 2; ++ii) + { + IVoiceChannel *channel = new CVoiceChannelVatlib(m_vatlib, this); + Q_ASSERT(channel); + m_hashChannelIndex.insert(ii, channel); + } // do processing this->startTimer(10); @@ -170,61 +169,6 @@ namespace BlackCore } } - /* - * Get voice rooms, with the latest status updated - */ - BlackMisc::Audio::CVoiceRoomList CVoiceVatlib::getComVoiceRoomsWithAudioStatus() const - { - QReadLocker lockForReading(&m_lockVoiceRooms); - Q_ASSERT_X(m_vatlibRooms.size() == 2, "CVoiceVatlib", "Wrong numer of COM voice rooms"); - - if (!m_vatlib->IsValid() || !m_vatlib->IsSetup()) return CVoiceRoomList::twoEmptyRooms(); - - QMutexLocker lockerVatlib(&m_mutexVatlib); - // valid state, update - CVoiceRoom com1 = this->m_vatlibRooms[0]; - CVoiceRoom com2 = this->m_vatlibRooms[1]; - com1.setConnected(m_vatlib->IsRoomConnected(static_cast(COM1))); - com2.setConnected(m_vatlib->IsRoomConnected(static_cast(COM2))); - com1.setAudioPlaying(com1.isConnected() ? m_vatlib->IsAudioPlaying(static_cast(COM1)) : false); - com2.setAudioPlaying(com2.isConnected() ? m_vatlib->IsAudioPlaying(static_cast(COM2)) : false); - CVoiceRoomList voiceRooms; - voiceRooms.push_back(com1); - voiceRooms.push_back(com2); - return voiceRooms; - } - - /* - * Voice room callsigns - */ - CCallsignList CVoiceVatlib::getVoiceRoomCallsigns(const IVoice::ComUnit comUnit) const - { - QReadLocker lockForReading(&m_lockCallsigns); - CCallsignList callsigns; - if (!this->m_vatlibRoomCallsigns.contains(comUnit)) return callsigns; - return this->m_vatlibRoomCallsigns[comUnit]; - } - - /* - * Enable audio - */ - void CVoiceVatlib::switchAudioOutput(const ComUnit comUnit, bool enable) - { - QMutexLocker lockerVatlib(&m_mutexVatlib); - Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); - Q_ASSERT_X(m_vatlib->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); - try - { - m_vatlib->SetOutputState(static_cast(comUnit), 0, enable); - QWriteLocker lockForWriting(&m_lockOutputEnabled); - this->m_outputEnabled[comUnit] = enable; - } - catch (...) - { - this->exceptionDispatcher(Q_FUNC_INFO); - } - } - void CVoiceVatlib::enableAudioLoopback(bool enable) { if (enable == m_isAudioLoopbackEnabled) @@ -334,203 +278,35 @@ namespace BlackCore return result; } - /* - * Callsign - */ - void CVoiceVatlib::setMyAircraftCallsign(const BlackMisc::Aviation::CCallsign &callsign) + IVoiceChannel *CVoiceVatlib::getVoiceChannel(qint32 channelIndex) const { - QWriteLocker lockForWriting(&m_lockMyCallsign); - m_aircraftCallsign = callsign; + IVoiceChannel *channel = m_hashChannelIndex.value(channelIndex, nullptr); + Q_ASSERT(channel); + + return channel; } /* - * Voice room + * Handle PTT */ - void CVoiceVatlib::joinVoiceRoom(const ComUnit comUnit, const BlackMisc::Audio::CVoiceRoom &voiceRoom) + void CVoiceVatlib::handlePushToTalk(bool value) { - QMutexLocker lockerVatlib(&m_mutexVatlib); - Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); - Q_ASSERT_X(m_vatlib->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); - if (!voiceRoom.isValid()) + qDebug() << "PTT"; + if (!this->m_vatlib) return; + + if (value) qDebug() << "Start transmitting..."; + else qDebug() << "Stop transmitting..."; + + // FIXME: Set only once channel to active for transmitting + if (value) { - qDebug() << "Error: Cannot join invalid voice room."; - return; + getVoiceChannel(0)->startTransmitting(); + getVoiceChannel(1)->startTransmitting(); } - - try + else { - // only connect, if not yet connected - if (this->m_vatlib->IsRoomConnected(static_cast(comUnit))) return; - changeConnectionStatus(comUnit, Connecting); - QString serverSpec = voiceRoom.getVoiceRoomUrl(); - QReadLocker lockForReading(&m_lockMyCallsign); - bool jr = m_vatlib->JoinRoom(static_cast(comUnit), m_aircraftCallsign.toQString().toLatin1().constData(), serverSpec.toLatin1().constData()); - if (jr) - { - // do not(!) set as connected right now, this will be done in "status changed" - // when room really is connected - this->setVoiceRoomForUnit(comUnit, voiceRoom); - } - else - { - qWarning("Could not join voice room"); - } - } - catch (...) - { - this->exceptionDispatcher(Q_FUNC_INFO); - } - } - - /* - * Leave a room - */ - void CVoiceVatlib::leaveVoiceRoom(const ComUnit comUnit) - { - QMutexLocker lockerVatlib(&m_mutexVatlib); - - Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); - Q_ASSERT_X(m_vatlib->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); - - try - { - // update "meta" object for room - this->setVoiceRoomForUnit(comUnit, CVoiceRoom()); // an empty voice room is easier to detect - - // only leave is room is physically connected - if (this->m_vatlib->IsRoomConnected(static_cast(comUnit))) - { - m_vatlib->LeaveRoom(static_cast(comUnit)); - this->m_vatlibRoomCallsigns[comUnit] = CCallsignList(); // empty list for this room - changeConnectionStatus(comUnit, Disconnecting); - } - } - catch (...) - { - this->exceptionDispatcher(Q_FUNC_INFO); - } - } - - /* - * Leave all voice rooms - */ - void CVoiceVatlib::leaveAllVoiceRooms() - { - this->leaveVoiceRoom(COM1); - this->leaveVoiceRoom(COM2); - } - - /* - * Room output volume as per COM unit - */ - void CVoiceVatlib::setRoomOutputVolume(const ComUnit comUnit, const qint32 volume) - { - QMutexLocker lockerVatlib(&m_mutexVatlib); - Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); - Q_ASSERT_X(m_vatlib->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); - - try - { - m_vatlib->SetRoomVolume(static_cast(comUnit), volume); - } - catch (...) - { - this->exceptionDispatcher(Q_FUNC_INFO); - } - } - - /* - * Start transmitting - */ - void CVoiceVatlib::startTransmitting(const ComUnit comUnit) - { - QMutexLocker lockerVatlib(&m_mutexVatlib); - Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); - Q_ASSERT_X(m_vatlib->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); - - try - { - m_vatlib->SetMicState(static_cast(comUnit), true); - } - catch (...) - { - this->exceptionDispatcher(Q_FUNC_INFO); - } - } - - /* - * Start transmitting - */ - void CVoiceVatlib::stopTransmitting(const ComUnit comUnit) - { - QMutexLocker lockerVatlib(&m_mutexVatlib); - Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); - Q_ASSERT_X(m_vatlib->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); - try - { - m_vatlib->SetMicState(static_cast(comUnit), false); - } - catch (...) - { - this->exceptionDispatcher(Q_FUNC_INFO); - } - } - - /* - * Change room status - */ - void CVoiceVatlib::changeRoomStatus(ComUnit comUnit, Cvatlib_Voice_Simple::roomStatusUpdate roomStatus) - { - qDebug() << comUnit << roomStatus; // KB_REMOVE - CVoiceRoom vr = this->voiceRoomForUnit(comUnit); - switch (roomStatus) - { - case Cvatlib_Voice_Simple::roomStatusUpdate_JoinSuccess: - { - m_lockOutputEnabled.lockForRead(); - bool isOutputEnabled = this->m_outputEnabled[comUnit]; - m_lockOutputEnabled.unlock(); - switchAudioOutput(comUnit, isOutputEnabled); - vr.setConnected(true); - this->setVoiceRoomForUnit(comUnit, vr); - changeConnectionStatus(comUnit, Connected); - emit userJoinedLeft(comUnit); - break; - } - case Cvatlib_Voice_Simple::roomStatusUpdate_JoinFail: - vr.setConnected(false); - this->setVoiceRoomForUnit(comUnit, vr); - changeConnectionStatus(comUnit, ConnectingFailed); - break; - case Cvatlib_Voice_Simple::roomStatusUpdate_UnexpectedDisconnectOrKicked: - vr.setConnected(false); - this->setVoiceRoomForUnit(comUnit, vr); - changeConnectionStatus(comUnit, DisconnectedError); - break; - case Cvatlib_Voice_Simple::roomStatusUpdate_LeaveComplete: - m_lockCallsigns.lockForWrite(); - m_voiceRoomCallsigns.clear(); - m_lockCallsigns.unlock(); - changeConnectionStatus(comUnit, Disconnected); - break; - case Cvatlib_Voice_Simple::roomStatusUpdate_UserJoinsLeaves: - // FIXME: We cannot call GetRoomUserList because vatlib is not reentrent safe. - emit userJoinedLeft(comUnit); - break; - case Cvatlib_Voice_Simple::roomStatusUpdate_RoomAudioStarted: - emit audioStarted(comUnit); - break; - case Cvatlib_Voice_Simple::roomStatusUpdate_RoomAudioStopped: - emit audioStopped(comUnit); - break; - case Cvatlib_Voice_Simple::roomStatusUpdate_AudioStarted: - emit globalAudioStarted(); - break; - case Cvatlib_Voice_Simple::roomStatusUpdate_AudioStopped: - emit globalAudioStopped(); - break; - default: - break; + getVoiceChannel(0)->stopTransmitting(); + getVoiceChannel(1)->stopTransmitting(); } } @@ -588,62 +364,6 @@ namespace BlackCore } } - /* - * User joined / left room - */ - void CVoiceVatlib::onUserJoinedLeft(const ComUnit comUnit) - { - QMutexLocker lockerVatlib(&m_mutexVatlib); - Q_ASSERT_X(m_vatlib->IsValid() && m_vatlib->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); - Q_ASSERT_X(m_temporaryUserRoomIndex == CVoiceVatlib::InvalidRoomIndex, "CVoiceClientVatlib::onUserJoinedLeft", "Cannot list users for two rooms in parallel!"); - try - { - // Paranoia... clear list completely - if (!m_vatlib->IsRoomConnected(static_cast(comUnit))) - { - this->m_voiceRoomCallsigns[comUnit] = CCallsignList(); - this->m_temporaryVoiceRoomCallsigns.clear(); - return; - } - - // Store the room index for the slot (called in static callback) - m_temporaryUserRoomIndex = static_cast(comUnit); - - // Callbacks already completed when function GetRoomUserList returns, - // thereafter m_voiceRoomCallsignsUpdate is filled with the latest callsigns - m_vatlib->GetRoomUserList(static_cast(comUnit), onRoomUserReceived, this); - m_temporaryUserRoomIndex = CVoiceVatlib::InvalidRoomIndex; // reset - - // we have all current users in m_temporaryVoiceRoomCallsigns - foreach(CCallsign callsign, m_voiceRoomCallsigns.value(comUnit)) - { - if (!m_temporaryVoiceRoomCallsigns.contains(callsign)) - { - // User has left - emit userLeftRoom(callsign); - } - } - - QWriteLocker lockForWriting(&m_lockCallsigns); - foreach(CCallsign callsign, m_temporaryVoiceRoomCallsigns) - { - if (!m_voiceRoomCallsigns.value(comUnit).contains(callsign)) - { - // he joined - emit userJoinedRoom(callsign); - } - } - - // Finally we update it with our new list - this->m_voiceRoomCallsigns[comUnit] = this->m_temporaryVoiceRoomCallsigns; - this->m_temporaryVoiceRoomCallsigns.clear(); - } - catch (...) - { - this->exceptionDispatcher(Q_FUNC_INFO); - } - } - /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ /********************************** shimlib callbacks ************************************/ /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ @@ -664,36 +384,8 @@ namespace BlackCore void CVoiceVatlib::onRoomStatusUpdate(Cvatlib_Voice_Simple *obj, Cvatlib_Voice_Simple::roomStatusUpdate upd, qint32 roomIndex, void *cbVar) { Q_UNUSED(obj) - ComUnit comUnit = static_cast(roomIndex); - CVoiceVatlib *voiceClientVatlib = cbvar_cast_voice(cbVar); - voiceClientVatlib->changeRoomStatus(comUnit, upd); - } - - /* - * Room user received - */ - void CVoiceVatlib::onRoomUserReceived(Cvatlib_Voice_Simple *obj, const char *name, void *cbVar) - { - Q_UNUSED(obj) - - // sanity check - QString callsign = QString(name); - if (callsign.isEmpty()) return; - - // add callsign - CVoiceVatlib *voiceClientVatlib = cbvar_cast_voice(cbVar); - ComUnit comUnit = static_cast(voiceClientVatlib->temporaryUserRoomIndex()); - - // add user - // callsign might contain: VATSIM id, user name - if (callsign.contains(" ")) - { - QStringList parts = callsign.split(" "); - callsign = parts[0]; - // I throw away VATSIM id here, maybe we could use it - } - - voiceClientVatlib->addTemporaryCallsignForRoom(comUnit, CCallsign(callsign)); + CVoiceVatlib *vatlibRoom = cbvar_cast_voice(cbVar); + vatlibRoom->onRoomStatusUpdate(roomIndex, upd); } /* @@ -718,33 +410,6 @@ namespace BlackCore cbvar_cast_voice(cbVar)->m_devices.push_back(outputDevice); } - /* - * COM unit to Voice room - */ - CVoiceRoom CVoiceVatlib::voiceRoomForUnit(const IVoice::ComUnit comUnit) const - { - QReadLocker lockForReading(&m_lockVoiceRooms); - return (comUnit == COM1) ? this->m_voiceRooms[0] : this->m_voiceRooms[1]; - } - - /* - * Voice room for respectice COM unit - */ - void CVoiceVatlib::setVoiceRoomForUnit(const IVoice::ComUnit comUnit, const CVoiceRoom &voiceRoom) - { - QWriteLocker lockForWriting(&m_lockVoiceRooms); - this->m_voiceRooms[comUnit == COM1 ? 0 : 1] = voiceRoom; - } - - /* - * Add temp.callsign for room - */ - void CVoiceVatlib::addTemporaryCallsignForRoom(const ComUnit /** comUnit **/, const CCallsign &callsign) - { - if (m_temporaryVoiceRoomCallsigns.contains(callsign)) return; - m_temporaryVoiceRoomCallsigns.push_back(callsign); - } - /* * Forward exception as signal */ @@ -783,26 +448,21 @@ namespace BlackCore } } - // Change voice room status and emit signal - void CVoiceVatlib::changeConnectionStatus(ComUnit comUnit, ConnectionStatus newStatus) + void CVoiceVatlib::onRoomStatusUpdate(qint32 roomIndex, Cvatlib_Voice_Simple::roomStatusUpdate roomStatus) { - QWriteLocker lockForWriting(&m_lockConnectionStatus); - ConnectionStatus currentStatus = m_connectionStatus.value(comUnit); - if (newStatus != currentStatus) + QList voiceChannels = m_hashChannelIndex.values(); + auto iterator = std::find_if(voiceChannels.begin(), voiceChannels.end(), [&](const IVoiceChannel * voiceChannel) { - if (newStatus == Connected) - { - CVoiceRoom vr = this->voiceRoomForUnit(comUnit); - vr.setConnected(true); - this->setVoiceRoomForUnit(comUnit, vr); - } + return voiceChannel->getRoomIndex() == roomIndex; + }); - // disconnecting the voice room will already be - // set in leave voice room - - m_connectionStatus.insert(comUnit, newStatus); - emit connectionStatusChanged(comUnit, currentStatus, newStatus); + if (iterator == voiceChannels.end()) + { + qWarning() << "Unknown room index"; + return; } + + (*iterator)->updateRoomStatus(roomStatus); } } // namespace diff --git a/src/blackcore/voice_vatlib.h b/src/blackcore/voice_vatlib.h index 7a4715f15..ab9b615e4 100644 --- a/src/blackcore/voice_vatlib.h +++ b/src/blackcore/voice_vatlib.h @@ -101,40 +101,8 @@ namespace BlackCore //! \copydoc IVoice::micTestResultAsString virtual QString micTestResultAsString() const override; - public slots: - //! \copydoc IVoice::setMyAircraftCallsign() - virtual void setMyAircraftCallsign(const BlackMisc::Aviation::CCallsign &callsign) override; - - //! \copydoc IVoice::joinVoiceRoom() - virtual void joinVoiceRoom(const ComUnit comUnit, const BlackMisc::Audio::CVoiceRoom &voiceRoom) override; - - //! \copydoc IVoice::leaveVoiceRoom() - virtual void leaveVoiceRoom(const ComUnit comUnit) override; - - //! \copydoc IVoice::leaveAllVoiceRooms() - virtual void leaveAllVoiceRooms() override; - - //! \copydoc IVoice::setRoomOutputVolume() - virtual void setRoomOutputVolume(const ComUnit comUnit, const qint32 volumne) override; - - //! \copydoc IVoice::startTransmitting() - virtual void startTransmitting(const ComUnit comUnit) override; - - //! \copydoc IVoice::stopTransmitting() - virtual void stopTransmitting(const ComUnit comUnit) override; - - //! \copydoc IVoice::getComVoiceRoomsWithAudioStatus() - virtual BlackMisc::Audio::CVoiceRoomList getComVoiceRoomsWithAudioStatus() const override; - - //! \copydoc IVoice::getComVoiceRooms() - virtual BlackMisc::Audio::CVoiceRoomList getComVoiceRooms() const override - { - QReadLocker lockForReading(&m_lockVoiceRooms); - return this->m_voiceRooms; - } - - //! \copydoc IVoice::getVoiceRoomCallsigns() - virtual BlackMisc::Aviation::CCallsignList getVoiceRoomCallsigns(const ComUnit comUnit) const override; + //! \copydoc IVoice::getVoiceChannel + virtual IVoiceChannel *getVoiceChannel(qint32 channelIndex) const override; //! \copydoc IVoice::setInputDevice virtual void setInputDevice(const BlackMisc::Audio::CAudioDevice &device) override; @@ -148,58 +116,15 @@ namespace BlackCore //! \copydoc IVoice::getCurrentOutputDevice() virtual BlackMisc::Audio::CAudioDevice getCurrentOutputDevice() const override; - //! \copydoc IVoice::switchAudioOutput - virtual void switchAudioOutput(const ComUnit comUnit, bool enable) override; - //! \copydoc IVoice::enableAudioLoopback virtual void enableAudioLoopback(bool enable = true) override; - //! \copydoc IVoice::isMuted - virtual bool isMuted() const override - { - QReadLocker lockForReading(&m_lockOutputEnabled); - if (this->m_outputEnabled.isEmpty()) return false; - bool enabled = this->m_outputEnabled[COM1] || this->m_outputEnabled[COM2]; - return !enabled; - } - /*! * \brief Starts or stops voice transmission * \param value */ void handlePushToTalk(bool value = false); - /************************************************ - * NON API METHODS: - * The following methods are not part of the - * public API. They are needed for internal - * workflow. - * *********************************************/ - - /*! - * \brief Voice room index - * \return - */ - qint32 temporaryUserRoomIndex() const - { - return m_temporaryUserRoomIndex; - } - - /*! - * \brief Room status update, used in callback - * \param comUnit - * \param roomStatus - */ - void changeRoomStatus(ComUnit comUnit, Cvatlib_Voice_Simple::roomStatusUpdate roomStatus); - - signals: - - /*! - * \brief User joined or left - * \param comUnit - */ - void userJoinedLeft(const ComUnit comUnit); - protected: // QObject overrides /*! @@ -212,48 +137,25 @@ namespace BlackCore void onEndFindSquelch(); void onEndMicTest(); - /*! - * \brief User (identified by callsign) joined or left voice room - */ - void onUserJoinedLeft(const ComUnit comUnit); - private: // shimlib callbacks static void onRoomStatusUpdate(Cvatlib_Voice_Simple *obj, Cvatlib_Voice_Simple::roomStatusUpdate upd, qint32 roomIndex, void *cbVar); - static void onRoomUserReceived(Cvatlib_Voice_Simple *obj, const char *name, void *cbVar); static void onInputHardwareDeviceReceived(Cvatlib_Voice_Simple *obj, const char *name, void *cbVar); static void onOutputHardwareDeviceReceived(Cvatlib_Voice_Simple *obj, const char *name, void *cbVar); - BlackMisc::Audio::CVoiceRoom voiceRoomForUnit(const ComUnit comUnit) const; - void setVoiceRoomForUnit(const IVoice::ComUnit comUnit, const BlackMisc::Audio::CVoiceRoom &voiceRoom); - void addTemporaryCallsignForRoom(const ComUnit comUnit, const BlackMisc::Aviation::CCallsign &callsign); - void removeUserFromRoom(const ComUnit comUnit, const QString &callsign); void exceptionDispatcher(const char *caller); - void enableAudio(const ComUnit comUnit); - void changeConnectionStatus(ComUnit comUnit, ConnectionStatus newStatus); + void onRoomStatusUpdate(qint32 roomIndex, Cvatlib_Voice_Simple::roomStatusUpdate roomStatus); TVatlibPointer m_vatlib; - // QScopedPointer m_audioOutput; #227 - BlackMisc::Aviation::CCallsign m_aircraftCallsign; /*!< own callsign to join voice rooms */ - BlackMisc::Audio::CVoiceRoomList m_voiceRooms; BlackMisc::Audio::CAudioDeviceList m_devices; /*!< in and output devices */ BlackMisc::Audio::CAudioDevice m_currentOutputDevice; BlackMisc::Audio::CAudioDevice m_currentInputDevice; std::atomic m_inputSquelch; std::atomic m_micTestResult; - QMap m_voiceRoomCallsigns; /*!< voice room callsigns */ - BlackMisc::Aviation::CCallsignList m_temporaryVoiceRoomCallsigns; /*!< temp. storage of voice rooms during update */ - QMap m_outputEnabled; /*!< output enabled, basically a mute flag */ - QMap m_connectionStatus; /*!< holds connection status for each com unit */ + QHash m_hashChannelIndex; bool m_isAudioLoopbackEnabled; /*!< A flag whether audio loopback is enabled or not */ - // Need to keep the roomIndex? - // KB: I would remove this approach, it is potentially unsafe - // Maybe just use 2 "wrapper" callbacks, which then set explicitly the voice room (it is only 2 methods) - qint32 m_temporaryUserRoomIndex; /*!< temp. storage of voice room, in order to retrieve it in static callback */ - const static qint32 InvalidRoomIndex = -1; /*! marks invalid room */ - // Thread serialization mutable QMutex m_lockCurrentOutputDevice; mutable QMutex m_lockCurrentInputDevice; diff --git a/src/blackcore/voice_vatlib_ptt.cpp b/src/blackcore/voice_vatlib_ptt.cpp deleted file mode 100644 index 573716fbf..000000000 --- a/src/blackcore/voice_vatlib_ptt.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright (C) 2013 VATSIM Community / authors - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "voice_vatlib.h" -#include - -using namespace BlackMisc::Audio; - -namespace BlackCore -{ - /* - * Handle PTT - */ - void CVoiceVatlib::handlePushToTalk(bool value) - { - qDebug() << "PTT"; - if (!this->m_voice) return; - CVoiceRoomList rooms = this->getComVoiceRoomsWithAudioStatus(); - CVoiceRoom room1 = rooms[0]; - CVoiceRoom room2 = rooms[1]; - - if (value) qDebug() << "Start transmitting..."; - else qDebug() << "Stop transmitting..."; - - if (room1.isConnected()) - { - if (value) - { - this->startTransmitting(IVoice::COM1); - } - else - { - this->stopTransmitting(IVoice::COM1); - } - } - if (room2.isConnected()) - { - if (value) - this->startTransmitting(IVoice::COM2); - else - this->stopTransmitting(IVoice::COM2); - } - } -} // namespace