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 setsAMDSTATUS,AMDCAUSEandAMDSTATS, soVD_amd.agiand 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.
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.
| 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 |
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.
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.
- Every 20 ms, VAMD computes a 32-band log-mel spectrum of the called party's audio.
- 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:
KNOWNVMgives MACHINE with causeKNOWNVM-<template>.SCREENERgives MACHINE with causeSCREENER-<template>.
- If no template matches, the unchanged Asterisk 13
app_amdcode decides the call with your arguments, exactly asAMD()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 = yesorSet(VAMDADAPT=…)): a per-call threshold, so steady background noise and faint sounds aren't counted as words.
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 miningUse the compiler flags from build.sh. Never add -march=native.
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"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.
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 = audioexten => 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 = shadowand runmodule reload app_vamd.so, or - change
VAMD(back toAMD(and rundialplan reload.
| 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.
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"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.
Record from the moment of answer, ideally in stereo with the called party on its own channel. Convert the recordings to 8 kHz.
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?"}]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.
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
0means voicemail/network message (KNOWNVM) and1means 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=1to 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,2or0for mono (default 2).
- Output: each new template is printed with its threshold, the best live-person score and the number of calls it adds.
-
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.txtagainst recordings. -
Rollback. Always keep the previous library so you can go back.
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)
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