Answering machine detection. More...
#include "asterisk.h"#include "asterisk/module.h"#include "asterisk/lock.h"#include "asterisk/channel.h"#include "asterisk/dsp.h"#include "asterisk/pbx.h"#include "asterisk/config.h"#include "asterisk/app.h"
Go to the source code of this file.
Defines | |
| #define | STATE_IN_SILENCE 2 |
| #define | STATE_IN_WORD 1 |
Functions | |
| static void | __reg_module (void) |
| static void | __unreg_module (void) |
| static int | amd_exec (struct ast_channel *chan, void *data) |
| static void | isAnsweringMachine (struct ast_channel *chan, void *data) |
| static int | load_config (int reload) |
| static int | load_module (void) |
| static int | reload (void) |
| static int | unload_module (void) |
Variables | |
| static struct ast_module_info | __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT , .description = "Answering Machine Detection Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "0901e4e500243c855563a2d78b0c03e4" , .load = load_module, .unload = unload_module, .reload = reload, } |
| static char * | app = "AMD" |
| static struct ast_module_info * | ast_module_info = &__mod_info |
| static int | dfltAfterGreetingSilence = 800 |
| static int | dfltBetweenWordsSilence = 50 |
| static int | dfltGreeting = 1500 |
| static int | dfltInitialSilence = 2500 |
| static int | dfltMaximumNumberOfWords = 3 |
| static int | dfltMaximumWordLength = 5000 |
| static int | dfltMaxWaitTimeForFrame = 50 |
| static int | dfltMinimumWordLength = 100 |
| static int | dfltSilenceThreshold = 256 |
| static int | dfltTotalAnalysisTime = 5000 |
Answering machine detection.
Definition in file app_amd.c.
| #define STATE_IN_SILENCE 2 |
Definition at line 130 of file app_amd.c.
Referenced by isAnsweringMachine().
| #define STATE_IN_WORD 1 |
Definition at line 129 of file app_amd.c.
Referenced by isAnsweringMachine().
| static int amd_exec | ( | struct ast_channel * | chan, | |
| void * | data | |||
| ) | [static] |
Definition at line 408 of file app_amd.c.
References isAnsweringMachine().
Referenced by load_module().
00409 { 00410 isAnsweringMachine(chan, data); 00411 00412 return 0; 00413 }
| static void isAnsweringMachine | ( | struct ast_channel * | chan, | |
| void * | data | |||
| ) | [static] |
Definition at line 146 of file app_amd.c.
References AST_APP_ARG, ast_codec_get_samples(), ast_debug, AST_DECLARE_APP_ARGS, ast_dsp_free(), ast_dsp_new(), ast_dsp_set_threshold(), ast_dsp_silence(), AST_FORMAT_SLINEAR, AST_FRAME_CNG, AST_FRAME_NULL, AST_FRAME_VOICE, ast_frfree, ast_log(), ast_read(), ast_set_read_format(), AST_STANDARD_APP_ARGS, ast_strlen_zero(), ast_verb, ast_waitfor(), ast_channel::cid, ast_callerid::cid_ani, ast_callerid::cid_rdnis, DEFAULT_SAMPLES_PER_MS, f, ast_frame::frametype, LOG_WARNING, ast_channel::name, parse(), pbx_builtin_setvar_helper(), ast_channel::readformat, STATE_IN_SILENCE, and STATE_IN_WORD.
Referenced by amd_exec().
00147 { 00148 int res = 0; 00149 struct ast_frame *f = NULL; 00150 struct ast_dsp *silenceDetector = NULL; 00151 int dspsilence = 0, readFormat, framelength = 0; 00152 int inInitialSilence = 1; 00153 int inGreeting = 0; 00154 int voiceDuration = 0; 00155 int silenceDuration = 0; 00156 int iTotalTime = 0; 00157 int iWordsCount = 0; 00158 int currentState = STATE_IN_WORD; 00159 int previousState = STATE_IN_SILENCE; 00160 int consecutiveVoiceDuration = 0; 00161 char amdCause[256] = "", amdStatus[256] = ""; 00162 char *parse = ast_strdupa(data); 00163 00164 /* Lets set the initial values of the variables that will control the algorithm. 00165 The initial values are the default ones. If they are passed as arguments 00166 when invoking the application, then the default values will be overwritten 00167 by the ones passed as parameters. */ 00168 int initialSilence = dfltInitialSilence; 00169 int greeting = dfltGreeting; 00170 int afterGreetingSilence = dfltAfterGreetingSilence; 00171 int totalAnalysisTime = dfltTotalAnalysisTime; 00172 int minimumWordLength = dfltMinimumWordLength; 00173 int betweenWordsSilence = dfltBetweenWordsSilence; 00174 int maximumNumberOfWords = dfltMaximumNumberOfWords; 00175 int silenceThreshold = dfltSilenceThreshold; 00176 int maximumWordLength = dfltMaximumWordLength; 00177 int maxWaitTimeForFrame = dfltMaxWaitTimeForFrame; 00178 00179 AST_DECLARE_APP_ARGS(args, 00180 AST_APP_ARG(argInitialSilence); 00181 AST_APP_ARG(argGreeting); 00182 AST_APP_ARG(argAfterGreetingSilence); 00183 AST_APP_ARG(argTotalAnalysisTime); 00184 AST_APP_ARG(argMinimumWordLength); 00185 AST_APP_ARG(argBetweenWordsSilence); 00186 AST_APP_ARG(argMaximumNumberOfWords); 00187 AST_APP_ARG(argSilenceThreshold); 00188 AST_APP_ARG(argMaximumWordLength); 00189 ); 00190 00191 ast_verb(3, "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat); 00192 00193 /* Lets parse the arguments. */ 00194 if (!ast_strlen_zero(parse)) { 00195 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */ 00196 AST_STANDARD_APP_ARGS(args, parse); 00197 if (!ast_strlen_zero(args.argInitialSilence)) 00198 initialSilence = atoi(args.argInitialSilence); 00199 if (!ast_strlen_zero(args.argGreeting)) 00200 greeting = atoi(args.argGreeting); 00201 if (!ast_strlen_zero(args.argAfterGreetingSilence)) 00202 afterGreetingSilence = atoi(args.argAfterGreetingSilence); 00203 if (!ast_strlen_zero(args.argTotalAnalysisTime)) 00204 totalAnalysisTime = atoi(args.argTotalAnalysisTime); 00205 if (!ast_strlen_zero(args.argMinimumWordLength)) 00206 minimumWordLength = atoi(args.argMinimumWordLength); 00207 if (!ast_strlen_zero(args.argBetweenWordsSilence)) 00208 betweenWordsSilence = atoi(args.argBetweenWordsSilence); 00209 if (!ast_strlen_zero(args.argMaximumNumberOfWords)) 00210 maximumNumberOfWords = atoi(args.argMaximumNumberOfWords); 00211 if (!ast_strlen_zero(args.argSilenceThreshold)) 00212 silenceThreshold = atoi(args.argSilenceThreshold); 00213 if (!ast_strlen_zero(args.argMaximumWordLength)) 00214 maximumWordLength = atoi(args.argMaximumWordLength); 00215 } else { 00216 ast_debug(1, "AMD using the default parameters.\n"); 00217 } 00218 00219 /* Find lowest ms value, that will be max wait time for a frame */ 00220 if (maxWaitTimeForFrame > initialSilence) 00221 maxWaitTimeForFrame = initialSilence; 00222 if (maxWaitTimeForFrame > greeting) 00223 maxWaitTimeForFrame = greeting; 00224 if (maxWaitTimeForFrame > afterGreetingSilence) 00225 maxWaitTimeForFrame = afterGreetingSilence; 00226 if (maxWaitTimeForFrame > totalAnalysisTime) 00227 maxWaitTimeForFrame = totalAnalysisTime; 00228 if (maxWaitTimeForFrame > minimumWordLength) 00229 maxWaitTimeForFrame = minimumWordLength; 00230 if (maxWaitTimeForFrame > betweenWordsSilence) 00231 maxWaitTimeForFrame = betweenWordsSilence; 00232 00233 /* Now we're ready to roll! */ 00234 ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " 00235 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n", 00236 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime, 00237 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength); 00238 00239 /* Set read format to signed linear so we get signed linear frames in */ 00240 readFormat = chan->readformat; 00241 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) { 00242 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name ); 00243 pbx_builtin_setvar_helper(chan , "AMDSTATUS", ""); 00244 pbx_builtin_setvar_helper(chan , "AMDCAUSE", ""); 00245 return; 00246 } 00247 00248 /* Create a new DSP that will detect the silence */ 00249 if (!(silenceDetector = ast_dsp_new())) { 00250 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name ); 00251 pbx_builtin_setvar_helper(chan , "AMDSTATUS", ""); 00252 pbx_builtin_setvar_helper(chan , "AMDCAUSE", ""); 00253 return; 00254 } 00255 00256 /* Set silence threshold to specified value */ 00257 ast_dsp_set_threshold(silenceDetector, silenceThreshold); 00258 00259 /* Now we go into a loop waiting for frames from the channel */ 00260 while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) { 00261 00262 /* If we fail to read in a frame, that means they hung up */ 00263 if (!(f = ast_read(chan))) { 00264 ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name); 00265 ast_debug(1, "Got hangup\n"); 00266 strcpy(amdStatus, "HANGUP"); 00267 res = 1; 00268 break; 00269 } 00270 00271 if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) { 00272 /* If the total time exceeds the analysis time then give up as we are not too sure */ 00273 if (f->frametype == AST_FRAME_VOICE) 00274 framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS); 00275 else 00276 framelength += 2 * maxWaitTimeForFrame; 00277 00278 iTotalTime += framelength; 00279 if (iTotalTime >= totalAnalysisTime) { 00280 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name ); 00281 ast_frfree(f); 00282 strcpy(amdStatus , "NOTSURE"); 00283 sprintf(amdCause , "TOOLONG-%d", iTotalTime); 00284 break; 00285 } 00286 00287 /* Feed the frame of audio into the silence detector and see if we get a result */ 00288 if (f->frametype != AST_FRAME_VOICE) 00289 dspsilence += 2 * maxWaitTimeForFrame; 00290 else { 00291 dspsilence = 0; 00292 ast_dsp_silence(silenceDetector, f, &dspsilence); 00293 } 00294 00295 if (dspsilence > 0) { 00296 silenceDuration = dspsilence; 00297 00298 if (silenceDuration >= betweenWordsSilence) { 00299 if (currentState != STATE_IN_SILENCE ) { 00300 previousState = currentState; 00301 ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name); 00302 } 00303 /* Find words less than word duration */ 00304 if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){ 00305 ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration); 00306 } 00307 currentState = STATE_IN_SILENCE; 00308 consecutiveVoiceDuration = 0; 00309 } 00310 00311 if (inInitialSilence == 1 && silenceDuration >= initialSilence) { 00312 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n", 00313 chan->name, silenceDuration, initialSilence); 00314 ast_frfree(f); 00315 strcpy(amdStatus , "MACHINE"); 00316 sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence); 00317 res = 1; 00318 break; 00319 } 00320 00321 if (silenceDuration >= afterGreetingSilence && inGreeting == 1) { 00322 ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n", 00323 chan->name, silenceDuration, afterGreetingSilence); 00324 ast_frfree(f); 00325 strcpy(amdStatus , "HUMAN"); 00326 sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence); 00327 res = 1; 00328 break; 00329 } 00330 00331 } else { 00332 consecutiveVoiceDuration += framelength; 00333 voiceDuration += framelength; 00334 00335 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the 00336 number of words if my previous state was Silence, which means that I moved into a word. */ 00337 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) { 00338 iWordsCount++; 00339 ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount); 00340 previousState = currentState; 00341 currentState = STATE_IN_WORD; 00342 } 00343 if (consecutiveVoiceDuration >= maximumWordLength){ 00344 ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration); 00345 ast_frfree(f); 00346 strcpy(amdStatus , "MACHINE"); 00347 sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration); 00348 break; 00349 } 00350 if (iWordsCount >= maximumNumberOfWords) { 00351 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount); 00352 ast_frfree(f); 00353 strcpy(amdStatus , "MACHINE"); 00354 sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords); 00355 res = 1; 00356 break; 00357 } 00358 00359 if (inGreeting == 1 && voiceDuration >= greeting) { 00360 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting); 00361 ast_frfree(f); 00362 strcpy(amdStatus , "MACHINE"); 00363 sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting); 00364 res = 1; 00365 break; 00366 } 00367 00368 if (voiceDuration >= minimumWordLength ) { 00369 if (silenceDuration > 0) 00370 ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration); 00371 silenceDuration = 0; 00372 } 00373 if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) { 00374 /* Only go in here once to change the greeting flag when we detect the 1st word */ 00375 if (silenceDuration > 0) 00376 ast_verb(3, "AMD: Channel [%s]. Before Greeting Time: silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration); 00377 inInitialSilence = 0; 00378 inGreeting = 1; 00379 } 00380 00381 } 00382 } 00383 ast_frfree(f); 00384 } 00385 00386 if (!res) { 00387 /* It took too long to get a frame back. Giving up. */ 00388 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name); 00389 strcpy(amdStatus , "NOTSURE"); 00390 sprintf(amdCause , "TOOLONG-%d", iTotalTime); 00391 } 00392 00393 /* Set the status and cause on the channel */ 00394 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus); 00395 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause); 00396 00397 /* Restore channel read format */ 00398 if (readFormat && ast_set_read_format(chan, readFormat)) 00399 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name); 00400 00401 /* Free the DSP used to detect silence */ 00402 ast_dsp_free(silenceDetector); 00403 00404 return; 00405 }
| static int load_config | ( | int | reload | ) | [static] |
Definition at line 415 of file app_amd.c.
References ast_category_browse(), ast_config_destroy(), ast_config_load, ast_dsp_get_threshold_from_settings(), ast_log(), ast_variable_browse(), ast_verb, CONFIG_FLAG_FILEUNCHANGED, CONFIG_STATUS_FILEINVALID, CONFIG_STATUS_FILEUNCHANGED, ast_variable::lineno, LOG_ERROR, LOG_WARNING, ast_variable::name, ast_variable::next, THRESHOLD_SILENCE, ast_variable::value, and var.
Referenced by load_module(), and reload().
00416 { 00417 struct ast_config *cfg = NULL; 00418 char *cat = NULL; 00419 struct ast_variable *var = NULL; 00420 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; 00421 00422 dfltSilenceThreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE); 00423 00424 if (!(cfg = ast_config_load("amd.conf", config_flags))) { 00425 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n"); 00426 return -1; 00427 } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) { 00428 return 0; 00429 } else if (cfg == CONFIG_STATUS_FILEINVALID) { 00430 ast_log(LOG_ERROR, "Config file amd.conf is in an invalid format. Aborting.\n"); 00431 return -1; 00432 } 00433 00434 cat = ast_category_browse(cfg, NULL); 00435 00436 while (cat) { 00437 if (!strcasecmp(cat, "general") ) { 00438 var = ast_variable_browse(cfg, cat); 00439 while (var) { 00440 if (!strcasecmp(var->name, "initial_silence")) { 00441 dfltInitialSilence = atoi(var->value); 00442 } else if (!strcasecmp(var->name, "greeting")) { 00443 dfltGreeting = atoi(var->value); 00444 } else if (!strcasecmp(var->name, "after_greeting_silence")) { 00445 dfltAfterGreetingSilence = atoi(var->value); 00446 } else if (!strcasecmp(var->name, "silence_threshold")) { 00447 dfltSilenceThreshold = atoi(var->value); 00448 } else if (!strcasecmp(var->name, "total_analysis_time")) { 00449 dfltTotalAnalysisTime = atoi(var->value); 00450 } else if (!strcasecmp(var->name, "min_word_length")) { 00451 dfltMinimumWordLength = atoi(var->value); 00452 } else if (!strcasecmp(var->name, "between_words_silence")) { 00453 dfltBetweenWordsSilence = atoi(var->value); 00454 } else if (!strcasecmp(var->name, "maximum_number_of_words")) { 00455 dfltMaximumNumberOfWords = atoi(var->value); 00456 } else if (!strcasecmp(var->name, "maximum_word_length")) { 00457 dfltMaximumWordLength = atoi(var->value); 00458 00459 } else { 00460 ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n", 00461 app, cat, var->name, var->lineno); 00462 } 00463 var = var->next; 00464 } 00465 } 00466 cat = ast_category_browse(cfg, cat); 00467 } 00468 00469 ast_config_destroy(cfg); 00470 00471 ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " 00472 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n", 00473 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime, 00474 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength); 00475 00476 return 0; 00477 }
| static int load_module | ( | void | ) | [static] |
Definition at line 484 of file app_amd.c.
References amd_exec(), AST_MODULE_LOAD_DECLINE, AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_SUCCESS, ast_register_application_xml, and load_config().
00485 { 00486 if (load_config(0)) 00487 return AST_MODULE_LOAD_DECLINE; 00488 if (ast_register_application_xml(app, amd_exec)) 00489 return AST_MODULE_LOAD_FAILURE; 00490 return AST_MODULE_LOAD_SUCCESS; 00491 }
| static int reload | ( | void | ) | [static] |
Definition at line 493 of file app_amd.c.
References AST_MODULE_LOAD_DECLINE, AST_MODULE_LOAD_SUCCESS, and load_config().
00494 { 00495 if (load_config(1)) 00496 return AST_MODULE_LOAD_DECLINE; 00497 return AST_MODULE_LOAD_SUCCESS; 00498 }
| static int unload_module | ( | void | ) | [static] |
Definition at line 479 of file app_amd.c.
References ast_unregister_application().
00480 { 00481 return ast_unregister_application(app); 00482 }
struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT , .description = "Answering Machine Detection Application" , .key = "This paragraph is copyright (c) 2006 by Digium, Inc. \In order for your module to load, it must return this \key via a function called \"key\". Any code which \includes this paragraph must be licensed under the GNU \General Public License version 2 or later (at your \option). In addition to Digium's general reservations \of rights, Digium expressly reserves the right to \allow other parties to license this paragraph under \different terms. Any use of Digium, Inc. trademarks or \logos (including \"Asterisk\" or \"Digium\") without \express written permission of Digium, Inc. is prohibited.\n" , .buildopt_sum = "0901e4e500243c855563a2d78b0c03e4" , .load = load_module, .unload = unload_module, .reload = reload, } [static] |
struct ast_module_info* ast_module_info = &__mod_info [static] |
int dfltAfterGreetingSilence = 800 [static] |
int dfltBetweenWordsSilence = 50 [static] |
int dfltGreeting = 1500 [static] |
int dfltInitialSilence = 2500 [static] |
int dfltMaximumNumberOfWords = 3 [static] |
int dfltMaximumWordLength = 5000 [static] |
int dfltMaxWaitTimeForFrame = 50 [static] |
int dfltMinimumWordLength = 100 [static] |
int dfltSilenceThreshold = 256 [static] |
int dfltTotalAnalysisTime = 5000 [static] |
1.6.2