Files
pilotclient/src/blackcore/context_audio_impl.cpp

412 lines
14 KiB
C++

/* 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 "context_audio_impl.h"
#include "context_network.h"
#include "context_ownaircraft.h"
#include "context_application.h"
#include "blacksound/soundgenerator.h"
#include "blackmisc/notificationsounds.h"
#include "blackmisc/voiceroomlist.h"
#include <QTimer>
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::Audio;
using namespace BlackMisc::Hardware;
using namespace BlackSound;
namespace BlackCore
{
/*
* Init this context
*/
CContextAudio::CContextAudio(CRuntimeConfig::ContextMode mode, CRuntime *runtime) :
IContextAudio(mode, runtime), m_voice(nullptr), m_keyboard(nullptr)
{
// 1. Init by "voice driver"
this->m_voice = new CVoiceVatlib();
m_voice->moveToThread(&m_threadVoice);
m_threadVoice.start();
m_keyboard = BlackInput::IKeyboard::getInstance();
// 3. own aircraft, if possible
if (this->getIContextOwnAircraft()) m_voice->setMyAircraftCallsign(this->getIContextOwnAircraft()->getOwnAircraft().getCallsign());
// 4. Signal / slots
connect(this->m_voice, &CVoiceVatlib::micTestFinished, this, &CContextAudio::audioTestCompleted);
connect(this->m_voice, &CVoiceVatlib::squelchTestFinished, this, &CContextAudio::audioTestCompleted);
connect(this->m_voice, &CVoiceVatlib::connectionStatusChanged, this, &CContextAudio::ps_connectionStatusChanged);
if (this->getIContextApplication()) this->connect(this->m_voice, &IVoice::statusMessage, this->getIContextApplication(), &IContextApplication::sendStatusMessage);
// 5. load sounds (init), not possible in own thread
QTimer::singleShot(10 * 1000, this, SLOT(ps_initNotificationSounds()));
}
/*
* Cleanup
*/
CContextAudio::~CContextAudio()
{
this->leaveAllVoiceRooms();
m_threadVoice.quit();
m_threadVoice.wait(1000);
}
/*
* Voice rooms for COM
*/
CVoiceRoomList CContextAudio::getComVoiceRoomsWithAudioStatus() const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
return this->m_voice->getComVoiceRoomsWithAudioStatus();
}
/*
* Voice rooms for COM
*/
CVoiceRoom CContextAudio::getCom1VoiceRoom(bool withAudioStatus) const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO, withAudioStatus);
if (withAudioStatus)
return this->m_voice->getComVoiceRoomsWithAudioStatus()[0];
else
return this->m_voice->getComVoiceRooms()[0];
}
/*
* Voice rooms for COM
*/
CVoiceRoom CContextAudio::getCom2VoiceRoom(bool withAudioStatus) const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO, withAudioStatus);
if (withAudioStatus)
return this->m_voice->getComVoiceRoomsWithAudioStatus()[1];
else
return this->m_voice->getComVoiceRooms()[1];
}
/*
* Voice rooms for COM (const)
*/
CVoiceRoomList CContextAudio::getComVoiceRooms() const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
return this->m_voice->getComVoiceRooms();
}
/*
* Leave all voice rooms
*/
void CContextAudio::leaveAllVoiceRooms()
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
this->m_voice->leaveAllVoiceRooms();
}
/*
* Audio devices
*/
CAudioDeviceList CContextAudio::getAudioDevices() const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
return this->m_voice->audioDevices();
}
/*
* Audio default devices
*/
CAudioDeviceList CContextAudio::getCurrentAudioDevices() const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
CAudioDeviceList devices;
devices.push_back(this->m_voice->getCurrentInputDevice());
devices.push_back(this->m_voice->getCurrentOutputDevice());
return devices;
}
/*
* Set current device
*/
void CContextAudio::setCurrentAudioDevice(const CAudioDevice &audioDevice)
{
Q_ASSERT(this->m_voice);
Q_ASSERT(audioDevice.getType() != CAudioDevice::Unknown);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO, audioDevice.toQString());
if (audioDevice.getType() == CAudioDevice::InputDevice)
{
this->m_voice->setInputDevice(audioDevice);
}
else
{
this->m_voice->setOutputDevice(audioDevice);
}
}
/*
* Set volumes
*/
void CContextAudio::setVolumes(const CComSystem &com1, const CComSystem &com2)
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO, com1.toQString(), com2.toQString());
// volumes
qint32 vol1 = com1.getVolumeOutput();
qint32 vol2 = com2.getVolumeOutput();
this->m_voice->setRoomOutputVolume(IVoice::COM1, vol1);
this->m_voice->setRoomOutputVolume(IVoice::COM2, vol2);
// enable / disable in the same step
this->m_voice->switchAudioOutput(IVoice::COM1, com1.isEnabled());
this->m_voice->switchAudioOutput(IVoice::COM2, com2.isEnabled());
}
/*
* Muted?
*/
bool CContextAudio::isMuted() const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
return this->m_voice->isMuted();
}
/*
* Set voice rooms
*/
void CContextAudio::setComVoiceRooms(const CVoiceRoomList &newRooms)
{
Q_ASSERT(this->m_voice);
Q_ASSERT(newRooms.size() == 2);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO, newRooms.toQString());
CVoiceRoomList currentRooms = this->m_voice->getComVoiceRooms();
CVoiceRoom currentRoom1 = currentRooms[0];
CVoiceRoom currentRoom2 = currentRooms[1];
CVoiceRoom newRoom1 = newRooms[0];
CVoiceRoom newRoom2 = newRooms[1];
bool changed = false;
// changed rooms? But only compare on "URL", not status as connected etc.
if (currentRoom1.getVoiceRoomUrl() != newRoom1.getVoiceRoomUrl())
{
this->m_voice->leaveVoiceRoom(IVoice::COM1);
if (newRoom1.isValid()) this->m_voice->joinVoiceRoom(IVoice::COM1, newRoom1);
changed = true;
}
if (currentRoom2.getVoiceRoomUrl() != newRoom2.getVoiceRoomUrl())
{
this->m_voice->leaveVoiceRoom(IVoice::COM2);
if (newRoom2.isValid()) this->m_voice->joinVoiceRoom(IVoice::COM2, newRoom2);
changed = true;
}
// changed not yet used, but I keep it for debugging
// changedVoiceRooms called by connectionStatusChanged;
Q_UNUSED(changed);
}
/*
* Room 1 callsigns
*/
CCallsignList CContextAudio::getCom1RoomCallsigns() const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
CCallsignList callsigns = this->m_voice->getVoiceRoomCallsigns(IVoice::COM1);
return callsigns;
}
/*
* Room 2 callsigns
*/
CCallsignList CContextAudio::getCom2RoomCallsigns() const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
CCallsignList callsigns = this->m_voice->getVoiceRoomCallsigns(IVoice::COM2);
return callsigns;
}
/*
* Room 1 users
*/
Network::CUserList CContextAudio::getCom1RoomUsers() const
{
Q_ASSERT(this->m_voice);
Q_ASSERT(this->getRuntime());
if (!this->getRuntime()->getIContextNetwork()) return Network::CUserList();
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
return this->getIContextNetwork()->getUsersForCallsigns(this->getCom1RoomCallsigns());
}
/*
* Room 2 users
*/
Network::CUserList CContextAudio::getCom2RoomUsers() const
{
Q_ASSERT(this->m_voice);
Q_ASSERT(this->getRuntime());
if (!this->getRuntime()->getIContextNetwork()) return Network::CUserList();
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
return this->getIContextNetwork()->getUsersForCallsigns(this->getCom2RoomCallsigns());
}
/*
* SELCAL tone
*/
void CContextAudio::playSelcalTone(const CSelcal &selcal) const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO, selcal.toQString());
CAudioDevice outputDevice = m_voice->getCurrentOutputDevice();
BlackSound::CSoundGenerator::playSelcal(90, selcal, outputDevice);
}
/*
* Notification
*/
void CContextAudio::playNotification(uint notification, bool considerSettings) const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO, QString::number(notification));
BlackSound::CNotificationSounds::Notification notificationSound = static_cast<BlackSound::CNotificationSounds::Notification>(notification);
if (considerSettings)
{
Q_ASSERT(this->getIContextSettings());
bool play = this->getIContextSettings()->getAudioSettings().getNotificationFlag(notificationSound);
if (!play) return;
}
BlackSound::CSoundGenerator::playNotificationSound(90, notificationSound);
}
/*
* Notification
*/
void CContextAudio::ps_initNotificationSounds()
{
// not possible in own thread
CSoundGenerator::playNotificationSound(0, CNotificationSounds::NotificationsLoadSounds);
}
/*
* Mic test.
*/
void CContextAudio::runMicrophoneTest()
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
this->m_voice->runMicrophoneTest();
}
/*
* Squelch test.
*/
void CContextAudio::runSquelchTest()
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
this->m_voice->runSquelchTest();
}
/*
* Microphone test
*/
QString CContextAudio::getMicrophoneTestResult() const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
return this->m_voice->micTestResultAsString();
}
/*
* Squelch value
*/
double CContextAudio::getSquelchValue() const
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
return static_cast<double>(this->m_voice->inputSquelch());
}
/*
* Audio loopback
*/
void CContextAudio::enableAudioLoopback(bool enable)
{
Q_ASSERT(this->m_voice);
if (this->getRuntime()->isSlotLogForAudioEnabled()) this->getRuntime()->logSlot(Q_FUNC_INFO);
m_voice->enableAudioLoopback(enable);
}
/*
* Settings changed
*/
void CContextAudio::ps_settingsChanged(uint typeValue)
{
if (this->getIContextOwnAircraft()) m_voice->setMyAircraftCallsign(this->getIContextOwnAircraft()->getOwnAircraft().getCallsign());
if (!this->getIContextSettings()) return;
IContextSettings::SettingsType type = static_cast<IContextSettings::SettingsType>(typeValue);
if (type == IContextSettings::SettingsHotKeys)
{
CKeyboardKeyList hotKeys = this->getIContextSettings()->getHotkeys();
Q_ASSERT(!hotKeys.isEmpty());
CKeyboardKey pttKey = hotKeys.findBy(&BlackMisc::Hardware::CKeyboardKey::getFunction, BlackMisc::Hardware::CKeyboardKey::HotkeyPtt).front();
m_keyboard->unregisterHotkey(m_handlePtt);
m_handlePtt = m_keyboard->registerHotkey(pttKey, m_voice, &CVoiceVatlib::handlePushToTalk);
}
}
/*
* Connection status changed
*/
void CContextAudio::ps_connectionStatusChanged(IVoice::ComUnit comUnit, IVoice::ConnectionStatus oldStatus, IVoice::ConnectionStatus newStatus)
{
Q_UNUSED(comUnit);
Q_UNUSED(oldStatus);
switch (newStatus)
{
case IVoice::Connected:
emit this->changedVoiceRooms(this->m_voice->getComVoiceRooms(), true);
break;
case IVoice::Disconnecting:
break;
case IVoice::Connecting:
break;
case IVoice::ConnectingFailed:
case IVoice::DisconnectedError:
{
const QString e = QString("Voice room %1 error").arg(comUnit);
qWarning(e.toUtf8().constData());
// no break here!
}
case IVoice::Disconnected:
// good chance to update aircraft
if (this->getIContextOwnAircraft()) m_voice->setMyAircraftCallsign(this->getIContextOwnAircraft()->getOwnAircraft().getCallsign());
emit this->changedVoiceRooms(this->m_voice->getComVoiceRooms(), false);
break;
default:
break;
}
}
} // namespace