#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 = "8586c2a7d357cb591cc3a6607a8f62d1" , .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 146 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().
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: %s)\n", chan->name, 00192 S_COR(chan->caller.ani.number.valid, chan->caller.ani.number.str, "(N/A)"), 00193 S_COR(chan->redirecting.from.number.valid, chan->redirecting.from.number.str, "(N/A)"), 00194 ast_getformatname(chan->readformat)); 00195 00196 /* Lets parse the arguments. */ 00197 if (!ast_strlen_zero(parse)) { 00198 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */ 00199 AST_STANDARD_APP_ARGS(args, parse); 00200 if (!ast_strlen_zero(args.argInitialSilence)) 00201 initialSilence = atoi(args.argInitialSilence); 00202 if (!ast_strlen_zero(args.argGreeting)) 00203 greeting = atoi(args.argGreeting); 00204 if (!ast_strlen_zero(args.argAfterGreetingSilence)) 00205 afterGreetingSilence = atoi(args.argAfterGreetingSilence); 00206 if (!ast_strlen_zero(args.argTotalAnalysisTime)) 00207 totalAnalysisTime = atoi(args.argTotalAnalysisTime); 00208 if (!ast_strlen_zero(args.argMinimumWordLength)) 00209 minimumWordLength = atoi(args.argMinimumWordLength); 00210 if (!ast_strlen_zero(args.argBetweenWordsSilence)) 00211 betweenWordsSilence = atoi(args.argBetweenWordsSilence); 00212 if (!ast_strlen_zero(args.argMaximumNumberOfWords)) 00213 maximumNumberOfWords = atoi(args.argMaximumNumberOfWords); 00214 if (!ast_strlen_zero(args.argSilenceThreshold)) 00215 silenceThreshold = atoi(args.argSilenceThreshold); 00216 if (!ast_strlen_zero(args.argMaximumWordLength)) 00217 maximumWordLength = atoi(args.argMaximumWordLength); 00218 } else { 00219 ast_debug(1, "AMD using the default parameters.\n"); 00220 } 00221 00222 /* Find lowest ms value, that will be max wait time for a frame */ 00223 if (maxWaitTimeForFrame > initialSilence) 00224 maxWaitTimeForFrame = initialSilence; 00225 if (maxWaitTimeForFrame > greeting) 00226 maxWaitTimeForFrame = greeting; 00227 if (maxWaitTimeForFrame > afterGreetingSilence) 00228 maxWaitTimeForFrame = afterGreetingSilence; 00229 if (maxWaitTimeForFrame > totalAnalysisTime) 00230 maxWaitTimeForFrame = totalAnalysisTime; 00231 if (maxWaitTimeForFrame > minimumWordLength) 00232 maxWaitTimeForFrame = minimumWordLength; 00233 if (maxWaitTimeForFrame > betweenWordsSilence) 00234 maxWaitTimeForFrame = betweenWordsSilence; 00235 00236 /* Now we're ready to roll! */ 00237 ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " 00238 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n", 00239 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime, 00240 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength); 00241 00242 /* Set read format to signed linear so we get signed linear frames in */ 00243 readFormat = chan->readformat; 00244 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) { 00245 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name ); 00246 pbx_builtin_setvar_helper(chan , "AMDSTATUS", ""); 00247 pbx_builtin_setvar_helper(chan , "AMDCAUSE", ""); 00248 return; 00249 } 00250 00251 /* Create a new DSP that will detect the silence */ 00252 if (!(silenceDetector = ast_dsp_new())) { 00253 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name ); 00254 pbx_builtin_setvar_helper(chan , "AMDSTATUS", ""); 00255 pbx_builtin_setvar_helper(chan , "AMDCAUSE", ""); 00256 return; 00257 } 00258 00259 /* Set silence threshold to specified value */ 00260 ast_dsp_set_threshold(silenceDetector, silenceThreshold); 00261 00262 /* Now we go into a loop waiting for frames from the channel */ 00263 while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) { 00264 00265 /* If we fail to read in a frame, that means they hung up */ 00266 if (!(f = ast_read(chan))) { 00267 ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name); 00268 ast_debug(1, "Got hangup\n"); 00269 strcpy(amdStatus, "HANGUP"); 00270 res = 1; 00271 break; 00272 } 00273 00274 if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) { 00275 /* If the total time exceeds the analysis time then give up as we are not too sure */ 00276 if (f->frametype == AST_FRAME_VOICE) { 00277 framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS); 00278 } else { 00279 framelength = 2 * maxWaitTimeForFrame; 00280 } 00281 00282 iTotalTime += framelength; 00283 if (iTotalTime >= totalAnalysisTime) { 00284 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name ); 00285 ast_frfree(f); 00286 strcpy(amdStatus , "NOTSURE"); 00287 sprintf(amdCause , "TOOLONG-%d", iTotalTime); 00288 break; 00289 } 00290 00291 /* Feed the frame of audio into the silence detector and see if we get a result */ 00292 if (f->frametype != AST_FRAME_VOICE) 00293 dspsilence += 2 * maxWaitTimeForFrame; 00294 else { 00295 dspsilence = 0; 00296 ast_dsp_silence(silenceDetector, f, &dspsilence); 00297 } 00298 00299 if (dspsilence > 0) { 00300 silenceDuration = dspsilence; 00301 00302 if (silenceDuration >= betweenWordsSilence) { 00303 if (currentState != STATE_IN_SILENCE ) { 00304 previousState = currentState; 00305 ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name); 00306 } 00307 /* Find words less than word duration */ 00308 if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){ 00309 ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration); 00310 } 00311 currentState = STATE_IN_SILENCE; 00312 consecutiveVoiceDuration = 0; 00313 } 00314 00315 if (inInitialSilence == 1 && silenceDuration >= initialSilence) { 00316 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n", 00317 chan->name, silenceDuration, initialSilence); 00318 ast_frfree(f); 00319 strcpy(amdStatus , "MACHINE"); 00320 sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence); 00321 res = 1; 00322 break; 00323 } 00324 00325 if (silenceDuration >= afterGreetingSilence && inGreeting == 1) { 00326 ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n", 00327 chan->name, silenceDuration, afterGreetingSilence); 00328 ast_frfree(f); 00329 strcpy(amdStatus , "HUMAN"); 00330 sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence); 00331 res = 1; 00332 break; 00333 } 00334 00335 } else { 00336 consecutiveVoiceDuration += framelength; 00337 voiceDuration += framelength; 00338 00339 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the 00340 number of words if my previous state was Silence, which means that I moved into a word. */ 00341 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) { 00342 iWordsCount++; 00343 ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount); 00344 previousState = currentState; 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 = "8586c2a7d357cb591cc3a6607a8f62d1" , .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] |