#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 = "a9c98e5d177805051735cb5b0b16b0a0" , .load = load_module, .unload = unload_module, .reload = reload, } |
static char * | app = "AMD" |
static struct ast_module_info * | ast_module_info = &__mod_info |
static char * | descrip |
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 char * | synopsis = "Attempts to detect answering machines" |
Definition in file app_amd.c.
#define STATE_IN_SILENCE 2 |
#define STATE_IN_WORD 1 |
static int amd_exec | ( | struct ast_channel * | chan, | |
void * | data | |||
) | [static] |
Definition at line 361 of file app_amd.c.
References chan, and isAnsweringMachine().
Referenced by load_module().
00362 { 00363 isAnsweringMachine(chan, data); 00364 00365 return 0; 00366 }
static void isAnsweringMachine | ( | struct ast_channel * | chan, | |
void * | data | |||
) | [static] |
Definition at line 99 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_strdupa, ast_strlen_zero(), ast_verb, ast_waitfor(), chan, ast_channel::cid, ast_callerid::cid_ani, ast_callerid::cid_rdnis, DEFAULT_SAMPLES_PER_MS, f, LOG_WARNING, ast_channel::name, parse(), pbx_builtin_setvar_helper(), ast_channel::readformat, STATE_IN_SILENCE, and STATE_IN_WORD.
Referenced by amd_exec().
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 /* Lets set the initial values of the variables that will control the algorithm. 00118 The initial values are the default ones. If they are passed as arguments 00119 when invoking the application, then the default values will be overwritten 00120 by the ones passed as parameters. */ 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 /* Lets parse the arguments. */ 00147 if (!ast_strlen_zero(parse)) { 00148 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */ 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 /* Find lowest ms value, that will be max wait time for a frame */ 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 /* Now we're ready to roll! */ 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 /* Set read format to signed linear so we get signed linear frames in */ 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 /* Create a new DSP that will detect the silence */ 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 /* Set silence threshold to specified value */ 00210 ast_dsp_set_threshold(silenceDetector, silenceThreshold); 00211 00212 /* Now we go into a loop waiting for frames from the channel */ 00213 while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) { 00214 00215 /* If we fail to read in a frame, that means they hung up */ 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 /* If the total time exceeds the analysis time then give up as we are not too sure */ 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 /* Feed the frame of audio into the silence detector and see if we get a result */ 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 /* Find words less than word duration */ 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 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the 00289 number of words if my previous state was Silence, which means that I moved into a word. */ 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 /* Only go in here once to change the greeting flag when we detect the 1st word */ 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 /* It took too long to get a frame back. Giving up. */ 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 /* Set the status and cause on the channel */ 00347 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus); 00348 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause); 00349 00350 /* Restore channel read format */ 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 /* Free the DSP used to detect silence */ 00355 ast_dsp_free(silenceDetector); 00356 00357 return; 00358 }
static int load_config | ( | int | reload | ) | [static] |
Definition at line 368 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_FILEUNCHANGED, LOG_ERROR, LOG_WARNING, THRESHOLD_SILENCE, and var.
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 }
static int load_module | ( | void | ) | [static] |
Definition at line 433 of file app_amd.c.
References amd_exec(), AST_MODULE_LOAD_DECLINE, AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_SUCCESS, ast_register_application, and load_config().
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 }
static int reload | ( | void | ) | [static] |
Definition at line 442 of file app_amd.c.
References AST_MODULE_LOAD_DECLINE, AST_MODULE_LOAD_SUCCESS, and load_config().
00443 { 00444 if (load_config(1)) 00445 return AST_MODULE_LOAD_DECLINE; 00446 return AST_MODULE_LOAD_SUCCESS; 00447 }
static int unload_module | ( | void | ) | [static] |
Definition at line 428 of file app_amd.c.
References ast_unregister_application().
00429 { 00430 return ast_unregister_application(app); 00431 }
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 = "a9c98e5d177805051735cb5b0b16b0a0" , .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] |
char* synopsis = "Attempts to detect answering machines" [static] |