Back to Home

AGC for Linux ALSA: implementation and setup

The article describes AGC implementation for volume normalization in VoIP systems on Linux ALSA. Compares speex, FFmpeg dynaudnorm plugins and custom C++20 code. Achieved 400 ms latency at +3% CPU.

AGC in action: from speex to C++20 for VoIP
Advertisement 728x90

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.

Google AdInline article slot

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.

Google AdInline article slot

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.

Google AdInline article slot

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

Advertisement 728x90

Read Next