Automatic Gain Control for Embedded Systems: Linux ALSA Implementation
The AGC (Automatic Gain Control) algorithm normalizes audio signal levels in real time, eliminating the need for manual adjustments. In Linux communication systems using ALSA, it tackles uneven volume from various sources. This implementation targets S16_LE format, 8000 Hz, mono, with latency up to 500 ms and CPU usage under 3%.
The goal is to process short voice transmissions without full-file averaging. Testing covers subjective criteria (speech intelligibility) and objective ones (dynamic range reduction).
Analyzing Off-the-Shelf Solutions: AI and Plugins
Nvidia's studiovoice model normalizes volume and suppresses noise but requires resampling from 48 kHz to 8 kHz, adding latency. Local use is impossible due to dependencies.
The ALSA speex plugin (based on speexdsp) activates with agc on and agc_level (1–32768). Configuration in /etc/asound.conf:
pcm.agc_pcm {
type speex
agc on
agc_level 23400
slave.pcm "dev_out"
}
pcm.test_agc {
type plug
slave.pcm agc_pcm
}
Test: arecord -D dev_in | aplay -D test_agc. Issues: initial fade-out, "crackling" from underruns, undocumented frames and filter_length. Verdict: pass.
FFmpeg dynaudnorm: Breaking Down the Algorithm
The dynaudnorm filter is a perfect fit: ffmpeg -f alsa -i dev_in -filter:a "dynaudnorm=f=20:g=33:m=100:c=1" output.wav.
Parameters:
f=20— 20 ms frame for short transmissions;g=33— Gaussian smoothing (balances latency and smoothness);m=100— max gain (tied to volume level);c=1— DC correction (removes offset from interference).
Algorithm:
- Input frame of size F;
- DC correction;
- Peak detection;
- Gain coefficient calculation;
- Gaussian filter for smoothing;
- Apply gain and output.
The filter doesn't suppress noise. Alternatives: FFmpeg's afftdn, anlmdn, arnndn, or ALSA's rnnoise with RNN.
Custom C++20 Implementation
Python/Jupyter prototype simulates real-time processing: 20 ms buffers without loading the full file. Oscilloscope views confirm dynamic range compression.
Final: C++20 with AutomaticGainControl and GaussianFilter classes. Project structure:
libagc— static library;agc_pipe— pipe utility;- ALSA plugin
libasound_module_pcm_agc.so.
agc_pipe options:
Allowed options:
-h [ --help ] Print help
-m [ --msec ] arg (=20) Frame length in msec
-c [ --channel ] arg (=1) Channels to filter
-r [ --rate ] arg (=8000) Sample rate
-f [ --filter ] arg (=13) Gaussian filter size
-p [ --peak ] arg (=0.950000) Max peak value
-g [ --gain ] arg (=100) Max gain value
-v [ --verbose ] Enable verbose logs
Test: arecord -D <input> -f S16_LE -t raw | ./agc_pipe -f 7 -g 99 | aplay -D <output> -f S16_LE -t raw.
Performance: +3% CPU for one source, ~400 ms latency (FrameLenMs=20, FilterSize=13). Uses std::span for safety.
Key Takeaways
- AGC reduces dynamic range without hurting speech clarity;
- DC correction is essential to eliminate hardware artifacts;
- Gaussian filtering ensures smooth transitions, minimizing clipping;
- C++20 OOP implementation beats FFmpeg in maintainability and extensibility;
- Ideal for embedded VoIP: low CPU load, minimal latency.
— Editorial Team
No comments yet.