diff options
| author | Anton <anton@picapica.im> | 2026-09-13 19:53:28 +0400 |
|---|---|---|
| committer | Anton <anton@picapica.im> | 2026-09-13 19:53:28 +0400 |
| commit | 9a84c95af6771590d4e5fe436e77bbe92a9dea92 (patch) | |
| tree | 71511a89b296d02d4106f9f031de806c8db422b7 /pica-client/dialogs | |
| parent | e62dd7d30bec7a679013dcce8ec4bc2b2f842109 (diff) | |
| download | pica-pica-9a84c95af6771590d4e5fe436e77bbe92a9dea92.tar.gz pica-pica-9a84c95af6771590d4e5fe436e77bbe92a9dea92.zip | |
pica-client: use webrtc-audio-processing instead of speexdsp for echo cancellation
Diffstat (limited to 'pica-client/dialogs')
| -rw-r--r-- | pica-client/dialogs/settingsdialog.cpp | 125 | ||||
| -rw-r--r-- | pica-client/dialogs/settingsdialog.h | 16 |
2 files changed, 119 insertions, 22 deletions
diff --git a/pica-client/dialogs/settingsdialog.cpp b/pica-client/dialogs/settingsdialog.cpp index a951b40..8685936 100644 --- a/pica-client/dialogs/settingsdialog.cpp +++ b/pica-client/dialogs/settingsdialog.cpp @@ -195,18 +195,38 @@ SettingsDialog::SettingsDialog(QWidget *parent) : audioTestStatus = new QLabel(this); audioTestStatus->setAlignment(Qt::AlignCenter); - cbEchoCancel = new QCheckBox(tr("Cancel acoustic echo"), this); - cbEchoCancel->setToolTip(tr("Removes the sound of the other person coming back out of your " - "microphone, which is what makes them hear themselves when you " - "are on speakers. Has no effect where the operating system " - "already does it.")); - - cbAutoCaptureGain = new QCheckBox(tr("Automatic microphone gain"), this); - cbAutoCaptureGain->setToolTip(tr("Turns the microphone's own recording level down during a " - "call if it is loud enough to distort, and puts it back " - "afterwards. A microphone set too loud clips, and clipping " - "cannot be repaired later - it also stops echo cancellation " - "working.")); + // Echo cancellation: removing the sound of the other person coming back + // out of the microphone, which is what makes them hear themselves when + // the call is on speakers. Exactly one of the three - two cancellers in + // series is worse than either alone, since the second one is left + // chasing a reference that is no longer in the signal. + QGroupBox *gbEchoCancel = new QGroupBox(tr("Audio echo cancellation"), this); + QVBoxLayout *echoCancelLayout = new QVBoxLayout(); + + // Parented to the group box, not the dialog: QRadioButton's automatic + // exclusivity groups by parent widget, and these three must not join the + // direct connection or multiple login buttons. + rbEchoCancelOwn = new QRadioButton(tr("Use own echo cancellation"), gbEchoCancel); + rbEchoCancelOwn->setToolTip(tr("Cancels the echo inside Pica Pica, using WebRTC's AEC3. " + "Works with any microphone and any sound system, and is the " + "only option where the platform provides nothing.")); + + rbEchoCancelPlatform = new QRadioButton(tr("Use platform provided echo cancellation"), gbEchoCancel); + + rbEchoCancelNone = new QRadioButton(tr("Disable echo cancellation"), gbEchoCancel); + rbEchoCancelNone->setToolTip(tr("Nothing is removed from the microphone signal. Right with a " + "headset, where there is no echo to cancel, and worth trying " + "if cancellation is cutting into your speech.")); + + echoCancelLayout->addWidget(rbEchoCancelOwn); + echoCancelLayout->addWidget(rbEchoCancelPlatform); + echoCancelLayout->addWidget(rbEchoCancelNone); + gbEchoCancel->setLayout(echoCancelLayout); + + // Fills in the platform option's tooltip and greys it out where there is + // no such thing. loadSettings() runs afterwards and is what actually + // picks one of the three. + updateEchoCancellationChoices(); btRingTest = new QPushButton(tr("Test ring 🔔"), this); connect(btRingTest, SIGNAL(clicked()), this, SLOT(toggleRingTest())); @@ -224,8 +244,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : audiodevlayout->addWidget(audioCaptureDev); audiodevlayout->addWidget(lbAudioPlaybackDev); audiodevlayout->addWidget(audioPlaybackDev); - audiodevlayout->addWidget(cbEchoCancel); - audiodevlayout->addWidget(cbAutoCaptureGain); + audiodevlayout->addWidget(gbEchoCancel); audiodevlayout->addWidget(btAudioTest); audiodevlayout->addWidget(audioTestStatus); audiodevlayout->addWidget(lbAudioRingDev); @@ -908,6 +927,43 @@ void SettingsDialog::audioDriverChanged(int index) fillAudioCaptureDevices(); fillAudioPlaybackDevices(); fillAudioRingDevices(); + + // ALSA has no echo cancellation of its own and PulseAudio does, so the + // platform option comes and goes with this choice. + updateEchoCancellationChoices(); +} + +void SettingsDialog::updateEchoCancellationChoices() +{ + // PlatformDriverName() reads the driver SetLinuxDriverName() published, + // which audioDriverChanged() has already updated by the time it calls + // this - so the answer follows the combo box rather than the saved + // setting. + const QString driver = AudioDevice::PlatformDriverName(CAPTURE); + const bool available = AudioDevice::PlatformEchoCancellationAvailable(driver); + + rbEchoCancelPlatform->setEnabled(available); + + if (available) + { + rbEchoCancelPlatform->setToolTip(tr("Lets the operating system or sound server cancel the " + "echo instead. Where it exists this is the better " + "one: it sits below Pica Pica and can see the real " + "speaker signal and the real clock.\n\n" + "With PulseAudio or PipeWire it also means picking an " + "echo cancelling source as the microphone above - a " + "plain input device is not processed.")); + } + else + { + rbEchoCancelPlatform->setToolTip(tr("Not available: the audio system in use does not " + "cancel echo for us.")); + + // A disabled button that is still the checked one would leave the + // group showing a choice that is not in effect. + if (rbEchoCancelPlatform->isChecked()) + rbEchoCancelOwn->setChecked(true); + } } void SettingsDialog::fillAudioCaptureDevices() @@ -1061,8 +1117,27 @@ void SettingsDialog::loadSettings() if (audioPlaybackDevItem >= 0) audioPlaybackDev->setCurrentIndex(audioPlaybackDevItem); - cbEchoCancel->setChecked(st.loadValue("audio.echo_cancel", 1).toBool()); - cbAutoCaptureGain->setChecked(st.loadValue("audio.auto_capture_gain", 0).toBool()); + // Not read from st directly: the setting has a default that depends on the + // platform, and an older on/off setting to fall back on. + switch (loadEchoCancellationSetting()) + { + case EchoCancellationPlatform: + rbEchoCancelPlatform->setChecked(true); + break; + + case EchoCancellationNone: + rbEchoCancelNone->setChecked(true); + break; + + case EchoCancellationOwn: + default: + rbEchoCancelOwn->setChecked(true); + break; + } + + // Covers a saved "platform" on a machine where it is no longer on offer - + // a Linux install whose audio system has since been moved to ALSA. + updateEchoCancellationChoices(); QString audioRingDevVal = st.loadValue("audio.ring_device", "default").toString(); int audioRingDevItem = audioRingDev->findData(audioRingDevVal); @@ -1116,8 +1191,6 @@ void SettingsDialog::storeSettings() st.storeValue("audio.capture_device", audioCaptureDev->itemData(audioCaptureDev->currentIndex()).toString()); st.storeValue("audio.playback_device", audioPlaybackDev->itemData(audioPlaybackDev->currentIndex()).toString()); st.storeValue("audio.ring_device", audioRingDev->itemData(audioRingDev->currentIndex()).toString()); - st.storeValue("audio.echo_cancel", cbEchoCancel->isChecked() ? "1" : "0"); - st.storeValue("audio.auto_capture_gain", cbAutoCaptureGain->isChecked() ? "1" : "0"); if (audioDriver) { @@ -1125,4 +1198,20 @@ void SettingsDialog::storeSettings() st.storeValue("audio.driver", drv); AudioDevice::SetLinuxDriverName(drv); } + + // After the driver, since on Linux that is what decides whether the + // platform option means anything - and updateEchoCancellationChoices() + // has already moved the selection off it if it does not. + EchoCancellationMode echoMode = EchoCancellationOwn; + + if (rbEchoCancelPlatform->isChecked()) + echoMode = EchoCancellationPlatform; + else if (rbEchoCancelNone->isChecked()) + echoMode = EchoCancellationNone; + + storeEchoCancellationSetting(echoMode); + + // Publish it to the audio threads, the same way the driver above is + // published - see AudioDevice::SetEchoCancellation(). + AudioDevice::SetEchoCancellation(echoMode); } diff --git a/pica-client/dialogs/settingsdialog.h b/pica-client/dialogs/settingsdialog.h index b879a74..ba04612 100644 --- a/pica-client/dialogs/settingsdialog.h +++ b/pica-client/dialogs/settingsdialog.h @@ -78,10 +78,18 @@ private: // a slot hidden behind one ends up declared but never generated. QComboBox *audioDriver; - // Whether to run our own acoustic echo cancellation. Ignored when the - // platform provides one - see AudioDevice::PlatformDriverName(). - QCheckBox *cbEchoCancel; - QCheckBox *cbAutoCaptureGain; + // Who takes the echo of the other person back out of the microphone + // signal: EchoCanceller, the platform, or nobody. See + // EchoCancellationMode - one of the three, never two at once. + // + // The platform option is only offered where there is something to offer; + // on Linux that depends on the audio system chosen above it, so it is + // enabled and disabled as that combo box changes rather than once at + // startup. + QRadioButton *rbEchoCancelOwn; + QRadioButton *rbEchoCancelPlatform; + QRadioButton *rbEchoCancelNone; + void updateEchoCancellationChoices(); QComboBox *videoDev; QPushButton *videoDevRefresh; |
