From ce772baf79e7c406bdfd366ad272904288542a8c Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Sat, 5 Oct 2019 00:09:49 +0200 Subject: [PATCH] Ref T739, moved code for voice client to IContextAudio * code can be used on both sides (UI/core) * the lots become normal public functions * the slots will be used for new DBus functions --- .../context/contextapplicationproxy.cpp | 3 - src/blackcore/context/contextaudio.cpp | 363 ++++++++++++++++- src/blackcore/context/contextaudio.h | 178 +++++--- src/blackcore/context/contextaudioimpl.cpp | 381 +----------------- src/blackcore/context/contextaudioimpl.h | 124 +----- 5 files changed, 488 insertions(+), 561 deletions(-) diff --git a/src/blackcore/context/contextapplicationproxy.cpp b/src/blackcore/context/contextapplicationproxy.cpp index 9701e32a2..bfbf5f9c7 100644 --- a/src/blackcore/context/contextapplicationproxy.cpp +++ b/src/blackcore/context/contextapplicationproxy.cpp @@ -63,9 +63,6 @@ namespace BlackCore s = connection.connect(serviceName, IContextApplication::ObjectPath(), IContextApplication::InterfaceName(), "registrationChanged", this, SIGNAL(registrationChanged())); Q_ASSERT(s); - s = connection.connect(serviceName, IContextApplication::ObjectPath(), IContextApplication::InterfaceName(), - "fakedSetComVoiceRoom", this, SIGNAL(fakedSetComVoiceRoom(BlackMisc::Audio::CVoiceRoomList))); - Q_ASSERT(s); s = connection.connect(serviceName, IContextApplication::ObjectPath(), IContextApplication::InterfaceName(), "hotkeyActionsRegistered", this, SIGNAL(hotkeyActionsRegistered(QStringList, BlackMisc::CIdentifier))); Q_ASSERT(s); diff --git a/src/blackcore/context/contextaudio.cpp b/src/blackcore/context/contextaudio.cpp index b7b98efb3..886ef440b 100644 --- a/src/blackcore/context/contextaudio.cpp +++ b/src/blackcore/context/contextaudio.cpp @@ -9,18 +9,58 @@ #include "contextaudio.h" #include "blackcore/context/contextaudio.h" -#include "blackcore/context/contextaudioempty.h" +#include "blackcore/context/contextnetwork.h" // for user login +#include "blackcore/context/contextownaircraft.h" // for COM integration +#include "blackcore/context/contextsimulator.h" // for COM intergration #include "blackcore/context/contextaudioimpl.h" #include "blackcore/context/contextaudioproxy.h" +#include "blackmisc/simplecommandparser.h" #include "blackmisc/dbusserver.h" +#include "blackmisc/verify.h" #include "blackmisc/icons.h" using namespace BlackMisc; +using namespace BlackMisc::Aviation; +using namespace BlackMisc::Audio; +using namespace BlackMisc::Network; +using namespace BlackMisc::PhysicalQuantities; +using namespace BlackMisc::Simulation; +using namespace BlackSound; +using namespace BlackCore::Afv::Clients; namespace BlackCore { namespace Context { + IContextAudio::IContextAudio(CCoreFacadeConfig::ContextMode mode, CCoreFacade *runtime) : + IContext(mode, runtime), m_voiceClient("https://voice1.vatsim.uk") + { + const CSettings as = m_audioSettings.getThreadLocal(); + this->setVoiceOutputVolume(as.getOutVolume()); + m_selcalPlayer = new CSelcalPlayer(CAudioDeviceInfo::getDefaultOutputDevice(), this); + + connect(&m_voiceClient, &CAfvClient::ptt, this, &IContextAudio::ptt); + + this->changeDeviceSettings(); + QPointer myself(this); + QTimer::singleShot(5000, this, [ = ] + { + if (!myself) { return; } + if (!sApp || sApp->isShuttingDown()) { return; } + myself->onChangedAudioSettings(); + myself->delayedInitMicrophone(); + }); + } + + void IContextAudio::delayedInitMicrophone() + { +#ifdef Q_OS_MAC + // m_voiceInputDevice = m_voice->createInputDevice(); + // m_voice->connectVoice(m_voiceInputDevice.get(), m_audioMixer.get(), IAudioMixer::InputMicrophone); + CLogMessage(this).info(u"MacOS delayed input device init"); +#endif + } + const QString &IContextAudio::InterfaceName() { static const QString s(BLACKCORE_CONTEXTAUDIO_INTERFACENAME); @@ -35,25 +75,332 @@ namespace BlackCore IContextAudio *IContextAudio::create(CCoreFacade *runtime, CCoreFacadeConfig::ContextMode mode, CDBusServer *server, QDBusConnection &connection) { + // for audio no empty context is available + // since IContextAudio provides audio on either side (core/GUI) we do not use ContextAudioEmpty + // ContextAudioEmpty would cause issue, as it is initializing "common parts" during shutdown switch (mode) { case CCoreFacadeConfig::Local: case CCoreFacadeConfig::LocalInDBusServer: + default: return (new CContextAudio(mode, runtime))->registerWithDBus(server); case CCoreFacadeConfig::Remote: return new CContextAudioProxy(CDBusServer::coreServiceName(connection), connection, mode, runtime); - default: - return new CContextAudioEmpty(runtime); // audio not mandatory + case CCoreFacadeConfig::NotUsed: + BLACK_VERIFY_X(false, Q_FUNC_INFO, "Empty context not supported for audio (since AFV)"); + return nullptr; } } + IContextAudio::~IContextAudio() + { + m_voiceClient.stop(); + } + + const CIdentifier &IContextAudio::audioRunsWhere() const + { + static const CIdentifier i("IContextAudio"); + return i; + } + QString IContextAudio::audioRunsWhereInfo() const { - if (this->isEmptyObject()) { return "no audio"; } - const CIdentifier i = this->audioRunsWhere(); - return this->isUsingImplementingObject() ? - QStringLiteral("Local audio on '%1', '%2'.").arg(i.getMachineName(), i.getProcessName()) : - QStringLiteral("Remote audio on '%1', '%2'.").arg(i.getMachineName(), i.getProcessName()); + static const QString s = QStringLiteral("Local audio on '%1', '%2'.").arg(audioRunsWhere().getMachineName(), audioRunsWhere().getProcessName()); + return s; + } + + bool IContextAudio::parseCommandLine(const QString &commandLine, const CIdentifier &originator) + { + Q_UNUSED(originator) + if (commandLine.isEmpty()) { return false; } + CSimpleCommandParser parser( + { + ".vol", ".volume", // output volume + ".mute", // mute + ".unmute" // unmute + }); + parser.parse(commandLine); + if (!parser.isKnownCommand()) { return false; } + + if (parser.matchesCommand(".mute")) + { + this->setMute(true); + return true; + } + else if (parser.matchesCommand(".unmute")) + { + this->setMute(false); + return true; + } + else if (parser.commandStartsWith("vol") && parser.countParts() > 1) + { + int v = parser.toInt(1); + this->setVoiceOutputVolume(v); + } + return false; + } + + CAudioDeviceInfoList IContextAudio::getAudioDevices() const + { + return CAudioDeviceInfoList::allDevices(); + } + + CAudioDeviceInfoList IContextAudio::getCurrentAudioDevices() const + { + // either the devices really used, or settings + CAudioDeviceInfo inputDevice = m_voiceClient.getInputDevice(); + if (!inputDevice.isValid()) { inputDevice = CAudioDeviceInfo::getDefaultInputDevice(); } + + CAudioDeviceInfo outputDevice = m_voiceClient.getOutputDevice(); + if (!outputDevice.isValid()) { outputDevice = CAudioDeviceInfo::getDefaultOutputDevice(); } + + CAudioDeviceInfoList devices; + devices.push_back(inputDevice); + devices.push_back(outputDevice); + return devices; + } + + void IContextAudio::setCurrentAudioDevices(const CAudioDeviceInfo &inputDevice, const CAudioDeviceInfo &outputDevice) + { + if (!inputDevice.getName().isEmpty()) { m_inputDeviceSetting.setAndSave(inputDevice.getName()); } + if (!outputDevice.getName().isEmpty()) { m_outputDeviceSetting.setAndSave(outputDevice.getName()); } + const bool changed = m_voiceClient.restartWithNewDevices(inputDevice, outputDevice); + if (changed) + { + emit this->changedSelectedAudioDevices(this->getCurrentAudioDevices()); + } + } + + void IContextAudio::setVoiceOutputVolume(int volume) + { + const bool wasMuted = this->isMuted(); + volume = CSettings::fixOutVolume(volume); + + const int currentVolume = m_voiceClient.getNormalizedOutputVolume(); + const bool changedVoiceOutput = (currentVolume != volume); + if (changedVoiceOutput) + { + m_voiceClient.setNormalizedOutputVolume(volume); + m_outVolumeBeforeMute = currentVolume; + + emit this->changedAudioVolume(volume); + if ((volume > 0 && wasMuted) || (volume < 1 && !wasMuted)) + { + // inform about muted + emit this->changedMute(volume < 1); + } + } + + CSettings as(m_audioSettings.getThreadLocal()); + if (as.getOutVolume() != volume) + { + as.setOutVolume(volume); + m_audioSettings.set(as); + } + } + + int IContextAudio::getVoiceOutputVolume() const + { + return m_voiceClient.getNormalizedOutputVolume(); + } + + void IContextAudio::setMute(bool muted) + { + if (this->isMuted() == muted) { return; } // avoid roundtrips / unnecessary signals + + if (m_voiceClient.isMuted() == muted) { return; } + m_voiceClient.setMuted(muted); + + // signal + emit this->changedMute(muted); + } + + bool IContextAudio::isMuted() const + { + return m_voiceClient.isMuted(); + } + + void IContextAudio::playSelcalTone(const CSelcal &selcal) + { + const CTime t = m_selcalPlayer->play(90, selcal); + const int ms = t.toMs(); + if (ms > 10) + { + // As of https://dev.swift-project.org/T558 play additional notification + const QPointer myself(this); + QTimer::singleShot(ms, this, [ = ] + { + if (!sApp || sApp->isShuttingDown() || !myself) { return; } + this->playNotification(CNotificationSounds::NotificationTextMessageSupervisor, true); + }); + } + } + + void IContextAudio::playNotification(CNotificationSounds::NotificationFlag notification, bool considerSettings, int volume) + { + if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << notification; } + + const CSettings settings = m_audioSettings.getThreadLocal(); + const bool play = !considerSettings || settings.isNotificationFlagSet(notification); + if (!play) { return; } + if (notification == CNotificationSounds::PTTClickKeyDown && (considerSettings && settings.noAudioTransmission())) + { + /** + if (!this->canTalk()) + { + // warning sound + notification = CNotificationSounds::NotificationNoAudioTransmission; + } + **/ + } + + if (volume < 0 || volume > 100) + { + volume = 90; + if (considerSettings) { volume = qMax(25, settings.getNotificationVolume()); } + } + m_notificationPlayer.play(notification, volume); + } + + void IContextAudio::enableAudioLoopback(bool enable) + { + m_voiceClient.setLoopBack(enable); + } + + bool IContextAudio::isAudioLoopbackEnabled() const + { + return m_voiceClient.isLoopback(); + } + + void IContextAudio::setVoiceSetup(const CVoiceSetup &setup) + { + // could be recycled for some AFV setup + Q_UNUSED(setup) + } + + CVoiceSetup IContextAudio::getVoiceSetup() const + { + return CVoiceSetup(); + } + + void IContextAudio::setVoiceTransmission(bool enable, PTTCOM com) + { + m_voiceClient.setPttForCom(enable, com); + } + + void IContextAudio::setVoiceTransmissionCom1(bool enabled) + { + this->setVoiceTransmission(enabled, COM1); + } + + void IContextAudio::setVoiceTransmissionCom2(bool enabled) + { + this->setVoiceTransmission(enabled, COM2); + } + + void IContextAudio::setVoiceTransmissionComActive(bool enabled) + { + this->setVoiceTransmission(enabled, COMActive); + } + + void IContextAudio::changeDeviceSettings() + { + const QString inputDeviceName = m_inputDeviceSetting.get(); + CAudioDeviceInfo input; + if (!inputDeviceName.isEmpty()) + { + const CAudioDeviceInfoList inputDevs = this->getAudioInputDevices(); + input = inputDevs.findByName(inputDeviceName); + } + + const QString outputDeviceName = m_outputDeviceSetting.get(); + CAudioDeviceInfo output; + if (!outputDeviceName.isEmpty()) + { + const CAudioDeviceInfoList outputDevs = this->getAudioOutputDevices(); + output = outputDevs.findByName(outputDeviceName); + } + + this->setCurrentAudioDevices(input, output); + } + + void IContextAudio::onChangedAudioSettings() + { + const CSettings s = m_audioSettings.get(); + const QString dir = s.getNotificationSoundDirectory(); + m_notificationPlayer.updateDirectory(dir); + this->setVoiceOutputVolume(s.getOutVolume()); + } + + void IContextAudio::audioIncreaseVolume(bool enabled) + { + if (!enabled) { return; } + const int v = qRound(this->getVoiceOutputVolume() * 1.2); + this->setVoiceOutputVolume(v); + } + + void IContextAudio::audioDecreaseVolume(bool enabled) + { + if (!enabled) { return; } + const int v = qRound(this->getVoiceOutputVolume() / 1.2); + this->setVoiceOutputVolume(v); + } + + CComSystem IContextAudio::getOwnComSystem(CComSystem::ComUnit unit) const + { + if (!this->getIContextOwnAircraft()) + { + // context not available + const double defFreq = 122.8; + switch (unit) + { + case CComSystem::Com1: return CComSystem::getCom1System(defFreq, defFreq); + case CComSystem::Com2: return CComSystem::getCom2System(defFreq, defFreq); + default: break; + } + return CComSystem::getCom1System(defFreq, defFreq); + } + return this->getIContextOwnAircraft()->getOwnComSystem(unit); + } + + bool IContextAudio::isComIntegratedWithSimulator() const + { + if (!this->getIContextSimulator()) { return false; } + return this->getIContextSimulator()->getSimulatorSettings().isComIntegrated(); + } + + void IContextAudio::xCtxChangedAircraftCockpit(const CSimulatedAircraft &aircraft, const CIdentifier &originator) + { + Q_UNUSED(aircraft) + Q_UNUSED(originator) + + /** + if (CIdentifiable::isMyIdentifier(originator)) { return; } + const bool integrated = this->isComIntegratedWithSimulator(); + if (integrated) + { + // set as in cockpit + const bool com1Rec = aircraft.getCom1System().isReceiveEnabled(); + const bool com2Rec = aircraft.getCom2System().isReceiveEnabled(); + } + **/ + } + + void IContextAudio::xCtxNetworkConnectionStatusChanged(const CConnectionStatus &from, const CConnectionStatus &to) + { + Q_UNUSED(from) + BLACK_VERIFY_X(this->getIContextNetwork(), Q_FUNC_INFO, "Missing network context"); + if (to.isConnected() && this->getIContextNetwork()) + { + const CUser connectedUser = this->getIContextNetwork()->getConnectedServer().getUser(); + m_voiceClient.connectTo(connectedUser.getId(), connectedUser.getPassword(), connectedUser.getCallsign().asString()); + m_voiceClient.start(CAudioDeviceInfo::getDefaultInputDevice(), CAudioDeviceInfo::getDefaultOutputDevice(), {0, 1}); + } + else if (to.isDisconnected()) + { + m_voiceClient.stop(); + m_voiceClient.disconnectFrom(); + } } } // ns } // ns diff --git a/src/blackcore/context/contextaudio.h b/src/blackcore/context/contextaudio.h index e15396434..81fdff2dd 100644 --- a/src/blackcore/context/contextaudio.h +++ b/src/blackcore/context/contextaudio.h @@ -11,21 +11,27 @@ #ifndef BLACKCORE_CONTEXT_CONTEXTAUDIO_H #define BLACKCORE_CONTEXT_CONTEXTAUDIO_H +#include "blackcore/afv/clients/afvclient.h" +#include "blackcore/audio/audiosettings.h" #include "blackcore/context/context.h" +#include "blackcore/actionbind.h" #include "blackcore/corefacade.h" #include "blackcore/corefacadeconfig.h" #include "blackcore/blackcoreexport.h" - +#include "blacksound/selcalplayer.h" +#include "blacksound/notificationplayer.h" +#include "blackmisc/macos/microphoneaccess.h" #include "blackmisc/audio/audiodeviceinfolist.h" #include "blackmisc/audio/notificationsounds.h" -#include "blackmisc/audio/voiceroom.h" -#include "blackmisc/audio/voiceroomlist.h" +#include "blackmisc/audio/audiosettings.h" #include "blackmisc/audio/voicesetup.h" #include "blackmisc/audio/ptt.h" #include "blackmisc/aviation/callsignset.h" #include "blackmisc/aviation/comsystem.h" #include "blackmisc/aviation/selcal.h" +#include "blackmisc/network/connectionstatus.h" #include "blackmisc/network/userlist.h" +#include "blackmisc/input/actionhotkeydefs.h" #include "blackmisc/identifier.h" #include @@ -35,12 +41,7 @@ class QDBusConnection; -namespace BlackMisc -{ - class CDBusServer; - namespace Audio { class CAudioDeviceInfo; } - namespace Aviation { class CCallsign; } -} +namespace BlackMisc { class CDBusServer; } //! \addtogroup dbus //! @{ @@ -63,9 +64,11 @@ namespace BlackCore Q_OBJECT Q_CLASSINFO("D-Bus Interface", BLACKCORE_CONTEXTAUDIO_INTERFACENAME) + friend class BlackCore::CCoreFacade; + protected: //! Constructor - IContextAudio(CCoreFacadeConfig::ContextMode mode, CCoreFacade *runtime) : IContext(mode, runtime) {} + IContextAudio(CCoreFacadeConfig::ContextMode mode, CCoreFacade *runtime); public: //! Interface name @@ -81,9 +84,61 @@ namespace BlackCore static IContextAudio *create(CCoreFacade *runtime, CCoreFacadeConfig::ContextMode mode, BlackMisc::CDBusServer *server, QDBusConnection &connection); //! Destructor - virtual ~IContextAudio() override {} + virtual ~IContextAudio() override; + + // -------- parts which can run in core and GUI, referring to local voice client ------------ + + //! Reference to voice client + BlackCore::Afv::Clients::CAfvClient &voiceClient() { return m_voiceClient; } + + //! Audio devices @{ + BlackMisc::Audio::CAudioDeviceInfoList getAudioDevices() const; + BlackMisc::Audio::CAudioDeviceInfoList getAudioInputDevices() const { return this->getAudioDevices().getInputDevices(); } + BlackMisc::Audio::CAudioDeviceInfoList getAudioOutputDevices() const { return this->getAudioDevices().getOutputDevices(); } + //! @} + + //! Get current audio device + //! \return input and output devices + BlackMisc::Audio::CAudioDeviceInfoList getCurrentAudioDevices() const; + + //! Set current audio device + //! \param audioDevice can be input or audio device + void setCurrentAudioDevices(const BlackMisc::Audio::CAudioDeviceInfo &audioDevice, const BlackMisc::Audio::CAudioDeviceInfo &outputDevice); + + //! Volume @{ + void setVoiceOutputVolume(int volume); + int getVoiceOutputVolume() const; + void setMute(bool muted); + bool isMuted() const; + //! @} + + //! SELCAL + void playSelcalTone(const BlackMisc::Aviation::CSelcal &selcal); + + //! Notification sounds + void playNotification(BlackMisc::Audio::CNotificationSounds::NotificationFlag notification, bool considerSettings, int volume = -1); + + //! Loopback @{ + void enableAudioLoopback(bool enable = true); + bool isAudioLoopbackEnabled() const; + //! @} + + //! Voice setup @{ + BlackMisc::Audio::CVoiceSetup getVoiceSetup() const; + void setVoiceSetup(const BlackMisc::Audio::CVoiceSetup &setup); + //! @} + + //! Info string about audio + QString audioRunsWhereInfo() const; + + //! Audio runs where + const BlackMisc::CIdentifier &audioRunsWhere() const; + + // -------- parts which can run in core and GUI, referring to local voice client ------------ signals: + // -------- local settings, not DBus relayed ------- + //! Audio volume changed //! \sa setVoiceOutputVolume void changedAudioVolume(int volume); @@ -100,58 +155,83 @@ namespace BlackCore //! Changed slection of audio devices void changedSelectedAudioDevices(const BlackMisc::Audio::CAudioDeviceInfoList &devices); + // -------- local settings, not DBus relayed ------- + public slots: - //! Audio devices @{ - virtual BlackMisc::Audio::CAudioDeviceInfoList getAudioDevices() const = 0; - BlackMisc::Audio::CAudioDeviceInfoList getAudioInputDevices() const { return this->getAudioDevices().getInputDevices(); } - BlackMisc::Audio::CAudioDeviceInfoList getAudioOutputDevices() const { return this->getAudioDevices().getOutputDevices(); } + // ------------- DBus --------------- + + //! \addtogroup swiftdotcommands + //! @{ + //!
+            //! .mute                          mute             BlackCore::Context::CContextAudio
+            //! .unmute                        unmute           BlackCore::Context::CContextAudio
+            //! .vol .volume   volume 0..100   set volume       BlackCore::Context::CContextAudio
+            //! 
+ //! @} + //! \copydoc IContextAudio::parseCommandLine + virtual bool parseCommandLine(const QString &commandLine, const BlackMisc::CIdentifier &originator) override; + + // ------------- DBus --------------- + + private: + //! Enable/disable voice transmission, nornally used with hotkey @{ + void setVoiceTransmission(bool enable, BlackMisc::Audio::PTTCOM com); + void setVoiceTransmissionCom1(bool enabled); + void setVoiceTransmissionCom2(bool enabled); + void setVoiceTransmissionComActive(bool enabled); //! @} - //! Audio runs where - virtual BlackMisc::CIdentifier audioRunsWhere() const = 0; + //! Change the device settings + void changeDeviceSettings(); - //! Info string about audio - QString audioRunsWhereInfo() const; + //! Changed audio settings + void onChangedAudioSettings(); - //! Get current audio device - //! \return input and output devices - virtual BlackMisc::Audio::CAudioDeviceInfoList getCurrentAudioDevices() const = 0; + //! Audio increase/decrease volume @{ + void audioIncreaseVolume(bool enabled); + void audioDecreaseVolume(bool enabled); + //! @} - //! Set current audio device - //! \param audioDevice can be input or audio device - virtual void setCurrentAudioDevices(const BlackMisc::Audio::CAudioDeviceInfo &inputDevice, const BlackMisc::Audio::CAudioDeviceInfo &outputDevice) = 0; + //! Get current COM unit from cockpit + //! \remark cross context + //! @{ + BlackMisc::Aviation::CComSystem getOwnComSystem(BlackMisc::Aviation::CComSystem::ComUnit unit) const; + bool isComIntegratedWithSimulator() const; + //! @} - //! Set voice output volume (0..300) - virtual void setVoiceOutputVolume(int volume) = 0; + //! Changed cockpit + //! \remark cross context + void xCtxChangedAircraftCockpit(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const BlackMisc::CIdentifier &originator); - //! Voice output volume (0..300) - virtual int getVoiceOutputVolume() const = 0; + //! Network connection status + void xCtxNetworkConnectionStatusChanged(const BlackMisc::Network::CConnectionStatus &from, const BlackMisc::Network::CConnectionStatus &to); - //! Set mute state - virtual void setMute(bool mute) = 0; + CActionBind m_actionPtt { BlackMisc::Input::pttHotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &IContextAudio::setVoiceTransmissionComActive }; + CActionBind m_actionPttCom1 { BlackMisc::Input::pttCom1HotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &IContextAudio::setVoiceTransmissionCom1 }; + CActionBind m_actionPttCom2 { BlackMisc::Input::pttCom2HotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &IContextAudio::setVoiceTransmissionCom2 }; + CActionBind m_actionAudioVolumeIncrease { BlackMisc::Input::audioVolumeIncreaseHotkeyAction(), BlackMisc::Input::audioVolumeIncreaseHotkeyIcon(), this, &IContextAudio::audioIncreaseVolume }; + CActionBind m_actionAudioVolumeDecrease { BlackMisc::Input::audioVolumeDecreaseHotkeyAction(), BlackMisc::Input::audioVolumeDecreaseHotkeyIcon(), this, &IContextAudio::audioDecreaseVolume }; - //! Is muted? - virtual bool isMuted() const = 0; + int m_outVolumeBeforeMute = 90; + static constexpr int MinUnmuteVolume = 20; //!< minimum volume when unmuted - //! Play SELCAL tone - virtual void playSelcalTone(const BlackMisc::Aviation::CSelcal &selcal) = 0; + // settings + BlackMisc::CSetting m_audioSettings { this, &IContextAudio::onChangedAudioSettings }; + BlackMisc::CSetting m_inputDeviceSetting { this, &IContextAudio::changeDeviceSettings }; + BlackMisc::CSetting m_outputDeviceSetting { this, &IContextAudio::changeDeviceSettings }; - //! Play notification sound - //! \param considerSettings consider settings (notification on/off), false means settings ignored - //! \param volume 0..100 - virtual void playNotification(BlackMisc::Audio::CNotificationSounds::NotificationFlag notification, bool considerSettings, int volume = -1) = 0; + // AFV + Afv::Clients::CAfvClient m_voiceClient; - //! Enable audio loopback - virtual void enableAudioLoopback(bool enable = true) = 0; + // Players + BlackSound::CSelcalPlayer *m_selcalPlayer = nullptr; + BlackSound::CNotificationPlayer m_notificationPlayer; - //! Is loobback enabled? - virtual bool isAudioLoopbackEnabled() const = 0; - - //! Get voice setup - virtual BlackMisc::Audio::CVoiceSetup getVoiceSetup() const = 0; - - //! Set voice setup - virtual void setVoiceSetup(const BlackMisc::Audio::CVoiceSetup &setup) = 0; +#ifdef Q_OS_MAC + BlackMisc::CMacOSMicrophoneAccess m_micAccess; +#endif + //! Init microphone + void delayedInitMicrophone(); }; } // ns } // ns diff --git a/src/blackcore/context/contextaudioimpl.cpp b/src/blackcore/context/contextaudioimpl.cpp index acab6ef7f..c273ef651 100644 --- a/src/blackcore/context/contextaudioimpl.cpp +++ b/src/blackcore/context/contextaudioimpl.cpp @@ -6,45 +6,15 @@ * or distributed except according to the terms contained in the LICENSE file. */ - #include "blackcore/context/contextaudioimpl.h" -#include "blackcore/context/contextnetwork.h" -#include "blackcore/context/contextownaircraft.h" // for COM integration -#include "blackcore/context/contextsimulator.h" // for COM intergration -#include "blackcore/application.h" -#include "blackcore/corefacade.h" -#include "blackmisc/simulation/simulatedaircraft.h" -#include "blackmisc/audio/audiodeviceinfo.h" -#include "blackmisc/audio/notificationsounds.h" -#include "blackmisc/audio/audiosettings.h" -#include "blackmisc/aviation/callsign.h" -#include "blackmisc/compare.h" #include "blackmisc/dbusserver.h" -#include "blackmisc/logcategory.h" -#include "blackmisc/logmessage.h" -#include "blackmisc/sequence.h" -#include "blackmisc/simplecommandparser.h" -#include "blackmisc/statusmessage.h" -#include "blackmisc/verify.h" #include #include #include -#include -#include using namespace BlackMisc; -using namespace BlackMisc::Aviation; -using namespace BlackMisc::Audio; -using namespace BlackMisc::Input; -using namespace BlackMisc::Audio; -using namespace BlackMisc::Network; -using namespace BlackMisc::PhysicalQuantities; -using namespace BlackMisc::Simulation; -using namespace BlackSound; -using namespace BlackCore::Vatsim; -using namespace BlackCore::Afv::Clients; namespace BlackCore { @@ -52,24 +22,8 @@ namespace BlackCore { CContextAudio::CContextAudio(CCoreFacadeConfig::ContextMode mode, CCoreFacade *runtime) : IContextAudio(mode, runtime), - CIdentifiable(this), - m_voiceClient(("https://voice1.vatsim.uk")) - { - const CSettings as = m_audioSettings.getThreadLocal(); - this->setVoiceOutputVolume(as.getOutVolume()); - m_selcalPlayer = new CSelcalPlayer(CAudioDeviceInfo::getDefaultOutputDevice(), this); - - connect(&m_voiceClient, &CAfvClient::ptt, this, &CContextAudio::ptt); - - this->changeDeviceSettings(); - QPointer myself(this); - QTimer::singleShot(5000, this, [ = ] - { - if (!myself) { return; } - if (!sApp || sApp->isShuttingDown()) { return; } - myself->onChangedAudioSettings(); - }); - } + CIdentifiable(this) + { } CContextAudio *CContextAudio::registerWithDBus(CDBusServer *server) { @@ -78,336 +32,5 @@ namespace BlackCore return this; } - CContextAudio::~CContextAudio() - { - m_voiceClient.stop(); - } - - CIdentifier CContextAudio::audioRunsWhere() const - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - static const CIdentifier i("CContextAudio"); - return i; - } - - CAudioDeviceInfoList CContextAudio::getAudioDevices() const - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - return CAudioDeviceInfoList::allDevices(); - } - - CAudioDeviceInfoList CContextAudio::getCurrentAudioDevices() const - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - - // either the devices really used, or settings - CAudioDeviceInfo inputDevice = m_voiceClient.getInputDevice(); - if (!inputDevice.isValid()) { inputDevice = CAudioDeviceInfo::getDefaultInputDevice(); } - - CAudioDeviceInfo outputDevice = m_voiceClient.getOutputDevice(); - if (!outputDevice.isValid()) { outputDevice = CAudioDeviceInfo::getDefaultOutputDevice(); } - - CAudioDeviceInfoList devices; - devices.push_back(inputDevice); - devices.push_back(outputDevice); - return devices; - } - - void CContextAudio::setCurrentAudioDevices(const CAudioDeviceInfo &inputDevice, const CAudioDeviceInfo &outputDevice) - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << inputDevice << outputDevice; } - if (!inputDevice.getName().isEmpty()) { m_inputDeviceSetting.setAndSave(inputDevice.getName()); } - if (!outputDevice.getName().isEmpty()) { m_outputDeviceSetting.setAndSave(outputDevice.getName()); } - const bool changed = m_voiceClient.restartWithNewDevices(inputDevice, outputDevice); - if (changed) - { - emit this->changedSelectedAudioDevices(this->getCurrentAudioDevices()); - } - } - - void CContextAudio::setVoiceOutputVolume(int volume) - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << volume; } - - const bool wasMuted = this->isMuted(); - volume = CSettings::fixOutVolume(volume); - - const int currentVolume = m_voiceClient.getNormalizedOutputVolume(); - const bool changedVoiceOutput = (currentVolume != volume); - if (changedVoiceOutput) - { - m_voiceClient.setNormalizedOutputVolume(volume); - m_outVolumeBeforeMute = currentVolume; - - emit this->changedAudioVolume(volume); - if ((volume > 0 && wasMuted) || (volume < 1 && !wasMuted)) - { - // inform about muted - emit this->changedMute(volume < 1); - } - } - - CSettings as(m_audioSettings.getThreadLocal()); - if (as.getOutVolume() != volume) - { - as.setOutVolume(volume); - m_audioSettings.set(as); - } - } - - int CContextAudio::getVoiceOutputVolume() const - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - return m_voiceClient.getNormalizedOutputVolume(); - } - - void CContextAudio::setMute(bool muted) - { - if (this->isMuted() == muted) { return; } // avoid roundtrips / unnecessary signals - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << muted; } - - if (m_voiceClient.isMuted() == muted) { return; } - m_voiceClient.setMuted(muted); - - // signal - emit this->changedMute(muted); - } - - bool CContextAudio::isMuted() const - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - return m_voiceClient.isMuted(); - } - - void CContextAudio::playSelcalTone(const CSelcal &selcal) - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << selcal; } - const CTime t = m_selcalPlayer->play(90, selcal); - const int ms = t.toMs(); - if (ms > 10) - { - // As of https://dev.swift-project.org/T558 play additional notification - const QPointer myself(this); - QTimer::singleShot(ms, this, [ = ] - { - if (!sApp || sApp->isShuttingDown() || !myself) { return; } - this->playNotification(CNotificationSounds::NotificationTextMessageSupervisor, true); - }); - } - } - - void CContextAudio::playNotification(CNotificationSounds::NotificationFlag notification, bool considerSettings, int volume) - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << notification; } - - const CSettings settings = m_audioSettings.getThreadLocal(); - const bool play = !considerSettings || settings.isNotificationFlagSet(notification); - if (!play) { return; } - if (notification == CNotificationSounds::PTTClickKeyDown && (considerSettings && settings.noAudioTransmission())) - { - /** - if (!this->canTalk()) - { - // warning sound - notification = CNotificationSounds::NotificationNoAudioTransmission; - } - **/ - } - - if (volume < 0 || volume > 100) - { - volume = 90; - if (considerSettings) { volume = qMax(25, settings.getNotificationVolume()); } - } - m_notificationPlayer.play(notification, volume); - } - - void CContextAudio::enableAudioLoopback(bool enable) - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - m_voiceClient.setLoopBack(enable); - } - - bool CContextAudio::isAudioLoopbackEnabled() const - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - return m_voiceClient.isLoopback(); - } - - void CContextAudio::setVoiceSetup(const CVoiceSetup &setup) - { - // could be recycled for some AFV setup - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - Q_UNUSED(setup) - } - - CVoiceSetup CContextAudio::getVoiceSetup() const - { - if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } - return CVoiceSetup(); - } - - bool CContextAudio::parseCommandLine(const QString &commandLine, const BlackMisc::CIdentifier &originator) - { - Q_UNUSED(originator) - if (commandLine.isEmpty()) { return false; } - CSimpleCommandParser parser( - { - ".vol", ".volume", // output volume - ".mute", // mute - ".unmute" // unmute - }); - parser.parse(commandLine); - if (!parser.isKnownCommand()) { return false; } - - if (parser.matchesCommand(".mute")) - { - this->setMute(true); - return true; - } - else if (parser.matchesCommand(".unmute")) - { - this->setMute(false); - return true; - } - else if (parser.commandStartsWith("vol") && parser.countParts() > 1) - { - int v = parser.toInt(1); - this->setVoiceOutputVolume(v); - } - return false; - } - - void CContextAudio::setVoiceTransmission(bool enable, PTTCOM com) - { - m_voiceClient.setPttForCom(enable, com); - } - - void CContextAudio::setVoiceTransmissionCom1(bool enabled) - { - this->setVoiceTransmission(enabled, COM1); - } - - void CContextAudio::setVoiceTransmissionCom2(bool enabled) - { - this->setVoiceTransmission(enabled, COM2); - } - - void CContextAudio::setVoiceTransmissionComActive(bool enabled) - { - this->setVoiceTransmission(enabled, COMActive); - } - - void CContextAudio::changeDeviceSettings() - { - const QString inputDeviceName = m_inputDeviceSetting.get(); - CAudioDeviceInfo input; - if (!inputDeviceName.isEmpty()) - { - const CAudioDeviceInfoList inputDevs = this->getAudioInputDevices(); - input = inputDevs.findByName(inputDeviceName); - } - - const QString outputDeviceName = m_outputDeviceSetting.get(); - CAudioDeviceInfo output; - if (!outputDeviceName.isEmpty()) - { - const CAudioDeviceInfoList outputDevs = this->getAudioOutputDevices(); - output = outputDevs.findByName(outputDeviceName); - } - - this->setCurrentAudioDevices(input, output); - } - - void CContextAudio::onChangedAudioSettings() - { - const CSettings s = m_audioSettings.get(); - const QString dir = s.getNotificationSoundDirectory(); - m_notificationPlayer.updateDirectory(dir); - this->setVoiceOutputVolume(s.getOutVolume()); - } - - void CContextAudio::audioIncreaseVolume(bool enabled) - { - if (!enabled) { return; } - const int v = qRound(this->getVoiceOutputVolume() * 1.2); - this->setVoiceOutputVolume(v); - } - - void CContextAudio::audioDecreaseVolume(bool enabled) - { - if (!enabled) { return; } - const int v = qRound(this->getVoiceOutputVolume() / 1.2); - this->setVoiceOutputVolume(v); - } - - CComSystem CContextAudio::getOwnComSystem(CComSystem::ComUnit unit) const - { - if (!this->getIContextOwnAircraft()) - { - // context not available - const double defFreq = 122.8; - switch (unit) - { - case CComSystem::Com1: return CComSystem::getCom1System(defFreq, defFreq); - case CComSystem::Com2: return CComSystem::getCom2System(defFreq, defFreq); - default: break; - } - return CComSystem::getCom1System(defFreq, defFreq); - } - return this->getIContextOwnAircraft()->getOwnComSystem(unit); - } - - bool CContextAudio::isComIntegratedWithSimulator() const - { - if (!this->getIContextSimulator()) { return false; } - return this->getIContextSimulator()->getSimulatorSettings().isComIntegrated(); - } - - void CContextAudio::xCtxChangedAircraftCockpit(const CSimulatedAircraft &aircraft, const CIdentifier &originator) - { - Q_UNUSED(aircraft) - Q_UNUSED(originator) - - /** - if (CIdentifiable::isMyIdentifier(originator)) { return; } - const bool integrated = this->isComIntegratedWithSimulator(); - if (integrated) - { - // set as in cockpit - const bool com1Rec = aircraft.getCom1System().isReceiveEnabled(); - const bool com2Rec = aircraft.getCom2System().isReceiveEnabled(); - } - **/ - } - - void CContextAudio::xCtxNetworkConnectionStatusChanged(const CConnectionStatus &from, const CConnectionStatus &to) - { - Q_UNUSED(from) - BLACK_VERIFY_X(this->getIContextNetwork(), Q_FUNC_INFO, "Missing network context"); - if (to.isConnected() && this->getIContextNetwork()) - { - const CUser connectedUser = this->getIContextNetwork()->getConnectedServer().getUser(); - m_voiceClient.connectTo(connectedUser.getId(), connectedUser.getPassword(), connectedUser.getCallsign().asString()); - m_voiceClient.start(CAudioDeviceInfo::getDefaultInputDevice(), CAudioDeviceInfo::getDefaultOutputDevice(), {0, 1}); - } - else if (to.isDisconnected()) - { - m_voiceClient.stop(); - m_voiceClient.disconnectFrom(); - } - } - - /** - #ifdef Q_OS_MAC - void CContextAudio::delayedInitMicrophone() - { - m_voiceInputDevice = m_voice->createInputDevice(); - m_voice->connectVoice(m_voiceInputDevice.get(), m_audioMixer.get(), IAudioMixer::InputMicrophone); - CLogMessage(this).info(u"MacOS delayed input device init"); - } - #endif - **/ - } // namespace } // namespace diff --git a/src/blackcore/context/contextaudioimpl.h b/src/blackcore/context/contextaudioimpl.h index 701df7a01..5dfe9b87e 100644 --- a/src/blackcore/context/contextaudioimpl.h +++ b/src/blackcore/context/contextaudioimpl.h @@ -12,29 +12,10 @@ #define BLACKCORE_CONTEXT_CONTEXTAUDIO_IMPL_H #include "blackcore/context/contextaudio.h" -#include "blackcore/afv/clients/afvclient.h" -#include "blackcore/audio/audiosettings.h" -#include "blackcore/actionbind.h" #include "blackcore/corefacadeconfig.h" #include "blackcore/blackcoreexport.h" -#include "blacksound/selcalplayer.h" -#include "blacksound/notificationplayer.h" -#include "blackmisc/audio/audiosettings.h" -#include "blackmisc/audio/audiodeviceinfolist.h" -#include "blackmisc/audio/notificationsounds.h" -#include "blackmisc/audio/voiceroomlist.h" -#include "blackmisc/audio/ptt.h" -#include "blackmisc/input/actionhotkeydefs.h" -#include "blackmisc/aviation/callsignset.h" -#include "blackmisc/aviation/comsystem.h" -#include "blackmisc/aviation/selcal.h" -#include "blackmisc/macos/microphoneaccess.h" -#include "blackmisc/identifiable.h" -#include "blackmisc/identifier.h" #include "blackmisc/network/userlist.h" -#include "blackmisc/settingscache.h" -#include "blackmisc/icons.h" -#include "blackmisc/network/connectionstatus.h" +#include "blackmisc/identifiable.h" #include #include @@ -45,13 +26,6 @@ // clazy:excludeall=const-signal-or-slot -namespace BlackMisc -{ - class CDBusServer; - namespace Audio { class CAudioDeviceInfo; } - namespace Aviation { class CCallsign; } -} - namespace BlackCore { class CCoreFacade; @@ -69,113 +43,19 @@ namespace BlackCore friend class BlackCore::CCoreFacade; friend class IContextAudio; - public: - //! Destructor - virtual ~CContextAudio() override; - - //! Reference to voice client - BlackCore::Afv::Clients::CAfvClient &voiceClient() { return m_voiceClient; } - public slots: // Interface implementations //! \publicsection //! @{ - virtual BlackMisc::CIdentifier audioRunsWhere() const override; - virtual BlackMisc::Audio::CAudioDeviceInfoList getAudioDevices() const override; - virtual BlackMisc::Audio::CAudioDeviceInfoList getCurrentAudioDevices() const override; - virtual void setCurrentAudioDevices(const BlackMisc::Audio::CAudioDeviceInfo &audioDevice, const BlackMisc::Audio::CAudioDeviceInfo &outputDevice) override; - virtual void setVoiceOutputVolume(int volume) override; - virtual int getVoiceOutputVolume() const override; - virtual void setMute(bool muted) override; - virtual bool isMuted() const override; - virtual void playSelcalTone(const BlackMisc::Aviation::CSelcal &selcal) override; - virtual void playNotification(BlackMisc::Audio::CNotificationSounds::NotificationFlag notification, bool considerSettings, int volume = -1) override; - virtual void enableAudioLoopback(bool enable = true) override; - virtual bool isAudioLoopbackEnabled() const override; - virtual BlackMisc::Audio::CVoiceSetup getVoiceSetup() const override; - virtual void setVoiceSetup(const BlackMisc::Audio::CVoiceSetup &setup) override; + // ---- FUNCTIONS GO HERE ---- //! @} - //! \addtogroup swiftdotcommands - //! @{ - //!
-            //! .mute                          mute             BlackCore::Context::CContextAudio
-            //! .unmute                        unmute           BlackCore::Context::CContextAudio
-            //! .vol .volume   volume 0..100   set volume       BlackCore::Context::CContextAudio
-            //! 
- //! @} - //! \copydoc IContextAudio::parseCommandLine - virtual bool parseCommandLine(const QString &commandLine, const BlackMisc::CIdentifier &originator) override; - protected: //! Constructor CContextAudio(CCoreFacadeConfig::ContextMode mode, CCoreFacade *runtime); //! Register myself in DBus CContextAudio *registerWithDBus(BlackMisc::CDBusServer *server); - - private: - //! Enable/disable voice transmission, nornally used with hotkey @{ - void setVoiceTransmission(bool enable, BlackMisc::Audio::PTTCOM com); - void setVoiceTransmissionCom1(bool enabled); - void setVoiceTransmissionCom2(bool enabled); - void setVoiceTransmissionComActive(bool enabled); - //! @} - - //! Connection in transition - bool inTransitionState() const; - - //! Change the device settings - void changeDeviceSettings(); - - //! Changed audio settings - void onChangedAudioSettings(); - - //! Audio increase/decrease volume @{ - void audioIncreaseVolume(bool enabled); - void audioDecreaseVolume(bool enabled); - //! @} - - //! Get current COM unit from cockpit - //! \remark cross context - //! @{ - BlackMisc::Aviation::CComSystem getOwnComSystem(BlackMisc::Aviation::CComSystem::ComUnit unit) const; - bool isComIntegratedWithSimulator() const; - //! @} - - //! Changed cockpit - //! \remark cross context - void xCtxChangedAircraftCockpit(const BlackMisc::Simulation::CSimulatedAircraft &aircraft, const BlackMisc::CIdentifier &originator); - - //! Network connection status - void xCtxNetworkConnectionStatusChanged(const BlackMisc::Network::CConnectionStatus &from, const BlackMisc::Network::CConnectionStatus &to); - - CActionBind m_actionPtt { BlackMisc::Input::pttHotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &CContextAudio::setVoiceTransmissionComActive }; - CActionBind m_actionPttCom1 { BlackMisc::Input::pttCom1HotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &CContextAudio::setVoiceTransmissionCom1 }; - CActionBind m_actionPttCom2 { BlackMisc::Input::pttCom2HotkeyAction(), BlackMisc::Input::pttHotkeyIcon(), this, &CContextAudio::setVoiceTransmissionCom2 }; - CActionBind m_actionAudioVolumeIncrease { BlackMisc::Input::audioVolumeIncreaseHotkeyAction(), BlackMisc::Input::audioVolumeIncreaseHotkeyIcon(), this, &CContextAudio::audioIncreaseVolume }; - CActionBind m_actionAudioVolumeDecrease { BlackMisc::Input::audioVolumeDecreaseHotkeyAction(), BlackMisc::Input::audioVolumeDecreaseHotkeyIcon(), this, &CContextAudio::audioDecreaseVolume }; - - int m_outVolumeBeforeMute = 90; - static constexpr int MinUnmuteVolume = 20; //!< minimum volume when unmuted - - /** - #ifdef Q_OS_MAC - BlackMisc::CMacOSMicrophoneAccess m_micAccess; - void delayedInitMicrophone(); - #endif - **/ - - BlackSound::CSelcalPlayer *m_selcalPlayer = nullptr; - BlackSound::CNotificationPlayer m_notificationPlayer; - - // settings - BlackMisc::CSetting m_audioSettings { this, &CContextAudio::onChangedAudioSettings }; - BlackMisc::CSetting m_inputDeviceSetting { this, &CContextAudio::changeDeviceSettings }; - BlackMisc::CSetting m_outputDeviceSetting { this, &CContextAudio::changeDeviceSettings }; - - // AFV - Afv::Clients::CAfvClient m_voiceClient; }; } // namespace } // namespace