24 using namespace swift::misc::aviation;
25 using namespace swift::misc::audio;
26 using namespace swift::misc::network;
27 using namespace swift::misc::physical_quantities;
28 using namespace swift::misc::simulation;
29 using namespace swift::sound;
30 using namespace swift::core::afv::clients;
34 namespace swift::core::context
36 IContextAudio::IContextAudio(CCoreFacadeConfig::ContextMode mode, CCoreFacade *runtime) : IContext(mode, runtime)
41 void IContextAudio::onChangedLocalDevices(
const CAudioDeviceInfoList &devices) { this->registerDevices(devices); }
43 const QString &IContextAudio::InterfaceName()
49 const QString &IContextAudio::ObjectPath()
55 IContextAudio *IContextAudio::create(CCoreFacade *runtime, CCoreFacadeConfig::ContextMode mode,
CDBusServer *server,
56 QDBusConnection &connection)
63 case CCoreFacadeConfig::Local:
return new CContextAudio(mode, runtime);
64 case CCoreFacadeConfig::LocalInDBusServer:
66 auto *context =
new CContextAudio(mode, runtime);
67 context->registerWithDBus(ObjectPath(), server);
70 case CCoreFacadeConfig::Remote:
71 return new CContextAudioProxy(CDBusServer::coreServiceName(connection), connection, mode, runtime);
72 case CCoreFacadeConfig::NotUsed:
73 SWIFT_VERIFY_X(
false, Q_FUNC_INFO,
"Empty context not supported for audio (since AFV)");
75 default:
SWIFT_VERIFY_X(
false, Q_FUNC_INFO,
"Unknown context mode");
return nullptr;
79 bool CContextAudioBase::parseCommandLine(
const QString &commandLine,
const CIdentifier &originator)
82 if (commandLine.isEmpty()) {
return false; }
88 parser.
parse(commandLine);
89 if (!parser.isKnownCommand()) {
return false; }
91 if (parser.matchesCommand(
".mute"))
93 this->setOutputMute(
true);
96 else if (parser.matchesCommand(
".unmute"))
98 this->setOutputMute(
false);
101 else if (parser.commandStartsWith(
"vol") && parser.countParts() > 1)
103 const int v = parser.toInt(1);
104 this->setMasterOutputVolume(v);
110 CContextAudioBase::CContextAudioBase(CCoreFacadeConfig::ContextMode mode, CCoreFacade *runtime)
113 CContextAudioBase::registerHelp();
115 if (CContextAudioBase::isNoAudioSet()) {
CLogMessage(
this).
info(u
"Voice client disabled"); }
116 else { this->initVoiceClient(); }
121 QPointer<CContextAudioBase> myself(
this);
125 const CSettings as = m_audioSettings.getThreadLocal();
126 this->setMasterOutputVolume(as.getOutVolume());
127 this->setComOutputVolume(CComSystem::Com1, as.getOutVolumeCom1());
128 this->setComOutputVolume(CComSystem::Com2, as.getOutVolumeCom2());
129 m_selcalPlayer =
new CSelcalPlayer(CAudioDeviceInfo::getDefaultOutputDevice(),
this);
131 myself->changeDeviceSettings();
132 myself->onChangedAudioSettings();
133 myself->onChangedLocalDevices(m_activeLocalDevices);
137 CContextAudioBase::~CContextAudioBase() { this->gracefulShutdown(); }
139 void CContextAudioBase::initVoiceClient()
141 if (m_voiceClient || !
sApp) {
return; }
144 if (devices != m_activeLocalDevices)
146 m_activeLocalDevices = devices;
147 emit this->changedLocalAudioDevices(devices);
151 if (!m_winCoInitialized)
153 HRESULT hr = CoInitializeEx(
nullptr, COINIT_MULTITHREADED);
157 if (hr == RPC_E_CHANGED_MODE)
159 CLogMessage(
this).
debug(u
"CoInitializeEx was already called with a different mode. Trying again.");
160 hr = CoInitializeEx(
nullptr, COINIT_APARTMENTTHREADED);
166 if (hr == S_OK || hr == S_FALSE) { m_winCoInitialized =
true; }
172 Q_ASSERT_X(m_voiceClient->thread() == qApp->thread(), Q_FUNC_INFO,
"Should be in main thread");
173 m_voiceClient->start();
174 Q_ASSERT_X(m_voiceClient->owner() ==
this, Q_FUNC_INFO,
"Wrong owner");
175 Q_ASSERT_X(m_voiceClient->thread() != qApp->thread(), Q_FUNC_INFO,
"Must NOT be in main thread");
184 connect(m_voiceClient, &CAfvClient::startedAudio,
this, &CContextAudioBase::startedAudio, Qt::QueuedConnection);
185 connect(m_voiceClient, &CAfvClient::stoppedAudio,
this, &CContextAudioBase::stoppedAudio, Qt::QueuedConnection);
186 connect(m_voiceClient, &CAfvClient::ptt,
this, &CContextAudioBase::ptt, Qt::QueuedConnection);
187 connect(m_voiceClient, &CAfvClient::changedOutputMute,
this, &CContextAudioBase::changedOutputMute,
188 Qt::QueuedConnection);
189 connect(m_voiceClient, &CAfvClient::connectionStatusChanged,
this,
190 &CContextAudioBase::onAfvConnectionStatusChanged, Qt::QueuedConnection);
191 connect(m_voiceClient, &CAfvClient::afvConnectionFailure,
this, &CContextAudioBase::onAfvConnectionFailure,
192 Qt::QueuedConnection);
195 void CContextAudioBase::terminateVoiceClient()
199 m_voiceClient->gracefulShutdown();
200 Q_ASSERT_X(CThreadUtils::isInThisThread(m_voiceClient), Q_FUNC_INFO,
"Needs to be back in current thread");
201 m_voiceClient->deleteLater();
202 m_voiceClient =
nullptr;
204 if (m_winCoInitialized)
207 m_winCoInitialized =
false;
213 void CContextAudioBase::gracefulShutdown()
215 this->terminateVoiceClient();
218 m_selcalPlayer->gracefulShutdown();
219 m_selcalPlayer =
nullptr;
221 QObject::disconnect(
this);
224 void CContextAudioBase::setRxTx(
bool rx1,
bool tx1,
bool rx2,
bool tx2)
226 if (m_voiceClient) { m_voiceClient->setRxTx(rx1, tx1, rx2, tx2); }
229 void CContextAudioBase::getRxTx(
bool &rx1,
bool &tx1,
bool &rx2,
bool &tx2)
const
231 if (m_voiceClient) { m_voiceClient->setRxTx(rx1, tx1, rx2, tx2); }
234 const CIdentifier &CContextAudioBase::audioRunsWhere()
const
236 static const CIdentifier i(
"CContextAudioBaseImpl");
242 if (!m_voiceClient) {
return false; }
243 return m_voiceClient->isEnabledComUnit(comUnit);
248 if (!m_voiceClient) {
return false; }
249 return m_voiceClient->isTransmittingComUnit(comUnit);
252 bool CContextAudioBase::connectAudioWithNetworkCredentials()
254 if (!m_voiceClient) {
return false; }
257 const CEcosystem ecoSystem = this->getIContextNetwork()->getConnectedServer().getEcosystem();
258 if (ecoSystem != CEcosystem::vatsim())
264 const CUser connectedUser = this->getIContextNetwork()->getConnectedServer().getUser();
267 this->unRegisterAudioCallsign(cs, this->identifier());
268 if (this->hasRegisteredAudioCallsign(cs))
273 CLogMessage(
this).
info(u
"About to connect to voice as '%1' '%2'") << connectedUser.
getId() << cs;
275 this->registerAudioCallsign(cs, this->identifier());
279 bool CContextAudioBase::isAudioConnected()
const {
return m_voiceClient && m_voiceClient->isConnected(); }
281 bool CContextAudioBase::isAudioStarted()
const {
return m_voiceClient && m_voiceClient->isStarted(); }
283 bool CContextAudioBase::isComUnitIntegrated()
const
285 return m_voiceClient && m_voiceClient->isComUnitIntegrated();
288 const QList<QCommandLineOption> &CContextAudioBase::getCmdLineOptions()
290 static const QList<QCommandLineOption> opts { QCommandLineOption(
291 { {
"n",
"noaudio" },
292 QCoreApplication::translate(
"CContextAudioBase",
"No audio for GUI or core.",
"noaudio") }) };
296 bool CContextAudioBase::isNoAudioSet()
298 if (!
sApp) {
return false; }
302 QString CContextAudioBase::audioRunsWhereInfo()
const
304 const QString s = QStringLiteral(
"[%1] Audio on '%2', '%3'.")
306 audioRunsWhere().getProcessName());
310 CAudioDeviceInfoList CContextAudioBase::getAudioDevices()
const {
return CAudioDeviceInfoList::allDevices(); }
324 const QString inputDeviceName = m_inputDeviceSetting.get();
325 const CAudioDeviceInfo inputDevice = this->getAudioInputDevices().findByNameOrDefault(
326 inputDeviceName, CAudioDeviceInfo::getDefaultInputDevice());
328 const QString outputDeviceName = m_outputDeviceSetting.get();
329 const CAudioDeviceInfo outputDevice = this->getAudioOutputDevices().findByNameOrDefault(
330 outputDeviceName, CAudioDeviceInfo::getDefaultOutputDevice());
338 void CContextAudioBase::setCurrentAudioDevices(
const CAudioDeviceInfo &inputDevice,
341 if (!m_voiceClient) {
return; }
342 if (!
sApp) {
return; }
344 if (!inputDevice.
getName().isEmpty() && inputDevice.
getName() != m_inputDeviceSetting.get())
346 Q_ASSERT_X(inputDevice.
isInputDevice(), Q_FUNC_INFO,
"Need input device");
348 CLogMessage::preformatted(m);
350 if (!outputDevice.
getName().isEmpty() && outputDevice.
getName() != m_outputDeviceSetting.get())
352 Q_ASSERT_X(outputDevice.
isOutputDevice(), Q_FUNC_INFO,
"Need output device");
354 CLogMessage::preformatted(m);
357 m_voiceClient->startAudio(inputDevice, outputDevice);
360 void CContextAudioBase::setMasterOutputVolume(
int volume)
362 if (!m_voiceClient) {
return; }
364 const bool wasMuted = this->isOutputMuted();
365 volume = CSettings::fixOutVolume(volume);
367 const int currentVolume = m_voiceClient->getNormalizedMasterOutputVolume();
368 const bool changedVoiceOutput = (currentVolume != volume);
369 if (changedVoiceOutput)
372 m_voiceClient->setNormalizedMasterOutputVolume(volume);
373 m_outMasterVolumeBeforeMute = volume;
375 emit this->changedAudioVolume(volume);
376 if ((volume > 0 && wasMuted) || (volume < 1 && !wasMuted))
379 emit this->changedOutputMute(volume < 1);
383 CSettings as(m_audioSettings.getThreadLocal());
384 if (as.getOutVolume() != volume)
386 as.setOutVolume(volume);
387 m_audioSettings.set(as);
393 if (comUnit != CComSystem::Com1 && comUnit != CComSystem::Com2) {
return; }
394 if (!m_voiceClient) {
return; }
396 volume = CSettings::fixOutVolume(volume);
398 const int currentVolume = m_voiceClient->getNormalizedComOutputVolume(comUnit);
399 const bool changedVoiceOutput = (currentVolume != volume);
400 if (changedVoiceOutput)
402 m_voiceClient->setNormalizedComOutputVolume(comUnit, volume);
403 emit this->changedAudioVolume(volume);
406 CSettings as(m_audioSettings.getThreadLocal());
407 if (comUnit == CComSystem::Com1 && as.getOutVolumeCom1() != volume)
409 as.setOutVolumeCom1(volume);
410 m_audioSettings.set(as);
412 else if (comUnit == CComSystem::Com2 && as.getOutVolumeCom2() != volume)
414 as.setOutVolumeCom2(volume);
415 m_audioSettings.set(as);
419 int CContextAudioBase::getMasterOutputVolume()
const
421 if (!m_voiceClient) {
return 0; }
422 return m_voiceClient->getNormalizedMasterOutputVolume();
427 if (!m_voiceClient) {
return 0; }
428 return m_voiceClient->getNormalizedComOutputVolume(comUnit);
431 void CContextAudioBase::setOutputMute(
bool muted)
433 if (!m_voiceClient) {
return; }
434 if (this->isOutputMuted() == muted) {
return; }
436 if (muted) { m_outMasterVolumeBeforeMute = m_voiceClient->getNormalizedMasterOutputVolume(); }
438 m_voiceClient->setOutputMuted(muted);
439 if (!muted) { m_voiceClient->setNormalizedMasterOutputVolume(m_outMasterVolumeBeforeMute); }
442 bool CContextAudioBase::isOutputMuted()
const
444 if (!m_voiceClient) {
return false; }
445 return m_voiceClient->isOutputMuted();
448 void CContextAudioBase::playSelcalTone(
const CSelcal &selcal)
450 using namespace std::chrono_literals;
451 const std::chrono::milliseconds ms = m_selcalPlayer->play(90, selcal);
455 const QPointer<const CContextAudioBase> myself(
this);
458 this->playNotification(CNotificationSounds::NotificationTextMessageSupervisor,
true);
466 if (isDebugEnabled())
468 CLogMessage(
this, CLogCategories::contextSlot()).
debug() << Q_FUNC_INFO << notification;
471 const CSettings settings = m_audioSettings.getThreadLocal();
473 if (!play) {
return; }
475 if (volume < 0 || volume > 100)
480 m_notificationPlayer.play(notification, volume);
483 void CContextAudioBase::enableAudioLoopback(
bool enable)
485 if (!m_voiceClient) {
return; }
486 m_voiceClient->setLoopBack(enable);
489 bool CContextAudioBase::isAudioLoopbackEnabled()
const
491 if (!m_voiceClient) {
return false; }
492 return m_voiceClient->isLoopback();
495 void CContextAudioBase::setVoiceTransmission(
bool enable)
497 if (!m_voiceClient) {
return; }
498 m_voiceClient->setPtt(enable);
501 void CContextAudioBase::changeDeviceSettings()
504 Q_ASSERT_X(devices.
size() == 2, Q_FUNC_INFO,
"Expect INPUT and OUTPUT device");
508 this->setCurrentAudioDevices(input, output);
511 void CContextAudioBase::onChangedAudioSettings()
513 const CSettings s = m_audioSettings.get();
515 m_notificationPlayer.updateDirectory(dir);
521 void CContextAudioBase::audioIncreaseVolume(
bool enabled)
523 if (!enabled) {
return; }
524 const int v = qRound(this->getMasterOutputVolume() * 1.05);
525 this->setMasterOutputVolume(v);
528 void CContextAudioBase::audioDecreaseVolume(
bool enabled)
530 if (!enabled) {
return; }
531 const int v = qRound(this->getMasterOutputVolume() / 1.05);
532 this->setMasterOutputVolume(v);
535 void CContextAudioBase::audioIncreaseVolumeCom1(
bool enabled)
537 if (!enabled) {
return; }
538 if (isComUnitIntegrated()) {
return; }
539 const int v = qRound(this->getComOutputVolume(CComSystem::Com1) * 1.05);
540 this->setComOutputVolume(CComSystem::Com1, v);
543 void CContextAudioBase::audioDecreaseVolumeCom1(
bool enabled)
545 if (!enabled) {
return; }
546 if (isComUnitIntegrated()) {
return; }
547 const int v = qRound(this->getComOutputVolume(CComSystem::Com1) / 1.05);
548 this->setComOutputVolume(CComSystem::Com1, v);
551 void CContextAudioBase::audioIncreaseVolumeCom2(
bool enabled)
553 if (!enabled) {
return; }
554 if (isComUnitIntegrated()) {
return; }
555 const int v = qRound(this->getComOutputVolume(CComSystem::Com2) * 1.05);
556 this->setComOutputVolume(CComSystem::Com2, v);
559 void CContextAudioBase::audioDecreaseVolumeCom2(
bool enabled)
561 if (!enabled) {
return; }
562 if (isComUnitIntegrated()) {
return; }
563 const int v = qRound(this->getComOutputVolume(CComSystem::Com2) / 1.05);
564 this->setComOutputVolume(CComSystem::Com2, v);
567 void CContextAudioBase::xCtxNetworkConnectionStatusChanged(
const CConnectionStatus &from,
570 if (!m_voiceClient) {
return; }
573 SWIFT_VERIFY_X(this->getIContextNetwork(), Q_FUNC_INFO,
"Missing network context");
576 if (to.
isConnected() && this->getIContextNetwork())
578 const bool connected = this->connectAudioWithNetworkCredentials();
583 else if (to.
isDisconnected()) { m_voiceClient->disconnectFrom(); }
586 void CContextAudioBase::onAfvConnectionStatusChanged(
int status)
588 if (!m_voiceClient) {
return; }
590 const CCallsign cs = m_voiceClient->getCallsign();
595 case CAfvClient::Connected: this->registerAudioCallsign(cs, this->identifier());
break;
596 case CAfvClient::Disconnected: this->unRegisterAudioCallsign(cs, this->identifier());
break;
600 void CContextAudioBase::onAfvConnectionFailure(
const CStatusMessage &msg)
602 if (!m_voiceClient) {
return; }
603 emit this->voiceClientFailure(msg);
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
static const QString & getShortVersionString()
Version as QVersionNumber.
bool isParserOptionSet(const QString &option) const
Delegates to QCommandLineParser::isSet.
data::CGlobalSetup getGlobalSetup() const
Global setup.
const context::IContextNetwork * getIContextNetwork() const
Direct access to contexts if a CCoreFacade has been initialized.
bool isShuttingDown() const
Is application shutting down?
bool isLocalContext() const
Local application? (not DBus)
ConnectionStatus
Connection status.
swift::misc::network::CUrl getAfvApiServerUrl() const
AFV voice server URL.
Base class with a member CIdentifier to be inherited by a class which has an identity in the environm...
Value object encapsulating information identifying a component of a modular distributed swift process...
Class for emitting a log message.
Derived & debug()
Set the severity to debug.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
size_type size() const
Returns number of elements in the sequence.
void push_back(const T &value)
Appends an element at the end of the sequence.
reference front()
Access the first element.
reference back()
Access the last element.
Utility methods for simple line parsing used with the command line.
void parse(const QString &commandLine)
Parse.
Streamable status message, e.g.
Value object encapsulating information of a audio device.
bool isInputDevice() const
Input device.
bool isOutputDevice() const
Output device.
const QString & getName() const
Get the device name.
Value object encapsulating a list of audio devices.
CAudioDeviceInfoList getOutputDevices() const
Get output devices in that list.
CAudioDeviceInfoList getInputDevices() const
Get output devices in that list.
Value object encapsulating information of audio related settings.
int getNotificationVolume() const
Get volume (notifications)
int getOutVolumeCom2() const
Get volume for com2 (audio) 0..100.
bool isNotificationFlagSet(CNotificationSounds::NotificationFlag notification) const
Notification flag (play notification?)
int getOutVolume() const
Get volume (audio) 0..100.
int getOutVolumeCom1() const
Get volume for com1 (audio) 0..100.
const QString & getNotificationSoundDirectory() const
Notification directory.
Value object encapsulating information of a callsign.
const QString & asString() const
Get callsign (normalized)
QString toQString(bool i18n=false) const
Cast as QString.
Value object encapsulating information about a connection status.
bool isConnected() const
Query status.
bool isDisconnected() const
Query status.
Ecosystem of server belonging together.
Value object encapsulating information of a user.
const QString & getPassword() const
Get password.
const QString & getId() const
Get id.
const aviation::CCallsign & getCallsign() const
Get associated callsign.
#define SWIFT_CORE_CONTEXTAUDIO_INTERFACENAME
DBus interface for context.
#define SWIFT_CORE_CONTEXTAUDIO_OBJECTPATH
DBus object path for context.
Free functions in swift::misc.
SWIFT_MISC_EXPORT const QString & boolToEnabledDisabled(bool v)
Bool to enabled/disabled.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
NotificationFlag
Play notification.
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.