Add missing desktop voice compressor controls

This commit is contained in:
evgeny committed 2026-11-02 16:17:03 +01:00
1 parent 1c0f40f247
commit 26f8491abc
8 files changed
+110 -15

No files matched your search

+44 -6
View File
@@ -227,7 +227,10 @@ void AudioDeviceSettingsPage::loadFromDb() {
m_savedRadioCaptureStereo = m_db->getUiStateInt("radio_capture_stereo", 0);
m_savedCompressorEnabled = m_db->getUiStateInt("compressor_enabled", 0);
m_savedCompressorMaxGainDb = m_db->getUiStateInt("compressor_max_gain_db", 25);
m_savedCompressorLookbackMs = m_db->getUiStateInt("compressor_lookback_ms", 200);
m_savedCompressorLookaheadMs = m_db->getUiStateInt("compressor_lookahead_ms", 100);
m_savedCompressorRiseRate = m_db->getUiStateInt("compressor_rise_rate", 10);
m_savedCompressorTargetLevelDb = m_db->getUiStateInt("compressor_target_level_db", 0);
m_jitterReserveSpin->setValue(m_db->getUiStateInt("call_jitter_max_reserve_ms", 1000) / 1000.0);
QByteArray range = m_db->getUiState("call_video_bitrate_range_kbps").toUtf8();
uint32_t minimum = CALL_VIDEO_RATE_MIN_BPS, maximum = CALL_VIDEO_RATE_MAX_BPS;
@@ -263,7 +266,10 @@ void AudioDeviceSettingsPage::loadFromDb() {
m_compressorEnabledCb->setChecked(m_savedCompressorEnabled != 0);
m_compressorMaxGainSlider->setValue(m_savedCompressorMaxGainDb);
m_compressorLookbackSlider->setValue(m_savedCompressorLookbackMs);
m_compressorLookaheadSlider->setValue(m_savedCompressorLookaheadMs);
m_compressorRiseRateSlider->setValue(m_savedCompressorRiseRate);
m_compressorTargetLevelSlider->setValue(m_savedCompressorTargetLevelDb);
m_aecEnabledCb->setChecked(m_savedAecEnabled != 0);
updateCompressorLabels();
}
@@ -294,7 +300,10 @@ void AudioDeviceSettingsPage::applyAndSave() {
int compEnabled = m_compressorEnabledCb->isChecked() ? 1 : 0;
int compMaxGain = m_compressorMaxGainSlider->value();
int compLookback = m_compressorLookbackSlider->value();
int compLookahead = m_compressorLookaheadSlider->value();
int compRiseRate = m_compressorRiseRateSlider->value();
int compTargetLevel = m_compressorTargetLevelSlider->value();
int jitterReserveMs = (int)std::lround(m_jitterReserveSpin->value() * 1000);
int aecEnabled = m_aecEnabledCb->isChecked() ? 1 : 0;
@@ -313,6 +322,14 @@ void AudioDeviceSettingsPage::applyAndSave() {
gui_bridge_post_setting(GUI_SETTING_CHAT, setting.first, value.constData());
}
// Эти параметры принадлежат GUI-рекордеру и не входят в chat_setting ядра.
const QPair<const char*, int> recorderSettings[] = {
{"compressor_lookback_ms", compLookback}, {"compressor_lookahead_ms", compLookahead},
{"compressor_target_level_db", compTargetLevel}
};
for (const auto& setting : recorderSettings)
gui_bridge_post_setting(GUI_SETTING_UI_STATE, setting.first, QByteArray::number(setting.second).constData());
m_savedOutputId = outputId;
m_savedInputId = inputId;
m_savedCodecPreset = codecPreset;
@@ -321,12 +338,16 @@ void AudioDeviceSettingsPage::applyAndSave() {
m_savedRadioCaptureStereo = radioCaptureStereo;
m_savedCompressorEnabled = compEnabled;
m_savedCompressorMaxGainDb = compMaxGain;
m_savedCompressorLookbackMs = compLookback;
m_savedCompressorLookaheadMs = compLookahead;
m_savedCompressorRiseRate = compRiseRate;
m_savedCompressorTargetLevelDb = compTargetLevel;
m_savedAecEnabled = aecEnabled;
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG,
"requested output_default=%d input_default=%d codec=%d radioOpus=%d/%dkbps stereo=%d compressor=%d gain=%ddB rise=%.1fx aec=%d",
"requested output_default=%d input_default=%d codec=%d radioOpus=%d/%dkbps stereo=%d "
"compressor=%d gain=%ddB back=%dms ahead=%dms rise=%.1fx target=%ddBFS aec=%d",
outputId.isEmpty(), inputId.isEmpty(), codecPreset, radioOpusMode, radioOpusBitrate, radioCaptureStereo,
compEnabled, compMaxGain, (float)compRiseRate, aecEnabled);
compEnabled, compMaxGain, compLookback, compLookahead, (float)compRiseRate, compTargetLevel, aecEnabled);
if (m_recorder) {
m_recorder->setCompressorEnabled(compEnabled != 0);
@@ -335,10 +356,10 @@ void AudioDeviceSettingsPage::applyAndSave() {
compCfg.sample_rate = 48000;
compCfg.channels = 1;
compCfg.max_gain_db = (float)compMaxGain;
compCfg.lookback_ms = 200;
compCfg.lookahead_ms = 100;
compCfg.lookback_ms = compLookback;
compCfg.lookahead_ms = compLookahead;
compCfg.rise_rate_per_sec = (float)compRiseRate;
compCfg.target_level = 1.0f;
compCfg.target_level = std::pow(10.0f, compTargetLevel / 20.0f);
m_recorder->configureCompressor(compCfg);
}
}
@@ -438,15 +459,29 @@ void AudioDeviceSettingsPage::buildCompressorUI(QVBoxLayout* layout) {
};
makeSliderRow(QCoreApplication::translate("ChatGui", "Max gain:"), m_compressorMaxGainSlider, m_compressorMaxGainLabel,
1, 50, 1);
0, 50, 1);
m_compressorMaxGainSlider->setObjectName("compressorMaxGain");
makeSliderRow(QCoreApplication::translate("ChatGui", "Lookback time:"), m_compressorLookbackSlider, m_compressorLookbackLabel,
50, 500, 10);
m_compressorLookbackSlider->setObjectName("compressorLookback");
makeSliderRow(QCoreApplication::translate("ChatGui", "Lookahead time:"), m_compressorLookaheadSlider, m_compressorLookaheadLabel,
20, 500, 10);
m_compressorLookaheadSlider->setObjectName("compressorLookahead");
makeSliderRow(QCoreApplication::translate("ChatGui", "Rise rate (x/sec):"), m_compressorRiseRateSlider, m_compressorRiseRateLabel,
2, 10, 1);
makeSliderRow(QCoreApplication::translate("ChatGui", "Target level:"), m_compressorTargetLevelSlider, m_compressorTargetLevelLabel,
-40, 0, 1);
m_compressorTargetLevelSlider->setObjectName("compressorTargetLevel");
auto connectSlider = [this](QSlider* slider) {
connect(slider, &QSlider::valueChanged, this, &AudioDeviceSettingsPage::updateCompressorLabels);
};
connectSlider(m_compressorMaxGainSlider);
connectSlider(m_compressorLookbackSlider);
connectSlider(m_compressorLookaheadSlider);
connectSlider(m_compressorRiseRateSlider);
connectSlider(m_compressorTargetLevelSlider);
updateCompressorLabels();
}
void AudioDeviceSettingsPage::updateCompressorLabels() {
@@ -456,4 +491,7 @@ void AudioDeviceSettingsPage::updateCompressorLabels() {
int v = m_compressorRiseRateSlider->value();
m_compressorRiseRateLabel->setText(QString::number(v) + "x");
}
m_compressorLookbackLabel->setText(QString::number(m_compressorLookbackSlider->value()) + QCoreApplication::translate("ChatGui", " ms"));
m_compressorLookaheadLabel->setText(QString::number(m_compressorLookaheadSlider->value()) + QCoreApplication::translate("ChatGui", " ms"));
m_compressorTargetLevelLabel->setText(QString::number(m_compressorTargetLevelSlider->value()) + " dBFS");
}
+11 -2
View File
@@ -36,7 +36,7 @@ private slots:
private:
void stopMicTest(); // Останавливает таймер, освобождает тестовый recorder, сбрасывает кнопку и индикатор; повтор безопасен.
void buildCompressorUI(class QVBoxLayout* layout); // Добавляет компрессор/AEC к переданному layout страницы.
void updateCompressorLabels(); // Показывает gain в dB и rise rate в x/sec для текущих положений ползунков.
void updateCompressorLabels(); // Показывает усиление, времена анализа/упреждения, скорость роста и целевой уровень.
DbManager* m_db; // Заимствованный источник ui_state; nullptr оставляет defaults/предыдущий снимок формы.
AudioRecorder* m_recorder = nullptr; // Заимствованный рекордер сообщений; должен жить до последнего applyAndSave.
@@ -55,13 +55,22 @@ private:
int m_savedCodecPreset = 1; // Стандартный пресет Opus.
QCheckBox* m_compressorEnabledCb; // Разрешение компрессора для следующей записи.
QSlider* m_compressorMaxGainSlider; // Gain 1..50 dB.
QSlider* m_compressorMaxGainSlider; // Gain 0..50 dB.
QSlider* m_compressorLookbackSlider; // Окно анализа 50..500 мс.
QSlider* m_compressorLookaheadSlider; // Упреждение 20..500 мс.
QSlider* m_compressorRiseRateSlider; // Rise rate 2..10 x/sec.
QSlider* m_compressorTargetLevelSlider; // Целевой уровень -40..0 dBFS.
QLabel* m_compressorMaxGainLabel; // Текущее значение ползунка gain.
QLabel* m_compressorLookbackLabel; // Текущее окно анализа, мс.
QLabel* m_compressorLookaheadLabel; // Текущее упреждение, мс.
QLabel* m_compressorRiseRateLabel; // Текущее значение ползунка rise rate.
QLabel* m_compressorTargetLevelLabel; // Текущий целевой уровень, dBFS.
int m_savedCompressorEnabled = 0; // Снимок разрешения компрессора.
int m_savedCompressorMaxGainDb = 25; // Снимок gain, dB.
int m_savedCompressorLookbackMs = 200; // Снимок окна анализа, мс; хранится только в ui_state.
int m_savedCompressorLookaheadMs = 100; // Снимок упреждения, мс; хранится только в ui_state.
int m_savedCompressorRiseRate = 10; // Снимок rise rate, x/sec.
int m_savedCompressorTargetLevelDb = 0; // Снимок целевого уровня, dBFS; хранится только в ui_state.
class QDoubleSpinBox* m_jitterReserveSpin; // Максимальный jitter reserve 0.5..2.0 сек; сохраняется в миллисекундах.
class QSpinBox* m_videoMinimumSpin; // Нижняя граница видеобитрейта звонка, kbit/s; не выше maximum.
+2 -1
View File
@@ -207,7 +207,8 @@ float AudioRecorder::peakLevel() const { return m_peakLevel.exchange(0.0f); }
void AudioRecorder::configureCompressor(const audio_compressor_config_t& config) {
m_compressorConfig = config; /* Применяется перед следующей записью, когда callback ещё не запущен. */
DEBUG_INFO(DEBUG_CATEGORY_CALL, "recorder: compressor configuration saved for next recording");
DEBUG_INFO(DEBUG_CATEGORY_CALL, "recorder: compressor saved for next recording gain=%.1fdB back=%dms ahead=%dms rise=%.1fx target=%.4f",
config.max_gain_db, config.lookback_ms, config.lookahead_ms, config.rise_rate_per_sec, config.target_level);
}
void AudioRecorder::setCompressorEnabled(bool enabled) {
+3 -3
View File
@@ -542,10 +542,10 @@ void MainWindow::setupSoundFromConfig() {
compCfg.sample_rate = 48000;
compCfg.channels = 1;
compCfg.max_gain_db = (float)m_db->getUiStateInt("compressor_max_gain_db", 25);
compCfg.lookback_ms = 200;
compCfg.lookahead_ms = 100;
compCfg.lookback_ms = m_db->getUiStateInt("compressor_lookback_ms", 200);
compCfg.lookahead_ms = m_db->getUiStateInt("compressor_lookahead_ms", 100);
compCfg.rise_rate_per_sec = (float)m_db->getUiStateInt("compressor_rise_rate", 10);
compCfg.target_level = 1.0f;
compCfg.target_level = std::pow(10.0f, m_db->getUiStateInt("compressor_target_level_db", 0) / 20.0f);
m_recorder->configureCompressor(compCfg);
}
}
+6 -2
View File
@@ -210,10 +210,14 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
m_soundPage = new SoundSettingsPage(db, m_pages);
m_pages->addWidget(m_soundPage);
m_audioDevicePage = new AudioDeviceSettingsPage(db, recorder, m_pages);
auto* audioScroll = new QScrollArea(m_pages);
audioScroll->setWidgetResizable(true);
audioScroll->setFrameShape(QFrame::NoFrame);
m_audioDevicePage = new AudioDeviceSettingsPage(db, recorder, audioScroll);
m_audioDevicePage->setConfigPath(m_configPath);
m_audioDevicePage->loadFromDb();
m_pages->addWidget(m_audioDevicePage);
audioScroll->setWidget(m_audioDevicePage);
m_pages->addWidget(audioScroll);
auto* radioScroll = new QScrollArea(m_pages);
radioScroll->setWidgetResizable(true);
+20 -1
View File
@@ -137,6 +137,13 @@ class SettingsSaveTest : public QObject {
if (audio) {
const auto combos = audio->findChildren<QComboBox*>();
combos[2]->setCurrentIndex(combos[2]->findData(2));
const QMap<QString, int> compressor{{"compressorMaxGain", 0}, {"compressorLookback", 350},
{"compressorLookahead", 220}, {"compressorTargetLevel", -9}};
for (auto it = compressor.cbegin(); it != compressor.cend(); ++it) {
auto* slider = audio->findChild<QSlider*>(it.key());
if (!slider) { QTest::qFail(qPrintable(it.key()), __FILE__, __LINE__); return result; }
slider->setValue(it.value());
}
}
return result;
}
@@ -162,6 +169,18 @@ class SettingsSaveTest : public QObject {
QCOMPARE(core.db.getUiState("call_video_bitrate_range_kbps"), QString::fromUtf8(chat_setting_get(core.inst,
"call_video_bitrate_range_kbps")));
QCOMPARE(VoiceEncoder::preset(), 2);
QCOMPARE(core.db.getUiStateInt("compressor_max_gain_db", -1), 0);
QCOMPARE(chat_setting_get_int(core.inst, "compressor_max_gain_db", -1), 0);
AudioDeviceSettingsPage restored(&core.db, nullptr); restored.loadFromDb();
const QMap<QString, QPair<const char*, int>> compressor{
{"compressorLookback", {"compressor_lookback_ms", 350}},
{"compressorLookahead", {"compressor_lookahead_ms", 220}},
{"compressorTargetLevel", {"compressor_target_level_db", -9}}};
for (auto it = compressor.cbegin(); it != compressor.cend(); ++it) {
QCOMPARE(core.db.getUiStateInt(it.value().first, -100), it.value().second);
auto* slider = restored.findChild<QSlider*>(it.key()); QVERIFY(slider);
QCOMPARE(slider->value(), it.value().second);
}
}
if (kind == Dialog) {
QCOMPARE(QString::fromUtf8(core.inst->name), QString("After"));
@@ -401,7 +420,7 @@ private slots:
QTest::addColumn<bool>("cancel");
for (int kind : {Storage, Audio, Dialog}) {
for (bool payload : {false, true}) {
const int count = kind == Storage ? 4 : kind == Audio ? 22 : payload ? 38 : 56;
const int count = kind == Storage ? 4 : kind == Audio ? 25 : payload ? 41 : 59;
QList<int> positions{1, 2, count};
if (count / 2 > 2) positions.append(count / 2);
for (int position : positions)
+12
View File
@@ -3,6 +3,18 @@
<TS version="2.1" language="en_US" sourcelanguage="en_US">
<context>
<name>ChatGui</name>
<message>
<source>Lookback time:</source>
<translation>Lookback time:</translation>
</message>
<message>
<source>Lookahead time:</source>
<translation>Lookahead time:</translation>
</message>
<message>
<source>Target level:</source>
<translation>Target level:</translation>
</message>
<message>
<source>Advertising power: maximum requested</source>
<translation>Advertising power: maximum requested</translation>
+12
View File
@@ -3,6 +3,18 @@
<TS version="2.1" language="ru_RU" sourcelanguage="en_US">
<context>
<name>ChatGui</name>
<message>
<source>Lookback time:</source>
<translation>Время анализа:</translation>
</message>
<message>
<source>Lookahead time:</source>
<translation>Время упреждения:</translation>
</message>
<message>
<source>Target level:</source>
<translation>Целевой уровень:</translation>
</message>
<message>
<source>Advertising power: maximum requested</source>
<translation>Мощность обнаружения: запрошена максимальная</translation>