Sat Apr 26 2014 22:01:26

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