12 changed files with 66 additions and 257 deletions
@ -1,146 +0,0 @@ |
|||||||
#include "audiocompressor.h" |
|
||||||
|
|
||||||
#include "../../lib/debug_config.h" |
|
||||||
#include <cmath> |
|
||||||
#include <algorithm> |
|
||||||
#include <cstring> |
|
||||||
|
|
||||||
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(); |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,59 +0,0 @@ |
|||||||
#pragma once |
|
||||||
|
|
||||||
#include <vector> |
|
||||||
#include <deque> |
|
||||||
#include <cstdint> |
|
||||||
|
|
||||||
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<int16_t>& 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<BlockLevel> m_blockLevels; |
|
||||||
|
|
||||||
struct PendingBlock { |
|
||||||
std::vector<int16_t> samples; |
|
||||||
size_t levelIndex; |
|
||||||
}; |
|
||||||
std::deque<PendingBlock> m_pending; |
|
||||||
std::vector<int16_t> m_accumulator; |
|
||||||
|
|
||||||
std::vector<int16_t> 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(); |
|
||||||
}; |
|
||||||
Loading…
Reference in new issue