/* Copyright (C) 2013 * swift Project Community / Contributors * * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, * or distributed except according to the terms contained in the LICENSE file. */ #include "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/audiodevice.h" #include "blackcore/corefacade.h" #include "blackcore/voice.h" #include "blackcore/vatsim/voicevatlib.h" #include "blackmisc/simulation/simulatedaircraft.h" #include "blackmisc/audio/audiodeviceinfo.h" #include "blackmisc/audio/notificationsounds.h" #include "blackmisc/audio/audiosettings.h" #include "blackmisc/audio/voiceroomlist.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 "blacksound/soundgenerator.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; namespace BlackCore { namespace Context { CContextAudio::CContextAudio(CCoreFacadeConfig::ContextMode mode, CCoreFacade *runtime) : IContextAudio(mode, runtime), CIdentifiable(this), m_voice(new CVoiceVatlib()), m_voiceClient(("https://voice1.vatsim.uk")) { initVoiceChannels(); initInputDevice(); initOutputDevice(); initAudioMixer(); this->setVoiceOutputVolume(m_audioSettings.getThreadLocal().getOutVolume()); m_selcalPlayer = new CSelcalPlayer(QAudioDeviceInfo::defaultOutputDevice(), this); this->changeDeviceSettings(); QPointer myself(this); QTimer::singleShot(5000, this, [ = ] { if (!myself) { return; } if (!sApp || sApp->isShuttingDown()) { return; } myself->onChangedAudioSettings(); }); } CContextAudio *CContextAudio::registerWithDBus(CDBusServer *server) { if (!server || m_mode != CCoreFacadeConfig::LocalInDBusServer) { return this; } server->addObject(IContextAudio::ObjectPath(), this); return this; } void CContextAudio::initVoiceChannels() { //! \todo KB 2018-11 those are supposed to be Qt::QueuedConnection, but not yet changed (risk to break something) m_channel1 = m_voice->createVoiceChannel(); connect(m_channel1.data(), &IVoiceChannel::connectionStatusChanged, this, &CContextAudio::onConnectionStatusChanged); connect(m_channel1.data(), &IVoiceChannel::userJoinedRoom, this, &CContextAudio::onUserJoinedRoom); connect(m_channel1.data(), &IVoiceChannel::userLeftRoom, this, &CContextAudio::onUserLeftRoom); m_channel2 = m_voice->createVoiceChannel(); connect(m_channel2.data(), &IVoiceChannel::connectionStatusChanged, this, &CContextAudio::onConnectionStatusChanged); connect(m_channel2.data(), &IVoiceChannel::userJoinedRoom, this, &CContextAudio::onUserJoinedRoom); connect(m_channel2.data(), &IVoiceChannel::userLeftRoom, this, &CContextAudio::onUserLeftRoom); m_unusedVoiceChannels.push_back(m_channel1); m_unusedVoiceChannels.push_back(m_channel2); m_voiceChannelOutputPortMapping[m_channel1] = IAudioMixer::OutputVoiceChannel1; m_voiceChannelOutputPortMapping[m_channel2] = IAudioMixer::OutputVoiceChannel2; } void CContextAudio::initInputDevice() { #ifdef Q_OS_MAC CMacOSMicrophoneAccess::AuthorizationStatus status = m_micAccess.getAuthorizationStatus(); if (status == CMacOSMicrophoneAccess::Authorized) { m_voiceInputDevice = m_voice->createInputDevice(); CLogMessage(this).info(u"MacOS specific input device init"); } else if (status == CMacOSMicrophoneAccess::NotDetermined) { m_voiceInputDevice.reset(new CAudioInputDeviceDummy(this)); connect(&m_micAccess, &CMacOSMicrophoneAccess::permissionRequestAnswered, this, &CContextAudio::delayedInitMicrophone); m_micAccess.requestAccess(); CLogMessage(this).info(u"MacOS requested input device"); } else { m_voiceInputDevice.reset(new CAudioInputDeviceDummy(this)); CLogMessage(this).error(u"Microphone access not granted. Voice input will not work."); } #else m_voiceInputDevice = m_voice->createInputDevice(); #endif } void CContextAudio::initOutputDevice() { m_voiceOutputDevice = m_voice->createOutputDevice(); } void CContextAudio::initAudioMixer() { m_audioMixer = m_voice->createAudioMixer(); if (! m_voiceInputDevice->isDummyDevice()) { m_voice->connectVoice(m_voiceInputDevice.get(), m_audioMixer.get(), IAudioMixer::InputMicrophone); } m_voice->connectVoice(m_channel1.data(), m_audioMixer.get(), IAudioMixer::InputVoiceChannel1); m_voice->connectVoice(m_channel2.data(), m_audioMixer.get(), IAudioMixer::InputVoiceChannel2); m_voice->connectVoice(m_audioMixer.get(), IAudioMixer::OutputDevice1, m_voiceOutputDevice.get()); m_voice->connectVoice(m_audioMixer.get(), IAudioMixer::OutputVoiceChannel1, m_channel1.data()); m_voice->connectVoice(m_audioMixer.get(), IAudioMixer::OutputVoiceChannel2, m_channel2.data()); m_audioMixer->makeMixerConnection(IAudioMixer::InputVoiceChannel1, IAudioMixer::OutputDevice1); m_audioMixer->makeMixerConnection(IAudioMixer::InputVoiceChannel2, IAudioMixer::OutputDevice1); } CContextAudio::~CContextAudio() { this->leaveAllVoiceRooms(); } CVoiceRoomList CContextAudio::getComVoiceRoomsWithAudioStatus() const { Q_ASSERT(m_voice); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } return this->getComVoiceRooms(); } CVoiceRoomList CContextAudio::getComVoiceRooms() const { Q_ASSERT(m_voice); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } CVoiceRoomList voiceRoomList; QSharedPointer voiceChannelCom1 = m_voiceChannelMapping.value(CComSystem::Com1); if (voiceChannelCom1) { CVoiceRoom room = voiceChannelCom1->getVoiceRoom(); voiceRoomList.push_back(room); } else { voiceRoomList.push_back(CVoiceRoom()); } QSharedPointer voiceChannelCom2 = m_voiceChannelMapping.value(CComSystem::Com2); if (voiceChannelCom2) { CVoiceRoom room = voiceChannelCom2->getVoiceRoom(); voiceRoomList.push_back(room); } else { voiceRoomList.push_back(CVoiceRoom()); } return voiceRoomList; } bool CContextAudio::canTalk() const { const CVoiceRoomList rooms = this->getComVoiceRoomsWithAudioStatus(); return rooms.countCanTalkTo() > 0; } void CContextAudio::leaveAllVoiceRooms() { Q_ASSERT(m_voice); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO;} m_voiceChannelMapping.clear(); if (m_channel1) { m_channel1->leaveVoiceRoom(); m_unusedVoiceChannels.push_back(m_channel1); } if (m_channel2) { m_channel2->leaveVoiceRoom(); m_unusedVoiceChannels.push_back(m_channel2); } } CIdentifier CContextAudio::audioRunsWhere() const { Q_ASSERT(m_voice); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } static const CIdentifier i("CContextAudio"); return i; } CAudioDeviceInfoList CContextAudio::getAudioDevices() const { Q_ASSERT(m_voice); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } CAudioDeviceInfoList devices = m_voiceOutputDevice->getOutputDevices(); devices.push_back(m_voiceInputDevice->getInputDevices()); return devices; } CAudioDeviceInfoList CContextAudio::getCurrentAudioDevices() const { Q_ASSERT(m_voice); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } CAudioDeviceInfoList devices; devices.push_back(m_voiceInputDevice->getCurrentInputDevice()); devices.push_back(m_voiceOutputDevice->getCurrentOutputDevice()); return devices; } void CContextAudio::setCurrentAudioDevice(const CAudioDeviceInfo &audioDevice) { Q_ASSERT(m_voice); Q_ASSERT(audioDevice.getType() != CAudioDeviceInfo::Unknown); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << audioDevice; } bool changed = false; if (audioDevice.getType() == CAudioDeviceInfo::InputDevice) { if (m_voiceInputDevice->getCurrentInputDevice() != audioDevice) { m_voiceInputDevice->setInputDevice(audioDevice); changed = true; } if (m_inputDeviceSetting.get() != audioDevice.getName()) { m_inputDeviceSetting.set(audioDevice.getName()); } } else { if (m_voiceOutputDevice->getCurrentOutputDevice() != audioDevice) { m_voiceOutputDevice->setOutputDevice(audioDevice); changed = true; } if (m_outputDeviceSetting.get() != audioDevice.getName()) { m_outputDeviceSetting.set(audioDevice.getName()); } } if (changed) { emit this->changedSelectedAudioDevices(this->getCurrentAudioDevices()); } } void CContextAudio::setVoiceOutputVolume(int volume) { Q_ASSERT(m_voiceOutputDevice); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << volume; } const bool wasMuted = isMuted(); // volume = qMin(CSettings::MaxAudioVolume, volume); bool changedVoiceOutput = m_voiceOutputDevice->getOutputVolume() != volume; if (changedVoiceOutput) { m_voiceOutputDevice->setOutputVolume(volume); m_outVolumeBeforeMute = m_voiceOutputDevice->getOutputVolume(); 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 { Q_ASSERT(m_voiceOutputDevice); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } return m_voiceOutputDevice->getOutputVolume(); } 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; } int newVolume; if (muted) { Q_ASSERT(m_voiceOutputDevice); m_outVolumeBeforeMute = m_voiceOutputDevice->getOutputVolume(); newVolume = 0; } else { newVolume = m_outVolumeBeforeMute < MinUnmuteVolume ? MinUnmuteVolume : m_outVolumeBeforeMute; m_outVolumeBeforeMute = newVolume; } // do not call setVoiceOutputVolume -> infinite loop if (newVolume != m_voiceOutputDevice->getOutputVolume()) { m_voiceOutputDevice->setOutputVolume(newVolume); emit this->changedAudioVolume(newVolume); } // signal emit this->changedMute(muted); } bool CContextAudio::isMuted() const { if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } return m_voiceOutputDevice->getOutputVolume() < 1; } void CContextAudio::setComVoiceRooms(const CVoiceRoomList &newRooms) { Q_ASSERT(m_voice); Q_ASSERT(newRooms.size() == 2); Q_ASSERT(getIContextOwnAircraft()); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << newRooms; } const CVoiceRoomList currentRooms = this->getComVoiceRooms(); const CVoiceRoom currentRoomCom1 = currentRooms[0]; const CVoiceRoom currentRoomCom2 = currentRooms[1]; CVoiceRoom newRoomCom1 = newRooms[0]; CVoiceRoom newRoomCom2 = newRooms[1]; const CCallsign ownCallsign(this->getIContextOwnAircraft()->getOwnAircraft().getCallsign()); const QString id = this->getIContextOwnAircraft()->getOwnAircraft().getPilotId(); bool changed = false; // changed rooms? But only compare on "URL", not status as connected etc. if (currentRoomCom1.getVoiceRoomUrl() != newRoomCom1.getVoiceRoomUrl()) { QSharedPointer oldVoiceChannel = m_voiceChannelMapping.value(CComSystem::Com1); if (oldVoiceChannel) { m_voiceChannelMapping.remove(CComSystem::Com1); // If the voice channel is not used by anybody else if (!m_voiceChannelMapping.values().contains(oldVoiceChannel)) { oldVoiceChannel->leaveVoiceRoom(); m_unusedVoiceChannels.push_back(oldVoiceChannel); } else { emit this->changedVoiceRooms(this->getComVoiceRooms(), false); } } if (newRoomCom1.isValid()) { QSharedPointer newVoiceChannel = this->getVoiceChannelBy(newRoomCom1); newVoiceChannel->setOwnAircraftCallsign(ownCallsign); newVoiceChannel->setUserId(id); const bool inUse = m_voiceChannelMapping.values().contains(newVoiceChannel); m_voiceChannelMapping.insert(CComSystem::Com1, newVoiceChannel); // If the voice channel is not used by anybody else if (!inUse) { newVoiceChannel->joinVoiceRoom(newRoomCom1); } else { emit this->changedVoiceRooms(getComVoiceRooms(), true); } } changed = true; } // changed rooms? But only compare on "URL", not status as connected etc. if (currentRoomCom2.getVoiceRoomUrl() != newRoomCom2.getVoiceRoomUrl()) { auto oldVoiceChannel = m_voiceChannelMapping.value(CComSystem::Com2); if (oldVoiceChannel) { m_voiceChannelMapping.remove(CComSystem::Com2); // If the voice channel is not used by anybody else if (!m_voiceChannelMapping.values().contains(oldVoiceChannel)) { oldVoiceChannel->leaveVoiceRoom(); m_unusedVoiceChannels.push_back(oldVoiceChannel); } else { emit this->changedVoiceRooms(this->getComVoiceRooms(), false); } } if (newRoomCom2.isValid()) { auto newVoiceChannel = getVoiceChannelBy(newRoomCom2); newVoiceChannel->setOwnAircraftCallsign(ownCallsign); newVoiceChannel->setUserId(id); bool inUse = m_voiceChannelMapping.values().contains(newVoiceChannel); m_voiceChannelMapping.insert(CComSystem::Com2, newVoiceChannel); // If the voice channel is not used by anybody else if (!inUse) { newVoiceChannel->joinVoiceRoom(newRoomCom2); } else { emit this->changedVoiceRooms(getComVoiceRooms(), true); } } changed = true; } // changed not yet used, but I keep it for debugging // changedVoiceRooms called by connectionStatusChanged; Q_UNUSED(changed); } CCallsignSet CContextAudio::getRoomCallsigns(CComSystem::ComUnit comUnitValue) const { Q_ASSERT(m_voice); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } const auto voiceChannel = m_voiceChannelMapping.value(comUnitValue); return voiceChannel ? voiceChannel->getVoiceRoomCallsigns() : CCallsignSet(); } Network::CUserList CContextAudio::getRoomUsers(CComSystem::ComUnit comUnit) const { Q_ASSERT(m_voice); Q_ASSERT(this->getRuntime()); if (!this->getRuntime()->getIContextNetwork()) return Network::CUserList(); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } return this->getIContextNetwork()->getUsersForCallsigns(this->getRoomCallsigns(comUnit)); } void CContextAudio::playSelcalTone(const CSelcal &selcal) { Q_ASSERT(m_voice); 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) { Q_ASSERT(m_voice); 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) { Q_ASSERT(m_audioMixer); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } if (enable) { m_audioMixer->makeMixerConnection(IAudioMixer::InputMicrophone, IAudioMixer::OutputDevice1); } else { m_audioMixer->removeMixerConnection(IAudioMixer::InputMicrophone, IAudioMixer::OutputDevice1); } } bool CContextAudio::isAudioLoopbackEnabled() const { Q_ASSERT(m_audioMixer); if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } return m_audioMixer->hasMixerConnection(IAudioMixer::InputMicrophone, IAudioMixer::OutputDevice1); } void CContextAudio::setVoiceSetup(const CVoiceSetup &setup) { if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } if (m_voice) { m_voice->setVoiceSetup(setup); } } CVoiceSetup CContextAudio::getVoiceSetup() const { if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; } return m_voice ? m_voice->getVoiceSetup() : 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, COM com) { // first apporach of T609 multiple COM QSharedPointer voiceChannelCom = nullptr; CComSystem::ComUnit usedUnit = CComSystem::Com1; if (com == COM1 && m_voiceChannelMapping.contains(CComSystem::Com1)) { usedUnit = CComSystem::Com1; voiceChannelCom = m_voiceChannelMapping.value(usedUnit); } else if (com == COM2 && m_voiceChannelMapping.contains(CComSystem::Com2)) { usedUnit = CComSystem::Com2; voiceChannelCom = m_voiceChannelMapping.value(usedUnit); } else if (com == COMActive && m_voiceChannelMapping.contains(CComSystem::Com1)) { usedUnit = CComSystem::Com1; voiceChannelCom = m_voiceChannelMapping.value(usedUnit); } else if (com == COMActive && m_voiceChannelMapping.contains(CComSystem::Com2)) { usedUnit = CComSystem::Com2; voiceChannelCom = m_voiceChannelMapping.value(usedUnit); } if (!voiceChannelCom) { return; } IAudioMixer::OutputPort mixerOutputPort = m_voiceChannelOutputPortMapping.value(voiceChannelCom); // use values from simulator? if (enable && this->isComIntegratedWithSimulator()) { const CComSystem comSystem = this->getOwnComSystem(usedUnit); enable = comSystem.isTransmitEnabled(); // consider muted } if (enable) { m_audioMixer->makeMixerConnection(IAudioMixer::InputMicrophone, mixerOutputPort); } else { m_audioMixer->removeMixerConnection(IAudioMixer::InputMicrophone, mixerOutputPort); } /** fixme KB 201908 to be removed if the above works if (!m_voiceChannelMapping.contains(CComSystem::Com1)) { return; } QSharedPointer voiceChannelCom1 = m_voiceChannelMapping.value(CComSystem::Com1); IAudioMixer::OutputPort mixerOutputPort = m_voiceChannelOutputPortMapping.value(voiceChannelCom1); if (enable) { m_audioMixer->makeMixerConnection(IAudioMixer::InputMicrophone, mixerOutputPort); } else { // Remove for both output ports, just in case. m_audioMixer->removeMixerConnection(IAudioMixer::InputMicrophone, IAudioMixer::OutputVoiceChannel1); m_audioMixer->removeMixerConnection(IAudioMixer::InputMicrophone, IAudioMixer::OutputVoiceChannel2); } **/ } 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::onConnectionStatusChanged( IVoiceChannel::ConnectionStatus oldStatus, IVoiceChannel::ConnectionStatus newStatus) { Q_UNUSED(oldStatus); switch (newStatus) { case IVoiceChannel::Connected: emit this->changedVoiceRooms(getComVoiceRooms(), true); break; case IVoiceChannel::Disconnecting: break; case IVoiceChannel::Connecting: break; case IVoiceChannel::ConnectingFailed: case IVoiceChannel::DisconnectedError: CLogMessage(this).warning(u"Voice channel disconnecting error"); Q_FALLTHROUGH(); case IVoiceChannel::Disconnected: emit this->changedVoiceRooms(getComVoiceRooms(), false); break; default: break; } } void CContextAudio::onUserJoinedRoom(const CCallsign & /**callsign**/) { emit this->changedVoiceRoomMembers(); } void CContextAudio::onUserLeftRoom(const CCallsign & /**callsign**/) { emit this->changedVoiceRoomMembers(); } void CContextAudio::changeDeviceSettings() { const QString inputDeviceName = m_inputDeviceSetting.get(); if (!inputDeviceName.isEmpty()) { for (auto device : m_voiceInputDevice->getInputDevices()) { if (device.getName() == inputDeviceName) { setCurrentAudioDevice(device); break; } } } const QString outputDeviceName = m_outputDeviceSetting.get(); if (!outputDeviceName.isEmpty()) { for (auto device : m_voiceOutputDevice->getOutputDevices()) { if (device.getName() == outputDeviceName) { setCurrentAudioDevice(device); break; } } } // device name } 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) { 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(); m_audioMixer->makeOrRemoveConnection(IAudioMixer::InputVoiceChannel1, IAudioMixer::OutputDevice1, com1Rec); m_audioMixer->makeOrRemoveConnection(IAudioMixer::InputVoiceChannel2, IAudioMixer::OutputDevice1, com2Rec); } else { // reset m_audioMixer->makeMixerConnectionIfNotExisting(IAudioMixer::InputVoiceChannel1, IAudioMixer::OutputDevice1); m_audioMixer->makeMixerConnectionIfNotExisting(IAudioMixer::InputVoiceChannel2, IAudioMixer::OutputDevice1); } } 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()) { CUser connectedUser = this->getIContextNetwork()->getConnectedServer().getUser(); m_voiceClient.setContextOwnAircraft(getIContextOwnAircraft()); m_voiceClient.connectTo(connectedUser.getId(), connectedUser.getPassword(), connectedUser.getCallsign().asString()); m_voiceClient.start(QAudioDeviceInfo::defaultInputDevice(), QAudioDeviceInfo::defaultOutputDevice(), {0, 1}); } else if (to.isDisconnected()) { m_voiceClient.stop(); m_voiceClient.disconnectFrom(); } } QSharedPointer CContextAudio::getVoiceChannelBy(const CVoiceRoom &voiceRoom) { QSharedPointer voiceChannel; for (const auto &channel : as_const(m_voiceChannelMapping)) { if (channel->getVoiceRoom().getVoiceRoomUrl() == voiceRoom.getVoiceRoomUrl()) { voiceChannel = channel; break; } } // If we haven't found a valid voice channel pointer, get an unused one if (!voiceChannel) { Q_ASSERT(!m_unusedVoiceChannels.isEmpty()); voiceChannel = m_unusedVoiceChannels.takeFirst(); } return voiceChannel; } #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