Skip to content

About

VAMD: voicemail/call-screener aware answering machine detection for Asterisk 13 and VICIdial. Drop-in AMD() replacement: 94% of machines caught, 0.09% of live people lost.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VAMD: voicemail-aware answering machine detection for Asterisk 13 and VICIdial

VAMD is a drop-in replacement for Asterisk's AMD() application. It recognises the recorded prompts that voicemail systems, mobile networks and call screeners play, using spectral fingerprints. It hangs those calls up in about 1.6 seconds, and leaves every other call to the unchanged, built-in Asterisk AMD rules.

Stock AMD guesses from the timing of speech and silence alone. To catch voicemails it has to be strict, and strict timing rules cut off real people who answer with a long sentence or after a pause. VAMD recognises machines by what they play, so it catches far more of them and can use gentler timing rules for everyone else.

  • Same arguments and results as AMD(). It sets AMDSTATUS, AMDCAUSE and AMDSTATS, so VD_amd.agi and VICIdial need no changes.
  • No speech-to-text, no cloud, no per-call cost. Matching is plain C inside the Asterisk module.
  • A shadow mode lets you run it alongside your current AMD and see what it would have done before it decides anything.

Results

These results are from production VICIdial diallers (Asterisk 13.29.2-vici) on UK outbound campaigns. Every call was reviewed from its recording and transcript; none were sampled.

Live production audit (library v8, 11,036 AMD calls in one session)

Result
Answering machines caught 94.2% (9,162 of 9,731)
Live people wrongly hung up as machines 1 of ~1,110 (0.09%), a receptionist's long greeting caught by the word-count rule
Live people hung up by prompt matching 0: all 8,993 prompt-matched hang-ups were recorded prompts
Typical decision time on a prompt match ~1.6 s after answer

Head-to-head with stock Asterisk 13 AMD

The same 2,922 recorded calls were scored by stock AMD and by VAMD with identical settings. None of these calls were used to build the templates.

Stock AMD() VAMD Change
Machines stopped before reaching an agent 85.5% 96.8% +11.3 points
Machines reaching agents 391 87 −78%
Share of agent calls that are live people 26.3% 56.6% about 2×
Median time to hang up a machine 2.46 s 1.62 s 0.8 s sooner

Live people hung up as machines, each system on its own production settings:

Stock AMD() VAMD
5 of 152 live answers (3.3%; 95% CI 1.4–7.5%) 1 of 1,110 (0.09%; 95% CI 0.02–0.51%)

That's about 36 times fewer (Fisher's exact test, p = 0.0001). Under Ofcom's guidance in the UK, every live person that AMD wrongly hangs up counts as an abandoned call.

The trade-off. When VAMD hangs up on a call screener (iPhone Live Voicemail or call screening, Google Call Assist, AI assistants), the person might have picked up after the screener. In the replay this happened on about 1.2% of screener hang-ups. If you'd rather send screened calls to agents, set screener_action = agent.

What the v8 library recognises (115 templates)

UK networks:

  • Voicemail and network messages: EE, O2, Vodafone, Three, giffgaff, Tesco Mobile, Lyca/Lebara.
  • Other prompts: the network's number read-out ("07…"), "forwarded to voicemail", "the person you have called is not available" and "can't take your call right now".

Call screeners:

  • iPhone Live Voicemail and call screening.
  • Google Call Assist and AI call assistants.

There are also a few South African network voicemails. Other countries need their own templates. See Mining new patterns.

How it works

  1. Every 20 ms, VAMD computes a 32-band log-mel spectrum of the called party's audio.
  2. The last 0.8 s (40 frames) is compared with every template in the library by normalised correlation. Each template has its own threshold and action:
    • KNOWNVM gives MACHINE with cause KNOWNVM-<template>.
    • SCREENER gives MACHINE with cause SCREENER-<template>.
  3. If no template matches, the unchanged Asterisk 13 app_amd code decides the call with your arguments, exactly as AMD() would.

The engine (src/vamd_core.c) is plain C89. The same file builds into the Asterisk module and into the offline tool (vamd_tool), so offline tests run exactly the production code.

Two optional improvements apply in active mode:

  • amd_clock = audio: stock Asterisk 13 AMD counts the wait for NULL frames as well as the audio, so its clock runs about 1.1 s fast and "5 seconds" is really about 3.9 s of audio. This setting counts real audio time instead.
  • Adaptive silence threshold (adaptive = yes or Set(VAMDADAPT=…)): a per-call threshold, so steady background noise and faint sounds aren't counted as words.

