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