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.
26 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
28 #include "asterisk/module.h"
29 #include "asterisk/lock.h"
30 #include "asterisk/options.h"
31 #include "asterisk/channel.h"
32 #include "asterisk/dsp.h"
33 #include "asterisk/pbx.h"
34 #include "asterisk/config.h"
35 #include "asterisk/app.h"
38 static char *tdesc = "Answering Machine Detection Application";
39 static char *app = "AMD";
40 static char *synopsis = "Attempts to detect answering machines";
41 static char *descrip =
42 " AMD([initialSilence][|greeting][|afterGreetingSilence][|totalAnalysisTime]\n"
43 " [|minimumWordLength][|betweenWordsSilence][|maximumNumberOfWords]\n"
44 " [|silenceThreshold])\n"
45 " This application attempts to detect answering machines at the beginning\n"
46 " of outbound calls. Simply call this application after the call\n"
47 " has been answered (outbound only, of course).\n"
48 " When loaded, AMD reads amd.conf and uses the parameters specified as\n"
49 " default values. Those default values get overwritten when calling AMD\n"
51 "- 'initialSilence' is the maximum silence duration before the greeting. If\n"
52 " exceeded then MACHINE.\n"
53 "- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n"
54 "- 'afterGreetingSilence' is the silence after detecting a greeting.\n"
55 " If exceeded then HUMAN.\n"
56 "- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n"
57 " on a HUMAN or MACHINE.\n"
58 "- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n"
59 "- 'betweenWordsSilence' is the minimum duration of silence after a word to \n"
60 " consider the audio that follows as a new word.\n"
61 "- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n"
62 " If exceeded then MACHINE.\n"
63 "- 'silenceThreshold' is the silence threshold.\n"
64 "This application sets the following channel variable upon completion:\n"
65 " AMDSTATUS - This is the status of the answering machine detection.\n"
66 " Possible values are:\n"
67 " MACHINE | HUMAN | NOTSURE | HANGUP\n"
68 " AMDCAUSE - Indicates the cause that led to the conclusion.\n"
69 " Possible values are:\n"
70 " TOOLONG-<%d total_time>\n"
71 " INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
72 " HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
73 " MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
74 " LONGGREETING-<%d voiceDuration>-<%d greeting>\n";
79 #define STATE_IN_WORD 1
80 #define STATE_IN_SILENCE 2
82 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
83 static int dfltInitialSilence = 2500;
84 static int dfltGreeting = 1500;
85 static int dfltAfterGreetingSilence = 800;
86 static int dfltTotalAnalysisTime = 5000;
87 static int dfltMinimumWordLength = 100;
88 static int dfltBetweenWordsSilence = 50;
89 static int dfltMaximumNumberOfWords = 3;
90 static int dfltSilenceThreshold = 256;
92 static void isAnsweringMachine(struct ast_channel *chan, void *data)
96 struct ast_frame *f = NULL;
98 struct ast_dsp *silenceDetector; /* silence detector dsp */
103 int inInitialSilence = 1;
105 int voiceDuration = 0;
106 int silenceDuration = 0;
109 int currentState = STATE_IN_SILENCE;
110 int previousState = STATE_IN_SILENCE;
111 int consecutiveVoiceDuration = 0;
112 char amdCause[256] = "";
113 char amdStatus[256] = "";
115 /* Lets set the initial values of the variables that will control the algorithm.
116 The initial values are the default ones. If they are passed as arguments
117 when invoking the application, then the default values will be overwritten
118 by the ones passed as parameters. */
119 int initialSilence = dfltInitialSilence;
120 int greeting = dfltGreeting;
121 int afterGreetingSilence = dfltAfterGreetingSilence;
122 int totalAnalysisTime = dfltTotalAnalysisTime;
123 int minimumWordLength = dfltMinimumWordLength;
124 int betweenWordsSilence = dfltBetweenWordsSilence;
125 int maximumNumberOfWords = dfltMaximumNumberOfWords;
126 int silenceThreshold = dfltSilenceThreshold;
129 AST_DECLARE_APP_ARGS(args,
130 AST_APP_ARG(argInitialSilence);
131 AST_APP_ARG(argGreeting);
132 AST_APP_ARG(argAfterGreetingSilence);
133 AST_APP_ARG(argTotalAnalysisTime);
134 AST_APP_ARG(argMinimumWordLength);
135 AST_APP_ARG(argBetweenWordsSilence);
136 AST_APP_ARG(argMaximumNumberOfWords);
137 AST_APP_ARG(argSilenceThreshold);
139 if (option_verbose > 2)
140 ast_verbose(VERBOSE_PREFIX_3 "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
142 /* Lets parse the arguments. */
143 if (ast_strlen_zero(data)) {
144 ast_log(LOG_NOTICE, "AMD using the default parameters.\n");
146 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
147 if (!(parse = ast_strdupa(data))) {
148 ast_log(LOG_WARNING, "Memory allocation failure\n");
149 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
150 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
154 AST_STANDARD_APP_ARGS(args, parse);
156 if (!ast_strlen_zero(args.argInitialSilence)) {
157 initialSilence = atoi(args.argInitialSilence);
159 if (!ast_strlen_zero(args.argGreeting)) {
160 greeting = atoi(args.argGreeting);
162 if (!ast_strlen_zero(args.argAfterGreetingSilence)) {
163 afterGreetingSilence = atoi(args.argAfterGreetingSilence);
165 if (!ast_strlen_zero(args.argTotalAnalysisTime)) {
166 totalAnalysisTime = atoi(args.argTotalAnalysisTime);
168 if (!ast_strlen_zero(args.argMinimumWordLength)) {
169 minimumWordLength = atoi(args.argMinimumWordLength);
171 if (!ast_strlen_zero(args.argBetweenWordsSilence)) {
172 betweenWordsSilence = atoi(args.argBetweenWordsSilence);
174 if (!ast_strlen_zero(args.argMaximumNumberOfWords)) {
175 maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
177 if (!ast_strlen_zero(args.argSilenceThreshold)) {
178 silenceThreshold = atoi(args.argSilenceThreshold);
182 /* Now we're ready to roll! */
184 if (option_verbose > 2)
185 ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
186 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
187 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
188 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
190 readFormat = chan->readformat;
191 res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
193 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
194 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
195 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
199 silenceDetector = ast_dsp_new();
200 if (!silenceDetector ) {
201 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
202 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
203 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
206 ast_dsp_set_threshold(silenceDetector, silenceThreshold );
208 while ((ret = ast_waitfor(chan, totalAnalysisTime)))
211 /* No Frame: Called Party Must Have Dropped */
212 if (option_verbose > 2)
213 ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
215 ast_log(LOG_DEBUG, "Got hangup\n");
216 strcpy(amdStatus , "HANGUP" );
217 strcpy(amdCause , "" );
222 /* No Frame: Called Party Must Have Dropped */
223 if (option_verbose > 2)
224 ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
226 ast_log(LOG_DEBUG, "Got hangup\n");
227 strcpy(amdStatus , "HANGUP" );
228 strcpy(amdCause , "" );
231 if (f->frametype == AST_FRAME_VOICE ) {
232 framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
233 iTotalTime += framelength;
234 if (iTotalTime >= totalAnalysisTime ) {
235 if (option_verbose > 2)
236 ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
238 strcpy(amdStatus , "NOTSURE" );
239 sprintf(amdCause , "TOOLONG-%d", iTotalTime );
243 ast_dsp_silence(silenceDetector, f, &dspsilence);
245 silenceDuration = dspsilence;
246 if (silenceDuration >= betweenWordsSilence ) {
247 if (currentState != STATE_IN_SILENCE ) {
248 previousState = currentState;
249 if (option_verbose > 2)
250 ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
252 currentState = STATE_IN_SILENCE;
253 consecutiveVoiceDuration = 0;
255 if (inInitialSilence == 1 && silenceDuration >= initialSilence ) {
256 if (option_verbose > 2)
257 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
258 silenceDuration, initialSilence );
260 strcpy(amdStatus , "MACHINE" );
261 sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence );
265 if (silenceDuration >= afterGreetingSilence && inGreeting == 1 ) {
266 if (option_verbose > 2)
267 ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
268 silenceDuration, afterGreetingSilence );
270 strcpy(amdStatus , "HUMAN" );
271 sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence );
275 consecutiveVoiceDuration += framelength;
276 voiceDuration += framelength;
278 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
279 number of words if my previous state was Silence, which means that I moved into a word. */
280 if (consecutiveVoiceDuration >= minimumWordLength ) {
281 if (currentState == STATE_IN_SILENCE ) {
283 if (option_verbose > 2)
284 ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount );
285 previousState = currentState;
286 currentState = STATE_IN_WORD;
290 if (iWordsCount >= maximumNumberOfWords ) {
291 if (option_verbose > 2)
292 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount );
294 strcpy(amdStatus , "MACHINE" );
295 sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords );
299 if (inGreeting == 1 && voiceDuration >= greeting ) {
300 if (option_verbose > 2)
301 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting);
303 strcpy(amdStatus , "MACHINE" );
304 sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting );
307 if (voiceDuration >= minimumWordLength ) {
309 inInitialSilence = 0;
317 /* It took too long to get a frame back. Giving up. */
318 if (option_verbose > 2)
319 ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
320 strcpy(amdStatus , "NOTSURE" );
321 sprintf(amdCause , "TOOLONG-%d", iTotalTime );
324 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus );
325 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause );
327 /* If We Started With A Valid Read Format, Return To It... */
328 if (readFormat && chan->_state == AST_STATE_UP) {
329 res = ast_set_read_format(chan, readFormat );
331 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
334 /* Free The Silence Detector DSP */
335 ast_dsp_free(silenceDetector );
341 static int amd_exec(struct ast_channel *chan, void *data)
346 isAnsweringMachine(chan, data);
347 LOCAL_USER_REMOVE(u);
352 static void load_config(void)
354 struct ast_config *cfg;
356 struct ast_variable *var;
358 cfg = ast_config_load("amd.conf");
361 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
365 cat = ast_category_browse(cfg, NULL);
368 if (!strcasecmp(cat, "amd") ) {
369 var = ast_variable_browse(cfg, cat);
371 if (!strcasecmp(var->name, "initial_silence")) {
372 dfltInitialSilence = atoi(var->value);
373 } else if (!strcasecmp(var->name, "greeting")) {
374 dfltGreeting = atoi(var->value);
375 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
376 dfltAfterGreetingSilence = atoi(var->value);
377 } else if (!strcasecmp(var->name, "silence_threshold")) {
378 dfltSilenceThreshold = atoi(var->value);
379 } else if (!strcasecmp(var->name, "total_analysis_time")) {
380 dfltTotalAnalysisTime = atoi(var->value);
381 } else if (!strcasecmp(var->name, "min_word_length")) {
382 dfltMinimumWordLength = atoi(var->value);
383 } else if (!strcasecmp(var->name, "between_words_silence")) {
384 dfltBetweenWordsSilence = atoi(var->value);
385 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
386 dfltMaximumNumberOfWords = atoi(var->value);
388 ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
389 app, cat, var->name, var->lineno);
394 cat = ast_category_browse(cfg, cat);
396 ast_config_destroy(cfg);
398 if (option_verbose > 2)
399 ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
400 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
401 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
402 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
407 int unload_module(void)
409 STANDARD_HANGUP_LOCALUSERS;
410 return ast_unregister_application(app);
413 int load_module(void)
416 return ast_register_application(app, amd_exec, synopsis, descrip);
425 char *description(void)
433 STANDARD_USECOUNT(res);
439 return ASTERISK_GPL_KEY;