Installation (VICIdial / Asterisk 13)

1. Get the module

Option A, prebuilt. prebuilt/app_vamd.so was built for Asterisk 13.29.2-vici (the VICIdial Asterisk 13 build on CentOS 7). Use it only with that exact version (asterisk -V). Its checksum is in prebuilt/MD5SUMS.

Option B, build from source. On the server, you need the Asterisk source tree that matches your running Asterisk:

cd src
./build.sh /usr/src/asterisk/asterisk-13.29.2    # writes ./app_vamd.so, compiled exactly like the tree's own modules
gcc -std=gnu89 -O2 -Wall -o ../tools/vamd_tool vamd_tool.c vamd_core.c -lm    # offline tool, used for mining

Use the compiler flags from build.sh. Never add -march=native.

2. Install

install -m 755 app_vamd.so              /usr/lib/asterisk/modules/app_vamd.so
install -m 600 libraries/vamd_v8_20261019.lib /etc/asterisk/vamd.lib
install -m 600 conf/vamd.conf           /etc/asterisk/vamd.conf     # starts in shadow mode
install -m 644 conf/logrotate-vamd      /etc/logrotate.d/vamd
asterisk -rx "module load app_vamd.so"
grep "VAMD: loaded" /var/log/asterisk/messages    # e.g. "loaded 115 templates ... mode shadow"

3. Use it in the dialplan

