diff options
author | kpfleming <kpfleming@f38db490-d61c-443f-a65b-d21fe96a405b> | 2006-02-14 18:44:18 +0000 |
---|---|---|
committer | kpfleming <kpfleming@f38db490-d61c-443f-a65b-d21fe96a405b> | 2006-02-14 18:44:18 +0000 |
commit | 9c8ab2d16506cdfd08b8c11d91ec0c5635f4577a (patch) | |
tree | ee3cfa6602bff487865e39f8990e2c40023f5453 | |
parent | e065c147f7494c7e99dfe828ca0c6cd773e4676e (diff) |
set properties for new files (i need to get this documented)
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@9960 f38db490-d61c-443f-a65b-d21fe96a405b
-rw-r--r-- | apps/app_amd.c | 824 | ||||
-rw-r--r-- | configs/amd.conf.sample | 48 |
2 files changed, 436 insertions, 436 deletions
diff --git a/apps/app_amd.c b/apps/app_amd.c index 8fc20c429..6ae81bd75 100644 --- a/apps/app_amd.c +++ b/apps/app_amd.c @@ -1,412 +1,412 @@ -/*
- * 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;
-}
-
+/* + * 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; +} + diff --git a/configs/amd.conf.sample b/configs/amd.conf.sample index ef9a88357..5e5072f69 100644 --- a/configs/amd.conf.sample +++ b/configs/amd.conf.sample @@ -1,24 +1,24 @@ -; 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
-
+; 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 + |