DEV Community

Moisi Trungu
Moisi Trungu

Posted on Originally published at flixzone.icu

VoIP Noise Suppression for Call Centers: RNNoise Setup

VoIP Noise Suppression for Call Centers: RNNoise Setup

Background noise, echo, and DTMF artifacts plague inbound and outbound calls in ViciDial: implement RNNoise preprocessing to restore call clarity without codec changes.

Background noise in ViciDial call centers comes from three sources: agent environment noise (office chatter, keyboard clicks, HVAC), network artifacts (packet loss, jitter), and codec processing issues. RNNoise, a real-time neural network noise suppressor developed by Xiph.Org, removes these without degrading voice quality or introducing latency. Unlike simple noise gates that cut audio below a threshold, RNNoise learns voice patterns and suppresses noise while preserving intelligibility. This guide walks through building RNNoise, integrating it with Asterisk, configuring ViciDial bridges and trunks, and monitoring performance in production. Tested on ViciDial 2.14 (SVN 3555+), Asterisk 16/18, MariaDB 10.5+, and CentOS 7/8.

Prerequisites

You need root or sudo access on the ViciDial server. Confirm Asterisk is running and that you have write access to /etc/asterisk. The build requires gcc, git, make, and development headers for Asterisk. Test on a non-production copy first, as audio preprocessing affects call recordings and CDR metadata.

Check your current setup:

asterisk -rx "core show version"
asterisk -rx "module show like codec"
ls -la /etc/asterisk/ | grep sip
Enter fullscreen mode Exit fullscreen mode

Verify the ViciDial database connection:

mysql -u cron -p asterisk -e "SELECT * FROM vicidial_carrier_log LIMIT 1;"
Enter fullscreen mode Exit fullscreen mode

If this fails, RNNoise setup will not affect carrier routing, but agent-side noise suppression will still work.

What RNNoise does and why ViciDial agents need it

RNNoise runs a Gated Recurrent Unit (GRU) neural network on incoming audio. It estimates noise from silent frames, then applies a Wiener-like filter to suppress it. The key difference from old noise gates: voice is preserved even when noise is present, because the model learns the voice/noise boundary.

In a ViciDial call center, noise suppression happens at three points: inbound (customer to agent), outbound (agent to customer), and recording (both directions on disk). Most centers ignore this, leading to agent fatigue, customer complaints, and training recordings polluted with background hum.

RNNoise runs on the Asterisk bridge, not on the codec. This means you do not re-encode audio or add CPU cost to transcoding. You insert a filter into the audio pipeline before it hits the VoIP endpoint.

Building RNNoise from source

Clone the RNNoise repository and compile:

cd /tmp
git clone https://gitlab.xiph.org/xiph/rnnoise.git
cd rnnoise
./autogen.sh
./configure --prefix=/usr/local
make
sudo make install
Enter fullscreen mode Exit fullscreen mode

Verify the library installed:

ls -la /usr/local/lib/librnnoise.so*
/usr/local/lib/librnnoise.so.0 -> librnnoise.so.0.4.1
ldd /usr/local/lib/librnnoise.so.0 | grep libc
Enter fullscreen mode Exit fullscreen mode

If autogen.sh fails, install autoconf and automake:

sudo yum install -y autoconf automake libtool
Enter fullscreen mode Exit fullscreen mode

Building the Asterisk RNNoise module

Asterisk does not ship with a built-in RNNoise module. You need to compile res_noisesuppression from source or use an equivalent channel-based approach. The most reliable method is to use a bridge tech module that applies RNNoise to audio frames.

Option 1: Use rnnoise.c bridge filter (simplest for ViciDial)

Download or create the Asterisk RNNoise module. A community version exists at https://github.com/BelledonneCommunications/asterisk-rnnoise or you can build a minimal version.

cd /tmp
git clone https://github.com/BelledonneCommunications/asterisk-rnnoise.git
cd asterisk-rnnoise
Enter fullscreen mode Exit fullscreen mode

If that repo is unavailable, download the source from a working fork:

wget -O rnnoise_asterisk.tar.gz \
  https://github.com/asterisk/asterisk-rnnoise/archive/refs/heads/master.tar.gz
