#include "asterisk.h"
#include <stdio.h>
#include <stdlib.h>
#include "asterisk/module.h"
#include "asterisk/lock.h"
#include "asterisk/options.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 void | load_config (void) |
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 | AST_MODFLAG_BUILDSUM, .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 = "6989f2ec67f8497e38c12890500c525b" , .load = load_module, .unload = unload_module, .reload = reload, } |
static char * | app = "AMD" |
static const 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 | dfltMaxWaitTimeForFrame = 50 |
static int | dfltMinimumWordLength = 100 |
static int | dfltSilenceThreshold = 256 |
static int | dfltTotalAnalysisTime = 5000 |
static char * | synopsis = "Attempts to detect answering machines" |
#define STATE_IN_SILENCE 2 |
#define STATE_IN_WORD 1 |
static int amd_exec | ( | struct ast_channel * | chan, | |
void * | data | |||
) | [static] |
Definition at line 344 of file app_amd.c.
References ast_module_user_add, ast_module_user_remove, ast_module_user::chan, and isAnsweringMachine().
Referenced by load_module().
00345 { 00346 struct ast_module_user *u = NULL; 00347 00348 u = ast_module_user_add(chan); 00349 isAnsweringMachine(chan, data); 00350 ast_module_user_remove(u); 00351 00352 return 0; 00353 }
static void isAnsweringMachine | ( | struct ast_channel * | chan, | |
void * | data | |||
) | [static] |
Definition at line 92 of file app_amd.c.
References AST_APP_ARG, ast_codec_get_samples(), 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_verbose(), ast_waitfor(), ast_channel::cid, ast_callerid::cid_ani, ast_callerid::cid_rdnis, DEFAULT_SAMPLES_PER_MS, f, LOG_DEBUG, LOG_WARNING, ast_channel::name, option_debug, option_verbose, parse(), pbx_builtin_setvar_helper(), ast_channel::readformat, STATE_IN_SILENCE, STATE_IN_WORD, and VERBOSE_PREFIX_3.
Referenced by amd_exec().
00093 { 00094 int res = 0; 00095 struct ast_frame *f = NULL; 00096 struct ast_dsp *silenceDetector = NULL; 00097 int dspsilence = 0, readFormat, framelength = 0; 00098 int inInitialSilence = 1; 00099 int inGreeting = 0; 00100 int voiceDuration = 0; 00101 int silenceDuration = 0; 00102 int iTotalTime = 0; 00103 int iWordsCount = 0; 00104 int currentState = STATE_IN_WORD; 00105 int previousState = STATE_IN_SILENCE; 00106 int consecutiveVoiceDuration = 0; 00107 char amdCause[256] = "", amdStatus[256] = ""; 00108 char *parse = ast_strdupa(data); 00109 00110 /* Lets set the initial values of the variables that will control the algorithm. 00111 The initial values are the default ones. If they are passed as arguments 00112 when invoking the application, then the default values will be overwritten 00113 by the ones passed as parameters. */ 00114 int initialSilence = dfltInitialSilence; 00115 int greeting = dfltGreeting; 00116 int afterGreetingSilence = dfltAfterGreetingSilence; 00117 int totalAnalysisTime = dfltTotalAnalysisTime; 00118 int minimumWordLength = dfltMinimumWordLength; 00119 int betweenWordsSilence = dfltBetweenWordsSilence; 00120 int maximumNumberOfWords = dfltMaximumNumberOfWords; 00121 int silenceThreshold = dfltSilenceThreshold; 00122 int maxWaitTimeForFrame = dfltMaxWaitTimeForFrame; 00123 00124 AST_DECLARE_APP_ARGS(args, 00125 AST_APP_ARG(argInitialSilence); 00126 AST_APP_ARG(argGreeting); 00127 AST_APP_ARG(argAfterGreetingSilence); 00128 AST_APP_ARG(argTotalAnalysisTime); 00129 AST_APP_ARG(argMinimumWordLength); 00130 AST_APP_ARG(argBetweenWordsSilence); 00131 AST_APP_ARG(argMaximumNumberOfWords); 00132 AST_APP_ARG(argSilenceThreshold); 00133 ); 00134 00135 if (option_verbose > 2) 00136 ast_verbose(VERBOSE_PREFIX_3 "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat); 00137 00138 /* Lets parse the arguments. */ 00139 if (!ast_strlen_zero(parse)) { 00140 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */ 00141 AST_STANDARD_APP_ARGS(args, parse); 00142 if (!ast_strlen_zero(args.argInitialSilence)) 00143 initialSilence = atoi(args.argInitialSilence); 00144 if (!ast_strlen_zero(args.argGreeting)) 00145 greeting = atoi(args.argGreeting); 00146 if (!ast_strlen_zero(args.argAfterGreetingSilence)) 00147 afterGreetingSilence = atoi(args.argAfterGreetingSilence); 00148 if (!ast_strlen_zero(args.argTotalAnalysisTime)) 00149 totalAnalysisTime = atoi(args.argTotalAnalysisTime); 00150 if (!ast_strlen_zero(args.argMinimumWordLength)) 00151 minimumWordLength = atoi(args.argMinimumWordLength); 00152 if (!ast_strlen_zero(args.argBetweenWordsSilence)) 00153 betweenWordsSilence = atoi(args.argBetweenWordsSilence); 00154 if (!ast_strlen_zero(args.argMaximumNumberOfWords)) 00155 maximumNumberOfWords = atoi(args.argMaximumNumberOfWords); 00156 if (!ast_strlen_zero(args.argSilenceThreshold)) 00157 silenceThreshold = atoi(args.argSilenceThreshold); 00158 } else if (option_debug) 00159 ast_log(LOG_DEBUG, "AMD using the default parameters.\n"); 00160 00161 /* Find lowest ms value, that will be max wait time for a frame */ 00162 if (maxWaitTimeForFrame > initialSilence) 00163 maxWaitTimeForFrame = initialSilence; 00164 if (maxWaitTimeForFrame > greeting) 00165 maxWaitTimeForFrame = greeting; 00166 if (maxWaitTimeForFrame > afterGreetingSilence) 00167 maxWaitTimeForFrame = afterGreetingSilence; 00168 if (maxWaitTimeForFrame > totalAnalysisTime) 00169 maxWaitTimeForFrame = totalAnalysisTime; 00170 if (maxWaitTimeForFrame > minimumWordLength) 00171 maxWaitTimeForFrame = minimumWordLength; 00172 if (maxWaitTimeForFrame > betweenWordsSilence) 00173 maxWaitTimeForFrame = betweenWordsSilence; 00174 00175 /* Now we're ready to roll! */ 00176 if (option_verbose > 2) 00177 ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " 00178 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n", 00179 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime, 00180 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold ); 00181 00182 /* Set read format to signed linear so we get signed linear frames in */ 00183 readFormat = chan->readformat; 00184 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) { 00185 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name ); 00186 pbx_builtin_setvar_helper(chan , "AMDSTATUS", ""); 00187 pbx_builtin_setvar_helper(chan , "AMDCAUSE", ""); 00188 return; 00189 } 00190 00191 /* Create a new DSP that will detect the silence */ 00192 if (!(silenceDetector = ast_dsp_new())) { 00193 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name ); 00194 pbx_builtin_setvar_helper(chan , "AMDSTATUS", ""); 00195 pbx_builtin_setvar_helper(chan , "AMDCAUSE", ""); 00196 return; 00197 } 00198 00199 /* Set silence threshold to specified value */ 00200 ast_dsp_set_threshold(silenceDetector, silenceThreshold); 00201 00202 /* Now we go into a loop waiting for frames from the channel */ 00203 while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) { 00204 00205 /* If we fail to read in a frame, that means they hung up */ 00206 if (!(f = ast_read(chan))) { 00207 if (option_verbose > 2) 00208 ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n"); 00209 if (option_debug) 00210 ast_log(LOG_DEBUG, "Got hangup\n"); 00211 strcpy(amdStatus, "HANGUP"); 00212 res = 1; 00213 break; 00214 } 00215 00216 if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) { 00217 /* If the total time exceeds the analysis time then give up as we are not too sure */ 00218 if (f->frametype == AST_FRAME_VOICE) 00219 framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS); 00220 else 00221 framelength += 2 * maxWaitTimeForFrame; 00222 00223 iTotalTime += framelength; 00224 if (iTotalTime >= totalAnalysisTime) { 00225 if (option_verbose > 2) 00226 ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name ); 00227 ast_frfree(f); 00228 strcpy(amdStatus , "NOTSURE"); 00229 sprintf(amdCause , "TOOLONG-%d", iTotalTime); 00230 break; 00231 } 00232 00233 /* Feed the frame of audio into the silence detector and see if we get a result */ 00234 if (f->frametype != AST_FRAME_VOICE) 00235 dspsilence += 2 * maxWaitTimeForFrame; 00236 else { 00237 dspsilence = 0; 00238 ast_dsp_silence(silenceDetector, f, &dspsilence); 00239 } 00240 00241 if (dspsilence > 0) { 00242 silenceDuration = dspsilence; 00243 00244 if (silenceDuration >= betweenWordsSilence) { 00245 if (currentState != STATE_IN_SILENCE ) { 00246 previousState = currentState; 00247 if (option_verbose > 2) 00248 ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n"); 00249 } 00250 currentState = STATE_IN_SILENCE; 00251 consecutiveVoiceDuration = 0; 00252 } 00253 00254 if (inInitialSilence == 1 && silenceDuration >= initialSilence) { 00255 if (option_verbose > 2) 00256 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n", 00257 silenceDuration, initialSilence); 00258 ast_frfree(f); 00259 strcpy(amdStatus , "MACHINE"); 00260 sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence); 00261 res = 1; 00262 break; 00263 } 00264 00265 if (silenceDuration >= afterGreetingSilence && inGreeting == 1) { 00266 if (option_verbose > 2) 00267 ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n", 00268 silenceDuration, afterGreetingSilence); 00269 ast_frfree(f); 00270 strcpy(amdStatus , "HUMAN"); 00271 sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence); 00272 res = 1; 00273 break; 00274 } 00275 00276 } else { 00277 consecutiveVoiceDuration += framelength; 00278 voiceDuration += framelength; 00279 00280 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the 00281 number of words if my previous state was Silence, which means that I moved into a word. */ 00282 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) { 00283 iWordsCount++; 00284 if (option_verbose > 2) 00285 ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount); 00286 previousState = currentState; 00287 currentState = STATE_IN_WORD; 00288 } 00289 00290 if (iWordsCount >= maximumNumberOfWords) { 00291 if (option_verbose > 2) 00292 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount); 00293 ast_frfree(f); 00294 strcpy(amdStatus , "MACHINE"); 00295 sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords); 00296 res = 1; 00297 break; 00298 } 00299 00300 if (inGreeting == 1 && voiceDuration >= greeting) { 00301 if (option_verbose > 2) 00302 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting); 00303 ast_frfree(f); 00304 strcpy(amdStatus , "MACHINE"); 00305 sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting); 00306 res = 1; 00307 break; 00308 } 00309 00310 if (voiceDuration >= minimumWordLength ) { 00311 silenceDuration = 0; 00312 inInitialSilence = 0; 00313 inGreeting = 1; 00314 } 00315 00316 } 00317 } 00318 ast_frfree(f); 00319 } 00320 00321 if (!res) { 00322 /* It took too long to get a frame back. Giving up. */ 00323 if (option_verbose > 2) 00324 ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name); 00325 strcpy(amdStatus , "NOTSURE"); 00326 sprintf(amdCause , "TOOLONG-%d", iTotalTime); 00327 } 00328 00329 /* Set the status and cause on the channel */ 00330 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus); 00331 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause); 00332 00333 /* Restore channel read format */ 00334 if (readFormat && ast_set_read_format(chan, readFormat)) 00335 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name); 00336 00337 /* Free the DSP used to detect silence */ 00338 ast_dsp_free(silenceDetector); 00339 00340 return; 00341 }
static void load_config | ( | void | ) | [static] |
Definition at line 355 of file app_amd.c.
References ast_category_browse(), ast_config_destroy(), ast_config_load(), ast_log(), ast_variable_browse(), ast_verbose(), LOG_ERROR, LOG_WARNING, option_verbose, var, and VERBOSE_PREFIX_3.
00356 { 00357 struct ast_config *cfg = NULL; 00358 char *cat = NULL; 00359 struct ast_variable *var = NULL; 00360 00361 if (!(cfg = ast_config_load("amd.conf"))) { 00362 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n"); 00363 return; 00364 } 00365 00366 cat = ast_category_browse(cfg, NULL); 00367 00368 while (cat) { 00369 if (!strcasecmp(cat, "general") ) { 00370 var = ast_variable_browse(cfg, cat); 00371 while (var) { 00372 if (!strcasecmp(var->name, "initial_silence")) { 00373 dfltInitialSilence = atoi(var->value); 00374 } else if (!strcasecmp(var->name, "greeting")) { 00375 dfltGreeting = atoi(var->value); 00376 } else if (!strcasecmp(var->name, "after_greeting_silence")) { 00377 dfltAfterGreetingSilence = atoi(var->value); 00378 } else if (!strcasecmp(var->name, "silence_threshold")) { 00379 dfltSilenceThreshold = atoi(var->value); 00380 } else if (!strcasecmp(var->name, "total_analysis_time")) { 00381 dfltTotalAnalysisTime = atoi(var->value); 00382 } else if (!strcasecmp(var->name, "min_word_length")) { 00383 dfltMinimumWordLength = atoi(var->value); 00384 } else if (!strcasecmp(var->name, "between_words_silence")) { 00385 dfltBetweenWordsSilence = atoi(var->value); 00386 } else if (!strcasecmp(var->name, "maximum_number_of_words")) { 00387 dfltMaximumNumberOfWords = atoi(var->value); 00388 } else { 00389 ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n", 00390 app, cat, var->name, var->lineno); 00391 } 00392 var = var->next; 00393 } 00394 } 00395 cat = ast_category_browse(cfg, cat); 00396 } 00397 00398 ast_config_destroy(cfg); 00399 00400 if (option_verbose > 2) 00401 ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " 00402 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n", 00403 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime, 00404 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold ); 00405 00406 return; 00407 }
static int load_module | ( | void | ) | [static] |
Definition at line 415 of file app_amd.c.
References amd_exec(), ast_register_application(), and load_config().
00416 { 00417 load_config(); 00418 return ast_register_application(app, amd_exec, synopsis, descrip); 00419 }
static int reload | ( | void | ) | [static] |
Definition at line 421 of file app_amd.c.
References load_config().
00422 { 00423 load_config(); 00424 return 0; 00425 }
static int unload_module | ( | void | ) | [static] |
Definition at line 409 of file app_amd.c.
References ast_module_user_hangup_all, and ast_unregister_application().
00410 { 00411 ast_module_user_hangup_all(); 00412 return ast_unregister_application(app); 00413 }
struct ast_module_info __mod_info = { .name = AST_MODULE, .flags = AST_MODFLAG_DEFAULT | AST_MODFLAG_BUILDSUM, .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 = "6989f2ec67f8497e38c12890500c525b" , .load = load_module, .unload = unload_module, .reload = reload, } [static] |
const 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 dfltMaxWaitTimeForFrame = 50 [static] |
int dfltMinimumWordLength = 100 [static] |
int dfltSilenceThreshold = 256 [static] |
int dfltTotalAnalysisTime = 5000 [static] |
char* synopsis = "Attempts to detect answering machines" [static] |