Wed Apr 6 11:29:37 2011

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: 277183 $")
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 const 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, const char *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: %s)\n", chan->name,
00192       S_COR(chan->caller.ani.number.valid, chan->caller.ani.number.str, "(N/A)"),
00193       S_COR(chan->redirecting.from.number.valid, chan->redirecting.from.number.str, "(N/A)"),
00194       ast_getformatname(chan->readformat));
00195 
00196    /* Lets parse the arguments. */
00197    if (!ast_strlen_zero(parse)) {
00198       /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
00199       AST_STANDARD_APP_ARGS(args, parse);
00200       if (!ast_strlen_zero(args.argInitialSilence))
00201          initialSilence = atoi(args.argInitialSilence);
00202       if (!ast_strlen_zero(args.argGreeting))
00203          greeting = atoi(args.argGreeting);
00204       if (!ast_strlen_zero(args.argAfterGreetingSilence))
00205          afterGreetingSilence = atoi(args.argAfterGreetingSilence);
00206       if (!ast_strlen_zero(args.argTotalAnalysisTime))
00207          totalAnalysisTime = atoi(args.argTotalAnalysisTime);
00208       if (!ast_strlen_zero(args.argMinimumWordLength))
00209          minimumWordLength = atoi(args.argMinimumWordLength);
00210       if (!ast_strlen_zero(args.argBetweenWordsSilence))
00211          betweenWordsSilence = atoi(args.argBetweenWordsSilence);
00212       if (!ast_strlen_zero(args.argMaximumNumberOfWords))
00213          maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
00214       if (!ast_strlen_zero(args.argSilenceThreshold))
00215          silenceThreshold = atoi(args.argSilenceThreshold);
00216       if (!ast_strlen_zero(args.argMaximumWordLength))
00217          maximumWordLength = atoi(args.argMaximumWordLength);
00218    } else {
00219       ast_debug(1, "AMD using the default parameters.\n");
00220    }
00221 
00222    /* Find lowest ms value, that will be max wait time for a frame */
00223    if (maxWaitTimeForFrame > initialSilence)
00224       maxWaitTimeForFrame = initialSilence;
00225    if (maxWaitTimeForFrame > greeting)
00226       maxWaitTimeForFrame = greeting;
00227    if (maxWaitTimeForFrame > afterGreetingSilence)
00228       maxWaitTimeForFrame = afterGreetingSilence;
00229    if (maxWaitTimeForFrame > totalAnalysisTime)
00230       maxWaitTimeForFrame = totalAnalysisTime;
00231    if (maxWaitTimeForFrame > minimumWordLength)
00232       maxWaitTimeForFrame = minimumWordLength;
00233    if (maxWaitTimeForFrame > betweenWordsSilence)
00234       maxWaitTimeForFrame = betweenWordsSilence;
00235 
00236    /* Now we're ready to roll! */
00237    ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00238       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
00239             initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
00240             minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);
00241 
00242    /* Set read format to signed linear so we get signed linear frames in */
00243    readFormat = chan->readformat;
00244    if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
00245       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
00246       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00247       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00248       return;
00249    }
00250 
00251    /* Create a new DSP that will detect the silence */
00252    if (!(silenceDetector = ast_dsp_new())) {
00253       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
00254       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00255       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00256       return;
00257    }
00258 
00259    /* Set silence threshold to specified value */
00260    ast_dsp_set_threshold(silenceDetector, silenceThreshold);
00261 
00262    /* Now we go into a loop waiting for frames from the channel */
00263    while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
00264 
00265       /* If we fail to read in a frame, that means they hung up */
00266       if (!(f = ast_read(chan))) {
00267          ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name);
00268          ast_debug(1, "Got hangup\n");
00269          strcpy(amdStatus, "HANGUP");
00270          res = 1;
00271          break;
00272       }
00273 
00274       if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
00275          /* If the total time exceeds the analysis time then give up as we are not too sure */
00276          if (f->frametype == AST_FRAME_VOICE) {
00277             framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
00278          } else {
00279             framelength = 2 * maxWaitTimeForFrame;
00280          }
00281 
00282          iTotalTime += framelength;
00283          if (iTotalTime >= totalAnalysisTime) {
00284             ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name );
00285             ast_frfree(f);
00286             strcpy(amdStatus , "NOTSURE");
00287             sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00288             break;
00289          }
00290 
00291          /* Feed the frame of audio into the silence detector and see if we get a result */
00292          if (f->frametype != AST_FRAME_VOICE)
00293             dspsilence += 2 * maxWaitTimeForFrame;
00294          else {
00295             dspsilence = 0;
00296             ast_dsp_silence(silenceDetector, f, &dspsilence);
00297          }
00298 
00299          if (dspsilence > 0) {
00300             silenceDuration = dspsilence;
00301             
00302             if (silenceDuration >= betweenWordsSilence) {
00303                if (currentState != STATE_IN_SILENCE ) {
00304                   previousState = currentState;
00305                   ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name);
00306                }
00307                /* Find words less than word duration */
00308                if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
00309                   ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration);
00310                }
00311                currentState  = STATE_IN_SILENCE;
00312                consecutiveVoiceDuration = 0;
00313             }
00314 
00315             if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
00316                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
00317                   chan->name, silenceDuration, initialSilence);
00318                ast_frfree(f);
00319                strcpy(amdStatus , "MACHINE");
00320                sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
00321                res = 1;
00322                break;
00323             }
00324             
00325             if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
00326                ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
00327                   chan->name, silenceDuration, afterGreetingSilence);
00328                ast_frfree(f);
00329                strcpy(amdStatus , "HUMAN");
00330                sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
00331                res = 1;
00332                break;
00333             }
00334             
00335          } else {
00336             consecutiveVoiceDuration += framelength;
00337             voiceDuration += framelength;
00338 
00339             /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
00340                number of words if my previous state was Silence, which means that I moved into a word. */
00341             if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
00342                iWordsCount++;
00343                ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount);
00344                previousState = currentState;
00345                currentState = STATE_IN_WORD;
00346             }
00347             if (consecutiveVoiceDuration >= maximumWordLength){
00348                ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration);
00349                ast_frfree(f);
00350                strcpy(amdStatus , "MACHINE");
00351                sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
00352                break;
00353             }
00354             if (iWordsCount >= maximumNumberOfWords) {
00355                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount);
00356                ast_frfree(f);
00357                strcpy(amdStatus , "MACHINE");
00358                sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
00359                res = 1;
00360                break;
00361             }
00362 
00363             if (inGreeting == 1 && voiceDuration >= greeting) {
00364                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting);
00365                ast_frfree(f);
00366                strcpy(amdStatus , "MACHINE");
00367                sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
00368                res = 1;
00369                break;
00370             }
00371 
00372             if (voiceDuration >= minimumWordLength ) {
00373                if (silenceDuration > 0)
00374                   ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration);
00375                silenceDuration = 0;
00376             }
00377             if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) {
00378                /* Only go in here once to change the greeting flag when we detect the 1st word */
00379                if (silenceDuration > 0)
00380                   ast_verb(3, "AMD: Channel [%s]. Before Greeting Time:  silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration);
00381                inInitialSilence = 0;
00382                inGreeting = 1;
00383             }
00384             
00385          }
00386       }
00387       ast_frfree(f);
00388    }
00389    
00390    if (!res) {
00391       /* It took too long to get a frame back. Giving up. */
00392       ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name);
00393       strcpy(amdStatus , "NOTSURE");
00394       sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00395    }
00396 
00397    /* Set the status and cause on the channel */
00398    pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
00399    pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
00400 
00401    /* Restore channel read format */
00402    if (readFormat && ast_set_read_format(chan, readFormat))
00403       ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
00404 
00405    /* Free the DSP used to detect silence */
00406    ast_dsp_free(silenceDetector);
00407 
00408    return;
00409 }
00410 
00411 
00412 static int amd_exec(struct ast_channel *chan, const char *data)
00413 {
00414    isAnsweringMachine(chan, data);
00415 
00416    return 0;
00417 }
00418 
00419 static int load_config(int reload)
00420 {
00421    struct ast_config *cfg = NULL;
00422    char *cat = NULL;
00423    struct ast_variable *var = NULL;
00424    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00425 
00426    dfltSilenceThreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
00427 
00428    if (!(cfg = ast_config_load("amd.conf", config_flags))) {
00429       ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
00430       return -1;
00431    } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
00432       return 0;
00433    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
00434       ast_log(LOG_ERROR, "Config file amd.conf is in an invalid format.  Aborting.\n");
00435       return -1;
00436    }
00437 
00438    cat = ast_category_browse(cfg, NULL);
00439 
00440    while (cat) {
00441       if (!strcasecmp(cat, "general") ) {
00442          var = ast_variable_browse(cfg, cat);
00443          while (var) {
00444             if (!strcasecmp(var->name, "initial_silence")) {
00445                dfltInitialSilence = atoi(var->value);
00446             } else if (!strcasecmp(var->name, "greeting")) {
00447                dfltGreeting = atoi(var->value);
00448             } else if (!strcasecmp(var->name, "after_greeting_silence")) {
00449                dfltAfterGreetingSilence = atoi(var->value);
00450             } else if (!strcasecmp(var->name, "silence_threshold")) {
00451                dfltSilenceThreshold = atoi(var->value);
00452             } else if (!strcasecmp(var->name, "total_analysis_time")) {
00453                dfltTotalAnalysisTime = atoi(var->value);
00454             } else if (!strcasecmp(var->name, "min_word_length")) {
00455                dfltMinimumWordLength = atoi(var->value);
00456             } else if (!strcasecmp(var->name, "between_words_silence")) {
00457                dfltBetweenWordsSilence = atoi(var->value);
00458             } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
00459                dfltMaximumNumberOfWords = atoi(var->value);
00460             } else if (!strcasecmp(var->name, "maximum_word_length")) {
00461                dfltMaximumWordLength = atoi(var->value);
00462 
00463             } else {
00464                ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
00465                   app, cat, var->name, var->lineno);
00466             }
00467             var = var->next;
00468          }
00469       }
00470       cat = ast_category_browse(cfg, cat);
00471    }
00472 
00473    ast_config_destroy(cfg);
00474 
00475    ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00476       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
00477       dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
00478       dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);
00479 
00480    return 0;
00481 }
00482 
00483 static int unload_module(void)
00484 {
00485    return ast_unregister_application(app);
00486 }
00487 
00488 static int load_module(void)
00489 {
00490    if (load_config(0))
00491       return AST_MODULE_LOAD_DECLINE;
00492    if (ast_register_application_xml(app, amd_exec))
00493       return AST_MODULE_LOAD_FAILURE;
00494    return AST_MODULE_LOAD_SUCCESS;
00495 }
00496 
00497 static int reload(void)
00498 {
00499    if (load_config(1))
00500       return AST_MODULE_LOAD_DECLINE;
00501    return AST_MODULE_LOAD_SUCCESS;
00502 }
00503 
00504 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
00505       .load = load_module,
00506       .unload = unload_module,
00507       .reload = reload,
00508 );

Generated on Wed Apr 6 11:29:37 2011 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7