VICIdial runs AMD on extension 8369 (or the "AMD send to" extension of your campaigns). Change AMD( to VAMD( and keep the arguments as they are:

exten => 8369,n,VAMD(4000,2000,650,5000,100,50,5,360,3000)
asterisk -rx "dialplan reload"

Nothing else changes. VD_amd.agi reads the same AMDSTATUS/AMDCAUSE, and VICIdial's AMD log shows causes such as KNOWNVM-o2_02 or SCREENER-iphone_05.

4. Shadow first, then active

In shadow mode (the default), every decision is still made by the native AMD code and VAMD only logs what it would have done. Each call adds a line to /var/log/astguiclient/VAMD_log.txt with these fields:

time | uniqueid | caller-id name | channel | mode | status | cause | ms | VAMD status | VAMD cause | VAMD ms | best template | score | templates | adaptive

Review a few hundred calls. Wherever possible, listen to the calls VAMD would have hung up. Then go active:

; /etc/asterisk/vamd.conf
mode = active
amd_clock = audio
exten => 8369,n,Set(VAMDADAPT=200,35,1000)
exten => 8369,n,VAMD(5000,3000,800,3500,150,50,5,450,5000)
asterisk -rx "module reload app_vamd.so"
asterisk -rx "dialplan reload"

These are the settings behind the results above. totalAnalysisTime drops from 5000 to 3500 because amd_clock = audio measures real audio time: 3500 ms of real audio is about what stock AMD's "5000" actually listened to.

Rollback:

  • Set mode = shadow and run module reload app_vamd.so, or
  • change VAMD( back to AMD( and run dialplan reload.

vamd.conf options ([general])

Option Default Meaning
library /etc/asterisk/vamd.lib Template library
logfile /var/log/astguiclient/VAMD_log.txt Per-call log (one line per call; the file is opened and closed each time, so a plain logrotate works)
mode shadow shadow = log only; active = prompt matches hang up
screener_action hangup agent = ignore screener templates and pass those calls to the AMD rules
amd_clock native audio = count real audio time (active mode only)
adaptive no Per-call silence threshold (active mode only). The channel variable VAMDADAPT (off, on, or <noise%>,<word%>[,<floor ms>]) overrides it per call
adaptive_noise / adaptive_word / adaptive_floor_ms 200 / 25 / 1000 Adaptive threshold parameters
match_from 0 Ignore template matches that end before this many ms
diag / diaglog no Extra per-frame diagnostics

Extra channel variables set after VAMD(): VAMDTEMPLATE, VAMDSCORE, VAMDMS and VAMDTHRESHOLD.

Test harness

conf/extensions-vamdtest.conf plays a recording into VAMD() and into AMD() and prints both results. Put your own test recordings (8 kHz WAV) in sounds/vamdtest/ and include the file from extensions.conf. Then run:

asterisk -rx "channel originate Local/play-<sound>@vamdtest extension detect@vamdtest"

Mining new patterns

The library will miss prompts it has never heard, such as other countries, new networks or new screener wording. You can add templates from your own recorded calls.

1. Record calls

Record from the moment of answer, ideally in stereo with the called party on its own channel. Convert the recordings to 8 kHz.

2. Transcribe and label

Transcribe the recordings with any speech-to-text service; this is only needed offline. For each batch of calls, make a folder with the recordings in rec/ and a results.json file:

[{"file": "call123.wav", "code": "call123", "status": "HUMAN", "transcript": "Hello? Who's this?"}]

3. Build the live-person corpus

This is the safety net. Every new template is checked against every live person in the corpus, and its threshold is set above the best score any of them reaches.

# raw files: 8 kHz signed 16-bit mono, called party only, first 8 s
mkdir -p batch1/raw
for f in batch1/rec/*.wav; do sox "$f" -t raw -r 8000 -e signed -b 16 -c 1 "batch1/raw/$(basename "${f%.*}").raw" remix 2 trim 0 8; done
tools/build_corpus.py corpus batch1=batch1=batch1/raw              # calls AMD passed, with speech and no voicemail wording
tools/build_corpus.py corpus reviewed=batch2=batch2/raw=labels     # or use a manual review (batch2/labels.json)

Leave out people who answered after a screener or voicemail prompt. Their recordings contain the prompt, and build_corpus.py filters them automatically. The bigger the corpus, the safer the templates. The v8 library was validated against 2,898 reviewed live people.

4. Mine templates for the calls you want to catch

Choose the target calls with a regular expression on their transcripts:

LIVE_CORPUS=corpus/live tools/mine_patterns.py libraries/vamd_v8_20261019.lib /tmp/vamd_v9.lib \
    "person you have called is not available" nvm3 0 batch1 batch3
  • Arguments: base library, output library, target regex, name prefix, action, then the call folders. Action 0 means voicemail/network message (KNOWNVM) and 1 means call screener (SCREENER).
  • Train and held-out split: the miner trains on half the target calls and reports how many of the held-out half the new templates catch. When you're happy with that, rerun with FINAL=1 to train on everything.
  • Settings (environment variables):
    • MAX_END: the template must match within this many 20 ms frames (default 165 = 3.3 s).
    • MARGIN: the safety margin above the best live-person score (default 0.10).
    • FLOOR: the minimum threshold (default 0.75).
    • CALLED_CHANNEL: the channel holding the called party, 1, 2 or 0 for mono (default 2).
  • Output: each new template is printed with its threshold, the best live-person score and the number of calls it adds.

5. Validate and deploy

  • Replay. Run held-out calls (both people and machines) through the exact production engine with vamd_tool run <library> <AMD args> < list_of_raw_files.

  • Shadow. Deploy the new library in shadow mode first:

    cp /tmp/vamd_v9.lib /etc/asterisk/vamd.lib
    asterisk -rx "module reload app_vamd.so"

    Then check the new templates in VAMD_log.txt against recordings.

  • Rollback. Always keep the previous library so you can go back.

Repository layout

src/        app_vamd13.c (Asterisk module), vamd_core.c/.h (engine), vamd_tool.c (offline tool), build.sh
libraries/  vamd_v8_20261019.lib (115 templates) + MD5SUMS
prebuilt/   app_vamd.so for Asterisk 13.29.2-vici + MD5SUMS
conf/       vamd.conf (shadow), vamd.conf.active-example, extensions-vamdtest.conf, logrotate-vamd
tools/      mine_patterns.py, build_corpus.py, vmre.py (voicemail wording regex)

Licence

GPL-2.0. app_vamd13.c is based on app_amd.c from Asterisk (Copyright (C) 2003–2006 Aheeva Technology, Claude Klimos; with the VICIdial additions), which is distributed under the GNU General Public License version 2. See LICENSE.

Keywords: vicidial, asterisk, amd, answering machine detection, voicemail detection, call screening, iphone live voicemail, predictive dialer, outbound call centre, ofcom abandoned calls, asterisk module

About

VAMD: voicemail/call-screener aware answering machine detection for Asterisk 13 and VICIdial. Drop-in AMD() replacement: 94% of machines caught, 0.09% of live people lost.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages