2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2003 - 2006, Aheeva Technology.
6 * Claude Klimos (claude.klimos@aheeva.com)
10 * See http://www.asterisk.org for more information about
11 * the Asterisk project. Please do not directly contact
12 * any of the maintainers of this project for assistance;
13 * the project provides a web site, mailing lists and IRC
14 * channels for your use.
16 * This program is free software, distributed under the terms of
17 * the GNU General Public License Version 2. See the LICENSE file
18 * at the top of the source tree.
24 #include "asterisk/module.h"
25 #include "asterisk/lock.h"
26 #include "asterisk/options.h"
27 #include "asterisk/channel.h"
28 #include "asterisk/dsp.h"
29 #include "asterisk/pbx.h"
30 #include "asterisk/config.h"
31 #include "asterisk/app.h"
34 static char *tdesc = "Answering Machine Detection Application";
35 static char *app = "AMD";
36 static char *synopsis = "Attempts to detect answering machines";
37 static char *descrip =
38 " AMD([initialSilence][|greeting][|afterGreetingSilence][|totalAnalysisTime]\n"
39 " [|minimumWordLength][|betweenWordsSilence][|maximumNumberOfWords]\n"
40 " [|silenceThreshold])\n"
41 " This application attempts to detect answering machines at the beginning\n"
42 " of outbound calls. Simply call this application after the call\n"
43 " has been answered (outbound only, of course).\n"
44 " When loaded, AMD reads amd.conf and uses the parameters specified as\n"
45 " default values. Those default values get overwritten when calling AMD\n"
47 "- 'initialSilence' is the maximum silence duration before the greeting. If\n"
48 " exceeded then MACHINE.\n"
49 "- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n"
50 "- 'afterGreetingSilence' is the silence after detecting a greeting.\n"
51 " If exceeded then HUMAN.\n"
52 "- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n"
53 " on a HUMAN or MACHINE.\n"
54 "- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n"
55 "- 'betweenWordsSilence' is the minimum duration of silence after a word to \n"
56 " consider the audio that follows as a new word.\n"
57 "- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n"
58 " If exceeded then MACHINE.\n"
59 "- 'silenceThreshold' is the silence threshold.\n"
60 "This application sets the following channel variable upon completion:\n"
61 " AMDSTATUS - This is the status of the answering machine detection.\n"
62 " Possible values are:\n"
63 " MACHINE | PERSON | NOTSURE | HANGUP\n"
64 " AMDCAUSE - Indicates the cause that led to the conclusion.\n"
65 " Possible values are:\n"
66 " TOOLONG-<%d total_time>\n"
67 " INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
68 " HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
69 " MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
70 " LONGGREETING-<%d voiceDuration>-<%d greeting>\n";
77 #define STATE_IN_WORD 1
78 #define STATE_IN_SILENCE 2
80 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
81 static int dfltInitialSilence = 2500;
82 static int dfltGreeting = 1500;
83 static int dfltAfterGreetingSilence = 800;
84 static int dfltTotalAnalysisTime = 5000;
85 static int dfltMinimumWordLength = 100;
86 static int dfltBetweenWordsSilence = 50;
87 static int dfltMaximumNumberOfWords = 3;
88 static int dfltSilenceThreshold = 256;
90 static void isAnsweringMachine(struct ast_channel *chan, void *data)
94 struct ast_frame *f = NULL;
96 struct ast_dsp *silenceDetector; /* silence detector dsp */
101 int inInitialSilence = 1;
103 int voiceDuration = 0;
104 int silenceDuration = 0;
107 int currentState = STATE_IN_SILENCE;
108 int previousState = STATE_IN_SILENCE;
109 int consecutiveVoiceDuration = 0;
110 char amdCause[256] = "";
111 char amdStatus[256] = "";
113 /* Lets set the initial values of the variables that will control the algorithm.
114 The initial values are the default ones. If they are passed as arguments
115 when invoking the application, then the default values will be overwritten
116 by the ones passed as parameters. */
117 int initialSilence = dfltInitialSilence;
118 int greeting = dfltGreeting;
119 int afterGreetingSilence = dfltAfterGreetingSilence;
120 int totalAnalysisTime = dfltTotalAnalysisTime;
121 int minimumWordLength = dfltMinimumWordLength;
122 int betweenWordsSilence = dfltBetweenWordsSilence;
123 int maximumNumberOfWords = dfltMaximumNumberOfWords;
124 int silenceThreshold = dfltSilenceThreshold;
127 AST_DECLARE_APP_ARGS(args,
128 AST_APP_ARG(argInitialSilence);
129 AST_APP_ARG(argGreeting);
130 AST_APP_ARG(argAfterGreetingSilence);
131 AST_APP_ARG(argTotalAnalysisTime);
132 AST_APP_ARG(argMinimumWordLength);
133 AST_APP_ARG(argBetweenWordsSilence);
134 AST_APP_ARG(argMaximumNumberOfWords);
135 AST_APP_ARG(argSilenceThreshold);
138 ast_verbose(VERBOSE_PREFIX_3 "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
140 /* Lets parse the arguments. */
141 if (ast_strlen_zero(data)) {
142 ast_log(LOG_NOTICE, "AMD using the default parameters.\n");
144 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
145 if (!(parse = ast_strdupa(data))) {
146 ast_log(LOG_WARNING, "Memory allocation failure\n");
147 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
148 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
152 AST_STANDARD_APP_ARGS(args, parse);
154 if (!ast_strlen_zero(args.argInitialSilence)) {
155 initialSilence = atoi(args.argInitialSilence);
157 if (!ast_strlen_zero(args.argGreeting)) {
158 greeting = atoi(args.argGreeting);
160 if (!ast_strlen_zero(args.argAfterGreetingSilence)) {
161 afterGreetingSilence = atoi(args.argAfterGreetingSilence);
163 if (!ast_strlen_zero(args.argTotalAnalysisTime)) {
164 totalAnalysisTime = atoi(args.argTotalAnalysisTime);
166 if (!ast_strlen_zero(args.argMinimumWordLength)) {
167 minimumWordLength = atoi(args.argMinimumWordLength);
169 if (!ast_strlen_zero(args.argBetweenWordsSilence)) {
170 betweenWordsSilence = atoi(args.argBetweenWordsSilence);
172 if (!ast_strlen_zero(args.argMaximumNumberOfWords)) {
173 maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
175 if (!ast_strlen_zero(args.argSilenceThreshold)) {
176 silenceThreshold = atoi(args.argSilenceThreshold);
180 /* Now we're ready to roll! */
182 ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
183 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
184 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
185 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
187 readFormat = chan->readformat;
188 res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
190 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
191 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
192 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
196 silenceDetector = ast_dsp_new();
197 if (!silenceDetector ) {
198 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
199 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
200 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
203 ast_dsp_set_threshold(silenceDetector, silenceThreshold );
205 while (ast_waitfor(chan, -1) > -1)
209 /* No Frame: Called Party Must Have Dropped */
210 ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
211 ast_log(LOG_DEBUG, "Got hangup\n");
212 strcpy(amdStatus , "HANGUP" );
213 strcpy(amdCause , "" );
216 framelength = (ast_codec_get_samples(f) / 8);
217 iTotalTime += framelength;
218 if (iTotalTime >= totalAnalysisTime ) {
219 ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
221 strcpy(amdStatus , "NOTSURE" );
222 sprintf(amdCause , "TOOLONG-%d", iTotalTime );
225 if (f->frametype == AST_FRAME_VOICE ) {
227 ast_dsp_silence(silenceDetector, f, &dspsilence);
229 silenceDuration = dspsilence;
230 /* ast_verbose(VERBOSE_PREFIX_3 "AMD: %d SILENCE: silenceDuration:%d afterGreetingSilence:%d inGreeting:%d\n", currentState, silenceDuration, afterGreetingSilence, inGreeting ); */
231 if (silenceDuration >= betweenWordsSilence ) {
232 if (currentState != STATE_IN_SILENCE ) {
233 previousState = currentState;
234 ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
236 currentState = STATE_IN_SILENCE;
237 consecutiveVoiceDuration = 0;
239 if (inInitialSilence == 1 && silenceDuration >= initialSilence ) {
240 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
241 silenceDuration, initialSilence );
243 strcpy(amdStatus , "MACHINE" );
244 sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence );
248 if (silenceDuration >= afterGreetingSilence && inGreeting == 1 ) {
249 ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
250 silenceDuration, afterGreetingSilence );
252 strcpy(amdStatus , "PERSON" );
253 sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence );
257 consecutiveVoiceDuration += framelength;
258 voiceDuration += framelength;
259 /* ast_verbose(VERBOSE_PREFIX_3 "AMD: %d VOICE: ConsecutiveVoice:%d voiceDuration:%d inGreeting:%d\n", currentState, consecutiveVoiceDuration, voiceDuration, inGreeting ); */
261 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
262 number of words if my previous state was Silence, which means that I moved into a word. */
263 if (consecutiveVoiceDuration >= minimumWordLength ) {
264 if (currentState == STATE_IN_SILENCE ) {
266 ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount );
267 previousState = currentState;
268 currentState = STATE_IN_WORD;
272 if (iWordsCount >= maximumNumberOfWords ) {
273 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount );
275 strcpy(amdStatus , "MACHINE" );
276 sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords );
280 if (inGreeting == 1 && voiceDuration >= greeting ) {
281 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n",
282 voiceDuration, greeting );
284 strcpy(amdStatus , "MACHINE" );
285 sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting );
288 if (voiceDuration >= minimumWordLength ) {
290 inInitialSilence = 0;
298 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus );
299 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause );
301 /* If We Started With A Valid Read Format, Return To It... */
303 res = ast_set_read_format(chan, readFormat );
305 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
308 /* Free The Silence Detector DSP */
309 ast_dsp_free(silenceDetector );
315 static int amd_exec(struct ast_channel *chan, void *data)
320 isAnsweringMachine(chan, data);
321 LOCAL_USER_REMOVE(u);
326 static void load_config(void)
328 struct ast_config *cfg;
330 struct ast_variable *var;
332 cfg = ast_config_load("amd.conf");
335 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
339 cat = ast_category_browse(cfg, NULL);
342 if (!strcasecmp(cat, "amd") ) {
343 var = ast_variable_browse(cfg, cat);
345 if (!strcasecmp(var->name, "initial_silence")) {
346 dfltInitialSilence = atoi(var->value);
347 } else if (!strcasecmp(var->name, "greeting")) {
348 dfltGreeting = atoi(var->value);
349 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
350 dfltAfterGreetingSilence = atoi(var->value);
351 } else if (!strcasecmp(var->name, "silence_threshold")) {
352 dfltSilenceThreshold = atoi(var->value);
353 } else if (!strcasecmp(var->name, "total_analysis_time")) {
354 dfltTotalAnalysisTime = atoi(var->value);
355 } else if (!strcasecmp(var->name, "min_word_length")) {
356 dfltMinimumWordLength = atoi(var->value);
357 } else if (!strcasecmp(var->name, "between_words_silence")) {
358 dfltBetweenWordsSilence = atoi(var->value);
359 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
360 dfltMaximumNumberOfWords = atoi(var->value);
362 ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
363 app, cat, var->name, var->lineno);
368 cat = ast_category_browse(cfg, cat);
370 ast_config_destroy(cfg);
372 ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
373 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
374 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
375 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
380 int unload_module(void)
382 STANDARD_HANGUP_LOCALUSERS;
383 return ast_unregister_application(app);
386 int load_module(void)
389 return ast_register_application(app, amd_exec, synopsis, descrip);
398 char *description(void)
406 STANDARD_USECOUNT(res);
412 return ASTERISK_GPL_KEY;