Wed Oct 28 11:45:24 2009

Asterisk developer's documentation


app_amd.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2003 - 2006, Aheeva Technology.
00005  *
00006  * Claude Klimos (claude.klimos@aheeva.com)
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  *
00018  * A license has been granted to Digium (via disclaimer) for the use of
00019  * this code.
00020  */
00021 
00022 /*! \file
00023  *
00024  * \brief Answering machine detection
00025  *
00026  * \author Claude Klimos (claude.klimos@aheeva.com)
00027  */
00028 
00029 
00030 #include "asterisk.h"
00031 
00032 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 103249 $")
00033 
00034 #include "asterisk/module.h"
00035 #include "asterisk/lock.h"
00036 #include "asterisk/channel.h"
00037 #include "asterisk/dsp.h"
00038 #include "asterisk/pbx.h"
00039 #include "asterisk/config.h"
00040 #include "asterisk/app.h"
00041 
00042 
00043 static char *app = "AMD";
00044 static char *synopsis = "Attempts to detect answering machines";
00045 static char *descrip =
00046 "  AMD([initialSilence],[greeting],[afterGreetingSilence],[totalAnalysisTime]\n"
00047 "      ,[minimumWordLength],[betweenWordsSilence],[maximumNumberOfWords]\n"
00048 "      ,[silenceThreshold],[|maximumWordLength])\n"
00049 "  This application attempts to detect answering machines at the beginning\n"
00050 "  of outbound calls.  Simply call this application after the call\n"
00051 "  has been answered (outbound only, of course).\n"
00052 "  When loaded, AMD reads amd.conf and uses the parameters specified as\n"
00053 "  default values. Those default values get overwritten when calling AMD\n"
00054 "  with parameters.\n"
00055 "- 'initialSilence' is the maximum silence duration before the greeting. If\n"
00056 "   exceeded then MACHINE.\n"
00057 "- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n"
00058 "- 'afterGreetingSilence' is the silence after detecting a greeting.\n"
00059 "   If exceeded then HUMAN.\n"
00060 "- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n"
00061 "   on a HUMAN or MACHINE.\n"
00062 "- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n"
00063 "- 'betweenWordsSilence' is the minimum duration of silence after a word to \n"
00064 "   consider the audio that follows as a new word.\n"
00065 "- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n"
00066 "   If exceeded then MACHINE.\n"
00067 "- 'silenceThreshold' is the silence threshold.\n"
00068 "- 'maximumWordLength' is the maximum duration of a word to accept. If exceeded then MACHINE\n"
00069 "This application sets the following channel variables upon completion:\n"
00070 "    AMDSTATUS - This is the status of the answering machine detection.\n"
00071 "                Possible values are:\n"
00072 "                MACHINE | HUMAN | NOTSURE | HANGUP\n"
00073 "    AMDCAUSE - Indicates the cause that led to the conclusion.\n"
00074 "               Possible values are:\n"
00075 "               TOOLONG-<%d total_time>\n"
00076 "               INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
00077 "               HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
00078 "               MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
00079 "               LONGGREETING-<%d voiceDuration>-<%d greeting>\n"
00080 "               MAXWORDLENGTH-<%d consecutiveVoiceDuration>\n";
00081 
00082 #define STATE_IN_WORD       1
00083 #define STATE_IN_SILENCE    2
00084 
00085 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
00086 static int dfltInitialSilence       = 2500;
00087 static int dfltGreeting             = 1500;
00088 static int dfltAfterGreetingSilence = 800;
00089 static int dfltTotalAnalysisTime    = 5000;
00090 static int dfltMinimumWordLength    = 100;
00091 static int dfltBetweenWordsSilence  = 50;
00092 static int dfltMaximumNumberOfWords = 3;
00093 static int dfltSilenceThreshold     = 256;
00094 static int dfltMaximumWordLength    = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */
00095 
00096 /* Set to the lowest ms value provided in amd.conf or application parameters */
00097 static int dfltMaxWaitTimeForFrame  = 50;
00098 
00099 static void isAnsweringMachine(struct ast_channel *chan, void *data)
00100 {
00101    int res = 0;
00102    struct ast_frame *f = NULL;
00103    struct ast_dsp *silenceDetector = NULL;
00104    int dspsilence = 0, readFormat, framelength = 0;
00105    int inInitialSilence = 1;
00106    int inGreeting = 0;
00107    int voiceDuration = 0;
00108    int silenceDuration = 0;
00109    int iTotalTime = 0;
00110    int iWordsCount = 0;
00111    int currentState = STATE_IN_WORD;
00112    int previousState = STATE_IN_SILENCE;
00113    int consecutiveVoiceDuration = 0;
00114    char amdCause[256] = "", amdStatus[256] = "";
00115    char *parse = ast_strdupa(data);
00116 
00117    /* Lets set the initial values of the variables that will control the algorithm.
00118       The initial values are the default ones. If they are passed as arguments
00119       when invoking the application, then the default values will be overwritten
00120       by the ones passed as parameters. */
00121    int initialSilence       = dfltInitialSilence;
00122    int greeting             = dfltGreeting;
00123    int afterGreetingSilence = dfltAfterGreetingSilence;
00124    int totalAnalysisTime    = dfltTotalAnalysisTime;
00125    int minimumWordLength    = dfltMinimumWordLength;
00126    int betweenWordsSilence  = dfltBetweenWordsSilence;
00127    int maximumNumberOfWords = dfltMaximumNumberOfWords;
00128    int silenceThreshold     = dfltSilenceThreshold;
00129    int maximumWordLength    = dfltMaximumWordLength;
00130    int maxWaitTimeForFrame  = dfltMaxWaitTimeForFrame;
00131 
00132    AST_DECLARE_APP_ARGS(args,
00133       AST_APP_ARG(argInitialSilence);
00134       AST_APP_ARG(argGreeting);
00135       AST_APP_ARG(argAfterGreetingSilence);
00136       AST_APP_ARG(argTotalAnalysisTime);
00137       AST_APP_ARG(argMinimumWordLength);
00138       AST_APP_ARG(argBetweenWordsSilence);
00139       AST_APP_ARG(argMaximumNumberOfWords);
00140       AST_APP_ARG(argSilenceThreshold);
00141       AST_APP_ARG(argMaximumWordLength);
00142    );
00143 
00144    ast_verb(3, "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
00145 
00146    /* Lets parse the arguments. */
00147    if (!ast_strlen_zero(parse)) {
00148       /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
00149       AST_STANDARD_APP_ARGS(args, parse);
00150       if (!ast_strlen_zero(args.argInitialSilence))
00151          initialSilence = atoi(args.argInitialSilence);
00152       if (!ast_strlen_zero(args.argGreeting))
00153          greeting = atoi(args.argGreeting);
00154       if (!ast_strlen_zero(args.argAfterGreetingSilence))
00155          afterGreetingSilence = atoi(args.argAfterGreetingSilence);
00156       if (!ast_strlen_zero(args.argTotalAnalysisTime))
00157          totalAnalysisTime = atoi(args.argTotalAnalysisTime);
00158       if (!ast_strlen_zero(args.argMinimumWordLength))
00159          minimumWordLength = atoi(args.argMinimumWordLength);
00160       if (!ast_strlen_zero(args.argBetweenWordsSilence))
00161          betweenWordsSilence = atoi(args.argBetweenWordsSilence);
00162       if (!ast_strlen_zero(args.argMaximumNumberOfWords))
00163          maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
00164       if (!ast_strlen_zero(args.argSilenceThreshold))
00165          silenceThreshold = atoi(args.argSilenceThreshold);
00166       if (!ast_strlen_zero(args.argMaximumWordLength))
00167          maximumWordLength = atoi(args.argMaximumWordLength);
00168    } else {
00169       ast_debug(1, "AMD using the default parameters.\n");
00170    }
00171 
00172    /* Find lowest ms value, that will be max wait time for a frame */
00173    if (maxWaitTimeForFrame > initialSilence)
00174       maxWaitTimeForFrame = initialSilence;
00175    if (maxWaitTimeForFrame > greeting)
00176       maxWaitTimeForFrame = greeting;
00177    if (maxWaitTimeForFrame > afterGreetingSilence)
00178       maxWaitTimeForFrame = afterGreetingSilence;
00179    if (maxWaitTimeForFrame > totalAnalysisTime)
00180       maxWaitTimeForFrame = totalAnalysisTime;
00181    if (maxWaitTimeForFrame > minimumWordLength)
00182       maxWaitTimeForFrame = minimumWordLength;
00183    if (maxWaitTimeForFrame > betweenWordsSilence)
00184       maxWaitTimeForFrame = betweenWordsSilence;
00185 
00186    /* Now we're ready to roll! */
00187    ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00188       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
00189             initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
00190             minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);
00191 
00192    /* Set read format to signed linear so we get signed linear frames in */
00193    readFormat = chan->readformat;
00194    if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
00195       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
00196       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00197       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00198       return;
00199    }
00200 
00201    /* Create a new DSP that will detect the silence */
00202    if (!(silenceDetector = ast_dsp_new())) {
00203       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
00204       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00205       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00206       return;
00207    }
00208 
00209    /* Set silence threshold to specified value */
00210    ast_dsp_set_threshold(silenceDetector, silenceThreshold);
00211 
00212    /* Now we go into a loop waiting for frames from the channel */
00213    while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
00214 
00215       /* If we fail to read in a frame, that means they hung up */
00216       if (!(f = ast_read(chan))) {
00217          ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name);
00218          ast_debug(1, "Got hangup\n");
00219          strcpy(amdStatus, "HANGUP");
00220          break;
00221       }
00222 
00223       if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
00224          /* If the total time exceeds the analysis time then give up as we are not too sure */
00225          if (f->frametype == AST_FRAME_VOICE)
00226             framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
00227          else
00228             framelength += 2 * maxWaitTimeForFrame;
00229 
00230          iTotalTime += framelength;
00231          if (iTotalTime >= totalAnalysisTime) {
00232             ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name );
00233             ast_frfree(f);
00234             strcpy(amdStatus , "NOTSURE");
00235             sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00236             break;
00237          }
00238 
00239          /* Feed the frame of audio into the silence detector and see if we get a result */
00240          if (f->frametype != AST_FRAME_VOICE)
00241             dspsilence += 2 * maxWaitTimeForFrame;
00242          else {
00243             dspsilence = 0;
00244             ast_dsp_silence(silenceDetector, f, &dspsilence);
00245          }
00246 
00247          if (dspsilence > 0) {
00248             silenceDuration = dspsilence;
00249             
00250             if (silenceDuration >= betweenWordsSilence) {
00251                if (currentState != STATE_IN_SILENCE ) {
00252                   previousState = currentState;
00253                   ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name);
00254                }
00255                /* Find words less than word duration */
00256                if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
00257                   ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration);
00258                }
00259                currentState  = STATE_IN_SILENCE;
00260                consecutiveVoiceDuration = 0;
00261             }
00262 
00263             if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
00264                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
00265                   chan->name, silenceDuration, initialSilence);
00266                ast_frfree(f);
00267                strcpy(amdStatus , "MACHINE");
00268                sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
00269                res = 1;
00270                break;
00271             }
00272             
00273             if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
00274                ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
00275                   chan->name, silenceDuration, afterGreetingSilence);
00276                ast_frfree(f);
00277                strcpy(amdStatus , "HUMAN");
00278                sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
00279                res = 1;
00280                break;
00281             }
00282             
00283          } else {
00284             consecutiveVoiceDuration += framelength;
00285             voiceDuration += framelength;
00286 
00287             /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
00288                number of words if my previous state was Silence, which means that I moved into a word. */
00289             if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
00290                iWordsCount++;
00291                ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount);
00292                previousState = currentState;
00293                currentState = STATE_IN_WORD;
00294             }
00295             if (consecutiveVoiceDuration >= maximumWordLength){
00296                ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration);
00297                ast_frfree(f);
00298                strcpy(amdStatus , "MACHINE");
00299                sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
00300                break;
00301             }
00302             if (iWordsCount >= maximumNumberOfWords) {
00303                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount);
00304                ast_frfree(f);
00305                strcpy(amdStatus , "MACHINE");
00306                sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
00307                res = 1;
00308                break;
00309             }
00310 
00311             if (inGreeting == 1 && voiceDuration >= greeting) {
00312                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting);
00313                ast_frfree(f);
00314                strcpy(amdStatus , "MACHINE");
00315                sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
00316                res = 1;
00317                break;
00318             }
00319 
00320             if (voiceDuration >= minimumWordLength ) {
00321                if (silenceDuration > 0)
00322                   ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration);
00323                silenceDuration = 0;
00324             }
00325             if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) {
00326                /* Only go in here once to change the greeting flag when we detect the 1st word */
00327                if (silenceDuration > 0)
00328                   ast_verb(3, "AMD: Channel [%s]. Before Greeting Time:  silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration);
00329                inInitialSilence = 0;
00330                inGreeting = 1;
00331             }
00332             
00333          }
00334       }
00335       ast_frfree(f);
00336    }
00337    
00338    if (!res) {
00339       /* It took too long to get a frame back. Giving up. */
00340       ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name);
00341       strcpy(amdStatus , "NOTSURE");
00342       sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00343    }
00344 
00345    /* Set the status and cause on the channel */
00346    pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
00347    pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
00348 
00349    /* Restore channel read format */
00350    if (readFormat && ast_set_read_format(chan, readFormat))
00351       ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
00352 
00353    /* Free the DSP used to detect silence */
00354    ast_dsp_free(silenceDetector);
00355 
00356    return;
00357 }
00358 
00359 
00360 static int amd_exec(struct ast_channel *chan, void *data)
00361 {
00362    isAnsweringMachine(chan, data);
00363 
00364    return 0;
00365 }
00366 
00367 static int load_config(int reload)
00368 {
00369    struct ast_config *cfg = NULL;
00370    char *cat = NULL;
00371    struct ast_variable *var = NULL;
00372    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00373 
00374    if (!(cfg = ast_config_load("amd.conf", config_flags))) {
00375       ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
00376       return -1;
00377    } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
00378       return 0;
00379 
00380    cat = ast_category_browse(cfg, NULL);
00381 
00382    while (cat) {
00383       if (!strcasecmp(cat, "general") ) {
00384          var = ast_variable_browse(cfg, cat);
00385          while (var) {
00386             if (!strcasecmp(var->name, "initial_silence")) {
00387                dfltInitialSilence = atoi(var->value);
00388             } else if (!strcasecmp(var->name, "greeting")) {
00389                dfltGreeting = atoi(var->value);
00390             } else if (!strcasecmp(var->name, "after_greeting_silence")) {
00391                dfltAfterGreetingSilence = atoi(var->value);
00392             } else if (!strcasecmp(var->name, "silence_threshold")) {
00393                dfltSilenceThreshold = atoi(var->value);
00394             } else if (!strcasecmp(var->name, "total_analysis_time")) {
00395                dfltTotalAnalysisTime = atoi(var->value);
00396             } else if (!strcasecmp(var->name, "min_word_length")) {
00397                dfltMinimumWordLength = atoi(var->value);
00398             } else if (!strcasecmp(var->name, "between_words_silence")) {
00399                dfltBetweenWordsSilence = atoi(var->value);
00400             } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
00401                dfltMaximumNumberOfWords = atoi(var->value);
00402             } else if (!strcasecmp(var->name, "maximum_word_length")) {
00403                dfltMaximumWordLength = atoi(var->value);
00404 
00405             } else {
00406                ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
00407                   app, cat, var->name, var->lineno);
00408             }
00409             var = var->next;
00410          }
00411       }
00412       cat = ast_category_browse(cfg, cat);
00413    }
00414 
00415    ast_config_destroy(cfg);
00416 
00417    ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00418       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
00419       dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
00420       dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);
00421 
00422    return 0;
00423 }
00424 
00425 static int unload_module(void)
00426 {
00427    return ast_unregister_application(app);
00428 }
00429 
00430 static int load_module(void)
00431 {
00432    if (load_config(0))
00433       return AST_MODULE_LOAD_DECLINE;
00434    if (ast_register_application(app, amd_exec, synopsis, descrip))
00435       return AST_MODULE_LOAD_FAILURE;
00436    return AST_MODULE_LOAD_SUCCESS;
00437 }
00438 
00439 static int reload(void)
00440 {
00441    if (load_config(1))
00442       return AST_MODULE_LOAD_DECLINE;
00443    return AST_MODULE_LOAD_SUCCESS;
00444 }
00445 
00446 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
00447       .load = load_module,
00448       .unload = unload_module,
00449       .reload = reload,
00450 );

Generated on Wed Oct 28 11:45:24 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6