Thu Apr 28 2011 17:13:27

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: 277188 $")
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 /*** DOCUMENTATION
00043    <application name="AMD" language="en_US">
00044       <synopsis>
00045          Attempt to detect answering machines.
00046       </synopsis>
00047       <syntax>
00048          <parameter name="initialSilence" required="false">
00049             <para>Is maximum initial silence duration before greeting.</para>
00050             <para>If this is exceeded set as MACHINE</para>
00051          </parameter>
00052          <parameter name="greeting" required="false">
00053             <para>is the maximum length of a greeting.</para>
00054             <para>If this is exceeded set as MACHINE</para>
00055          </parameter>
00056          <parameter name="afterGreetingSilence" required="false">
00057             <para>Is the silence after detecting a greeting.</para>
00058             <para>If this is exceeded set as HUMAN</para>
00059          </parameter>
00060          <parameter name="totalAnalysis Time" required="false">
00061             <para>Is the maximum time allowed for the algorithm</para>
00062             <para>to decide HUMAN or MACHINE</para>
00063          </parameter>
00064          <parameter name="miniumWordLength" required="false">
00065             <para>Is the minimum duration of Voice considered to be a word</para>
00066          </parameter>
00067          <parameter name="betweenWordSilence" required="false">
00068             <para>Is the minimum duration of silence after a word to
00069             consider the audio that follows to be a new word</para>
00070          </parameter>
00071          <parameter name="maximumNumberOfWords" required="false">
00072             <para>Is the maximum number of words in a greeting</para>
00073             <para>If this is exceeded set as MACHINE</para>
00074          </parameter>
00075          <parameter name="silenceThreshold" required="false">
00076             <para>How long do we consider silence</para>
00077          </parameter>
00078          <parameter name="maximumWordLength" required="false">
00079             <para>Is the maximum duration of a word to accept.</para>
00080             <para>If exceeded set as MACHINE</para>
00081          </parameter>
00082       </syntax>
00083       <description>
00084          <para>This application attempts to detect answering machines at the beginning
00085          of outbound calls. Simply call this application after the call
00086          has been answered (outbound only, of course).</para>
00087          <para>When loaded, AMD reads amd.conf and uses the parameters specified as
00088          default values. Those default values get overwritten when the calling AMD
00089          with parameters.</para>
00090          <para>This application sets the following channel variables:</para>
00091          <variablelist>
00092             <variable name="AMDSTATUS">
00093                <para>This is the status of the answering machine detection</para>
00094                <value name="MACHINE" />
00095                <value name="HUMAN" />
00096                <value name="NOTSURE" />
00097                <value name="HANGUP" />
00098             </variable>
00099             <variable name="AMDCAUSE">
00100                <para>Indicates the cause that led to the conclusion</para>
00101                <value name="TOOLONG">
00102                   Total Time.
00103                </value>
00104                <value name="INITIALSILENCE">
00105                   Silence Duration - Initial Silence.
00106                </value>
00107                <value name="HUMAN">
00108                   Silence Duration - afterGreetingSilence.
00109                </value>
00110                <value name="LONGGREETING">
00111                   Voice Duration - Greeting.
00112                </value>
00113                <value name="MAXWORDLENGTH">
00114                   Word Count - maximum number of words.
00115                </value> 
00116             </variable>
00117          </variablelist>
00118       </description>
00119       <see-also>
00120          <ref type="application">WaitForSilence</ref>
00121          <ref type="application">WaitForNoise</ref>
00122       </see-also>
00123    </application>
00124 
00125  ***/
00126 
00127 static char *app = "AMD";
00128 
00129 #define STATE_IN_WORD       1
00130 #define STATE_IN_SILENCE    2
00131 
00132 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
00133 static int dfltInitialSilence       = 2500;
00134 static int dfltGreeting             = 1500;
00135 static int dfltAfterGreetingSilence = 800;
00136 static int dfltTotalAnalysisTime    = 5000;
00137 static int dfltMinimumWordLength    = 100;
00138 static int dfltBetweenWordsSilence  = 50;
00139 static int dfltMaximumNumberOfWords = 3;
00140 static int dfltSilenceThreshold     = 256;
00141 static int dfltMaximumWordLength    = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */
00142 
00143 /* Set to the lowest ms value provided in amd.conf or application parameters */
00144 static int dfltMaxWaitTimeForFrame  = 50;
00145 
00146 static void isAnsweringMachine(struct ast_channel *chan, void *data)
00147 {
00148    int res = 0;
00149    struct ast_frame *f = NULL;
00150    struct ast_dsp *silenceDetector = NULL;
00151    int dspsilence = 0, readFormat, framelength = 0;
00152    int inInitialSilence = 1;
00153    int inGreeting = 0;
00154    int voiceDuration = 0;
00155    int silenceDuration = 0;
00156    int iTotalTime = 0;
00157    int iWordsCount = 0;
00158    int currentState = STATE_IN_WORD;
00159    int previousState = STATE_IN_SILENCE;
00160    int consecutiveVoiceDuration = 0;
00161    char amdCause[256] = "", amdStatus[256] = "";
00162    char *parse = ast_strdupa(data);
00163 
00164    /* Lets set the initial values of the variables that will control the algorithm.
00165       The initial values are the default ones. If they are passed as arguments
00166       when invoking the application, then the default values will be overwritten
00167       by the ones passed as parameters. */
00168    int initialSilence       = dfltInitialSilence;
00169    int greeting             = dfltGreeting;
00170    int afterGreetingSilence = dfltAfterGreetingSilence;
00171    int totalAnalysisTime    = dfltTotalAnalysisTime;
00172    int minimumWordLength    = dfltMinimumWordLength;
00173    int betweenWordsSilence  = dfltBetweenWordsSilence;
00174    int maximumNumberOfWords = dfltMaximumNumberOfWords;
00175    int silenceThreshold     = dfltSilenceThreshold;
00176    int maximumWordLength    = dfltMaximumWordLength;
00177    int maxWaitTimeForFrame  = dfltMaxWaitTimeForFrame;
00178 
00179    AST_DECLARE_APP_ARGS(args,
00180       AST_APP_ARG(argInitialSilence);
00181       AST_APP_ARG(argGreeting);
00182       AST_APP_ARG(argAfterGreetingSilence);
00183       AST_APP_ARG(argTotalAnalysisTime);
00184       AST_APP_ARG(argMinimumWordLength);
00185       AST_APP_ARG(argBetweenWordsSilence);
00186       AST_APP_ARG(argMaximumNumberOfWords);
00187       AST_APP_ARG(argSilenceThreshold);
00188       AST_APP_ARG(argMaximumWordLength);
00189    );
00190 
00191    ast_verb(3, "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
00192 
00193    /* Lets parse the arguments. */
00194    if (!ast_strlen_zero(parse)) {
00195       /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
00196       AST_STANDARD_APP_ARGS(args, parse);
00197       if (!ast_strlen_zero(args.argInitialSilence))
00198          initialSilence = atoi(args.argInitialSilence);
00199       if (!ast_strlen_zero(args.argGreeting))
00200          greeting = atoi(args.argGreeting);
00201       if (!ast_strlen_zero(args.argAfterGreetingSilence))
00202          afterGreetingSilence = atoi(args.argAfterGreetingSilence);
00203       if (!ast_strlen_zero(args.argTotalAnalysisTime))
00204          totalAnalysisTime = atoi(args.argTotalAnalysisTime);
00205       if (!ast_strlen_zero(args.argMinimumWordLength))
00206          minimumWordLength = atoi(args.argMinimumWordLength);
00207       if (!ast_strlen_zero(args.argBetweenWordsSilence))
00208          betweenWordsSilence = atoi(args.argBetweenWordsSilence);
00209       if (!ast_strlen_zero(args.argMaximumNumberOfWords))
00210          maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
00211       if (!ast_strlen_zero(args.argSilenceThreshold))
00212          silenceThreshold = atoi(args.argSilenceThreshold);
00213       if (!ast_strlen_zero(args.argMaximumWordLength))
00214          maximumWordLength = atoi(args.argMaximumWordLength);
00215    } else {
00216       ast_debug(1, "AMD using the default parameters.\n");
00217    }
00218 
00219    /* Find lowest ms value, that will be max wait time for a frame */
00220    if (maxWaitTimeForFrame > initialSilence)
00221       maxWaitTimeForFrame = initialSilence;
00222    if (maxWaitTimeForFrame > greeting)
00223       maxWaitTimeForFrame = greeting;
00224    if (maxWaitTimeForFrame > afterGreetingSilence)
00225       maxWaitTimeForFrame = afterGreetingSilence;
00226    if (maxWaitTimeForFrame > totalAnalysisTime)
00227       maxWaitTimeForFrame = totalAnalysisTime;
00228    if (maxWaitTimeForFrame > minimumWordLength)
00229       maxWaitTimeForFrame = minimumWordLength;
00230    if (maxWaitTimeForFrame > betweenWordsSilence)
00231       maxWaitTimeForFrame = betweenWordsSilence;
00232 
00233    /* Now we're ready to roll! */
00234    ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00235       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
00236             initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
00237             minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);
00238 
00239    /* Set read format to signed linear so we get signed linear frames in */
00240    readFormat = chan->readformat;
00241    if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
00242       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
00243       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00244       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00245       return;
00246    }
00247 
00248    /* Create a new DSP that will detect the silence */
00249    if (!(silenceDetector = ast_dsp_new())) {
00250       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
00251       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00252       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00253       return;
00254    }
00255 
00256    /* Set silence threshold to specified value */
00257    ast_dsp_set_threshold(silenceDetector, silenceThreshold);
00258 
00259    /* Now we go into a loop waiting for frames from the channel */
00260    while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
00261 
00262       /* If we fail to read in a frame, that means they hung up */
00263       if (!(f = ast_read(chan))) {
00264          ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name);
00265          ast_debug(1, "Got hangup\n");
00266          strcpy(amdStatus, "HANGUP");
00267          res = 1;
00268          break;
00269       }
00270 
00271       if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
00272          /* If the total time exceeds the analysis time then give up as we are not too sure */
00273          if (f->frametype == AST_FRAME_VOICE) {
00274             framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
00275          } else {
00276             framelength = 2 * maxWaitTimeForFrame;
00277          }
00278 
00279          iTotalTime += framelength;
00280          if (iTotalTime >= totalAnalysisTime) {
00281             ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name );
00282             ast_frfree(f);
00283             strcpy(amdStatus , "NOTSURE");
00284             sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00285             break;
00286          }
00287 
00288          /* Feed the frame of audio into the silence detector and see if we get a result */
00289          if (f->frametype != AST_FRAME_VOICE)
00290             dspsilence += 2 * maxWaitTimeForFrame;
00291          else {
00292             dspsilence = 0;
00293             ast_dsp_silence(silenceDetector, f, &dspsilence);
00294          }
00295 
00296          if (dspsilence > 0) {
00297             silenceDuration = dspsilence;
00298             
00299             if (silenceDuration >= betweenWordsSilence) {
00300                if (currentState != STATE_IN_SILENCE ) {
00301                   previousState = currentState;
00302                   ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name);
00303                }
00304                /* Find words less than word duration */
00305                if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
00306                   ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration);
00307                }
00308                currentState  = STATE_IN_SILENCE;
00309                consecutiveVoiceDuration = 0;
00310             }
00311 
00312             if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
00313                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
00314                   chan->name, silenceDuration, initialSilence);
00315                ast_frfree(f);
00316                strcpy(amdStatus , "MACHINE");
00317                sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
00318                res = 1;
00319                break;
00320             }
00321             
00322             if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
00323                ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
00324                   chan->name, silenceDuration, afterGreetingSilence);
00325                ast_frfree(f);
00326                strcpy(amdStatus , "HUMAN");
00327                sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
00328                res = 1;
00329                break;
00330             }
00331             
00332          } else {
00333             consecutiveVoiceDuration += framelength;
00334             voiceDuration += framelength;
00335 
00336             /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
00337                number of words if my previous state was Silence, which means that I moved into a word. */
00338             if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
00339                iWordsCount++;
00340                ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount);
00341                previousState = currentState;
00342                currentState = STATE_IN_WORD;
00343             }
00344             if (consecutiveVoiceDuration >= maximumWordLength){
00345                ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration);
00346                ast_frfree(f);
00347                strcpy(amdStatus , "MACHINE");
00348                sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
00349                break;
00350             }
00351             if (iWordsCount >= maximumNumberOfWords) {
00352                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount);
00353                ast_frfree(f);
00354                strcpy(amdStatus , "MACHINE");
00355                sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
00356                res = 1;
00357                break;
00358             }
00359 
00360             if (inGreeting == 1 && voiceDuration >= greeting) {
00361                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting);
00362                ast_frfree(f);
00363                strcpy(amdStatus , "MACHINE");
00364                sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
00365                res = 1;
00366                break;
00367             }
00368 
00369             if (voiceDuration >= minimumWordLength ) {
00370                if (silenceDuration > 0)
00371                   ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration);
00372                silenceDuration = 0;
00373             }
00374             if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) {
00375                /* Only go in here once to change the greeting flag when we detect the 1st word */
00376                if (silenceDuration > 0)
00377                   ast_verb(3, "AMD: Channel [%s]. Before Greeting Time:  silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration);
00378                inInitialSilence = 0;
00379                inGreeting = 1;
00380             }
00381             
00382          }
00383       }
00384       ast_frfree(f);
00385    }
00386    
00387    if (!res) {
00388       /* It took too long to get a frame back. Giving up. */
00389       ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name);
00390       strcpy(amdStatus , "NOTSURE");
00391       sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00392    }
00393 
00394    /* Set the status and cause on the channel */
00395    pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
00396    pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
00397 
00398    /* Restore channel read format */
00399    if (readFormat && ast_set_read_format(chan, readFormat))
00400       ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
00401 
00402    /* Free the DSP used to detect silence */
00403    ast_dsp_free(silenceDetector);
00404 
00405    return;
00406 }
00407 
00408 
00409 static int amd_exec(struct ast_channel *chan, void *data)
00410 {
00411    isAnsweringMachine(chan, data);
00412 
00413    return 0;
00414 }
00415 
00416 static int load_config(int reload)
00417 {
00418    struct ast_config *cfg = NULL;
00419    char *cat = NULL;
00420    struct ast_variable *var = NULL;
00421    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00422 
00423    dfltSilenceThreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
00424 
00425    if (!(cfg = ast_config_load("amd.conf", config_flags))) {
00426       ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
00427       return -1;
00428    } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
00429       return 0;
00430    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
00431       ast_log(LOG_ERROR, "Config file amd.conf is in an invalid format.  Aborting.\n");
00432       return -1;
00433    }
00434 
00435    cat = ast_category_browse(cfg, NULL);
00436 
00437    while (cat) {
00438       if (!strcasecmp(cat, "general") ) {
00439          var = ast_variable_browse(cfg, cat);
00440          while (var) {
00441             if (!strcasecmp(var->name, "initial_silence")) {
00442                dfltInitialSilence = atoi(var->value);
00443             } else if (!strcasecmp(var->name, "greeting")) {
00444                dfltGreeting = atoi(var->value);
00445             } else if (!strcasecmp(var->name, "after_greeting_silence")) {
00446                dfltAfterGreetingSilence = atoi(var->value);
00447             } else if (!strcasecmp(var->name, "silence_threshold")) {
00448                dfltSilenceThreshold = atoi(var->value);
00449             } else if (!strcasecmp(var->name, "total_analysis_time")) {
00450                dfltTotalAnalysisTime = atoi(var->value);
00451             } else if (!strcasecmp(var->name, "min_word_length")) {
00452                dfltMinimumWordLength = atoi(var->value);
00453             } else if (!strcasecmp(var->name, "between_words_silence")) {
00454                dfltBetweenWordsSilence = atoi(var->value);
00455             } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
00456                dfltMaximumNumberOfWords = atoi(var->value);
00457             } else if (!strcasecmp(var->name, "maximum_word_length")) {
00458                dfltMaximumWordLength = atoi(var->value);
00459 
00460             } else {
00461                ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
00462                   app, cat, var->name, var->lineno);
00463             }
00464             var = var->next;
00465          }
00466       }
00467       cat = ast_category_browse(cfg, cat);
00468    }
00469 
00470    ast_config_destroy(cfg);
00471 
00472    ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00473       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
00474       dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
00475       dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);
00476 
00477    return 0;
00478 }
00479 
00480 static int unload_module(void)
00481 {
00482    return ast_unregister_application(app);
00483 }
00484 
00485 static int load_module(void)
00486 {
00487    if (load_config(0))
00488       return AST_MODULE_LOAD_DECLINE;
00489    if (ast_register_application_xml(app, amd_exec))
00490       return AST_MODULE_LOAD_FAILURE;
00491    return AST_MODULE_LOAD_SUCCESS;
00492 }
00493 
00494 static int reload(void)
00495 {
00496    if (load_config(1))
00497       return AST_MODULE_LOAD_DECLINE;
00498    return AST_MODULE_LOAD_SUCCESS;
00499 }
00500 
00501 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
00502       .load = load_module,
00503       .unload = unload_module,
00504       .reload = reload,
00505 );