#include "MainWindow.h" #include "AudioLevel.h" #include "ui_MainWindow.h" #include namespace SpeexWebRTCTest { namespace { Q_LOGGING_CATEGORY(Gui, "gui") } QAudioFormat getCaptureFormat() { QAudioFormat format; format.setSampleRate(48000); format.setChannelCount(1); format.setSampleSize(16); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); return format; } QAudioFormat getOutputFormat() { return getCaptureFormat(); } QAudioFormat getMonitorFormat() { QAudioFormat format; format.setSampleRate(48000); format.setChannelCount(2); format.setSampleSize(16); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); return format; } MainWindow::MainWindow() : ui(new Ui::MainWindow) { ui->setupUi(this); audioInputThread_.start(); audioOutputThread_.start(); qDebug(Gui) << "Enumerating audio devices..."; for (auto& deviceInfo : QAudioDeviceInfo::availableDevices(QAudio::AudioInput)) { ui->inputDeviceSelector->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); ui->monitorDeviceSelector->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); } for (auto& deviceInfo : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) ui->outputDeviceSelector->addItem(deviceInfo.deviceName(), QVariant::fromValue(deviceInfo)); connect(ui->inputDeviceSelector, QOverload::of(&QComboBox::activated), this, &MainWindow::changeDevicesConfiguration); connect(ui->outputDeviceSelector, QOverload::of(&QComboBox::activated), this, &MainWindow::changeDevicesConfiguration); connect(ui->speexRadioButton, &QRadioButton::toggled, this, &MainWindow::switchBackend); connect(ui->noiseGroupBox, &QGroupBox::toggled, this, &MainWindow::changeNoiseReductionSettings); connect(ui->noiseSuppressionDial, &QDial::valueChanged, this, &MainWindow::changeNoiseReductionSettings); connect(ui->agcGroupBox, &QGroupBox::toggled, this, &MainWindow::changeAGCSettings); connect(ui->agcLevelDial, &QDial::valueChanged, this, &MainWindow::changeAGCSettings); connect(ui->agcMaxGainDial, &QDial::valueChanged, this, &MainWindow::changeAGCSettings); connect(ui->agcMaxIncrementDial, &QDial::valueChanged, this, &MainWindow::changeAGCSettings); connect(ui->agcMaxDecrementDial, &QDial::valueChanged, this, &MainWindow::changeAGCSettings); connect(ui->aecGroupBox, &QGroupBox::toggled, this, &MainWindow::changeAECSettings); connect(ui->aecSuppressionDial, &QDial::valueChanged, this, &MainWindow::changeAECSettings); initializeAudio(QAudioDeviceInfo::defaultInputDevice(), QAudioDeviceInfo::defaultOutputDevice(), QAudioDeviceInfo::defaultInputDevice()); startRecording(); } MainWindow::~MainWindow() { stopRecording(); audioInputThread_.exit(0); audioOutputThread_.exit(0); } void fixFormatForDevice(QAudioFormat& format, const QAudioDeviceInfo& info) { if (!info.isFormatSupported(format)) { QAudioFormat newFormat = info.nearestFormat(format); qWarning(Gui).nospace() << "Preferred format " << format << " is not supported by device " << info.deviceName() << "."; qWarning(Gui) << "Trying to use nearest format" << newFormat; format = newFormat; } } void MainWindow::initializeAudio(const QAudioDeviceInfo& inputDeviceInfo, const QAudioDeviceInfo& outputDeviceInfo, const QAudioDeviceInfo& monitorDeviceInfo) { qDebug(Gui) << "Initializing audio processing tract..."; auto captureFormat = getCaptureFormat(); auto outputFormat = getOutputFormat(); auto monitorFormat = getMonitorFormat(); fixFormatForDevice(captureFormat, inputDeviceInfo); fixFormatForDevice(outputFormat, outputDeviceInfo); fixFormatForDevice(monitorFormat, monitorDeviceInfo); audioInput_.reset(new QAudioInput(inputDeviceInfo, captureFormat)); audioOutput_.reset(new QAudioOutput(outputDeviceInfo, outputFormat)); monitorInput_.reset(new QAudioInput(monitorDeviceInfo, monitorFormat)); audioInput_->moveToThread(&audioInputThread_); audioOutput_->moveToThread(&audioOutputThread_); monitorInput_->moveToThread(&audioInputThread_); processor_.reset(new AudioProcessor(captureFormat, monitorFormat, monitorBuffer_)); connect(processor_.get(), &AudioProcessor::voiceActivityChanged, this, &MainWindow::updateVoiceActivity); connect(processor_.get(), &AudioProcessor::inputLevelsChanged, this, &MainWindow::updateInputAudioLevels); connect(processor_.get(), &AudioProcessor::outputLevelsChanged, this, &MainWindow::updateOutputAudioLevels); } void MainWindow::startRecording() { qDebug(Gui) << "Starting audio processing..."; processor_->open(QIODevice::ReadWrite | QIODevice::Truncate); monitorBuffer_.open(QIODevice::ReadWrite | QIODevice::Truncate); audioInput_->start(processor_.get()); audioOutput_->start(processor_.get()); monitorInput_->start(&monitorBuffer_); qInfo(Gui) << "input buffer size:" << audioInput_->bufferSize(); qInfo(Gui) << "output buffer size:" << audioOutput_->bufferSize(); } void MainWindow::stopRecording() { qDebug(Gui) << "Stopping audio processing..."; if (monitorInput_) monitorInput_->stop(); if (audioInput_) audioInput_->stop(); if (audioOutput_) audioOutput_->stop(); monitorBuffer_.close(); if (processor_) processor_->close(); } void MainWindow::changeDevicesConfiguration() { stopRecording(); initializeAudio(ui->inputDeviceSelector->itemData(ui->inputDeviceSelector->currentIndex()) .value(), ui->outputDeviceSelector->itemData(ui->outputDeviceSelector->currentIndex()) .value(), ui->monitorDeviceSelector->itemData(ui->monitorDeviceSelector->currentIndex()) .value()); } void MainWindow::switchBackend() { Backend newBackend = ui->speexRadioButton->isChecked() ? Backend::Speex : Backend::WebRTC; qInfo(Gui) << "Switching DSP backend..."; setupDials(newBackend); processor_->switchBackend(newBackend); } QString levelFromCode(int value) { switch (value) { case 0: return "Low"; case 1: return "Moderate"; case 2: return "High"; case 3: return "Very High"; default: throw std::invalid_argument("Invalid code for level"); } } void MainWindow::setupDials(Backend backend) { ui->noiseGroupBox->setChecked(false); ui->noiseSuppressionDial->setValue(0); ui->agcGroupBox->setChecked(false); ui->agcLevelDial->setValue(0); ui->agcLevelValue->setText("0 dBFS"); ui->agcMaxGainDial->setValue(0); ui->agcMaxGainValue->setText("0 dB"); ui->agcMaxIncrementDial->setValue(0); ui->agcMaxIncrementValue->setText("0 dB/sec"); ui->agcMaxDecrementDial->setValue(0); ui->agcMaxDecrementValue->setText("0 dB/sec"); ui->aecGroupBox->setChecked(false); ui->aecSuppressionDial->setValue(0); updateVoiceActivity(false); if (backend == Backend::Speex) { ui->noiseSuppressionDial->setMaximum(60); ui->noiseSuppressionValue->setText("0 dBFS"); ui->agcMaxIncrementLabel->setVisible(true); ui->agcMaxIncrementDial->setVisible(true); ui->agcMaxIncrementValue->setVisible(true); ui->agcMaxDecrementLabel->setVisible(true); ui->agcMaxDecrementDial->setVisible(true); ui->agcMaxDecrementValue->setVisible(true); ui->aecSuppressionDial->setMaximum(60); ui->aecSuppressionValue->setText("0 dB"); } else { ui->noiseSuppressionDial->setMaximum(3); ui->noiseSuppressionValue->setText(levelFromCode(0)); ui->agcMaxIncrementLabel->setVisible(false); ui->agcMaxIncrementDial->setVisible(false); ui->agcMaxIncrementValue->setVisible(false); ui->agcMaxDecrementLabel->setVisible(false); ui->agcMaxDecrementDial->setVisible(false); ui->agcMaxDecrementValue->setVisible(false); ui->aecSuppressionDial->setMaximum(2); ui->aecSuppressionValue->setText(levelFromCode(0)); } } void MainWindow::changeNoiseReductionSettings() { if (currentBackend() == Backend::Speex) { std::int32_t enabled = ui->noiseGroupBox->isChecked() ? 1 : 0; processor_->setEffectParam("noise_reduction_enabled", enabled); std::int32_t maxAttenuation = -ui->noiseSuppressionDial->value(); ui->noiseSuppressionValue->setText(QString("%1 dB").arg(-maxAttenuation)); processor_->setEffectParam("noise_reduction_max_attenuation", maxAttenuation); } else { processor_->setEffectParam("noise_reduction_enabled", ui->noiseGroupBox->isChecked()); int suppressionLevel = ui->noiseSuppressionDial->value(); ui->noiseSuppressionValue->setText(levelFromCode(suppressionLevel)); processor_->setEffectParam("noise_reduction_suppression_level", suppressionLevel); } } void MainWindow::changeAGCSettings() { if (currentBackend() == Backend::Speex) { std::int32_t enabled = ui->agcGroupBox->isChecked() ? 1 : 0; processor_->setEffectParam("gain_control_enabled", enabled); std::int32_t level = QAudio::convertVolume(-ui->agcLevelDial->value(), QAudio::DecibelVolumeScale, QAudio::LinearVolumeScale) * 32768; ui->agcLevelValue->setText(QString("%1 dBFS").arg(-ui->agcLevelDial->value())); processor_->setEffectParam("gain_control_level", level); std::int32_t maxGain = ui->agcMaxGainDial->value(); ui->agcMaxGainValue->setText(QString("%1 dB").arg(maxGain)); processor_->setEffectParam("gain_control_max_gain", maxGain); std::int32_t maxIncrement = ui->agcMaxIncrementDial->value(); ui->agcMaxIncrementValue->setText(QString("%1 dB/sec").arg(maxIncrement)); processor_->setEffectParam("gain_control_max_increment", maxIncrement); std::int32_t maxDecrement = ui->agcMaxDecrementDial->value(); ui->agcMaxDecrementValue->setText(QString("%1 dB/sec").arg(maxDecrement)); processor_->setEffectParam("gain_control_max_decrement", maxDecrement); } else { processor_->setEffectParam("gain_control_enabled", ui->agcGroupBox->isChecked()); int level = ui->agcLevelDial->value(); ui->agcLevelValue->setText(QString("%1 dBFS").arg(-ui->agcLevelDial->value())); processor_->setEffectParam("gain_control_target_level", level); int maxGain = ui->agcMaxGainDial->value(); ui->agcMaxGainValue->setText(QString("%1 dB").arg(maxGain)); processor_->setEffectParam("gain_control_max_gain", maxGain); } } void MainWindow::changeAECSettings() { if (currentBackend() == Backend::Speex) { std::int32_t enabled = ui->aecGroupBox->isChecked() ? 1 : 0; processor_->setEffectParam("echo_cancellation_enabled", enabled); std::int32_t maxAttenuation = -ui->aecSuppressionDial->value(); ui->aecSuppressionValue->setText(QString("%1 dB").arg(-maxAttenuation)); processor_->setEffectParam("echo_cancellation_max_attenuation", maxAttenuation); } else { processor_->setEffectParam("echo_cancellation_enabled", ui->aecGroupBox->isChecked()); int suppressionLevel = ui->aecSuppressionDial->value(); ui->aecSuppressionValue->setText(levelFromCode(suppressionLevel)); processor_->setEffectParam("echo_cancellation_suppression_level", suppressionLevel); } } Backend MainWindow::currentBackend() const { return processor_->getCurrentBackend(); } void MainWindow::updateVoiceActivity(bool active) { if (active) { ui->vadLabel->setText("Voice active"); ui->vadLabel->setStyleSheet("QLabel { color : #2aa82b; }"); } else { ui->vadLabel->setText("Voice inactive"); ui->vadLabel->setStyleSheet("QLabel { color : black; }"); } } void MainWindow::updateInputAudioLevels(const QVector& levels) { if (inputAudioLevels_.count() != levels.size()) { qDeleteAll(inputAudioLevels_); inputAudioLevels_.clear(); for (int i = 0; i < levels.size(); ++i) { auto* level = new AudioLevel(ui->centralWidget); inputAudioLevels_.append(level); ui->inputLevelsLayout->addWidget(level); } } for (int i = 0; i < levels.count(); ++i) inputAudioLevels_.at(i)->setLevel(levels.at(i)); } void MainWindow::updateOutputAudioLevels(const QVector& levels) { if (outputAudioLevels_.count() != levels.size()) { qDeleteAll(outputAudioLevels_); outputAudioLevels_.clear(); for (int i = 0; i < levels.size(); ++i) { auto* level = new AudioLevel(ui->centralWidget); outputAudioLevels_.append(level); ui->outputLevelsLayout->addWidget(level); } } for (int i = 0; i < levels.count(); ++i) outputAudioLevels_.at(i)->setLevel(levels.at(i)); } } // namespace SpeexWebRTCTest