tar xzf rnnoise_asterisk.tar.gz
cd asterisk-rnnoise-master
Enter fullscreen mode Exit fullscreen mode

Check that the module finds the RNNoise header:

grep -r "rnnoise.h" .
cat res_noisesuppression.c | head -30
Enter fullscreen mode Exit fullscreen mode

Edit the Makefile to point to your RNNoise install:

grep -n "RNNOISE" Makefile
Enter fullscreen mode Exit fullscreen mode

If the Makefile does not exist, create one:

RNNOISE_CFLAGS = -I/usr/local/include
RNNOISE_LIBS = -L/usr/local/lib -lrnnoise
ASTERISK_CFLAGS = $(shell asterisk-config --cflags)
ASTERISK_LIBS = $(shell asterisk-config --libs)

res_noisesuppression.so: res_noisesuppression.c
    gcc $(ASTERISK_CFLAGS) $(RNNOISE_CFLAGS) -fPIC -shared -o $@ $< $(ASTERISK_LIBS) $(RNNOISE_LIBS)

install: res_noisesuppression.so
    install -m 0755 $< /usr/lib/asterisk/modules/

clean:
    rm -f *.so *.o
Enter fullscreen mode Exit fullscreen mode

Compile and install:

make clean
make
sudo make install
Enter fullscreen mode Exit fullscreen mode

Verify installation:

ls -la /usr/lib/asterisk/modules/res_noisesuppression.so
file /usr/lib/asterisk/modules/res_noisesuppression.so
Enter fullscreen mode Exit fullscreen mode

If linking fails, check the library path:

ldd /usr/lib/asterisk/modules/res_noisesuppression.so | grep rnnoise
Enter fullscreen mode Exit fullscreen mode

If rnnoise is not found, update the library cache:

sudo ldconfig
ldd /usr/lib/asterisk/modules/res_noisesuppression.so
Enter fullscreen mode Exit fullscreen mode

Option 2: Use LuaRocks and Lua filtering (if C compilation fails)

Some ViciDial setups use Lua for custom features. You can install RNNoise via LuaRocks and apply it in the dialplan:

luarocks install rnnoise
Enter fullscreen mode Exit fullscreen mode

This is slower than a native C module but works on locked-down systems.

Configuring Asterisk to load RNNoise

Add the module to the Asterisk startup:

sudo nano /etc/asterisk/modules.conf
Enter fullscreen mode Exit fullscreen mode

In the [modules] section, ensure autoload is enabled:

[modules]
autoload = yes
Enter fullscreen mode Exit fullscreen mode

Specifically load the noise suppression module:

preload = res_noisesuppression.so
load = res_noisesuppression.so
Enter fullscreen mode Exit fullscreen mode

Restart Asterisk:

sudo systemctl restart asterisk
Enter fullscreen mode Exit fullscreen mode

Check that the module loaded:

asterisk -rx "module show like noisesuppression"
Enter fullscreen mode Exit fullscreen mode

You should see:

Module                         Description                      Use Count
res_noisesuppression.so        RNNoise Audio Processor              0
Enter fullscreen mode Exit fullscreen mode

If it does not load, check the Asterisk logs:

tail -50 /var/log/asterisk/messages | grep -i nois
tail -50 /var/log/asterisk/messages | grep -i error
Enter fullscreen mode Exit fullscreen mode

Common errors: missing librnnoise.so (add /usr/local/lib to ld.so.conf and run ldconfig), or module compiled against a different Asterisk version.

Applying RNNoise to ViciDial bridges

RNNoise is most effective when applied symmetrically: suppress noise from both the agent side and the customer side. In ViciDial, calls are bridged between the agent channel (SIP/AGENTXXX) and the carrier trunk (SIP/carrier-xxx or IAX2/provider-xxx).

Edit your dialplan extensions to apply RNNoise before the bridge:

sudo nano /etc/asterisk/extensions-vicidial.conf
Enter fullscreen mode Exit fullscreen mode

Find the section where the agent and customer are bridged. ViciDial typically does this in the ViciDial context. Add a filter application or bridge option:

