From 1dac0b8c81746cfa9c68a85f603a9f163c5b5c50 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 29 Jul 2026 00:52:05 +0300 Subject: [PATCH] unify compressor: remove C++ duplicate audiocompressor, use lib/audio_compressor.c in chatgui --- lib/audio_compressor.c | 4 +- tools/chatgui/CMakeLists.txt | 1 - tools/chatgui/src/audiocompressor.cpp | 146 ------------------ tools/chatgui/src/audiocompressor.h | 59 ------- tools/chatgui/src/audiodevicesettingspage.cpp | 21 +-- tools/chatgui/src/audiodevicesettingspage.h | 1 - tools/chatgui/src/audiorecorder.cpp | 40 +++-- tools/chatgui/src/audiorecorder.h | 9 +- tools/chatgui/src/inputbar.cpp | 7 +- tools/chatgui/src/mainwindow.cpp | 19 ++- tools/chatgui/src/voicemessageencoder.cpp | 13 +- tools/chatgui/src/voicemessageencoder.h | 3 +- 12 files changed, 66 insertions(+), 257 deletions(-) delete mode 100644 tools/chatgui/src/audiocompressor.cpp delete mode 100644 tools/chatgui/src/audiocompressor.h diff --git a/lib/audio_compressor.c b/lib/audio_compressor.c index d72c5bf8..4a9640c7 100644 --- a/lib/audio_compressor.c +++ b/lib/audio_compressor.c @@ -1,6 +1,6 @@ #include "audio_compressor.h" -#include "../../../lib/debug_config.h" -#include "../../../lib/mem.h" +#include "debug_config.h" +#include "mem.h" #include #include #include diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index f42701e1..1d95d4ff 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -115,7 +115,6 @@ add_executable(chatgui src/soundsettingspage.cpp src/sound_manager.cpp src/audiorecorder.cpp - src/audiocompressor.cpp src/voicemessageencoder.cpp src/voiceplayback.cpp src/media_blocks.cpp diff --git a/tools/chatgui/src/audiocompressor.cpp b/tools/chatgui/src/audiocompressor.cpp deleted file mode 100644 index f663bdb6..00000000 --- a/tools/chatgui/src/audiocompressor.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "audiocompressor.h" - -#include "../../lib/debug_config.h" -#include -#include -#include - -void AudioCompressor::configure(const Config& cfg) { - m_cfg = cfg; - m_blockSamples = m_cfg.sampleRate * m_cfg.blockDurationMs / 1000 * m_cfg.channels; - m_lookbackBlocks = m_cfg.lookbackMs / m_cfg.blockDurationMs; - m_lookaheadBlocks = m_cfg.lookaheadMs / m_cfg.blockDurationMs; - m_riseFactorPerBlock = powf(m_cfg.riseRatePer500ms, (float)m_cfg.blockDurationMs / 500.0f); - m_maxGain = powf(10.0f, m_cfg.maxGainDb / 20.0f); - DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "config rate=%d ch=%d block=%dms window=%dms+%dms maxGain=%.0fdB riseRate=%.1fx/500ms target=%.0fdBFS", - m_cfg.sampleRate, m_cfg.channels, m_cfg.blockDurationMs, - m_cfg.lookbackMs, m_cfg.lookaheadMs, m_cfg.maxGainDb, - m_cfg.riseRatePer500ms, 20.0f * log10f(m_cfg.targetLevel)); -} - -void AudioCompressor::reset() { - m_blockLevels.clear(); - m_pending.clear(); - m_accumulator.clear(); - m_output.clear(); - m_gainSmoothed = 1.0f; - m_dbgCounter = 0; -} - -void AudioCompressor::push(const int16_t* samples, size_t count) { - if (!m_enabled) { - m_output.insert(m_output.end(), samples, samples + count); - return; - } - - size_t remaining = count; - const int16_t* src = samples; - while (remaining > 0) { - size_t need = (size_t)m_blockSamples - m_accumulator.size(); - size_t take = std::min(remaining, need); - m_accumulator.insert(m_accumulator.end(), src, src + take); - src += take; - remaining -= take; - if (m_accumulator.size() == (size_t)m_blockSamples) { - float level = computeBlockLevel(m_accumulator.data(), m_accumulator.size()); - - BlockLevel bl; - bl.level = level; - m_blockLevels.push_back(bl); - - PendingBlock pb; - pb.samples = std::move(m_accumulator); - pb.levelIndex = m_blockLevels.size() - 1; - m_pending.push_back(std::move(pb)); - - m_accumulator.clear(); - } - } - - while (m_pending.size() > (size_t)m_lookaheadBlocks) { - processPendingBlock(); - } -} - -float AudioCompressor::computeBlockLevel(const int16_t* samples, size_t count) const { - float sumSq = 0.0f; - float peak = 0.0f; - for (size_t i = 0; i < count; i++) { - float v = (float)samples[i] / 32768.0f; - sumSq += v * v; - float av = fabsf(v); - if (av > peak) peak = av; - } - float rms = sqrtf(sumSq / (float)count); - return (rms * 2.0f + peak) / 2.0f; -} - -void AudioCompressor::processPendingBlock() { - PendingBlock& block = m_pending.front(); - size_t idx = block.levelIndex; - - size_t winStart = idx >= (size_t)m_lookbackBlocks ? idx - (size_t)m_lookbackBlocks : 0; - size_t winEnd = std::min(idx + (size_t)m_lookaheadBlocks, m_blockLevels.size() - 1); - - float envelope = 0.0f; - for (size_t i = winStart; i <= winEnd; i++) { - if (m_blockLevels[i].level > envelope) envelope = m_blockLevels[i].level; - } - if (envelope < 0.0001f) envelope = 0.0001f; - - float G_raw = m_cfg.targetLevel / envelope; - - float G_prev = m_gainSmoothed; - if (G_raw > m_gainSmoothed) { - float maxRise = m_gainSmoothed * m_riseFactorPerBlock; - m_gainSmoothed = std::min(G_raw, maxRise); - } else { - m_gainSmoothed = G_raw; - } - - m_gainSmoothed = std::min(m_gainSmoothed, m_maxGain); - - float gainDb = 20.0f * log10f(m_gainSmoothed); - float prevDb = 20.0f * log10f(G_prev > 0.0001f ? G_prev : 0.0001f); - if (m_dbgCounter % 25 == 0 || fabsf(gainDb - prevDb) > 3.0f) { - DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "block %zu level=%.4f envelope=%.4f gainRaw=%.1fdB gain=%.1fdB", - idx, m_blockLevels[idx].level, envelope, 20.0f * log10f(G_raw), gainDb); - } - m_dbgCounter++; - - for (size_t i = 0; i < block.samples.size(); i++) { - float v = (float)block.samples[i] * m_gainSmoothed; - if (v > 32767.0f) v = 32767.0f; - if (v < -32768.0f) v = -32768.0f; - m_output.push_back((int16_t)(int)v); - } - - m_pending.pop_front(); -} - -void AudioCompressor::flush() { - if (!m_enabled) return; - - if (!m_accumulator.empty()) { - float level = computeBlockLevel(m_accumulator.data(), m_accumulator.size()); - - BlockLevel bl; - bl.level = level; - m_blockLevels.push_back(bl); - - PendingBlock pb; - pb.samples = std::move(m_accumulator); - pb.levelIndex = m_blockLevels.size() - 1; - m_pending.push_back(std::move(pb)); - - m_accumulator.clear(); - } - - int remain = (int)m_pending.size(); - DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "flush %d pending blocks (gainSmoothed=%.1fdB)", remain, - 20.0f * log10f(m_gainSmoothed)); - - while (!m_pending.empty()) { - processPendingBlock(); - } -} diff --git a/tools/chatgui/src/audiocompressor.h b/tools/chatgui/src/audiocompressor.h deleted file mode 100644 index 4e7f6073..00000000 --- a/tools/chatgui/src/audiocompressor.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include -#include -#include - -class AudioCompressor { -public: - struct Config { - int sampleRate = 48000; - int channels = 1; - int blockDurationMs = 20; - int lookbackMs = 200; - int lookaheadMs = 100; - float maxGainDb = 30.0f; - float riseRatePer500ms = 2.0f; - float targetLevel = 0.25f; - }; - - void configure(const Config& cfg); - void reset(); - - void setEnabled(bool enabled) { m_enabled = enabled; } - bool isEnabled() const { return m_enabled; } - - void push(const int16_t* samples, size_t count); - const std::vector& outputBuffer() const { return m_output; } - bool hasPending() const { return !m_pending.empty() || !m_accumulator.empty(); } - void flush(); - -private: - Config m_cfg; - bool m_enabled = true; - - struct BlockLevel { - float level; - }; - std::deque m_blockLevels; - - struct PendingBlock { - std::vector samples; - size_t levelIndex; - }; - std::deque m_pending; - std::vector m_accumulator; - - std::vector m_output; - - int m_blockSamples = 960; - int m_lookbackBlocks = 10; - int m_lookaheadBlocks = 5; - float m_riseFactorPerBlock = 1.0f; - float m_maxGain = 31.62f; - float m_gainSmoothed = 1.0f; - int m_dbgCounter = 0; - - float computeBlockLevel(const int16_t* samples, size_t count) const; - void processPendingBlock(); -}; diff --git a/tools/chatgui/src/audiodevicesettingspage.cpp b/tools/chatgui/src/audiodevicesettingspage.cpp index 0705e4ea..9b6c9eda 100644 --- a/tools/chatgui/src/audiodevicesettingspage.cpp +++ b/tools/chatgui/src/audiodevicesettingspage.cpp @@ -1,8 +1,11 @@ #include "audiodevicesettingspage.h" #include "sound_manager.h" #include "audiorecorder.h" -#include "audiocompressor.h" #include "voicemessageencoder.h" + +extern "C" { +#include "../../lib/audio_compressor.h" +} #include "../../lib/debug_config.h" #include "../db/db_manager.h" #include "../transport/gui_bridge.h" @@ -236,15 +239,15 @@ void AudioDeviceSettingsPage::applyAndSave() { if (m_recorder) { m_recorder->setCompressorEnabled(compEnabled != 0); if (m_recorder->compressor() && compEnabled) { - AudioCompressor::Config compCfg; - compCfg.sampleRate = 48000; + audio_compressor_config_t compCfg = {0}; + compCfg.sample_rate = 48000; compCfg.channels = 1; - compCfg.maxGainDb = (float)compMaxGain; - compCfg.lookbackMs = compLookback; - compCfg.lookaheadMs = compLookahead; - compCfg.riseRatePer500ms = (float)compRiseRateTenths / 10.0f; - compCfg.targetLevel = powf(10.0f, (float)compTargetLevel / 20.0f); - m_recorder->compressor()->configure(compCfg); + compCfg.max_gain_db = (float)compMaxGain; + compCfg.lookback_ms = compLookback; + compCfg.lookahead_ms = compLookahead; + compCfg.rise_rate_per_500ms = (float)compRiseRateTenths / 10.0f; + compCfg.target_level = powf(10.0f, (float)compTargetLevel / 20.0f); + audio_compressor_configure(m_recorder->compressor(), &compCfg); } } } diff --git a/tools/chatgui/src/audiodevicesettingspage.h b/tools/chatgui/src/audiodevicesettingspage.h index 13f0dfb9..7058cc38 100644 --- a/tools/chatgui/src/audiodevicesettingspage.h +++ b/tools/chatgui/src/audiodevicesettingspage.h @@ -9,7 +9,6 @@ #include class DbManager; class AudioRecorder; -class AudioCompressor; class AudioDeviceSettingsPage : public QWidget { Q_OBJECT diff --git a/tools/chatgui/src/audiorecorder.cpp b/tools/chatgui/src/audiorecorder.cpp index d23c2c77..bd2ccab5 100644 --- a/tools/chatgui/src/audiorecorder.cpp +++ b/tools/chatgui/src/audiorecorder.cpp @@ -1,8 +1,11 @@ #include "audiorecorder.h" -#include "audiocompressor.h" #include "miniaudio.h" #include "sound_manager.h" +extern "C" { +#include "../../lib/audio_compressor.h" +} + #include "../../lib/debug_config.h" #include #include @@ -19,7 +22,7 @@ void audioCaptureCallback(ma_device* pDevice, void* pOutput, const void* pInput, size_t add = (size_t)frameCount * (size_t)self->m_channels; if (self->m_compressor && self->m_compressorEnabled) { - self->m_compressor->push(src, add); + audio_compressor_push(self->m_compressor, src, add); } else { size_t cur = self->m_pcmBuffer.size(); self->m_pcmBuffer.resize(cur + add); @@ -47,11 +50,13 @@ void audioCaptureCallback(ma_device* pDevice, void* pOutput, const void* pInput, AudioRecorder::AudioRecorder(QObject* parent) : QObject(parent) { g_recorder = this; - m_compressor = new AudioCompressor; - AudioCompressor::Config cfg; - cfg.sampleRate = m_sampleRate; - cfg.channels = m_channels; - m_compressor->configure(cfg); + m_compressor = audio_compressor_create(); + if (m_compressor) { + audio_compressor_config_t cfg = {0}; + cfg.sample_rate = m_sampleRate; + cfg.channels = m_channels; + audio_compressor_configure(m_compressor, &cfg); + } m_durationTimer.setInterval(100); connect(&m_durationTimer, &QTimer::timeout, this, [this]() { if (m_recording) { @@ -63,7 +68,7 @@ AudioRecorder::AudioRecorder(QObject* parent) AudioRecorder::~AudioRecorder() { shutdown(); - delete m_compressor; + audio_compressor_destroy(m_compressor); m_compressor = nullptr; if (g_recorder == this) g_recorder = nullptr; } @@ -98,7 +103,7 @@ void AudioRecorder::startRecording() { m_rmsIdx = 0; m_elapsedMs = 0; - if (m_compressor) m_compressor->reset(); + if (m_compressor) audio_compressor_reset(m_compressor); ma_context* ctx = SoundManager::instance()->context(); if (!ctx) { DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "no audio context"); return; } @@ -158,10 +163,10 @@ void AudioRecorder::stopRecording() { } m_recording = false; - if (m_compressor && m_compressorEnabled) m_compressor->flush(); + if (m_compressor && m_compressorEnabled) audio_compressor_flush(m_compressor); int duration = m_elapsedMs; - size_t bufSize = (m_compressor && m_compressorEnabled) ? m_compressor->outputBuffer().size() : m_pcmBuffer.size(); + size_t bufSize = (m_compressor && m_compressorEnabled) ? audio_compressor_output_size(m_compressor) : m_pcmBuffer.size(); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "recording stopped, duration=%dms, pcmSamples=%zu, compressor=%d", duration, bufSize, m_compressorEnabled ? 1 : 0); emit recordingStopped(duration); @@ -178,13 +183,18 @@ float AudioRecorder::peakLevel() const { return v; } -const std::vector& AudioRecorder::buffer() const { - if (m_compressor && m_compressorEnabled) return m_compressor->outputBuffer(); - return m_pcmBuffer; +const int16_t* AudioRecorder::bufferData() const { + if (m_compressor && m_compressorEnabled) return audio_compressor_output(m_compressor); + return m_pcmBuffer.data(); +} + +size_t AudioRecorder::bufferSize() const { + if (m_compressor && m_compressorEnabled) return audio_compressor_output_size(m_compressor); + return m_pcmBuffer.size(); } void AudioRecorder::setCompressorEnabled(bool enabled) { m_compressorEnabled = enabled; - if (m_compressor) m_compressor->setEnabled(enabled); + if (m_compressor) audio_compressor_set_enabled(m_compressor, enabled); DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "compressor %s", enabled ? "enabled" : "disabled"); } diff --git a/tools/chatgui/src/audiorecorder.h b/tools/chatgui/src/audiorecorder.h index 2901c9e4..87bf7b16 100644 --- a/tools/chatgui/src/audiorecorder.h +++ b/tools/chatgui/src/audiorecorder.h @@ -7,7 +7,7 @@ #include struct ma_device; -class AudioCompressor; +struct audio_compressor; class AudioRecorder : public QObject { Q_OBJECT @@ -28,11 +28,12 @@ public: float peakLevel() const; - const std::vector& buffer() const; + const int16_t* bufferData() const; + size_t bufferSize() const; const std::vector& waveformLevels() const { return m_waveformLevels; } int durationMs() const; - AudioCompressor* compressor() const { return m_compressor; } + struct audio_compressor* compressor() const { return m_compressor; } void setCompressorEnabled(bool enabled); bool isCompressorEnabled() const { return m_compressorEnabled; } @@ -58,7 +59,7 @@ private: QTimer m_durationTimer; int m_elapsedMs = 0; - AudioCompressor* m_compressor = nullptr; + struct audio_compressor* m_compressor = nullptr; bool m_compressorEnabled = false; friend void audioCaptureCallback(ma_device* pDevice, void* pOutput, const void* pInput, unsigned int frameCount); diff --git a/tools/chatgui/src/inputbar.cpp b/tools/chatgui/src/inputbar.cpp index 61140786..b22b72fa 100644 --- a/tools/chatgui/src/inputbar.cpp +++ b/tools/chatgui/src/inputbar.cpp @@ -220,8 +220,9 @@ void InputBar::onPttReleased() { return; } - const std::vector& pcm = m_recorder->buffer(); - if (pcm.empty()) return; + const int16_t* pcm = m_recorder->bufferData(); + size_t pcmSize = m_recorder->bufferSize(); + if (pcmSize == 0) return; QString mediaPath = m_mediaDirBase + "/" + m_channelIdForRecord; VoiceEncoder::ensureMediaDir(mediaPath); @@ -231,7 +232,7 @@ void InputBar::onPttReleased() { QString fileName = VoiceEncoder::generateFileName(); QString tempPath = mediaPath + "/" + fileName; float durationSec = 0; - int frames = VoiceEncoder::encodeToFile(pcm, 48000, 1, tempPath, durationSec); + int frames = VoiceEncoder::encodeToFile(pcm, pcmSize, 48000, 1, tempPath, durationSec); if (frames <= 0) { QFile::remove(tempPath); return; diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index c48d0452..bbf7674c 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -9,8 +9,11 @@ #include "invite_link.h" #include "sound_manager.h" #include "audiorecorder.h" -#include "audiocompressor.h" #include "voicemessageencoder.h" + +extern "C" { +#include "../../lib/audio_compressor.h" +} #include "animtimer.h" #include "../db/db_manager.h" #include "../transport/utun_node.h" @@ -213,13 +216,13 @@ void MainWindow::setupSoundFromConfig() { int compEnabled = m_db->getUiStateInt("compressor_enabled", 0); m_recorder->setCompressorEnabled(compEnabled != 0); if (m_recorder->compressor()) { - AudioCompressor::Config compCfg; - compCfg.maxGainDb = (float)m_db->getUiStateInt("compressor_max_gain_db", 30); - compCfg.lookbackMs = m_db->getUiStateInt("compressor_lookback_ms", 200); - compCfg.lookaheadMs = m_db->getUiStateInt("compressor_lookahead_ms", 100); - compCfg.riseRatePer500ms = (float)m_db->getUiStateInt("compressor_rise_rate_tenths", 20) / 10.0f; - compCfg.targetLevel = powf(10.0f, (float)m_db->getUiStateInt("compressor_target_level_db", -12) / 20.0f); - m_recorder->compressor()->configure(compCfg); + audio_compressor_config_t compCfg = {0}; + compCfg.max_gain_db = (float)m_db->getUiStateInt("compressor_max_gain_db", 30); + compCfg.lookback_ms = m_db->getUiStateInt("compressor_lookback_ms", 200); + compCfg.lookahead_ms = m_db->getUiStateInt("compressor_lookahead_ms", 100); + compCfg.rise_rate_per_500ms = (float)m_db->getUiStateInt("compressor_rise_rate_tenths", 20) / 10.0f; + compCfg.target_level = powf(10.0f, (float)m_db->getUiStateInt("compressor_target_level_db", -12) / 20.0f); + audio_compressor_configure(m_recorder->compressor(), &compCfg); } } DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "AudioRecorder initialized (captureDevice=%d, opusPreset=%d, compressor=%d)", diff --git a/tools/chatgui/src/voicemessageencoder.cpp b/tools/chatgui/src/voicemessageencoder.cpp index c04f1f1c..68339dcb 100644 --- a/tools/chatgui/src/voicemessageencoder.cpp +++ b/tools/chatgui/src/voicemessageencoder.cpp @@ -28,15 +28,15 @@ void VoiceEncoder::setPreset(int preset) { int VoiceEncoder::preset() { return s_preset; } -int VoiceEncoder::encodeToFile(const std::vector& pcm, int sampleRate, int channels, +int VoiceEncoder::encodeToFile(const int16_t* pcm, size_t pcmCount, int sampleRate, int channels, const QString& filePath, float& outDurationSec) { - if (pcm.empty() || sampleRate <= 0 || channels <= 0) { - DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoiceEncoder: invalid input (pcm=%zu rate=%d ch=%d)", pcm.size(), sampleRate, channels); + if (!pcm || pcmCount == 0 || sampleRate <= 0 || channels <= 0) { + DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoiceEncoder: invalid input (pcm=%zu rate=%d ch=%d)", pcmCount, sampleRate, channels); return -1; } int frameSamples = sampleRate * FRAME_MS / 1000; - size_t totalSamples = pcm.size() / channels; + size_t totalSamples = pcmCount / (size_t)channels; if (totalSamples < (size_t)frameSamples) return -1; opus_codec_encoder_t* enc = opus_codec_encoder_create(sampleRate, channels); @@ -54,7 +54,7 @@ int VoiceEncoder::encodeToFile(const std::vector& pcm, int sampleRate, return -1; } DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoiceEncoder::encodeToFile: writing to %s pcm=%zu samples preset=%d", - qPrintable(filePath), pcm.size(), s_preset); + qPrintable(filePath), pcmCount, s_preset); /* write header */ uint32_t magic = OPUS_MAGIC; @@ -68,11 +68,10 @@ int VoiceEncoder::encodeToFile(const std::vector& pcm, int sampleRate, int frameCount = 0; uint8_t packet[MAX_PACKET]; - const int16_t* pcmData = pcm.data(); size_t pcmOff = 0; while (pcmOff + (size_t)frameSamples * channels <= totalSamples) { - int len = opus_codec_encode(enc, pcmData + pcmOff, frameSamples, packet, MAX_PACKET); + int len = opus_codec_encode(enc, pcm + pcmOff, frameSamples, packet, MAX_PACKET); pcmOff += (size_t)frameSamples * channels; if (len > 0) { diff --git a/tools/chatgui/src/voicemessageencoder.h b/tools/chatgui/src/voicemessageencoder.h index 5a47870c..006d4d49 100644 --- a/tools/chatgui/src/voicemessageencoder.h +++ b/tools/chatgui/src/voicemessageencoder.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include class VoiceEncoder { @@ -9,7 +8,7 @@ public: static void setPreset(int preset); static int preset(); - static int encodeToFile(const std::vector& pcm, int sampleRate, int channels, + static int encodeToFile(const int16_t* pcm, size_t pcmCount, int sampleRate, int channels, const QString& filePath, float& outDurationSec); static QString generateFileName(); static QString mediaDir(const QString& dbPath, const QString& channelId);