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