00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
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
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
00108
00109
00110
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
00135 if (!ast_strlen_zero(parse)) {
00136
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
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
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
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
00182 ast_dsp_set_threshold(silenceDetector, silenceThreshold);
00183
00184
00185 while ((res = ast_waitfor(chan, totalAnalysisTime)) > -1) {
00186
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
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
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
00250
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
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
00297 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
00298 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
00299
00300
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
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 );