943 lines
32 KiB
C++
943 lines
32 KiB
C++
// Copyright 2007-2023 The Mumble Developers. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the LICENSE file at the root of the
|
|
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
|
|
|
#include "AudioConfigDialog.h"
|
|
|
|
#include "Accessibility.h"
|
|
#include "AudioInput.h"
|
|
#include "AudioOutput.h"
|
|
#include "AudioOutputSample.h"
|
|
#include "AudioOutputToken.h"
|
|
#include "NetworkConfig.h"
|
|
#include "Utils.h"
|
|
#include "Global.h"
|
|
|
|
#include <QSignalBlocker>
|
|
|
|
#include <cstdint>
|
|
|
|
const QString AudioOutputDialog::name = QLatin1String("AudioOutputWidget");
|
|
const QString AudioInputDialog::name = QLatin1String("AudioInputWidget");
|
|
|
|
|
|
static ConfigWidget *AudioInputDialogNew(Settings &st) {
|
|
return new AudioInputDialog(st);
|
|
}
|
|
|
|
static ConfigWidget *AudioOutputDialogNew(Settings &st) {
|
|
return new AudioOutputDialog(st);
|
|
}
|
|
|
|
static ConfigRegistrar registrarAudioInputDialog(1000, AudioInputDialogNew);
|
|
static ConfigRegistrar registrarAudioOutputDialog(1010, AudioOutputDialogNew);
|
|
|
|
void AudioInputDialog::hideEvent(QHideEvent *) {
|
|
qtTick->stop();
|
|
}
|
|
|
|
void AudioInputDialog::showEvent(QShowEvent *) {
|
|
qtTick->start(20);
|
|
|
|
// In case the user has changed the audio output device and is now coming
|
|
// back.
|
|
updateEchoEnableState();
|
|
}
|
|
|
|
AudioInputDialog::AudioInputDialog(Settings &st) : ConfigWidget(st) {
|
|
qtTick = new QTimer(this);
|
|
qtTick->setObjectName(QLatin1String("Tick"));
|
|
|
|
setupUi(this);
|
|
|
|
qlInputHelp->setVisible(false);
|
|
|
|
if (AudioInputRegistrar::qmNew) {
|
|
QList< QString > keys = AudioInputRegistrar::qmNew->keys();
|
|
foreach (QString key, keys) { qcbSystem->addItem(key); }
|
|
}
|
|
qcbSystem->setEnabled(qcbSystem->count() > 1);
|
|
|
|
qcbTransmit->addItem(tr("Continuous"), Settings::Continuous);
|
|
qcbTransmit->addItem(tr("Voice Activity"), Settings::VAD);
|
|
qcbTransmit->addItem(tr("Push To Talk"), Settings::PushToTalk);
|
|
|
|
abSpeech->qcBelow = Qt::red;
|
|
abSpeech->qcInside = Qt::yellow;
|
|
abSpeech->qcAbove = Qt::green;
|
|
|
|
qcbDevice->view()->setTextElideMode(Qt::ElideRight);
|
|
|
|
on_qcbMuteCue_clicked(Global::get().s.bTxMuteCue);
|
|
on_Tick_timeout();
|
|
on_qcbIdleAction_currentIndexChanged(Global::get().s.iaeIdleAction);
|
|
|
|
// Hide the slider by default
|
|
showSpeexNoiseSuppressionSlider(false);
|
|
|
|
#ifndef USE_RENAMENOISE
|
|
// Hide options related to ReNameNoise
|
|
qrbNoiseSupReNameNoise->setVisible(false);
|
|
qrbNoiseSupBoth->setVisible(false);
|
|
#endif
|
|
}
|
|
|
|
QString AudioInputDialog::title() const {
|
|
return tr("Audio Input");
|
|
}
|
|
|
|
const QString &AudioInputDialog::getName() const {
|
|
return AudioInputDialog::name;
|
|
}
|
|
|
|
QIcon AudioInputDialog::icon() const {
|
|
return QIcon(QLatin1String("skin:config_audio_input.png"));
|
|
}
|
|
|
|
void AudioInputDialog::load(const Settings &r) {
|
|
int i;
|
|
QList< QString > keys;
|
|
|
|
if (AudioInputRegistrar::qmNew)
|
|
keys = AudioInputRegistrar::qmNew->keys();
|
|
else
|
|
keys.clear();
|
|
i = keys.indexOf(AudioInputRegistrar::current);
|
|
if (i >= 0)
|
|
loadComboBox(qcbSystem, i);
|
|
|
|
verifyMicrophonePermission();
|
|
|
|
loadCheckBox(qcbExclusive, r.bExclusiveInput);
|
|
|
|
qlePushClickPathOn->setText(r.qsTxAudioCueOn);
|
|
qlePushClickPathOff->setText(r.qsTxAudioCueOff);
|
|
qleMuteCuePath->setText(r.qsTxMuteCue);
|
|
s.muteCueShown = r.muteCueShown;
|
|
|
|
|
|
loadComboBox(qcbTransmit, r.atTransmit);
|
|
loadSlider(qsTransmitHold, r.iVoiceHold);
|
|
loadSlider(qsTransmitMin, static_cast< int >(r.fVADmin * 32767.0f + 0.5f));
|
|
loadSlider(qsTransmitMax, static_cast< int >(r.fVADmax * 32767.0f + 0.5f));
|
|
loadSlider(qsFrames, (r.iFramesPerPacket == 1) ? 1 : (r.iFramesPerPacket / 2 + 1));
|
|
loadSlider(qsDoublePush, static_cast< int >(static_cast< float >(r.uiDoublePush) / 1000.f + 0.5f));
|
|
loadSlider(qsPTTHold, static_cast< int >(r.pttHold));
|
|
|
|
if (r.vsVAD == Settings::Amplitude)
|
|
qrbAmplitude->setChecked(true);
|
|
else
|
|
qrbSNR->setChecked(true);
|
|
|
|
loadCheckBox(qcbPushWindow, r.bShowPTTButtonWindow);
|
|
loadCheckBox(qcbEnableCuePTT, r.audioCueEnabledPTT);
|
|
loadCheckBox(qcbEnableCueVAD, r.audioCueEnabledVAD);
|
|
updateAudioCueEnabled();
|
|
loadCheckBox(qcbMuteCue, r.bTxMuteCue);
|
|
loadSlider(qsQuality, r.iQuality);
|
|
loadCheckBox(qcbAllowLowDelay, r.bAllowLowDelay);
|
|
if (r.iSpeexNoiseCancelStrength != 0) {
|
|
loadSlider(qsSpeexNoiseSupStrength, -r.iSpeexNoiseCancelStrength);
|
|
} else {
|
|
loadSlider(qsSpeexNoiseSupStrength, 14);
|
|
}
|
|
|
|
bool allowReNameNoise = SAMPLE_RATE == 48000;
|
|
|
|
if (!allowReNameNoise) {
|
|
const QString tooltip = QObject::tr("RNNoise is not available due to a sample rate mismatch.");
|
|
qrbNoiseSupReNameNoise->setEnabled(false);
|
|
qrbNoiseSupReNameNoise->setToolTip(tooltip);
|
|
qrbNoiseSupBoth->setEnabled(false);
|
|
qrbNoiseSupBoth->setToolTip(tooltip);
|
|
}
|
|
|
|
switch (r.noiseCancelMode) {
|
|
case Settings::NoiseCancelOff:
|
|
loadCheckBox(qrbNoiseSupDeactivated, true);
|
|
break;
|
|
case Settings::NoiseCancelSpeex:
|
|
loadCheckBox(qrbNoiseSupSpeex, true);
|
|
break;
|
|
case Settings::NoiseCancelRNN:
|
|
#ifdef USE_RENAMENOISE
|
|
if (allowReNameNoise) {
|
|
loadCheckBox(qrbNoiseSupReNameNoise, true);
|
|
} else {
|
|
// We have to switch to speex as a fallback
|
|
loadCheckBox(qrbNoiseSupSpeex, true);
|
|
}
|
|
#else
|
|
// We have to switch to speex as a fallback
|
|
loadCheckBox(qrbNoiseSupSpeex, true);
|
|
#endif
|
|
break;
|
|
case Settings::NoiseCancelBoth:
|
|
#ifdef USE_RENAMENOISE
|
|
if (allowReNameNoise) {
|
|
loadCheckBox(qrbNoiseSupBoth, true);
|
|
} else {
|
|
// We have to switch to speex as a fallback
|
|
loadCheckBox(qrbNoiseSupSpeex, true);
|
|
}
|
|
#else
|
|
// We have to switch to speex as a fallback
|
|
loadCheckBox(qrbNoiseSupSpeex, true);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
loadSlider(qsAmp, 20000 - r.iMinLoudness);
|
|
|
|
// Idle auto actions
|
|
qsbIdle->setValue(static_cast< int >(r.iIdleTime) / 60);
|
|
loadComboBox(qcbIdleAction, r.iaeIdleAction);
|
|
loadCheckBox(qcbUndoIdleAction, r.bUndoIdleActionUponActivity);
|
|
|
|
updateEchoEnableState();
|
|
}
|
|
|
|
void AudioInputDialog::verifyMicrophonePermission() {
|
|
if (!AudioInputRegistrar::qmNew) {
|
|
return;
|
|
}
|
|
|
|
AudioInputRegistrar *air = AudioInputRegistrar::qmNew->value(qcbSystem->currentText());
|
|
if (air->isMicrophoneAccessDeniedByOS()) {
|
|
qcbDevice->setEnabled(false);
|
|
if (air->name == QLatin1String("CoreAudio")) {
|
|
qlInputHelp->setVisible(true);
|
|
qlInputHelp->setText(
|
|
tr("Access to the microphone was denied. Please allow Mumble to use the microphone "
|
|
"by changing the settings in System Preferences -> Security & Privacy -> Privacy -> "
|
|
"Microphone."));
|
|
} else if (air->name == QLatin1String("WASAPI")) {
|
|
qlInputHelp->setVisible(true);
|
|
qlInputHelp->setText(tr("Access to the microphone was denied. Please check that your operating system's "
|
|
"microphone settings allow Mumble to use the microphone."));
|
|
}
|
|
} else {
|
|
qcbDevice->setEnabled(true);
|
|
qlInputHelp->setVisible(false);
|
|
qlInputHelp->setText("");
|
|
}
|
|
}
|
|
|
|
void AudioInputDialog::save() const {
|
|
s.iQuality = qsQuality->value();
|
|
s.bAllowLowDelay = qcbAllowLowDelay->isChecked();
|
|
s.iSpeexNoiseCancelStrength = (qsSpeexNoiseSupStrength->value() == 14) ? 0 : -qsSpeexNoiseSupStrength->value();
|
|
|
|
if (qrbNoiseSupDeactivated->isChecked()) {
|
|
s.noiseCancelMode = Settings::NoiseCancelOff;
|
|
} else if (qrbNoiseSupBoth->isChecked()) {
|
|
s.noiseCancelMode = Settings::NoiseCancelBoth;
|
|
} else if (qrbNoiseSupReNameNoise->isChecked()) {
|
|
s.noiseCancelMode = Settings::NoiseCancelRNN;
|
|
} else {
|
|
s.noiseCancelMode = Settings::NoiseCancelSpeex;
|
|
}
|
|
|
|
s.iMinLoudness = 18000 - qsAmp->value() + 2000;
|
|
s.iVoiceHold = qsTransmitHold->value();
|
|
s.fVADmin = static_cast< float >(qsTransmitMin->value()) / 32767.0f;
|
|
s.fVADmax = static_cast< float >(qsTransmitMax->value()) / 32767.0f;
|
|
s.vsVAD = qrbSNR->isChecked() ? Settings::SignalToNoise : Settings::Amplitude;
|
|
s.iFramesPerPacket = qsFrames->value();
|
|
s.iFramesPerPacket = (s.iFramesPerPacket == 1) ? 1 : ((s.iFramesPerPacket - 1) * 2);
|
|
s.uiDoublePush = static_cast< unsigned int >(qsDoublePush->value() * 1000);
|
|
s.pttHold = static_cast< quint64 >(qsPTTHold->value());
|
|
s.atTransmit = static_cast< Settings::AudioTransmit >(qcbTransmit->currentIndex());
|
|
|
|
// Idle auto actions
|
|
s.iIdleTime = static_cast< unsigned int >(qsbIdle->value() * 60);
|
|
s.iaeIdleAction = static_cast< Settings::IdleAction >(qcbIdleAction->currentIndex());
|
|
s.bUndoIdleActionUponActivity = qcbUndoIdleAction->isChecked();
|
|
|
|
s.bShowPTTButtonWindow = qcbPushWindow->isChecked();
|
|
s.audioCueEnabledPTT = qcbEnableCuePTT->isChecked();
|
|
s.audioCueEnabledVAD = qcbEnableCueVAD->isChecked();
|
|
s.qsTxAudioCueOn = qlePushClickPathOn->text();
|
|
s.qsTxAudioCueOff = qlePushClickPathOff->text();
|
|
|
|
s.bTxMuteCue = qcbMuteCue->isChecked();
|
|
s.qsTxMuteCue = qleMuteCuePath->text();
|
|
|
|
s.qsAudioInput = qcbSystem->currentText();
|
|
s.echoOption = static_cast< EchoCancelOptionID >(qcbEcho->currentData().toInt());
|
|
s.bExclusiveInput = qcbExclusive->isChecked();
|
|
|
|
if (AudioInputRegistrar::qmNew) {
|
|
AudioInputRegistrar *air = AudioInputRegistrar::qmNew->value(qcbSystem->currentText());
|
|
int idx = qcbDevice->currentIndex();
|
|
if (idx > -1) {
|
|
air->setDeviceChoice(qcbDevice->itemData(idx), s);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AudioInputDialog::on_qsFrames_valueChanged(int v) {
|
|
int val = (v == 1) ? 10 : (v - 1) * 20;
|
|
qlFrames->setText(tr("%1 ms").arg(val));
|
|
updateBitrate();
|
|
|
|
Mumble::Accessibility::setSliderSemanticValue(qsFrames, QString("%1 %2").arg(val).arg(tr("milliseconds")));
|
|
}
|
|
|
|
void AudioInputDialog::on_qsDoublePush_valueChanged(int v) {
|
|
if (v == 0) {
|
|
qlDoublePush->setText(tr("Off"));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsDoublePush, tr("Off"));
|
|
} else {
|
|
qlDoublePush->setText(tr("%1 ms").arg(v));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsDoublePush, QString("%1 %2").arg(v).arg(tr("milliseconds")));
|
|
}
|
|
}
|
|
|
|
void AudioInputDialog::on_qsPTTHold_valueChanged(int v) {
|
|
if (v == 0) {
|
|
qlPTTHold->setText(tr("Off"));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsPTTHold, tr("Off"));
|
|
} else {
|
|
qlPTTHold->setText(tr("%1 ms").arg(v));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsPTTHold, QString("%1 %2").arg(v).arg(tr("milliseconds")));
|
|
}
|
|
}
|
|
|
|
void AudioInputDialog::on_qsTransmitHold_valueChanged(int v) {
|
|
float val = static_cast< float >(v * 10);
|
|
val = val / 1000.0f;
|
|
qlTransmitHold->setText(tr("%1 s").arg(val, 0, 'f', 2));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsTransmitHold,
|
|
QString("%1 %2").arg(val, 0, 'f', 2).arg(tr("seconds")));
|
|
}
|
|
|
|
void AudioInputDialog::on_qsQuality_valueChanged(int v) {
|
|
qlQuality->setText(tr("%1 kb/s").arg(static_cast< float >(v) / 1000.0f, 0, 'f', 1));
|
|
updateBitrate();
|
|
|
|
Mumble::Accessibility::setSliderSemanticValue(
|
|
qsQuality, QString("%1 %2").arg(static_cast< float >(v) / 1000.0f, 0, 'f', 1).arg(tr("kilobits per second")));
|
|
}
|
|
|
|
void AudioInputDialog::on_qsSpeexNoiseSupStrength_valueChanged(int v) {
|
|
QPalette pal;
|
|
|
|
if (v < 15) {
|
|
qlSpeexNoiseSupStrength->setText(tr("Off"));
|
|
pal.setColor(qlSpeexNoiseSupStrength->foregroundRole(), Qt::red);
|
|
Mumble::Accessibility::setSliderSemanticValue(qsSpeexNoiseSupStrength, tr("Off"));
|
|
} else {
|
|
qlSpeexNoiseSupStrength->setText(tr("-%1 dB").arg(v));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsSpeexNoiseSupStrength,
|
|
QString("-%1 %2").arg(v).arg(tr("decibels")));
|
|
}
|
|
qlSpeexNoiseSupStrength->setPalette(pal);
|
|
}
|
|
|
|
void AudioInputDialog::on_qsAmp_valueChanged(int v) {
|
|
v = 18000 - v + 2000;
|
|
float d = 20000.0f / static_cast< float >(v);
|
|
qlAmp->setText(QString::fromLatin1("%1").arg(d, 0, 'f', 2));
|
|
|
|
Mumble::Accessibility::setSliderSemanticValue(qsAmp, QString("%1").arg(d, 0, 'f', 2));
|
|
}
|
|
|
|
void AudioInputDialog::on_qsTransmitMin_valueChanged() {
|
|
Mumble::Accessibility::setSliderSemanticValue(qsTransmitMin, Mumble::Accessibility::SliderMode::READ_PERCENT, "%");
|
|
}
|
|
|
|
void AudioInputDialog::on_qsTransmitMax_valueChanged() {
|
|
Mumble::Accessibility::setSliderSemanticValue(qsTransmitMax, Mumble::Accessibility::SliderMode::READ_PERCENT, "%");
|
|
}
|
|
|
|
void AudioInputDialog::updateBitrate() {
|
|
if (!qsQuality || !qsFrames || !qlBitrate)
|
|
return;
|
|
int q = qsQuality->value();
|
|
int p = qsFrames->value();
|
|
|
|
int audiorate, overhead, posrate;
|
|
|
|
audiorate = q;
|
|
|
|
// 50 packets, in bits, IP + UDP + Crypt + type + seq + frameheader
|
|
overhead = 100 * 8 * (20 + 8 + 4 + 1 + 2 + p);
|
|
|
|
// TCP is 12 more bytes than UDP
|
|
if (NetworkConfig::TcpModeEnabled())
|
|
overhead += 100 * 8 * 12;
|
|
|
|
if (Global::get().s.bTransmitPosition)
|
|
posrate = 12;
|
|
else
|
|
posrate = 0;
|
|
|
|
posrate = posrate * 100 * 8;
|
|
|
|
overhead = overhead / p;
|
|
posrate = posrate / p;
|
|
|
|
int total = audiorate + overhead + posrate;
|
|
|
|
QPalette pal;
|
|
|
|
if (Global::get().uiSession && (total > Global::get().iMaxBandwidth)) {
|
|
pal.setColor(qlBitrate->foregroundRole(), Qt::red);
|
|
}
|
|
|
|
qlBitrate->setPalette(pal);
|
|
|
|
QString v = tr("%1 kbit/s (Audio %2, Position %4, Overhead %3)")
|
|
.arg(total / 1000.0, 0, 'f', 1)
|
|
.arg(audiorate / 1000.0, 0, 'f', 1)
|
|
.arg(overhead / 1000.0, 0, 'f', 1)
|
|
.arg(posrate / 1000.0, 0, 'f', 1);
|
|
|
|
qlBitrate->setText(v);
|
|
|
|
qsQuality->setMinimum(8000);
|
|
|
|
qsQuality->setAccessibleDescription(v);
|
|
qsFrames->setAccessibleDescription(v);
|
|
}
|
|
|
|
void AudioInputDialog::on_qcbEnableCuePTT_clicked() {
|
|
updateAudioCueEnabled();
|
|
}
|
|
|
|
void AudioInputDialog::on_qcbEnableCueVAD_clicked() {
|
|
updateAudioCueEnabled();
|
|
}
|
|
|
|
void AudioInputDialog::updateAudioCueEnabled() {
|
|
bool enabled = qcbEnableCuePTT->isChecked() || qcbEnableCueVAD->isChecked();
|
|
|
|
qpbPushClickBrowseOn->setEnabled(enabled);
|
|
qpbPushClickBrowseOff->setEnabled(enabled);
|
|
qpbPushClickPreview->setEnabled(enabled);
|
|
qpbPushClickReset->setEnabled(enabled);
|
|
qlePushClickPathOn->setEnabled(enabled);
|
|
qlePushClickPathOff->setEnabled(enabled);
|
|
qlPushClickOn->setEnabled(enabled);
|
|
qlPushClickOff->setEnabled(enabled);
|
|
}
|
|
|
|
void AudioInputDialog::on_qpbPushClickBrowseOn_clicked() {
|
|
QString defaultpath(qlePushClickPathOn->text());
|
|
QString qsnew = AudioOutputSample::browseForSndfile(defaultpath);
|
|
if (!qsnew.isEmpty())
|
|
qlePushClickPathOn->setText(qsnew);
|
|
}
|
|
|
|
void AudioInputDialog::on_qpbPushClickBrowseOff_clicked() {
|
|
QString defaultpath(qlePushClickPathOff->text());
|
|
QString qsnew = AudioOutputSample::browseForSndfile(defaultpath);
|
|
if (!qsnew.isEmpty())
|
|
qlePushClickPathOff->setText(qsnew);
|
|
}
|
|
|
|
void AudioInputDialog::on_qpbPushClickPreview_clicked() {
|
|
AudioOutputPtr ao = Global::get().ao;
|
|
if (ao) {
|
|
AudioOutputToken sample = ao->playSample(qlePushClickPathOn->text(), Global::get().s.cueVolume);
|
|
if (sample) {
|
|
sample.connect< AudioOutputSample >(&AudioOutputSample::playbackFinished, *this,
|
|
&AudioInputDialog::continuePlayback);
|
|
} else {
|
|
// If we fail to playback the first play on play at least off
|
|
ao->playSample(qlePushClickPathOff->text(), Global::get().s.cueVolume);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AudioInputDialog::on_qcbMuteCue_clicked(bool b) {
|
|
qleMuteCuePath->setEnabled(b);
|
|
qpbMuteCueBrowse->setEnabled(b);
|
|
qpbMuteCuePreview->setEnabled(b);
|
|
s.muteCueShown = true;
|
|
}
|
|
|
|
void AudioInputDialog::on_qpbMuteCueBrowse_clicked() {
|
|
QString defaultpath(qleMuteCuePath->text());
|
|
QString qsnew = AudioOutputSample::browseForSndfile(defaultpath);
|
|
if (!qsnew.isEmpty())
|
|
qleMuteCuePath->setText(qsnew);
|
|
}
|
|
|
|
|
|
void AudioInputDialog::on_qpbMuteCuePreview_clicked() {
|
|
AudioOutputPtr ao = Global::get().ao;
|
|
if (ao) {
|
|
ao->playSample(qleMuteCuePath->text(), Global::get().s.cueVolume);
|
|
}
|
|
}
|
|
|
|
void AudioInputDialog::continuePlayback() {
|
|
AudioOutputPtr ao = Global::get().ao;
|
|
if (ao) {
|
|
ao->playSample(qlePushClickPathOff->text(), Global::get().s.cueVolume);
|
|
}
|
|
}
|
|
|
|
void AudioInputDialog::on_qpbPushClickReset_clicked() {
|
|
qlePushClickPathOn->setText(Settings::cqsDefaultPushClickOn);
|
|
qlePushClickPathOff->setText(Settings::cqsDefaultPushClickOff);
|
|
}
|
|
|
|
void AudioInputDialog::on_qcbTransmit_currentIndexChanged(int v) {
|
|
switch (v) {
|
|
case 0:
|
|
qswTransmit->setCurrentWidget(qwContinuous);
|
|
break;
|
|
case 1:
|
|
qswTransmit->setCurrentWidget(qwVAD);
|
|
break;
|
|
case 2:
|
|
qswTransmit->setCurrentWidget(qwPTT);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AudioInputDialog::on_qcbSystem_currentIndexChanged(int) {
|
|
qcbDevice->clear();
|
|
|
|
QList< audioDevice > choices;
|
|
|
|
if (AudioInputRegistrar::qmNew) {
|
|
auto air = AudioInputRegistrar::qmNew->value(qcbSystem->currentText());
|
|
QVariant current = air->getDeviceChoice();
|
|
choices = air->getDeviceChoices();
|
|
|
|
for (int i = 0; i < choices.size(); ++i) {
|
|
auto &choice = choices.at(i);
|
|
qcbDevice->addItem(choice.first, choice.second);
|
|
qcbDevice->setItemData(i, choice.first.toHtmlEscaped(), Qt::ToolTipRole);
|
|
|
|
if (choice.second == current) {
|
|
qcbDevice->setCurrentIndex(i);
|
|
}
|
|
}
|
|
|
|
updateEchoEnableState();
|
|
|
|
qcbExclusive->setEnabled(air->canExclusive());
|
|
}
|
|
|
|
qcbDevice->setEnabled(!choices.isEmpty());
|
|
verifyMicrophonePermission();
|
|
}
|
|
|
|
void AudioInputDialog::updateEchoEnableState() {
|
|
AudioInputRegistrar *air = AudioInputRegistrar::qmNew->value(qcbSystem->currentText());
|
|
|
|
AudioOutputDialog *outputWidget =
|
|
static_cast< AudioOutputDialog * >(ConfigDialog::getConfigWidget(AudioOutputDialog::name));
|
|
QString outputInterface;
|
|
if (outputWidget) {
|
|
outputInterface = outputWidget->getCurrentlySelectedOutputInterfaceName();
|
|
} else {
|
|
// Fallback for when the outputWIdget is not (yet) available -> use the value from the current settings
|
|
outputInterface = s.qsAudioOutput;
|
|
}
|
|
|
|
qcbEcho->clear();
|
|
qcbEcho->setToolTip(QObject::tr("If enabled this tries to cancel out echo from the audio stream."));
|
|
qcbEcho->setCurrentIndex(0);
|
|
|
|
bool hasUsableEchoOption = false;
|
|
|
|
qcbEcho->insertItem(0, tr("Disabled"), "disabled");
|
|
qcbEcho->setItemData(0, tr("Disable echo cancellation."), Qt::ToolTipRole);
|
|
|
|
int i = 0;
|
|
for (EchoCancelOptionID echoid : air->echoOptions) {
|
|
if (air->canEcho(echoid, outputInterface)) {
|
|
++i;
|
|
hasUsableEchoOption = true;
|
|
const EchoCancelOption &echoOption = EchoCancelOption::getOptions()[static_cast< std::size_t >(echoid)];
|
|
qcbEcho->insertItem(i, echoOption.description, static_cast< int >(echoid));
|
|
qcbEcho->setItemData(i, echoOption.explanation, Qt::ToolTipRole);
|
|
if (s.echoOption == echoid) {
|
|
qcbEcho->setCurrentIndex(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasUsableEchoOption) {
|
|
qcbEcho->setEnabled(true);
|
|
} else {
|
|
qcbEcho->setCurrentIndex(0);
|
|
qcbEcho->setEnabled(false);
|
|
qcbEcho->setToolTip(QObject::tr("Echo cancellation is not supported for the interface "
|
|
"combination \"%1\" (in) and \"%2\" (out).")
|
|
.arg(air->name)
|
|
.arg(outputInterface));
|
|
}
|
|
}
|
|
|
|
void AudioInputDialog::showSpeexNoiseSuppressionSlider(bool show) {
|
|
qlSpeexNoiseSup->setVisible(show);
|
|
qsSpeexNoiseSupStrength->setVisible(show);
|
|
qlSpeexNoiseSupStrength->setVisible(show);
|
|
}
|
|
|
|
void AudioInputDialog::on_Tick_timeout() {
|
|
AudioInputPtr ai = Global::get().ai;
|
|
|
|
if (!ai.get() || !ai->sppPreprocess)
|
|
return;
|
|
|
|
abSpeech->iBelow = qsTransmitMin->value();
|
|
abSpeech->iAbove = qsTransmitMax->value();
|
|
|
|
if (qrbAmplitude->isChecked()) {
|
|
abSpeech->iValue = static_cast< int >((32767.f / 96.0f) * (96.0f + ai->dPeakCleanMic) + 0.5f);
|
|
} else {
|
|
abSpeech->iValue = static_cast< int >(ai->fSpeechProb * 32767.0f + 0.5f);
|
|
}
|
|
abSpeech->update();
|
|
}
|
|
|
|
|
|
void AudioInputDialog::on_qcbIdleAction_currentIndexChanged(int v) {
|
|
const Settings::IdleAction action = static_cast< Settings::IdleAction >(v);
|
|
|
|
const bool enabled = (action != Settings::Nothing);
|
|
|
|
qlIdle->setEnabled(enabled);
|
|
qlIdle2->setEnabled(enabled);
|
|
qsbIdle->setEnabled(enabled);
|
|
qcbUndoIdleAction->setEnabled(enabled);
|
|
}
|
|
|
|
void AudioInputDialog::on_qrbNoiseSupSpeex_toggled(bool checked) {
|
|
showSpeexNoiseSuppressionSlider(checked);
|
|
}
|
|
|
|
void AudioInputDialog::on_qrbNoiseSupBoth_toggled(bool checked) {
|
|
showSpeexNoiseSuppressionSlider(checked);
|
|
}
|
|
|
|
void AudioOutputDialog::enablePulseAudioAttenuationOptionsFor(const QString &outputName) {
|
|
if (outputName == QLatin1String("PulseAudio")) {
|
|
qcbOnlyAttenuateSameOutput->show();
|
|
qcbAttenuateLoopbacks->show();
|
|
} else {
|
|
qcbOnlyAttenuateSameOutput->hide();
|
|
qcbAttenuateLoopbacks->hide();
|
|
}
|
|
}
|
|
|
|
AudioOutputDialog::AudioOutputDialog(Settings &st) : ConfigWidget(st) {
|
|
setupUi(this);
|
|
|
|
if (AudioOutputRegistrar::qmNew) {
|
|
QList< QString > keys = AudioOutputRegistrar::qmNew->keys();
|
|
foreach (QString key, keys) { qcbSystem->addItem(key); }
|
|
}
|
|
qcbSystem->setEnabled(qcbSystem->count() > 1);
|
|
|
|
qcbLoopback->addItem(tr("None"), Settings::None);
|
|
qcbLoopback->addItem(tr("Local"), Settings::Local);
|
|
qcbLoopback->addItem(tr("Server"), Settings::Server);
|
|
|
|
qcbDevice->view()->setTextElideMode(Qt::ElideRight);
|
|
|
|
// Distance in cm
|
|
qsMinDistance->setRange(0, 200);
|
|
// Distance in m
|
|
qsbMinimumDistance->setRange(0.0, 1000.0);
|
|
// Distance in cm
|
|
qsMaxDistance->setRange(10, 2000);
|
|
// Distance in m
|
|
qsbMaximumDistance->setRange(1.0, 10000.0);
|
|
qsMinimumVolume->setRange(0, 100);
|
|
qsbMinimumVolume->setRange(qsMinimumVolume->minimum(), qsMinimumVolume->maximum());
|
|
qsBloom->setRange(0, 75);
|
|
qsbBloom->setRange(qsBloom->minimum(), qsBloom->maximum());
|
|
|
|
QString minDistanceTooltip = tr("Distance at which audio volume from another player starts decreasing");
|
|
QString maxDistanceTooltip = tr("Distance at which a player's audio volume has reached its minimum value");
|
|
QString minVolumeTooltip =
|
|
tr("The minimum volume a player's audio will fade out to with increasing distance. Set to 0% for it to fade "
|
|
"into complete silence for a realistic maximum hearing distance.");
|
|
QString bloomTooltip = tr("If an audio source is close enough, blooming will cause the audio to be played on all "
|
|
"speakers more or less regardless of their position (albeit with lower volume)");
|
|
|
|
qlMinDistance->setToolTip(minDistanceTooltip);
|
|
qsMinDistance->setToolTip(minDistanceTooltip);
|
|
qsbMinimumDistance->setToolTip(minDistanceTooltip);
|
|
qlMaxDistance->setToolTip(maxDistanceTooltip);
|
|
qsMaxDistance->setToolTip(maxDistanceTooltip);
|
|
qsbMaximumDistance->setToolTip(maxDistanceTooltip);
|
|
qlMinimumVolume->setToolTip(minVolumeTooltip);
|
|
qsMinimumVolume->setToolTip(minVolumeTooltip);
|
|
qsbMinimumVolume->setToolTip(minVolumeTooltip);
|
|
qlBloom->setToolTip(bloomTooltip);
|
|
qsBloom->setToolTip(bloomTooltip);
|
|
qsbBloom->setToolTip(bloomTooltip);
|
|
}
|
|
|
|
QString AudioOutputDialog::title() const {
|
|
return tr("Audio Output");
|
|
}
|
|
|
|
const QString &AudioOutputDialog::getName() const {
|
|
return AudioOutputDialog::name;
|
|
}
|
|
|
|
QIcon AudioOutputDialog::icon() const {
|
|
return QIcon(QLatin1String("skin:config_audio_output.png"));
|
|
}
|
|
|
|
QString AudioOutputDialog::getCurrentlySelectedOutputInterfaceName() const {
|
|
if (qcbSystem) {
|
|
return qcbSystem->currentText();
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
void AudioOutputDialog::load(const Settings &r) {
|
|
int i;
|
|
QList< QString > keys;
|
|
|
|
if (AudioOutputRegistrar::qmNew)
|
|
keys = AudioOutputRegistrar::qmNew->keys();
|
|
else
|
|
keys.clear();
|
|
i = keys.indexOf(AudioOutputRegistrar::current);
|
|
if (i >= 0)
|
|
loadComboBox(qcbSystem, i);
|
|
|
|
loadCheckBox(qcbExclusive, r.bExclusiveOutput);
|
|
loadSlider(qsDelay, r.iOutputDelay);
|
|
loadSlider(qsVolume, static_cast< int >(r.fVolume * 100.0f + 0.5f));
|
|
loadSlider(qsOtherVolume, static_cast< int >((1.0f - r.fOtherVolume) * 100.0f + 0.5f));
|
|
loadCheckBox(qcbAttenuateOthersOnTalk, r.bAttenuateOthersOnTalk);
|
|
loadCheckBox(qcbAttenuateOthers, r.bAttenuateOthers);
|
|
loadCheckBox(qcbAttenuateUsersOnPrioritySpeak, r.bAttenuateUsersOnPrioritySpeak);
|
|
loadCheckBox(qcbOnlyAttenuateSameOutput, r.bOnlyAttenuateSameOutput);
|
|
loadCheckBox(qcbAttenuateLoopbacks, r.bAttenuateLoopbacks);
|
|
|
|
const bool attenuationActive = r.bAttenuateOthers || r.bAttenuateOthersOnTalk;
|
|
qcbOnlyAttenuateSameOutput->setEnabled(attenuationActive);
|
|
qcbAttenuateLoopbacks->setEnabled(attenuationActive && r.bOnlyAttenuateSameOutput);
|
|
|
|
enablePulseAudioAttenuationOptionsFor(AudioOutputRegistrar::current);
|
|
|
|
loadSlider(qsJitter, r.iJitterBufferSize);
|
|
loadComboBox(qcbLoopback, r.lmLoopMode);
|
|
loadSlider(qsPacketDelay, static_cast< int >(r.dMaxPacketDelay));
|
|
loadSlider(qsPacketLoss, static_cast< int >(r.dPacketLoss * 100.0f + 0.5f));
|
|
qsbMinimumDistance->setValue(r.fAudioMinDistance);
|
|
qsbMaximumDistance->setValue(r.fAudioMaxDistance);
|
|
qsbMinimumVolume->setValue(static_cast< int >(r.fAudioMaxDistVolume * 100));
|
|
qsbBloom->setValue(static_cast< int >(r.fAudioBloom * 100));
|
|
loadCheckBox(qcbHeadphones, r.bPositionalHeadphone);
|
|
loadCheckBox(qcbPositional, r.bPositionalAudio);
|
|
|
|
qsOtherVolume->setEnabled(r.bAttenuateOthersOnTalk || r.bAttenuateOthers);
|
|
qlOtherVolume->setEnabled(r.bAttenuateOthersOnTalk || r.bAttenuateOthers);
|
|
qcbAttenuateLoopbacks->setEnabled(r.bOnlyAttenuateSameOutput);
|
|
}
|
|
|
|
void AudioOutputDialog::save() const {
|
|
s.iOutputDelay = qsDelay->value();
|
|
s.fVolume = static_cast< float >(qsVolume->value()) / 100.0f;
|
|
s.fOtherVolume = 1.0f - (static_cast< float >(qsOtherVolume->value()) / 100.0f);
|
|
s.bAttenuateOthersOnTalk = qcbAttenuateOthersOnTalk->isChecked();
|
|
s.bAttenuateOthers = qcbAttenuateOthers->isChecked();
|
|
s.bOnlyAttenuateSameOutput = qcbOnlyAttenuateSameOutput->isChecked();
|
|
s.bAttenuateLoopbacks = qcbAttenuateLoopbacks->isChecked();
|
|
s.bAttenuateUsersOnPrioritySpeak = qcbAttenuateUsersOnPrioritySpeak->isChecked();
|
|
s.iJitterBufferSize = qsJitter->value();
|
|
s.qsAudioOutput = qcbSystem->currentText();
|
|
s.lmLoopMode = static_cast< Settings::LoopMode >(qcbLoopback->currentIndex());
|
|
s.dMaxPacketDelay = static_cast< float >(qsPacketDelay->value());
|
|
s.dPacketLoss = static_cast< float >(qsPacketLoss->value()) / 100.0f;
|
|
s.fAudioMinDistance = static_cast< float >(qsbMinimumDistance->value());
|
|
s.fAudioMaxDistance = static_cast< float >(qsbMaximumDistance->value());
|
|
s.fAudioMaxDistVolume = static_cast< float >(qsbMinimumVolume->value()) / 100.0f;
|
|
s.fAudioBloom = static_cast< float >(qsbBloom->value()) / 100.0f;
|
|
s.bPositionalAudio = qcbPositional->isChecked();
|
|
s.bPositionalHeadphone = qcbHeadphones->isChecked();
|
|
s.bExclusiveOutput = qcbExclusive->isChecked();
|
|
|
|
|
|
if (AudioOutputRegistrar::qmNew) {
|
|
AudioOutputRegistrar *aor = AudioOutputRegistrar::qmNew->value(qcbSystem->currentText());
|
|
int idx = qcbDevice->currentIndex();
|
|
if (idx > -1) {
|
|
aor->setDeviceChoice(qcbDevice->itemData(idx), s);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AudioOutputDialog::on_qcbSystem_currentIndexChanged(int) {
|
|
qcbDevice->clear();
|
|
|
|
QList< audioDevice > choices;
|
|
|
|
if (AudioOutputRegistrar::qmNew) {
|
|
auto aor = AudioOutputRegistrar::qmNew->value(qcbSystem->currentText());
|
|
QVariant current = aor->getDeviceChoice();
|
|
choices = aor->getDeviceChoices();
|
|
|
|
for (int i = 0; i < choices.size(); ++i) {
|
|
auto &choice = choices.at(i);
|
|
qcbDevice->addItem(choice.first, choice.second);
|
|
qcbDevice->setItemData(i, choice.first.toHtmlEscaped(), Qt::ToolTipRole);
|
|
|
|
if (choice.second == current) {
|
|
qcbDevice->setCurrentIndex(i);
|
|
}
|
|
}
|
|
|
|
bool canmute = aor->canMuteOthers();
|
|
qsOtherVolume->setEnabled(canmute);
|
|
qcbAttenuateOthersOnTalk->setEnabled(canmute);
|
|
|
|
enablePulseAudioAttenuationOptionsFor(aor->name);
|
|
|
|
qcbAttenuateOthers->setEnabled(canmute);
|
|
qlOtherVolume->setEnabled(canmute);
|
|
|
|
bool usesdelay = aor->usesOutputDelay();
|
|
qsDelay->setEnabled(usesdelay);
|
|
qlDelay->setEnabled(usesdelay);
|
|
|
|
qcbExclusive->setEnabled(aor->canExclusive());
|
|
}
|
|
|
|
qcbDevice->setEnabled(!choices.isEmpty());
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsJitter_valueChanged(int v) {
|
|
qlJitter->setText(tr("%1 ms").arg(v * 10));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsJitter, QString("%1 %2").arg(v * 10).arg(tr("milliseconds")));
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsVolume_valueChanged(int v) {
|
|
QPalette pal;
|
|
|
|
if (v > 100) {
|
|
pal.setColor(qlVolume->foregroundRole(), Qt::red);
|
|
}
|
|
qlVolume->setPalette(pal);
|
|
|
|
qlVolume->setText(tr("%1 %").arg(v));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsVolume, Mumble::Accessibility::SliderMode::READ_PERCENT, "%");
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsOtherVolume_valueChanged(int v) {
|
|
qlOtherVolume->setText(tr("%1 %").arg(v));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsOtherVolume, Mumble::Accessibility::SliderMode::READ_PERCENT, "%");
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsPacketDelay_valueChanged(int v) {
|
|
qlPacketDelay->setText(tr("%1 ms").arg(v));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsPacketDelay, QString("%1 %2").arg(v).arg(tr("milliseconds")));
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsPacketLoss_valueChanged(int v) {
|
|
qlPacketLoss->setText(tr("%1 %").arg(v));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsPacketLoss, Mumble::Accessibility::SliderMode::READ_PERCENT, "%");
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsDelay_valueChanged(int v) {
|
|
qlDelay->setText(tr("%1 ms").arg(v * 10));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsDelay, QString("%1 %2").arg(v * 10).arg(tr("milliseconds")));
|
|
}
|
|
|
|
void AudioOutputDialog::on_qcbLoopback_currentIndexChanged(int v) {
|
|
bool ena = false;
|
|
if (v == 1)
|
|
ena = true;
|
|
|
|
qsPacketDelay->setEnabled(ena);
|
|
qlPacketDelay->setEnabled(ena);
|
|
qsPacketLoss->setEnabled(ena);
|
|
qlPacketLoss->setEnabled(ena);
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsMinDistance_valueChanged(int value) {
|
|
QSignalBlocker blocker(qsbMinimumDistance);
|
|
qsbMinimumDistance->setValue(value / 10.0);
|
|
|
|
// Ensure that max distance is always a least 1m larger than min distance
|
|
qsbMaximumDistance->setValue(std::max(qsbMaximumDistance->value(), (value / 10.0) + 1));
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsbMinimumDistance_valueChanged(double value) {
|
|
QSignalBlocker blocker(qsMinDistance);
|
|
qsMinDistance->setValue(static_cast< int >(value * 10));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsMinDistance, QString("%1 %2").arg(value * 10).arg(tr("meters")));
|
|
|
|
// Ensure that max distance is always a least 1m larger than min distance
|
|
qsMaxDistance->setValue(std::max(qsMaxDistance->value(), static_cast< int >(value * 10) + 10));
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsMaxDistance_valueChanged(int value) {
|
|
QSignalBlocker blocker(qsbMaximumDistance);
|
|
qsbMaximumDistance->setValue(value / 10.0);
|
|
|
|
// Ensure that min distance is always a least 1m less than max distance
|
|
qsbMinimumDistance->setValue(std::min(qsbMinimumDistance->value(), (value / 10.0) - 1));
|
|
}
|
|
void AudioOutputDialog::on_qsbMaximumDistance_valueChanged(double value) {
|
|
QSignalBlocker blocker(qsMaxDistance);
|
|
qsMaxDistance->setValue(static_cast< int >(value * 10));
|
|
Mumble::Accessibility::setSliderSemanticValue(qsMaxDistance, QString("%1 %2").arg(value * 10).arg(tr("meters")));
|
|
|
|
// Ensure that min distance is always a least 1m less than max distance
|
|
qsMinDistance->setValue(std::min(qsMinDistance->value(), static_cast< int >(value * 10) - 10));
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsMinimumVolume_valueChanged(int value) {
|
|
QSignalBlocker blocker(qsbMinimumVolume);
|
|
qsbMinimumVolume->setValue(value);
|
|
Mumble::Accessibility::setSliderSemanticValue(qsMinimumVolume, Mumble::Accessibility::SliderMode::READ_PERCENT,
|
|
"%");
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsbMinimumVolume_valueChanged(int value) {
|
|
QSignalBlocker blocker(qsMinimumVolume);
|
|
qsMinimumVolume->setValue(value);
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsBloom_valueChanged(int value) {
|
|
QSignalBlocker blocker(qsbBloom);
|
|
qsbBloom->setValue(value);
|
|
Mumble::Accessibility::setSliderSemanticValue(qsBloom, Mumble::Accessibility::SliderMode::READ_PERCENT, "%");
|
|
}
|
|
|
|
void AudioOutputDialog::on_qsbBloom_valueChanged(int value) {
|
|
QSignalBlocker blocker(qsBloom);
|
|
qsBloom->setValue(value);
|
|
}
|
|
|
|
void AudioOutputDialog::on_qcbAttenuateOthersOnTalk_clicked(bool checked) {
|
|
bool b = qcbAttenuateOthers->isChecked() || checked;
|
|
qsOtherVolume->setEnabled(b);
|
|
qlOtherVolume->setEnabled(b);
|
|
|
|
qcbOnlyAttenuateSameOutput->setEnabled(b);
|
|
qcbAttenuateLoopbacks->setEnabled(b && qcbOnlyAttenuateSameOutput->isChecked());
|
|
}
|
|
|
|
void AudioOutputDialog::on_qcbAttenuateOthers_clicked(bool checked) {
|
|
bool b = qcbAttenuateOthersOnTalk->isChecked() || checked;
|
|
qsOtherVolume->setEnabled(b);
|
|
qlOtherVolume->setEnabled(b);
|
|
|
|
qcbOnlyAttenuateSameOutput->setEnabled(b);
|
|
qcbAttenuateLoopbacks->setEnabled(b && qcbOnlyAttenuateSameOutput->isChecked());
|
|
}
|
|
|
|
void AudioOutputDialog::on_qcbOnlyAttenuateSameOutput_clicked(bool checked) {
|
|
qcbAttenuateLoopbacks->setEnabled(checked);
|
|
}
|