set properties for new files (i need to get this documented)
authorKevin P. Fleming <kpfleming@digium.com>
Tue, 14 Feb 2006 18:44:18 +0000 (18:44 +0000)
committerKevin P. Fleming <kpfleming@digium.com>
Tue, 14 Feb 2006 18:44:18 +0000 (18:44 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@9960 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_amd.c
configs/amd.conf.sample

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