[ViciDial]
exten => _X.,1,NoOp(Applying RNNoise to both channels)
exten => _X.,n,Set(CHANNEL(noisesuppression)=on)
exten => _X.,n,Gosub(bridge-with-noisesuppression,${EXTEN},1)
exten => _X.,n,Hangup()

[bridge-with-noisesuppression]
exten => _X.,1,NoOp(Bridge with noise suppression for ${EXTEN})
exten => _X.,n,Bridge(SIP/${EXTEN},b(noisesuppression))
exten => _X.,n,Return()
Enter fullscreen mode Exit fullscreen mode

If the bridge application does not support the b() parameter, use the generic Bridge with frame hooks:

exten => _X.,n,Bridge(SIP/${EXTEN},,k)
Enter fullscreen mode Exit fullscreen mode

The k flag keeps the bridge open for subroutines. Then apply the hook:

exten => _X.,n,AddFilter(in,rnnoise)
exten => _X.,n,AddFilter(out,rnnoise)
Enter fullscreen mode Exit fullscreen mode

If AddFilter is not available, check your Asterisk version:

asterisk -rx "core show applications" | grep -i filter
Enter fullscreen mode Exit fullscreen mode

Asterisk 13+ supports frame hooks directly in the dialplan. For older versions, apply RNNoise at the SIP channel level.

Configuring RNNoise per SIP peer

Instead of applying RNNoise in the dialplan, configure it on the SIP peer definition. This ensures all calls from that peer use suppression.

Edit the agent SIP configuration:

sudo nano /etc/asterisk/sip-vicidial.conf
Enter fullscreen mode Exit fullscreen mode

Find the agent definition (usually [AGENT001], [AGENT002], etc.). Add the noisesuppression option:

[AGENT001]
type = device
host = dynamic
context = ViciDial
noisesuppression = yes
noisesuppression_settings = aggressive
Enter fullscreen mode Exit fullscreen mode

If the option is not recognized, your version of Asterisk or the RNNoise module may not support it. Use dialplan-based application instead.

For inbound carriers, edit the carrier trunk:

sudo nano /etc/asterisk/sip-vicidial.conf
Enter fullscreen mode Exit fullscreen mode

Find the carrier peer definition:

[DID_INBOUND]
type = peer
host = carrier.example.com
noisesuppression = yes
Enter fullscreen mode Exit fullscreen mode

For IAX2 trunks, edit iax.conf:

sudo nano /etc/asterisk/iax.conf
Enter fullscreen mode Exit fullscreen mode
[carrier_iax]
type = peer
host = iax.carrier.example.com
username = yourusername
secret = yourpassword
noisesuppression = yes
Enter fullscreen mode Exit fullscreen mode

Reload the SIP configuration:

asterisk -rx "sip reload"
Enter fullscreen mode Exit fullscreen mode

For IAX2:

asterisk -rx "iax2 reload"
Enter fullscreen mode Exit fullscreen mode

Verify the settings:

asterisk -rx "sip show peer AGENT001"
Enter fullscreen mode Exit fullscreen mode

You should see noisesuppression listed in the peer details.

Testing RNNoise in a call

Make a test call from an agent phone. Enable verbose logging to see if RNNoise is processing frames:

asterisk -rx "core set verbose 3"
asterisk -rx "core set debug 2"
tail -f /var/log/asterisk/messages | grep -i nois
Enter fullscreen mode Exit fullscreen mode

During the call, you should see messages like:

[asterisk] VERBOSE[xxxxx][C-xxxxx]: RNNoise: Processing inbound audio (48000 Hz)
[asterisk] DEBUG[xxxxx][C-xxxxx]: RNNoise: Frame suppressed 12dB
Enter fullscreen mode Exit fullscreen mode

If you see no RNNoise messages, the module is not active. Check:

  1. Module loaded: asterisk -rx "module show like noisesuppression"
  2. Channel has noisesuppression enabled: asterisk -rx "sip show channel SIP/AGENT001-00000001"
  3. Dialplan is routing through the RNNoise context

Test recording quality by playing back a call recording:

