--- /dev/null
+/*\r
+ * Asterisk -- An open source telephony toolkit.\r
+ *\r
+ * Copyright (C) 2003 - 2006, Aheeva Technology.\r
+ *\r
+ * Claude Klimos (claude.klimos@aheeva.com)\r
+ *\r
+ * Disclaimed to Digium\r
+ *\r
+ * See http://www.asterisk.org for more information about\r
+ * the Asterisk project. Please do not directly contact\r
+ * any of the maintainers of this project for assistance;\r
+ * the project provides a web site, mailing lists and IRC\r
+ * channels for your use.\r
+ *\r
+ * This program is free software, distributed under the terms of\r
+ * the GNU General Public License Version 2. See the LICENSE file\r
+ * at the top of the source tree.\r
+ */\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+\r
+#include "asterisk/module.h"\r
+#include "asterisk/lock.h"\r
+#include "asterisk/options.h"\r
+#include "asterisk/channel.h"\r
+#include "asterisk/dsp.h"\r
+#include "asterisk/pbx.h"\r
+#include "asterisk/config.h"\r
+#include "asterisk/app.h"\r
+\r
+\r
+static char *tdesc = "Answering Machine Detection Application";\r
+static char *app = "AMD";\r
+static char *synopsis = "Attempts to detect answering machines";\r
+static char *descrip =\r
+" AMD([initialSilence][|greeting][|afterGreetingSilence][|totalAnalysisTime]\n"\r
+" [|minimumWordLength][|betweenWordsSilence][|maximumNumberOfWords]\n"\r
+" [|silenceThreshold])\n"\r
+" This application attempts to detect answering machines at the beginning\n"\r
+" of outbound calls. Simply call this application after the call\n"\r
+" has been answered (outbound only, of course).\n"\r
+" When loaded, AMD reads amd.conf and uses the parameters specified as\n"\r
+" default values. Those default values get overwritten when calling AMD\n"\r
+" with parameters.\n"\r
+"- 'initialSilence' is the maximum silence duration before the greeting. If\n"\r
+" exceeded then MACHINE.\n"\r
+"- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n"\r
+"- 'afterGreetingSilence' is the silence after detecting a greeting.\n"\r
+" If exceeded then HUMAN.\n"\r
+"- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n"\r
+" on a HUMAN or MACHINE.\n"\r
+"- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n"\r
+"- 'betweenWordsSilence' is the minimum duration of silence after a word to \n"\r
+" consider the audio that follows as a new word.\n"\r
+"- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n"\r
+" If exceeded then MACHINE.\n"\r
+"- 'silenceThreshold' is the silence threshold.\n"\r
+"This application sets the following channel variable upon completion:\n"\r
+" AMDSTATUS - This is the status of the answering machine detection.\n"\r
+" Possible values are:\n"\r
+" AMD_MACHINE | AMD_PERSON | AMD_NOTSURE | AMD_HANGUP\n"\r
+" AMDCAUSE - Indicates the cause that led to the conclusion.\n"\r
+" Possible values are:\n"\r
+" AMD_TOOLONG-<%d total_time>\n"\r
+" AMD_INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"\r
+" AMD_HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"\r
+" AMD_MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"\r
+" AMD_LONGGREETING-<%d voiceDuration>-<%d greeting>\n";\r
+\r
+\r
+STANDARD_LOCAL_USER;\r
+\r
+LOCAL_USER_DECL;\r
+\r
+#define STATE_IN_WORD 1\r
+#define STATE_IN_SILENCE 2\r
+\r
+/* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */\r
+static int dfltInitialSilence = 2500;\r
+static int dfltGreeting = 1500;\r
+static int dfltAfterGreetingSilence = 800;\r
+static int dfltTotalAnalysisTime = 5000;\r
+static int dfltMinimumWordLength = 100;\r
+static int dfltBetweenWordsSilence = 50;\r
+static int dfltMaximumNumberOfWords = 3;\r
+static int dfltSilenceThreshold = 256;\r
+\r
+static void isAnsweringMachine(struct ast_channel *chan, void *data)\r
+{\r
+ int res = 0;\r
+\r
+ struct ast_frame *f = NULL;\r
+\r
+ struct ast_dsp *silenceDetector; /* silence detector dsp */\r
+ int dspsilence = 0;\r
+ int readFormat;\r
+\r
+ int inInitialSilence = 1;\r
+ int inGreeting = 0;\r
+ int voiceDuration = 0;\r
+ int silenceDuration = 0;\r
+ int iTotalTime = 0;\r
+ int iWordsCount = 0;\r
+ int currentState = STATE_IN_SILENCE;\r
+ int previousState = STATE_IN_SILENCE;\r
+ int consecutiveVoiceDuration = 0;\r
+ char amdCause[256] = "";\r
+ char amdStatus[256] = "";\r
+\r
+ /* Lets set the initial values of the variables that will control the algorithm.\r
+ The initial values are the default ones. If they are passed as arguments\r
+ when invoking the application, then the default values will be overwritten\r
+ by the ones passed as parameters. */\r
+ int initialSilence = dfltInitialSilence;\r
+ int greeting = dfltGreeting;\r
+ int afterGreetingSilence = dfltAfterGreetingSilence;\r
+ int totalAnalysisTime = dfltTotalAnalysisTime;\r
+ int minimumWordLength = dfltMinimumWordLength;\r
+ int betweenWordsSilence = dfltBetweenWordsSilence;\r
+ int maximumNumberOfWords = dfltMaximumNumberOfWords;\r
+ int silenceThreshold = dfltSilenceThreshold;\r
+\r
+ char *parse;\r
+ AST_DECLARE_APP_ARGS(args,\r
+ AST_APP_ARG(argInitialSilence);\r
+ AST_APP_ARG(argGreeting);\r
+ AST_APP_ARG(argAfterGreetingSilence);\r
+ AST_APP_ARG(argTotalAnalysisTime);\r
+ AST_APP_ARG(argMinimumWordLength);\r
+ AST_APP_ARG(argBetweenWordsSilence);\r
+ AST_APP_ARG(argMaximumNumberOfWords);\r
+ AST_APP_ARG(argSilenceThreshold);\r
+ );\r
+\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);\r
+\r
+ /* Lets parse the arguments. */\r
+ if (ast_strlen_zero(data)) {\r
+ ast_log(LOG_NOTICE, "AMD using the default parameters.\n");\r
+ } else {\r
+ /* Some arguments have been passed. Lets parse them and overwrite the defaults. */\r
+ if (!(parse = ast_strdupa(data))) {\r
+ ast_log(LOG_WARNING, "Memory allocation failure\n");\r
+ pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );\r
+ pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );\r
+ return;\r
+ }\r
+\r
+ AST_STANDARD_APP_ARGS(args, parse);\r
+\r
+ if (!ast_strlen_zero(args.argInitialSilence)) {\r
+ initialSilence = atoi(args.argInitialSilence);\r
+ }\r
+ if (!ast_strlen_zero(args.argGreeting)) {\r
+ greeting = atoi(args.argGreeting);\r
+ }\r
+ if (!ast_strlen_zero(args.argAfterGreetingSilence)) {\r
+ afterGreetingSilence = atoi(args.argAfterGreetingSilence);\r
+ }\r
+ if (!ast_strlen_zero(args.argTotalAnalysisTime)) {\r
+ totalAnalysisTime = atoi(args.argTotalAnalysisTime);\r
+ }\r
+ if (!ast_strlen_zero(args.argMinimumWordLength)) {\r
+ minimumWordLength = atoi(args.argMinimumWordLength);\r
+ }\r
+ if (!ast_strlen_zero(args.argBetweenWordsSilence)) {\r
+ betweenWordsSilence = atoi(args.argBetweenWordsSilence);\r
+ }\r
+ if (!ast_strlen_zero(args.argMaximumNumberOfWords)) {\r
+ maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);\r
+ }\r
+ if (!ast_strlen_zero(args.argSilenceThreshold)) {\r
+ silenceThreshold = atoi(args.argSilenceThreshold);\r
+ }\r
+ }\r
+\r
+ /* Now we're ready to roll! */\r
+\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "\r
+ "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",\r
+ initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,\r
+ minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );\r
+\r
+ readFormat = chan->readformat;\r
+ res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);\r
+ if (res < 0 ) {\r
+ ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );\r
+ pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );\r
+ pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );\r
+ return;\r
+ }\r
+\r
+ silenceDetector = ast_dsp_new();\r
+ if (!silenceDetector ) {\r
+ ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );\r
+ pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );\r
+ pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );\r
+ return;\r
+ }\r
+ ast_dsp_set_threshold(silenceDetector, silenceThreshold );\r
+\r
+ while (ast_waitfor(chan, -1) > -1)\r
+ {\r
+ f = ast_read(chan);\r
+ if (!f ) {\r
+ /* No Frame: Called Party Must Have Dropped */\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");\r
+ ast_log(LOG_DEBUG, "Got hangup\n");\r
+ strcpy(amdStatus , "AMD_HANGUP" );\r
+ strcpy(amdCause , "" );\r
+ break;\r
+ }\r
+ iTotalTime += 20;\r
+ if (iTotalTime >= totalAnalysisTime ) {\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );\r
+ ast_frfree(f);\r
+ strcpy(amdStatus , "AMD_NOTSURE" );\r
+ sprintf(amdCause , "AMD_TOOLONG-%d", iTotalTime );\r
+ break;\r
+ }\r
+ if (f->frametype == AST_FRAME_VOICE ) {\r
+ dspsilence = 0;\r
+ ast_dsp_silence(silenceDetector, f, &dspsilence);\r
+ if (dspsilence ) {\r
+ silenceDuration = dspsilence;\r
+ /* ast_verbose(VERBOSE_PREFIX_3 "AMD: %d SILENCE: silenceDuration:%d afterGreetingSilence:%d inGreeting:%d\n", currentState, silenceDuration, afterGreetingSilence, inGreeting ); */\r
+ if (silenceDuration >= betweenWordsSilence ) {\r
+ if (currentState != STATE_IN_SILENCE ) {\r
+ previousState = currentState;\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");\r
+ }\r
+ currentState = STATE_IN_SILENCE;\r
+ consecutiveVoiceDuration = 0;\r
+ }\r
+ if (inInitialSilence == 1 && silenceDuration >= initialSilence ) {\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",\r
+ silenceDuration, initialSilence );\r
+ ast_frfree(f);\r
+ strcpy(amdStatus , "AMD_MACHINE" );\r
+ sprintf(amdCause , "AMD_INITIALSILENCE-%d-%d", silenceDuration, initialSilence );\r
+ break;\r
+ }\r
+\r
+ if (silenceDuration >= afterGreetingSilence && inGreeting == 1 ) {\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",\r
+ silenceDuration, afterGreetingSilence );\r
+ ast_frfree(f);\r
+ strcpy(amdStatus , "AMD_PERSON" );\r
+ sprintf(amdCause , "AMD_HUMAN-%d-%d", silenceDuration, afterGreetingSilence );\r
+ break;\r
+ }\r
+ } else {\r
+ consecutiveVoiceDuration += 20;\r
+ voiceDuration += 20;\r
+ /* ast_verbose(VERBOSE_PREFIX_3 "AMD: %d VOICE: ConsecutiveVoice:%d voiceDuration:%d inGreeting:%d\n", currentState, consecutiveVoiceDuration, voiceDuration, inGreeting ); */\r
+\r
+ /* If I have enough consecutive voice to say that I am in a Word, I can only increment the\r
+ number of words if my previous state was Silence, which means that I moved into a word. */\r
+ if (consecutiveVoiceDuration >= minimumWordLength ) {\r
+ if (currentState == STATE_IN_SILENCE ) {\r
+ iWordsCount++;\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount );\r
+ previousState = currentState;\r
+ currentState = STATE_IN_WORD;\r
+ }\r
+ }\r
+\r
+ if (iWordsCount >= maximumNumberOfWords ) {\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount );\r
+ ast_frfree(f);\r
+ strcpy(amdStatus , "AMD_MACHINE" );\r
+ sprintf(amdCause , "AMD_MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords );\r
+ break;\r
+ }\r
+\r
+ if (inGreeting == 1 && voiceDuration >= greeting ) {\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n",\r
+ voiceDuration, greeting );\r
+ ast_frfree(f);\r
+ strcpy(amdStatus , "AMD_MACHINE" );\r
+ sprintf(amdCause , "AMD_LONGGREETING-%d-%d", voiceDuration, greeting );\r
+ break;\r
+ }\r
+ if (voiceDuration >= minimumWordLength ) {\r
+ silenceDuration = 0;\r
+ inInitialSilence = 0;\r
+ inGreeting = 1;\r
+ }\r
+ }\r
+ }\r
+ ast_frfree(f);\r
+ }\r
+\r
+ pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus );\r
+ pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause );\r
+\r
+ /* If We Started With A Valid Read Format, Return To It... */\r
+ if (readFormat) {\r
+ res = ast_set_read_format(chan, readFormat );\r
+ if (res)\r
+ ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);\r
+ }\r
+\r
+ /* Free The Silence Detector DSP */\r
+ ast_dsp_free(silenceDetector );\r
+\r
+ return;\r
+}\r
+\r
+\r
+static int amd_exec(struct ast_channel *chan, void *data)\r
+{\r
+ struct localuser *u;\r
+\r
+ LOCAL_USER_ADD(u);\r
+ isAnsweringMachine(chan, data);\r
+ LOCAL_USER_REMOVE(u);\r
+\r
+ return 0;\r
+}\r
+\r
+static void load_config(void)\r
+{\r
+ struct ast_config *cfg;\r
+ char *cat;\r
+ struct ast_variable *var;\r
+\r
+ cfg = ast_config_load("amd.conf");\r
+\r
+ if (!cfg) {\r
+ ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");\r
+ return;\r
+ }\r
+\r
+ cat = ast_category_browse(cfg, NULL);\r
+\r
+ while (cat) {\r
+ if (!strcasecmp(cat, "amd") ) {\r
+ var = ast_variable_browse(cfg, cat);\r
+ while (var) {\r
+ if (!strcasecmp(var->name, "initial_silence")) {\r
+ dfltInitialSilence = atoi(var->value);\r
+ } else if (!strcasecmp(var->name, "greeting")) {\r
+ dfltGreeting = atoi(var->value);\r
+ } else if (!strcasecmp(var->name, "after_greeting_silence")) {\r
+ dfltAfterGreetingSilence = atoi(var->value);\r
+ } else if (!strcasecmp(var->name, "silence_threshold")) {\r
+ dfltSilenceThreshold = atoi(var->value);\r
+ } else if (!strcasecmp(var->name, "total_analysis_time")) {\r
+ dfltTotalAnalysisTime = atoi(var->value);\r
+ } else if (!strcasecmp(var->name, "min_word_length")) {\r
+ dfltMinimumWordLength = atoi(var->value);\r
+ } else if (!strcasecmp(var->name, "between_words_silence")) {\r
+ dfltBetweenWordsSilence = atoi(var->value);\r
+ } else if (!strcasecmp(var->name, "maximum_number_of_words")) {\r
+ dfltMaximumNumberOfWords = atoi(var->value);\r
+ } else {\r
+ ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",\r
+ app, cat, var->name, var->lineno);\r
+ }\r
+ var = var->next;\r
+ }\r
+ }\r
+ cat = ast_category_browse(cfg, cat);\r
+ }\r
+ ast_config_destroy(cfg);\r
+\r
+ ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "\r
+ "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",\r
+ dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,\r
+ dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );\r
+\r
+ return;\r
+}\r
+\r
+int unload_module(void)\r
+{\r
+ STANDARD_HANGUP_LOCALUSERS;\r
+ return ast_unregister_application(app);\r
+}\r
+\r
+int load_module(void)\r
+{\r
+ load_config();\r
+ return ast_register_application(app, amd_exec, synopsis, descrip);\r
+}\r
+\r
+int reload(void)\r
+{\r
+ load_config();\r
+ return 0;\r
+}\r
+\r
+char *description(void)\r
+{\r
+ return tdesc;\r
+}\r
+\r
+int usecount(void)\r
+{\r
+ int res;\r
+ STANDARD_USECOUNT(res);\r
+ return res;\r
+}\r
+\r
+char *key()\r
+{\r
+ return ASTERISK_GPL_KEY;\r
+}\r
+\r