diff --git a/tools/chatgui/src/audiodevicesettingspage.cpp b/tools/chatgui/src/audiodevicesettingspage.cpp index d582e832b..6da9bbf60 100644 --- a/tools/chatgui/src/audiodevicesettingspage.cpp +++ b/tools/chatgui/src/audiodevicesettingspage.cpp @@ -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 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"); } diff --git a/tools/chatgui/src/audiodevicesettingspage.h b/tools/chatgui/src/audiodevicesettingspage.h index 72bc46954..db5b829d4 100644 --- a/tools/chatgui/src/audiodevicesettingspage.h +++ b/tools/chatgui/src/audiodevicesettingspage.h @@ -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. diff --git a/tools/chatgui/src/audiorecorder.cpp b/tools/chatgui/src/audiorecorder.cpp index 76fef6794..5a9443950 100644 --- a/tools/chatgui/src/audiorecorder.cpp +++ b/tools/chatgui/src/audiorecorder.cpp @@ -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) { diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index d453fe88a..6836bda3e 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -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); } } diff --git a/tools/chatgui/src/settingsdialog.cpp b/tools/chatgui/src/settingsdialog.cpp index e9b265d17..404756df1 100644 --- a/tools/chatgui/src/settingsdialog.cpp +++ b/tools/chatgui/src/settingsdialog.cpp @@ -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); diff --git a/tools/chatgui/tests/test_settings_save.cpp b/tools/chatgui/tests/test_settings_save.cpp index 3ccf01285..05b70d512 100644 --- a/tools/chatgui/tests/test_settings_save.cpp +++ b/tools/chatgui/tests/test_settings_save.cpp @@ -137,6 +137,13 @@ class SettingsSaveTest : public QObject { if (audio) { const auto combos = audio->findChildren(); combos[2]->setCurrentIndex(combos[2]->findData(2)); + const QMap compressor{{"compressorMaxGain", 0}, {"compressorLookback", 350}, + {"compressorLookahead", 220}, {"compressorTargetLevel", -9}}; + for (auto it = compressor.cbegin(); it != compressor.cend(); ++it) { + auto* slider = audio->findChild(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> 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(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("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 positions{1, 2, count}; if (count / 2 > 2) positions.append(count / 2); for (int position : positions) diff --git a/tools/chatgui/translations/chatgui_en.ts b/tools/chatgui/translations/chatgui_en.ts index 93d4d233a..1e18ae9b6 100644 --- a/tools/chatgui/translations/chatgui_en.ts +++ b/tools/chatgui/translations/chatgui_en.ts @@ -3,6 +3,18 @@ ChatGui + + Lookback time: + Lookback time: + + + Lookahead time: + Lookahead time: + + + Target level: + Target level: + Advertising power: maximum requested Advertising power: maximum requested diff --git a/tools/chatgui/translations/chatgui_ru.ts b/tools/chatgui/translations/chatgui_ru.ts index bed005f62..f886ccb5c 100644 --- a/tools/chatgui/translations/chatgui_ru.ts +++ b/tools/chatgui/translations/chatgui_ru.ts @@ -3,6 +3,18 @@ ChatGui + + Lookback time: + Время анализа: + + + Lookahead time: + Время упреждения: + + + Target level: + Целевой уровень: + Advertising power: maximum requested Мощность обнаружения: запрошена максимальная