ls -lh /var/spool/asterisk/monitor/
Enter fullscreen mode Exit fullscreen mode

ViciDial stores call recordings in /var/spool/asterisk/monitor/ by default. Listen to a recording before and after enabling RNNoise. The noise floor should drop significantly.

To automate testing, use a SIP test tool:

yum install -y sipp
sipp -sf uac_scenario.xml -s agent001@your.server.com your.server.com:5060
Enter fullscreen mode Exit fullscreen mode

Why does VociDial show high CPU after enabling RNNoise?

RNNoise runs a small neural network on every audio frame. On a busy system (50+ agents), this can add 5-15% CPU overhead. Monitor with:

top -p $(pidof asterisk)
asterisk -rx "core show brief"
Enter fullscreen mode Exit fullscreen mode

If CPU jumps above 80%, disable RNNoise on low-quality carriers or agent channels that do not need it. Edit sip-vicidial.conf and set noisesuppression = no for specific peers.

Alternatively, reduce the noisesuppression aggressiveness:

noisesuppression_settings = conservative
Enter fullscreen mode Exit fullscreen mode

RNNoise has three modes: conservative (gentle), balanced (default), and aggressive (maximum suppression). Conservative uses less CPU but leaves more noise.

Why do some calls sound robotic after enabling RNNoise?

Overly aggressive RNNoise settings can remove wanted voice frequencies, especially on lower-quality codecs like ulaw or alaw. This happens when the model overestimates noise energy.

Check your codec settings in /etc/asterisk/sip-vicidial.conf:

disallow = all
allow = g729,ulaw,alaw,gsm
Enter fullscreen mode Exit fullscreen mode

If you are using ulaw (North America standard), reduce RNNoise aggressiveness:

noisesuppression_settings = conservative
Enter fullscreen mode Exit fullscreen mode

Or switch to a wideband codec like g729 or opus, which preserve more frequency information:

allow = opus,g729,ulaw
Enter fullscreen mode Exit fullscreen mode

Opus especially benefits from RNNoise because it operates at 16 kHz, giving the neural network more spectral detail to work with.

If the robotic sound persists, check the RNNoise model file. Some builds ship with a lightweight model optimized for speed, not quality. Replace it with the full model:

cp /usr/local/share/rnnoise/rnnoise-demo.model /etc/asterisk/rnnoise.model
Enter fullscreen mode Exit fullscreen mode

Edit your dialplan to point to the full model:

exten => _X.,n,Set(RNNOISE_MODEL=/etc/asterisk/rnnoise.model)
Enter fullscreen mode Exit fullscreen mode

Monitoring RNNoise in production

Log suppression statistics to the database so you can track noise reduction over time. Create a table:

CREATE TABLE IF NOT EXISTS vicidial_noisesuppression_log (
  id INT PRIMARY KEY AUTO_INCREMENT,
  call_id VARCHAR(50),
  agent_id INT,
  carrier_id INT,
  inbound_suppression_db DECIMAL(5,2),
  outbound_suppression_db DECIMAL(5,2),
  call_date DATETIME,
  call_duration INT,
  INDEX(call_date),
  INDEX(agent_id),
  FOREIGN KEY (call_id) REFERENCES vicidial_log(uniqueid)
) ENGINE=InnoDB;
Enter fullscreen mode Exit fullscreen mode

Create a Lua script to log suppression events. In your dialplan, add:

exten => _X.,n,LuaRun(log_noise_suppression.lua)
Enter fullscreen mode Exit fullscreen mode

The Lua script (log_noise_suppression.lua):

-- Log RNNoise suppression metrics
local db = assert(io.open("/tmp/noisesuppression.fifo", "w"))
local suppression_db = channel.noisesuppression_level or 0
db:write(string.format("%s|%s|%f\n", 
  channel.uniqueid, 
  channel.AGENT_ID, 
  suppression_db))
db:close()
Enter fullscreen mode Exit fullscreen mode

In a cron job, read the FIFO and insert into the database:

