Mon May 14 04:42:52 2007

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$")
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 static void isAnsweringMachine(struct ast_channel *chan, void *data)
00090 {
00091    int res = 0;
00092    struct ast_frame *f = NULL;
00093    struct ast_dsp *silenceDetector = NULL;
00094    int dspsilence = 0, readFormat, framelength;
00095    int inInitialSilence = 1;
00096    int inGreeting = 0;
00097    int voiceDuration = 0;
00098    int silenceDuration = 0;
00099    int iTotalTime = 0;
00100    int iWordsCount = 0;
00101    int currentState = STATE_IN_SILENCE;
00102    int previousState = STATE_IN_SILENCE;
00103    int consecutiveVoiceDuration = 0;
00104    char amdCause[256] = "", amdStatus[256] = "";
00105    char *parse = ast_strdupa(data);
00106 
00107    /* Lets set the initial values of the variables that will control the algorithm.
00108       The initial values are the default ones. If they are passed as arguments
00109       when invoking the application, then the default values will be overwritten
00110       by the ones passed as parameters. */
00111    int initialSilence       = dfltInitialSilence;
00112    int greeting             = dfltGreeting;
00113    int afterGreetingSilence = dfltAfterGreetingSilence;
00114    int totalAnalysisTime    = dfltTotalAnalysisTime;
00115    int minimumWordLength    = dfltMinimumWordLength;
00116    int betweenWordsSilence  = dfltBetweenWordsSilence;
00117    int maximumNumberOfWords = dfltMaximumNumberOfWords;
00118    int silenceThreshold     = dfltSilenceThreshold;
00119 
00120    AST_DECLARE_APP_ARGS(args,
00121               AST_APP_ARG(argInitialSilence);
00122               AST_APP_ARG(argGreeting);
00123               AST_APP_ARG(argAfterGreetingSilence);
00124               AST_APP_ARG(argTotalAnalysisTime);
00125               AST_APP_ARG(argMinimumWordLength);
00126               AST_APP_ARG(argBetweenWordsSilence);
00127               AST_APP_ARG(argMaximumNumberOfWords);
00128               AST_APP_ARG(argSilenceThreshold);
00129    );
00130 
00131    if (option_verbose > 2)
00132       ast_verbose(VERBOSE_PREFIX_3 "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
00133 
00134    /* Lets parse the arguments. */
00135    if (!ast_strlen_zero(parse)) {
00136       /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
00137       AST_STANDARD_APP_ARGS(args, parse);
00138       if (!ast_strlen_zero(args.argInitialSilence))
00139          initialSilence = atoi(args.argInitialSilence);
00140       if (!ast_strlen_zero(args.argGreeting))
00141          greeting = atoi(args.argGreeting);
00142       if (!ast_strlen_zero(args.argAfterGreetingSilence))
00143          afterGreetingSilence = atoi(args.argAfterGreetingSilence);
00144       if (!ast_strlen_zero(args.argTotalAnalysisTime))
00145          totalAnalysisTime = atoi(args.argTotalAnalysisTime);
00146       if (!ast_strlen_zero(args.argMinimumWordLength))
00147          minimumWordLength = atoi(args.argMinimumWordLength);
00148       if (!ast_strlen_zero(args.argBetweenWordsSilence))
00149          betweenWordsSilence = atoi(args.argBetweenWordsSilence);
00150       if (!ast_strlen_zero(args.argMaximumNumberOfWords))
00151          maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
00152       if (!ast_strlen_zero(args.argSilenceThreshold))
00153          silenceThreshold = atoi(args.argSilenceThreshold);
00154    } else if (option_debug)
00155       ast_log(LOG_DEBUG, "AMD using the default parameters.\n");
00156 
00157    /* Now we're ready to roll! */
00158    if (option_verbose > 2)
00159       ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00160       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
00161             initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
00162             minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
00163 
00164    /* Set read format to signed linear so we get signed linear frames in */
00165    readFormat = chan->readformat;
00166    if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
00167       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
00168       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00169       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00170       return;
00171    }
00172 
00173    /* Create a new DSP that will detect the silence */
00174    if (!(silenceDetector = ast_dsp_new())) {
00175       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
00176       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00177       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00178       return;
00179    }
00180 
00181    /* Set silence threshold to specified value */
00182    ast_dsp_set_threshold(silenceDetector, silenceThreshold);
00183 
00184    /* Now we go into a loop waiting for frames from the channel */
00185    while ((res = ast_waitfor(chan, totalAnalysisTime)) > -1) {
00186       /* If we fail to read in a frame, that means they hung up */
00187       if (!(f = ast_read(chan))) {
00188          if (option_verbose > 2)
00189             ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
00190          if (option_debug)
00191             ast_log(LOG_DEBUG, "Got hangup\n");
00192          strcpy(amdStatus, "HANGUP");
00193          break;
00194       }
00195 
00196       if (f->frametype == AST_FRAME_VOICE) {
00197          /* If the total time exceeds the analysis time then give up as we are not too sure */
00198          framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
00199          iTotalTime += framelength;
00200          if (iTotalTime >= totalAnalysisTime) {
00201             if (option_verbose > 2) 
00202                ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
00203             ast_frfree(f);
00204             strcpy(amdStatus , "NOTSURE");
00205             sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00206             break;
00207          }
00208 
00209          /* Feed the frame of audio into the silence detector and see if we get a result */
00210          dspsilence = 0;
00211          ast_dsp_silence(silenceDetector, f, &dspsilence);
00212          if (dspsilence) {
00213             silenceDuration = dspsilence;
00214             
00215             if (silenceDuration >= betweenWordsSilence) {
00216                if (currentState != STATE_IN_SILENCE ) {
00217                   previousState = currentState;
00218                   if (option_verbose > 2)
00219                      ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
00220                }
00221                currentState  = STATE_IN_SILENCE;
00222                consecutiveVoiceDuration = 0;
00223             }
00224             
00225             if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
00226                if (option_verbose > 2)
00227                   ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
00228                          silenceDuration, initialSilence);
00229                ast_frfree(f);
00230                strcpy(amdStatus , "MACHINE");
00231                sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
00232                break;
00233             }
00234             
00235             if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
00236                if (option_verbose > 2)
00237                   ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
00238                          silenceDuration, afterGreetingSilence);
00239                ast_frfree(f);
00240                strcpy(amdStatus , "HUMAN");
00241                sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
00242                break;
00243             }
00244             
00245          } else {
00246             consecutiveVoiceDuration += framelength;
00247             voiceDuration += framelength;
00248             
00249             /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
00250                number of words if my previous state was Silence, which means that I moved into a word. */
00251             if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
00252                iWordsCount++;
00253                if (option_verbose > 2)
00254                   ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount);
00255                previousState = currentState;
00256                currentState = STATE_IN_WORD;
00257             }
00258             
00259             if (iWordsCount >= maximumNumberOfWords) {
00260                if (option_verbose > 2)
00261                   ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount);
00262                ast_frfree(f);
00263                strcpy(amdStatus , "MACHINE");
00264                sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
00265                break;
00266             }
00267             
00268             if (inGreeting == 1 && voiceDuration >= greeting) {
00269                if (option_verbose > 2)
00270                   ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting);
00271                ast_frfree(f);
00272                strcpy(amdStatus , "MACHINE");
00273                sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
00274                break;
00275             }
00276             
00277             if (voiceDuration >= minimumWordLength ) {
00278                silenceDuration = 0;
00279                inInitialSilence = 0;
00280                inGreeting = 1;
00281             }
00282             
00283          }
00284       }
00285       ast_frfree(f);
00286    }
00287    
00288    if (!res) {
00289       /* It took too long to get a frame back. Giving up. */
00290       if (option_verbose > 2)
00291          ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name);
00292       strcpy(amdStatus , "NOTSURE");
00293       sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00294    }
00295 
00296    /* Set the status and cause on the channel */
00297    pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
00298    pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
00299 
00300    /* Restore channel read format */
00301    if (readFormat && ast_set_read_format(chan, readFormat))
00302       ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
00303 
00304    /* Free the DSP used to detect silence */
00305    ast_dsp_free(silenceDetector);
00306 
00307    return;
00308 }
00309 
00310 
00311 static int amd_exec(struct ast_channel *chan, void *data)
00312 {
00313    struct ast_module_user *u = NULL;
00314 
00315    u = ast_module_user_add(chan);
00316    isAnsweringMachine(chan, data);
00317    ast_module_user_remove(u);
00318 
00319    return 0;
00320 }
00321 
00322 static void load_config(void)
00323 {
00324    struct ast_config *cfg = NULL;
00325    char *cat = NULL;
00326    struct ast_variable *var = NULL;
00327 
00328    if (!(cfg = ast_config_load("amd.conf"))) {
00329       ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
00330       return;
00331    }
00332 
00333    cat = ast_category_browse(cfg, NULL);
00334 
00335    while (cat) {
00336       if (!strcasecmp(cat, "general") ) {
00337          var = ast_variable_browse(cfg, cat);
00338          while (var) {
00339             if (!strcasecmp(var->name, "initial_silence")) {
00340                dfltInitialSilence = atoi(var->value);
00341             } else if (!strcasecmp(var->name, "greeting")) {
00342                dfltGreeting = atoi(var->value);
00343             } else if (!strcasecmp(var->name, "after_greeting_silence")) {
00344                dfltAfterGreetingSilence = atoi(var->value);
00345             } else if (!strcasecmp(var->name, "silence_threshold")) {
00346                dfltSilenceThreshold = atoi(var->value);
00347             } else if (!strcasecmp(var->name, "total_analysis_time")) {
00348                dfltTotalAnalysisTime = atoi(var->value);
00349             } else if (!strcasecmp(var->name, "min_word_length")) {
00350                dfltMinimumWordLength = atoi(var->value);
00351             } else if (!strcasecmp(var->name, "between_words_silence")) {
00352                dfltBetweenWordsSilence = atoi(var->value);
00353             } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
00354                dfltMaximumNumberOfWords = atoi(var->value);
00355             } else {
00356                ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
00357                   app, cat, var->name, var->lineno);
00358             }
00359             var = var->next;
00360          }
00361       }
00362       cat = ast_category_browse(cfg, cat);
00363    }
00364 
00365    ast_config_destroy(cfg);
00366 
00367    if (option_verbose > 2)
00368       ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00369       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
00370             dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
00371             dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
00372 
00373    return;
00374 }
00375 
00376 static int unload_module(void)
00377 {
00378    ast_module_user_hangup_all();
00379    return ast_unregister_application(app);
00380 }
00381 
00382 static int load_module(void)
00383 {
00384    load_config();
00385    return ast_register_application(app, amd_exec, synopsis, descrip);
00386 }
00387 
00388 static int reload(void)
00389 {
00390    load_config();
00391    return 0;
00392 }
00393 
00394 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
00395       .load = load_module,
00396       .unload = unload_module,
00397       .reload = reload,
00398           );

Generated on Mon May 14 04:42:52 2007 for Asterisk - the Open Source PBX by  doxygen 1.5.1