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