cat > /usr/local/bin/log_noisesuppression.sh << 'EOF'
#!/bin/bash
while read line; do
  call_id=$(echo $line | cut -d'|' -f1)
  agent_id=$(echo $line | cut -d'|' -f2)
  suppression=$(echo $line | cut -d'|' -f3)

  mysql -u cron -p asterisk -e "INSERT INTO vicidial_noisesuppression_log 
    (call_id, agent_id, inbound_suppression_db, call_date) 
    VALUES ('$call_id', '$agent_id', '$suppression', NOW())"
done < /tmp/noisesuppression.fifo
EOF

chmod +x /usr/local/bin/log_noisesuppression.sh
Enter fullscreen mode Exit fullscreen mode

Run the script in the background:

/usr/local/bin/log_noisesuppression.sh &
Enter fullscreen mode Exit fullscreen mode

Query suppression trends:

SELECT agent_id, AVG(inbound_suppression_db) as avg_suppression, COUNT(*) as calls
FROM vicidial_noisesuppression_log
WHERE call_date > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY agent_id
ORDER BY avg_suppression DESC;
Enter fullscreen mode Exit fullscreen mode

High suppression on a specific agent indicates a noisy environment. Low suppression indicates good quality or a quiet channel.

Recording and CDR impact

RNNoise affects what gets stored on disk. Recordings made after RNNoise processing will have less noise but are not reversible. If your compliance requirements demand original audio, record before RNNoise and after separately.

Configure recording split in extensions-vicidial.conf:

exten => _X.,1,MixMonitor(${RECORDED_FILE_BEFORE}.wav)
exten => _X.,n,Set(CHANNEL(noisesuppression)=on)
exten => _X.,n,MixMonitor(${RECORDED_FILE_AFTER}.wav)
Enter fullscreen mode Exit fullscreen mode

ViciDial's call_recordings table references the file path. If you create two files, update the vicidial.php agent interface to link both:

SELECT * FROM call_recordings WHERE recording_id = 'xxxxx';
Enter fullscreen mode Exit fullscreen mode

The recording_id ties to the vicidial_log uniqueid. One uniqueid, one recording file. To store both, you would need to modify ViciDial's recording logic, which is beyond this scope.

For most centers, a single RNNoise-processed recording is acceptable and preferred.

Troubleshooting

Module fails to load with "undefined reference to rnnoise_create"

The compiled module cannot find the RNNoise library. Ensure ldconfig has indexed it:

echo /usr/local/lib | sudo tee -a /etc/ld.so.conf.d/rnnoise.conf
sudo ldconfig
ldd /usr/lib/asterisk/modules/res_noisesuppression.so | grep rnnoise
Enter fullscreen mode Exit fullscreen mode

If still missing, rebuild the module with explicit LDFLAGS:

LDFLAGS="-L/usr/local/lib" make clean && make && sudo make install
Enter fullscreen mode Exit fullscreen mode

Calls drop or become one-way after enabling RNNoise

RNNoise on one side but not the other causes asymmetry. The side with suppression removes noise, the other side does not. This can confuse Asterisk's echo cancellation.

Ensure both directions have RNNoise:

[AGENT001]
noisesuppression = yes

[carrier_sip]
noisesuppression = yes
Enter fullscreen mode Exit fullscreen mode

Or apply it symmetrically in the dialplan:

exten => _X.,1,Set(CHANNEL(noisesuppression)=on)
exten => _X.,n,Bridge(SIP/${EXTEN})
Enter fullscreen mode Exit fullscreen mode

If calls still drop, disable echo cancellation on the trunk and rely on RNNoise alone:

[carrier_sip]
echocancel = no
noisesuppression = yes
Enter fullscreen mode Exit fullscreen mode

RNNoise is not suppressing anything; verbose log shows no messages

The module may be loaded but not applied to any channels. Check:

asterisk -rx "sip show peer AGENT001" | grep noisesuppression
Enter fullscreen mode Exit fullscreen mode

If the output is blank, noisesuppression is not set on that peer. Add it explicitly:

[AGENT001]
type = device
noisesuppression = yes
Enter fullscreen mode Exit fullscreen mode

Reload and test:

