/* 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 #include using namespace BlackMisc::Audio; using namespace BlackMisc::Aviation; namespace BlackCore { /* * Constructor */ CVoiceVatlib::CVoiceVatlib(QObject *parent) : IVoice(parent), m_voice(Cvatlib_Voice_Simple::Create()), m_audioOutput(new QAudioOutput()), m_inputSquelch(-1), m_micTestResult(Cvatlib_Voice_Simple::agc_Ok), m_temporaryUserRoomIndex(CVoiceVatlib::InvalidRoomIndex), m_lockVoiceRooms(QReadWriteLock::Recursive), m_lockCallsigns(QReadWriteLock::Recursive), m_lockCurrentOutputDevice(QReadWriteLock::Recursive), m_lockCurrentInputDevice(QReadWriteLock::Recursive), m_lockDeviceList(QReadWriteLock::Recursive), m_lockOutputEnabled(QReadWriteLock::Recursive), m_lockSquelch(QReadWriteLock::Recursive), m_lockTestResult(QReadWriteLock::Recursive), m_lockMyCallsign(QReadWriteLock::Recursive), m_lockConnectionStatus(QReadWriteLock::Recursive), m_mutexVatlib(QMutex::Recursive) { try { m_voice->Setup(true, 3290, 2, 1, onRoomStatusUpdate, this); m_voice->GetInputDevices(onInputHardwareDeviceReceived, this); m_voice->GetOutputDevices(onOutputHardwareDeviceReceived, this); // TODO: read audio device settings here and init with the same devices // If not, settings are there or it is the first run, use the default one // TODO: KB, setting this kind of default device results in an error // setInputDevice(defaultAudioInputDevice()); // setOutputDevice(defaultAudioOutputDevice()); connect(this, &CVoiceVatlib::userJoinedLeft, this, &CVoiceVatlib::onUserJoinedLeft, Qt::QueuedConnection); this->m_voiceRooms.push_back(CVoiceRoom()); // COM1 this->m_voiceRooms.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); // do processing this->startTimer(10); } catch (...) { this->exceptionDispatcher(Q_FUNC_INFO); } } /* * Destructor */ CVoiceVatlib::~CVoiceVatlib() {} /* * Devices */ const BlackMisc::Audio::CAudioDeviceList &CVoiceVatlib::audioDevices() const { QReadLocker lockForReading(&m_lockDeviceList); return m_devices; } /* * Default input device */ const BlackMisc::Audio::CAudioDevice CVoiceVatlib::defaultAudioInputDevice() const { // Constructor creates already a default device return BlackMisc::Audio::CAudioDevice(BlackMisc::Audio::CAudioDevice::InputDevice, BlackMisc::Audio::CAudioDevice::defaultDeviceIndex(), "default"); } /* * Default output device */ const BlackMisc::Audio::CAudioDevice CVoiceVatlib::defaultAudioOutputDevice() const { // Constructor creates already a default device return BlackMisc::Audio::CAudioDevice(BlackMisc::Audio::CAudioDevice::OutputDevice, BlackMisc::Audio::CAudioDevice::defaultDeviceIndex(), "default"); } /* * Current output device */ CAudioDevice CVoiceVatlib::getCurrentOutputDevice() const { QReadLocker lockForReading(&m_lockCurrentOutputDevice); return m_currentOutputDevice; } /* * Current input device */ CAudioDevice CVoiceVatlib::getCurrentInputDevice() const { QReadLocker lockForReading(&m_lockCurrentInputDevice); return m_currentInputDevice; } /* * Set input device */ void CVoiceVatlib::setInputDevice(const BlackMisc::Audio::CAudioDevice &device) { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); if (!device.isValid()) { qWarning() << "Cannot set invalid input device!"; return; } try { if (!m_voice->SetInputDevice(device.getIndex())) { qWarning() << "Setting input device failed"; } if (!m_voice->IsInputDeviceAlive()) { qWarning() << "Input device hit a fatal error"; } QWriteLocker lockForWriting(&m_lockCurrentInputDevice); this->m_currentInputDevice = device; } catch (...) { exceptionDispatcher(Q_FUNC_INFO); } } /* * Set output device */ void CVoiceVatlib::setOutputDevice(const BlackMisc::Audio::CAudioDevice &device) { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); if (!device.isValid()) { qWarning() << "Cannot set invalid output device!"; return; } try { // there is no return value here: https://dev.vatsim-germany.org/issues/115 m_voice->SetOutputDevice(0, device.getIndex()); if (!m_voice->IsOutputDeviceAlive(0)) { qWarning() << "Output device hit a fatal error"; } QWriteLocker lockForWriting(&m_lockCurrentOutputDevice); this->m_currentOutputDevice = device; } catch (...) { exceptionDispatcher(Q_FUNC_INFO); } } /* * Get voice rooms, with the latest status updated */ BlackMisc::Audio::CVoiceRoomList CVoiceVatlib::getComVoiceRoomsWithAudioStatus() const { QReadLocker lockForReading(&m_lockVoiceRooms); Q_ASSERT_X(m_voiceRooms.size() == 2, "CVoiceVatlib", "Wrong numer of COM voice rooms"); CVoiceRoomList voiceRooms; QMutexLocker lockerVatlib(&m_mutexVatlib); if (m_voice->IsValid() && m_voice->IsSetup()) { // valid state, update CVoiceRoom com1 = this->m_voiceRooms[0]; CVoiceRoom com2 = this->m_voiceRooms[1]; com1.setConnected(m_voice->IsRoomConnected(static_cast(COM1))); com2.setConnected(m_voice->IsRoomConnected(static_cast(COM2))); com1.setAudioPlaying(com1.isConnected() ? m_voice->IsAudioPlaying(static_cast(COM1)) : false); com2.setAudioPlaying(com2.isConnected() ? m_voice->IsAudioPlaying(static_cast(COM2)) : false); voiceRooms.push_back(com1); voiceRooms.push_back(com2); } else { CVoiceRoom def; voiceRooms.push_back(def); voiceRooms.push_back(def); } return voiceRooms; } /* * Voice room callsigns */ CCallsignList CVoiceVatlib::getVoiceRoomCallsigns(const IVoice::ComUnit comUnit) const { QReadLocker lockForReading(&m_lockCallsigns); CCallsignList callsigns; if (!this->m_voiceRoomCallsigns.contains(comUnit)) return callsigns; return this->m_voiceRoomCallsigns[comUnit]; } /* * Enable audio */ void CVoiceVatlib::switchAudioOutput(const ComUnit comUnit, bool enable) { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); Q_ASSERT_X(m_voice->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); try { m_voice->SetOutputState(static_cast(comUnit), 0, enable); QWriteLocker lockForWriting(&m_lockOutputEnabled); this->m_outputEnabled[comUnit] = enable; } catch (...) { this->exceptionDispatcher(Q_FUNC_INFO); } } /* * Squelch test */ void CVoiceVatlib::runSquelchTest() { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); try { m_voice->BeginFindSquelch(); } catch (...) { this->exceptionDispatcher(Q_FUNC_INFO); } // Start the timer only if no exception was thrown before QTimer::singleShot(5000, this, SLOT(onEndFindSquelch())); } /* * Start microphone test */ void CVoiceVatlib::runMicrophoneTest() { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); try { m_voice->BeginMicTest(); // Start the timer only if no exception was thrown before QTimer::singleShot(5000, this, SLOT(onEndMicTest())); } catch (...) { this->exceptionDispatcher(Q_FUNC_INFO); } } /* * Input squelch volume */ float CVoiceVatlib::inputSquelch() const { QReadLocker lockForReading(&m_lockSquelch); return m_inputSquelch; } /* * Mic test */ qint32 CVoiceVatlib::micTestResult() const { QReadLocker lockForReading(&m_lockTestResult); return m_micTestResult; } /* * Mic test, result as string */ QString CVoiceVatlib::micTestResultAsString() const { QString result; QReadLocker lockForReading(&m_lockTestResult); switch (m_micTestResult) { case Cvatlib_Voice_Simple::agc_Ok: result = "The test went ok"; break; case Cvatlib_Voice_Simple::agc_BkgndNoiseLoud: result = "The overall background noise is very loud and may be a nuisance to others"; break; case Cvatlib_Voice_Simple::agc_TalkDrownedOut: result = "The overall background noise is loud enough that others probably wont be able to distinguish speech from it"; break; case Cvatlib_Voice_Simple::agc_TalkMicHot: result = "The overall mic volume is too hot, you should lower the volume in the windows volume control panel"; break; case Cvatlib_Voice_Simple::agc_TalkMicCold: result = "The overall mic volume is too cold, you should raise the volume in the windows control panel and enable mic boost if needed"; break; default: result = "Unknown result."; break; } return result; } /* * Callsign */ void CVoiceVatlib::setMyAircraftCallsign(const BlackMisc::Aviation::CCallsign &callsign) { QWriteLocker lockForWriting(&m_lockMyCallsign); m_aircraftCallsign = callsign; } /* * Voice room */ void CVoiceVatlib::joinVoiceRoom(const ComUnit comUnit, const BlackMisc::Audio::CVoiceRoom &voiceRoom) { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); Q_ASSERT_X(m_voice->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); if (!voiceRoom.isValid()) { qDebug() << "Error: Cannot join invalid voice room."; return; } try { CVoiceRoom vr = this->voiceRoomForUnit(comUnit); if (vr.isConnected()) return; // already joined vr = voiceRoom; vr.setConnected(true); this->setVoiceRoomForUnit(comUnit, vr); changeConnectionStatus(comUnit, Connecting); QString serverSpec = voiceRoom.getVoiceRoomUrl(); QReadLocker lockForReading(&m_lockMyCallsign); m_voice->JoinRoom(static_cast(comUnit), m_aircraftCallsign.toQString().toLatin1().constData(), serverSpec.toLatin1().constData()); } catch (...) { this->exceptionDispatcher(Q_FUNC_INFO); } } /* * Leave a room */ void CVoiceVatlib::leaveVoiceRoom(const ComUnit comUnit) { CVoiceRoom vr = this->voiceRoomForUnit(comUnit); if (!vr.isConnected()) return; QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); Q_ASSERT_X(m_voice->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); try { m_voice->LeaveRoom(static_cast(comUnit)); vr.setConnected(false); this->setVoiceRoomForUnit(comUnit, vr); 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_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); Q_ASSERT_X(m_voice->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); try { m_voice->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_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); Q_ASSERT_X(m_voice->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); try { m_voice->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_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); Q_ASSERT_X(m_voice->IsRoomValid(static_cast(comUnit)), "CVoiceVatlib", "Room index out of bounds!"); try { m_voice->SetMicState(static_cast(comUnit), false); } catch (...) { this->exceptionDispatcher(Q_FUNC_INFO); } } /* * Change room status */ void CVoiceVatlib::changeRoomStatus(ComUnit comUnit, Cvatlib_Voice_Simple::roomStatusUpdate roomStatus) { 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(); 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; } } /* * Process voice handling */ void CVoiceVatlib::timerEvent(QTimerEvent *) { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); try { this->m_voice->DoProcessing(); } catch (...) { this->exceptionDispatcher(Q_FUNC_INFO); } } /* * Find squelch */ void CVoiceVatlib::onEndFindSquelch() { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); try { m_voice->EndFindSquelch(); QWriteLocker lockForWriting(&m_lockSquelch); m_inputSquelch = m_voice->GetInputSquelch(); emit squelchTestFinished(); } catch (...) { this->exceptionDispatcher(Q_FUNC_INFO); } } void CVoiceVatlib::onEndMicTest() { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->IsSetup(), "CVoiceVatlib", "Cvatlib_Voice_Simple invalid or not setup!"); try { QWriteLocker lockForWriting(&m_lockTestResult); m_micTestResult = m_voice->EndMicTest(); emit micTestFinished(); } catch (...) { this->exceptionDispatcher(Q_FUNC_INFO); } } /* * User joined / left room */ void CVoiceVatlib::onUserJoinedLeft(const ComUnit comUnit) { QMutexLocker lockerVatlib(&m_mutexVatlib); Q_ASSERT_X(m_voice->IsValid() && m_voice->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... if (!m_voice->IsRoomConnected(static_cast(comUnit))) 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_voice->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 ************************************/ /********************************** * * * * * * * * * * * * * * * * * * * ************************************/ /*! * \brief Cast from *void to *CVoiceVatlib * \param cbvar * \return */ CVoiceVatlib *cbvar_cast_voice(void *cbvar) { return static_cast(cbvar); } /* * Room status update */ 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)); } /* * Input hardware received */ void CVoiceVatlib::onInputHardwareDeviceReceived(Cvatlib_Voice_Simple *obj, const char *name, void *cbVar) { Q_UNUSED(obj) BlackMisc::Audio::CAudioDevice inputDevice(BlackMisc::Audio::CAudioDevice::InputDevice, cbvar_cast_voice(cbVar)->m_devices.count(BlackMisc::Audio::CAudioDevice::InputDevice), QString(name)); QWriteLocker lockForWriting(&(cbvar_cast_voice(cbVar)->m_lockDeviceList)); cbvar_cast_voice(cbVar)->m_devices.push_back(inputDevice); } /* * Output hardware received */ void CVoiceVatlib::onOutputHardwareDeviceReceived(Cvatlib_Voice_Simple *obj, const char *name, void *cbVar) { Q_UNUSED(obj) BlackMisc::Audio::CAudioDevice outputDevice(BlackMisc::Audio::CAudioDevice::OutputDevice, cbvar_cast_voice(cbVar)->m_devices.count(BlackMisc::Audio::CAudioDevice::OutputDevice), QString(name)); QWriteLocker lockForWriting(&(cbvar_cast_voice(cbVar)->m_lockDeviceList)); 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 */ void CVoiceVatlib::exceptionDispatcher(const char *caller) { 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 this->exception(msg); qDebug() << "NetworkNotConnectedException caught in " << caller << "\n" << e.what(); } catch (const VatlibException &e) { msg.append("VatlibException").append(" ").append(e.what()); emit this->exception(msg, true); qFatal("VatlibException caught in %s\n%s", caller, e.what()); } catch (const std::exception &e) { msg.append("std::exception").append(" ").append(e.what()); emit this->exception(msg, true); qFatal("std::exception caught in %s\n%s", caller, e.what()); } catch (...) { msg.append("unknown exception"); emit this->exception(msg, true); qFatal("Unknown exception caught in %s", caller); } } // Change voice room status and emit signal void CVoiceVatlib::changeConnectionStatus(ComUnit comUnit, ConnectionStatus newStatus) { QWriteLocker lockForWriting(&m_lockConnectionStatus); ConnectionStatus currentStatus = m_connectionStatus.value(comUnit); if (newStatus != currentStatus) { m_connectionStatus.insert(comUnit, newStatus); emit connectionStatusChanged(comUnit, currentStatus, newStatus); } } } // namespace