#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 360 of file app_amd.c.
References chan, and isAnsweringMachine().
Referenced by load_module().
00361 { 00362 isAnsweringMachine(chan, data); 00363 00364 return 0; 00365 }
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 break; 00221 } 00222 00223 if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) { 00224 /* If the total time exceeds the analysis time then give up as we are not too sure */ 00225 if (f->frametype == AST_FRAME_VOICE) 00226 framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS); 00227 else 00228 framelength += 2 * maxWaitTimeForFrame; 00229 00230 iTotalTime += framelength; 00231 if (iTotalTime >= totalAnalysisTime) { 00232 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name ); 00233 ast_frfree(f); 00234 strcpy(amdStatus , "NOTSURE"); 00235 sprintf(amdCause , "TOOLONG-%d", iTotalTime); 00236 break; 00237 } 00238 00239 /* Feed the frame of audio into the silence detector and see if we get a result */ 00240 if (f->frametype != AST_FRAME_VOICE) 00241 dspsilence += 2 * maxWaitTimeForFrame; 00242 else { 00243 dspsilence = 0; 00244 ast_dsp_silence(silenceDetector, f, &dspsilence); 00245 } 00246 00247 if (dspsilence > 0) { 00248 silenceDuration = dspsilence; 00249 00250 if (silenceDuration >= betweenWordsSilence) { 00251 if (currentState != STATE_IN_SILENCE ) { 00252 previousState = currentState; 00253 ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name); 00254 } 00255 /* Find words less than word duration */ 00256 if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){ 00257 ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration); 00258 } 00259 currentState = STATE_IN_SILENCE; 00260 consecutiveVoiceDuration = 0; 00261 } 00262 00263 if (inInitialSilence == 1 && silenceDuration >= initialSilence) { 00264 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n", 00265 chan->name, silenceDuration, initialSilence); 00266 ast_frfree(f); 00267 strcpy(amdStatus , "MACHINE"); 00268 sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence); 00269 res = 1; 00270 break; 00271 } 00272 00273 if (silenceDuration >= afterGreetingSilence && inGreeting == 1) { 00274 ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n", 00275 chan->name, silenceDuration, afterGreetingSilence); 00276 ast_frfree(f); 00277 strcpy(amdStatus , "HUMAN"); 00278 sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence); 00279 res = 1; 00280 break; 00281 } 00282 00283 } else { 00284 consecutiveVoiceDuration += framelength; 00285 voiceDuration += framelength; 00286 00287 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the 00288 number of words if my previous state was Silence, which means that I moved into a word. */ 00289 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) { 00290 iWordsCount++; 00291 ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount); 00292 previousState = currentState; 00293 currentState = STATE_IN_WORD; 00294 } 00295 if (consecutiveVoiceDuration >= maximumWordLength){ 00296 ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration); 00297 ast_frfree(f); 00298 strcpy(amdStatus , "MACHINE"); 00299 sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration); 00300 break; 00301 } 00302 if (iWordsCount >= maximumNumberOfWords) { 00303 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount); 00304 ast_frfree(f); 00305 strcpy(amdStatus , "MACHINE"); 00306 sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords); 00307 res = 1; 00308 break; 00309 } 00310 00311 if (inGreeting == 1 && voiceDuration >= greeting) { 00312 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting); 00313 ast_frfree(f); 00314 strcpy(amdStatus , "MACHINE"); 00315 sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting); 00316 res = 1; 00317 break; 00318 } 00319 00320 if (voiceDuration >= minimumWordLength ) { 00321 if (silenceDuration > 0) 00322 ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration); 00323 silenceDuration = 0; 00324 } 00325 if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) { 00326 /* Only go in here once to change the greeting flag when we detect the 1st word */ 00327 if (silenceDuration > 0) 00328 ast_verb(3, "AMD: Channel [%s]. Before Greeting Time: silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration); 00329 inInitialSilence = 0; 00330 inGreeting = 1; 00331 } 00332 00333 } 00334 } 00335 ast_frfree(f); 00336 } 00337 00338 if (!res) { 00339 /* It took too long to get a frame back. Giving up. */ 00340 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name); 00341 strcpy(amdStatus , "NOTSURE"); 00342 sprintf(amdCause , "TOOLONG-%d", iTotalTime); 00343 } 00344 00345 /* Set the status and cause on the channel */ 00346 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus); 00347 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause); 00348 00349 /* Restore channel read format */ 00350 if (readFormat && ast_set_read_format(chan, readFormat)) 00351 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name); 00352 00353 /* Free the DSP used to detect silence */ 00354 ast_dsp_free(silenceDetector); 00355 00356 return; 00357 }
static int load_config | ( | int | reload | ) | [static] |
Definition at line 367 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.
00368 { 00369 struct ast_config *cfg = NULL; 00370 char *cat = NULL; 00371 struct ast_variable *var = NULL; 00372 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 }; 00373 00374 dfltSilenceThreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE); 00375 00376 if (!(cfg = ast_config_load("amd.conf", config_flags))) { 00377 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n"); 00378 return -1; 00379 } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) 00380 return 0; 00381 00382 cat = ast_category_browse(cfg, NULL); 00383 00384 while (cat) { 00385 if (!strcasecmp(cat, "general") ) { 00386 var = ast_variable_browse(cfg, cat); 00387 while (var) { 00388 if (!strcasecmp(var->name, "initial_silence")) { 00389 dfltInitialSilence = atoi(var->value); 00390 } else if (!strcasecmp(var->name, "greeting")) { 00391 dfltGreeting = atoi(var->value); 00392 } else if (!strcasecmp(var->name, "after_greeting_silence")) { 00393 dfltAfterGreetingSilence = atoi(var->value); 00394 } else if (!strcasecmp(var->name, "silence_threshold")) { 00395 dfltSilenceThreshold = atoi(var->value); 00396 } else if (!strcasecmp(var->name, "total_analysis_time")) { 00397 dfltTotalAnalysisTime = atoi(var->value); 00398 } else if (!strcasecmp(var->name, "min_word_length")) { 00399 dfltMinimumWordLength = atoi(var->value); 00400 } else if (!strcasecmp(var->name, "between_words_silence")) { 00401 dfltBetweenWordsSilence = atoi(var->value); 00402 } else if (!strcasecmp(var->name, "maximum_number_of_words")) { 00403 dfltMaximumNumberOfWords = atoi(var->value); 00404 } else if (!strcasecmp(var->name, "maximum_word_length")) { 00405 dfltMaximumWordLength = atoi(var->value); 00406 00407 } else { 00408 ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n", 00409 app, cat, var->name, var->lineno); 00410 } 00411 var = var->next; 00412 } 00413 } 00414 cat = ast_category_browse(cfg, cat); 00415 } 00416 00417 ast_config_destroy(cfg); 00418 00419 ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " 00420 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n", 00421 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime, 00422 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength); 00423 00424 return 0; 00425 }
static int load_module | ( | void | ) | [static] |
Definition at line 432 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().
00433 { 00434 if (load_config(0)) 00435 return AST_MODULE_LOAD_DECLINE; 00436 if (ast_register_application(app, amd_exec, synopsis, descrip)) 00437 return AST_MODULE_LOAD_FAILURE; 00438 return AST_MODULE_LOAD_SUCCESS; 00439 }
static int reload | ( | void | ) | [static] |
Definition at line 441 of file app_amd.c.
References AST_MODULE_LOAD_DECLINE, AST_MODULE_LOAD_SUCCESS, and load_config().
00442 { 00443 if (load_config(1)) 00444 return AST_MODULE_LOAD_DECLINE; 00445 return AST_MODULE_LOAD_SUCCESS; 00446 }
static int unload_module | ( | void | ) | [static] |
Definition at line 427 of file app_amd.c.
References ast_unregister_application().
00428 { 00429 return ast_unregister_application(app); 00430 }
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] |