This repository has been archived on 2023-10-25. You can view files and clone it, but cannot push or open issues or pull requests.
speex-webrtc-test/src/MainWindow.cpp
Kirill Kirilenko 3fd40888aa Remove sleep_until from processing loop
Fix DSP parameters control in MainWindow
Move QAudioInput and QAudioOutput to different threads
2020-11-12 21:18:47 +03:00

395 lines
12 KiB
C++

#include "MainWindow.h"
#include "AudioLevel.h"
#include "ui_MainWindow.h"
#include <QLoggingCategory>
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<int>::of(&QComboBox::activated), this,
&MainWindow::changeDevicesConfiguration);
connect(ui->outputDeviceSelector, QOverload<int>::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<QAudioDeviceInfo>(),
ui->outputDeviceSelector->itemData(ui->outputDeviceSelector->currentIndex())
.value<QAudioDeviceInfo>(),
ui->monitorDeviceSelector->itemData(ui->monitorDeviceSelector->currentIndex())
.value<QAudioDeviceInfo>());
}
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<qreal>& 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<qreal>& 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