asterisk -rx "sip reload"
Enter fullscreen mode Exit fullscreen mode

Make a call and check verbose output:

asterisk -rx "core set verbose 3"
Enter fullscreen mode Exit fullscreen mode

If still no output, the module is not recognizing the configuration option. This happens if the module was compiled against a different Asterisk version. Rebuild against your current Asterisk:

asterisk -rx "core show version"
make distclean
./configure --with-asterisk-includes=/usr/include/asterisk
make
sudo make install
asterisk -rx "module reload res_noisesuppression"
Enter fullscreen mode Exit fullscreen mode

Audio is delayed or jittery after RNNoise

RNNoise adds minimal latency (2-5 ms) per frame. If you hear stuttering, check the audio frame size. Asterisk defaults to 20 ms frames. RNNoise expects consistent frame timing.

Check Asterisk frame timing:

asterisk -rx "core show settings" | grep -i frame
Enter fullscreen mode Exit fullscreen mode

If the frame size is not 20 ms, adjust it in asterisk.conf:

[options]
transmit_silence_during_record = yes
astmm_audio_frame_size = 20
Enter fullscreen mode Exit fullscreen mode

Restart Asterisk:

sudo systemctl restart asterisk
Enter fullscreen mode Exit fullscreen mode

Frequently asked questions

How much CPU does RNNoise use per call?

RNNoise consumes approximately 1-3% CPU per concurrent call on a modern dual-core processor. On a 50-agent system with 40 concurrent calls, expect 40-120% CPU from RNNoise alone. This scales with codec and agent count. Monitor your system after enabling it on half your agents, then scale up if load remains acceptable.

Can I use RNNoise with Cisco or Polycom phones?

RNNoise runs on the Asterisk server, not on the phone. Any SIP phone that works with Asterisk will benefit from RNNoise applied to calls. Phone-side noise cancellation and server-side RNNoise can both be active; they do not conflict. However, if the phone has aggressive echo cancellation, disable it to avoid double-processing.

Does RNNoise work with call recordings?

Yes. Recordings are captured after RNNoise processing, so the suppressed audio is what gets stored. If you need the original unprocessed audio for compliance, record the call before applying RNNoise, or use separate input/output chains in MixMonitor.

Will RNNoise affect speech recognition or DTMF detection?

RNNoise does not remove or modify DTMF tones because they are distinct frequency pairs. However, poor audio quality before RNNoise can make DTMF harder to detect. RNNoise improves the signal-to-noise ratio, typically helping DTMF detection. Transcription (speech-to-text) benefits from RNNoise because clearer audio trains the model better, but very aggressive RNNoise can remove some voice frequencies that recognition engines rely on. Use the conservative mode if you are doing real-time transcription.

Can I disable RNNoise on specific calls or carriers?

Yes. Set noisesuppression = no on specific SIP peers in sip-vicidial.conf, or use a dialplan condition:

exten => _X.,1,GotoIf($["${CALLERID(number)}" = "8005551234"]?skip_noisesuppression)
exten => _X.,n,Set(CHANNEL(noisesuppression)=on)
exten => _X.,n(skip_noisesuppression):Bridge(SIP/${EXTEN})
Enter fullscreen mode Exit fullscreen mode

Summary

You have built and installed RNNoise, configured the Asterisk module, applied suppression to agent and carrier channels, and logged suppression metrics to the database. RNNoise is now active on both inbound and outbound calls in your ViciDial system.

Monitor /var/log/asterisk/messages for errors, run a test call, and listen to a recording before and after to confirm noise reduction. CPU usage should remain below 20% on your dialplan engine. If you notice audio quality degradation, reduce aggressiveness from aggressive to conservative, or switch to a wideband codec like opus.

In production, check the vicidial_noisesuppression_log table weekly to identify agents or carriers with consistently high noise levels. Noisy agents may need a better microphone or adjusted office settings. Noisy carriers may warrant a provider change.

Next steps: tune noisesuppression_settings per carrier based on baseline quality, integrate RNNoise metrics into your agent scorecards, and evaluate speech-to-text or quality assurance tools that benefit from cleaner audio.

Top comments (0)