Add missing desktop voice compressor controls
This commit is contained in:
1 parent
1c0f40f247
commit
26f8491abc
8 files changed
+110
-15
No files matched your search
@@ -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");
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user