Thu Oct 11 06:41:58 2012

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

Generated on Thu Oct 11 06:41:58 2012 for Asterisk - the Open Source PBX by  doxygen 1.5.6