Sun Aug 15 20:33:26 2010

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: 232355 $")
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          iTotalTime += framelength;
00224          if (iTotalTime >= totalAnalysisTime) {
00225             if (option_verbose > 2) 
00226                ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
00227             ast_frfree(f);
00228             strcpy(amdStatus , "NOTSURE");
00229             sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00230             break;
00231          }
00232 
00233          /* Feed the frame of audio into the silence detector and see if we get a result */
00234          if (f->frametype != AST_FRAME_VOICE)
00235             dspsilence += 2 * maxWaitTimeForFrame;
00236          else {
00237             dspsilence = 0;
00238             ast_dsp_silence(silenceDetector, f, &dspsilence);
00239          }
00240 
00241          if (dspsilence > 0) {
00242             silenceDuration = dspsilence;
00243             
00244             if (silenceDuration >= betweenWordsSilence) {
00245                if (currentState != STATE_IN_SILENCE ) {
00246                   previousState = currentState;
00247                   if (option_verbose > 2)
00248                      ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
00249                }
00250                currentState  = STATE_IN_SILENCE;
00251                consecutiveVoiceDuration = 0;
00252             }
00253             
00254             if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
00255                if (option_verbose > 2)
00256                   ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
00257                          silenceDuration, initialSilence);
00258                ast_frfree(f);
00259                strcpy(amdStatus , "MACHINE");
00260                sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
00261                res = 1;
00262                break;
00263             }
00264             
00265             if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
00266                if (option_verbose > 2)
00267                   ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
00268                          silenceDuration, afterGreetingSilence);
00269                ast_frfree(f);
00270                strcpy(amdStatus , "HUMAN");
00271                sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
00272                res = 1;
00273                break;
00274             }
00275             
00276          } else {
00277             consecutiveVoiceDuration += framelength;
00278             voiceDuration += framelength;
00279             
00280             /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
00281                number of words if my previous state was Silence, which means that I moved into a word. */
00282             if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
00283                iWordsCount++;
00284                if (option_verbose > 2)
00285                   ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount);
00286                previousState = currentState;
00287                currentState = STATE_IN_WORD;
00288             }
00289             
00290             if (iWordsCount >= maximumNumberOfWords) {
00291                if (option_verbose > 2)
00292                   ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount);
00293                ast_frfree(f);
00294                strcpy(amdStatus , "MACHINE");
00295                sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
00296                res = 1;
00297                break;
00298             }
00299             
00300             if (inGreeting == 1 && voiceDuration >= greeting) {
00301                if (option_verbose > 2)
00302                   ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting);
00303                ast_frfree(f);
00304                strcpy(amdStatus , "MACHINE");
00305                sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
00306                res = 1;
00307                break;
00308             }
00309             
00310             if (voiceDuration >= minimumWordLength ) {
00311                silenceDuration = 0;
00312                inInitialSilence = 0;
00313                inGreeting = 1;
00314             }
00315             
00316          }
00317       }
00318       ast_frfree(f);
00319    }
00320    
00321    if (!res) {
00322       /* It took too long to get a frame back. Giving up. */
00323       if (option_verbose > 2)
00324          ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name);
00325       strcpy(amdStatus , "NOTSURE");
00326       sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00327    }
00328 
00329    /* Set the status and cause on the channel */
00330    pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
00331    pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
00332 
00333    /* Restore channel read format */
00334    if (readFormat && ast_set_read_format(chan, readFormat))
00335       ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
00336 
00337    /* Free the DSP used to detect silence */
00338    ast_dsp_free(silenceDetector);
00339 
00340    return;
00341 }
00342 
00343 
00344 static int amd_exec(struct ast_channel *chan, void *data)
00345 {
00346    struct ast_module_user *u = NULL;
00347 
00348    u = ast_module_user_add(chan);
00349    isAnsweringMachine(chan, data);
00350    ast_module_user_remove(u);
00351 
00352    return 0;
00353 }
00354 
00355 static void load_config(void)
00356 {
00357    struct ast_config *cfg = NULL;
00358    char *cat = NULL;
00359    struct ast_variable *var = NULL;
00360 
00361    if (!(cfg = ast_config_load("amd.conf"))) {
00362       ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
00363       return;
00364    }
00365 
00366    cat = ast_category_browse(cfg, NULL);
00367 
00368    while (cat) {
00369       if (!strcasecmp(cat, "general") ) {
00370          var = ast_variable_browse(cfg, cat);
00371          while (var) {
00372             if (!strcasecmp(var->name, "initial_silence")) {
00373                dfltInitialSilence = atoi(var->value);
00374             } else if (!strcasecmp(var->name, "greeting")) {
00375                dfltGreeting = atoi(var->value);
00376             } else if (!strcasecmp(var->name, "after_greeting_silence")) {
00377                dfltAfterGreetingSilence = atoi(var->value);
00378             } else if (!strcasecmp(var->name, "silence_threshold")) {
00379                dfltSilenceThreshold = atoi(var->value);
00380             } else if (!strcasecmp(var->name, "total_analysis_time")) {
00381                dfltTotalAnalysisTime = atoi(var->value);
00382             } else if (!strcasecmp(var->name, "min_word_length")) {
00383                dfltMinimumWordLength = atoi(var->value);
00384             } else if (!strcasecmp(var->name, "between_words_silence")) {
00385                dfltBetweenWordsSilence = atoi(var->value);
00386             } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
00387                dfltMaximumNumberOfWords = atoi(var->value);
00388             } else {
00389                ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
00390                   app, cat, var->name, var->lineno);
00391             }
00392             var = var->next;
00393          }
00394       }
00395       cat = ast_category_browse(cfg, cat);
00396    }
00397 
00398    ast_config_destroy(cfg);
00399 
00400    if (option_verbose > 2)
00401       ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00402       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
00403             dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
00404             dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
00405 
00406    return;
00407 }
00408 
00409 static int unload_module(void)
00410 {
00411    ast_module_user_hangup_all();
00412    return ast_unregister_application(app);
00413 }
00414 
00415 static int load_module(void)
00416 {
00417    load_config();
00418    return ast_register_application(app, amd_exec, synopsis, descrip);
00419 }
00420 
00421 static int reload(void)
00422 {
00423    load_config();
00424    return 0;
00425 }
00426 
00427 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
00428       .load = load_module,
00429       .unload = unload_module,
00430       .reload = reload,
00431           );

Generated on Sun Aug 15 20:33:26 2010 for Asterisk - the Open Source PBX by  doxygen 1.4.7