#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 = "361d7bb937402d51e4658efb5b4d76e4" , .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 345 of file app_amd.c.
References ast_module_user_add, ast_module_user_remove, ast_module_user::chan, and isAnsweringMachine().
Referenced by load_module().
00346 { 00347 struct ast_module_user *u = NULL; 00348 00349 u = ast_module_user_add(chan); 00350 isAnsweringMachine(chan, data); 00351 ast_module_user_remove(u); 00352 00353 return 0; 00354 }
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 00224 iTotalTime += framelength; 00225 if (iTotalTime >= totalAnalysisTime) { 00226 if (option_verbose > 2) 00227 ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name ); 00228 ast_frfree(f); 00229 strcpy(amdStatus , "NOTSURE"); 00230 sprintf(amdCause , "TOOLONG-%d", iTotalTime); 00231 break; 00232 } 00233 00234 /* Feed the frame of audio into the silence detector and see if we get a result */ 00235 if (f->frametype != AST_FRAME_VOICE) 00236 dspsilence += 2 * maxWaitTimeForFrame; 00237 else { 00238 dspsilence = 0; 00239 ast_dsp_silence(silenceDetector, f, &dspsilence); 00240 } 00241 00242 if (dspsilence > 0) { 00243 silenceDuration = dspsilence; 00244 00245 if (silenceDuration >= betweenWordsSilence) { 00246 if (currentState != STATE_IN_SILENCE ) { 00247 previousState = currentState; 00248 if (option_verbose > 2) 00249 ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n"); 00250 } 00251 currentState = STATE_IN_SILENCE; 00252 consecutiveVoiceDuration = 0; 00253 } 00254 00255 if (inInitialSilence == 1 && silenceDuration >= initialSilence) { 00256 if (option_verbose > 2) 00257 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n", 00258 silenceDuration, initialSilence); 00259 ast_frfree(f); 00260 strcpy(amdStatus , "MACHINE"); 00261 sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence); 00262 res = 1; 00263 break; 00264 } 00265 00266 if (silenceDuration >= afterGreetingSilence && inGreeting == 1) { 00267 if (option_verbose > 2) 00268 ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n", 00269 silenceDuration, afterGreetingSilence); 00270 ast_frfree(f); 00271 strcpy(amdStatus , "HUMAN"); 00272 sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence); 00273 res = 1; 00274 break; 00275 } 00276 00277 } else { 00278 consecutiveVoiceDuration += framelength; 00279 voiceDuration += framelength; 00280 00281 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the 00282 number of words if my previous state was Silence, which means that I moved into a word. */ 00283 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) { 00284 iWordsCount++; 00285 if (option_verbose > 2) 00286 ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount); 00287 previousState = currentState; 00288 currentState = STATE_IN_WORD; 00289 } 00290 00291 if (iWordsCount >= maximumNumberOfWords) { 00292 if (option_verbose > 2) 00293 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount); 00294 ast_frfree(f); 00295 strcpy(amdStatus , "MACHINE"); 00296 sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords); 00297 res = 1; 00298 break; 00299 } 00300 00301 if (inGreeting == 1 && voiceDuration >= greeting) { 00302 if (option_verbose > 2) 00303 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting); 00304 ast_frfree(f); 00305 strcpy(amdStatus , "MACHINE"); 00306 sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting); 00307 res = 1; 00308 break; 00309 } 00310 00311 if (voiceDuration >= minimumWordLength ) { 00312 silenceDuration = 0; 00313 inInitialSilence = 0; 00314 inGreeting = 1; 00315 } 00316 00317 } 00318 } 00319 ast_frfree(f); 00320 } 00321 00322 if (!res) { 00323 /* It took too long to get a frame back. Giving up. */ 00324 if (option_verbose > 2) 00325 ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name); 00326 strcpy(amdStatus , "NOTSURE"); 00327 sprintf(amdCause , "TOOLONG-%d", iTotalTime); 00328 } 00329 00330 /* Set the status and cause on the channel */ 00331 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus); 00332 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause); 00333 00334 /* Restore channel read format */ 00335 if (readFormat && ast_set_read_format(chan, readFormat)) 00336 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name); 00337 00338 /* Free the DSP used to detect silence */ 00339 ast_dsp_free(silenceDetector); 00340 00341 return; 00342 }
static void load_config | ( | void | ) | [static] |
Definition at line 356 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.
00357 { 00358 struct ast_config *cfg = NULL; 00359 char *cat = NULL; 00360 struct ast_variable *var = NULL; 00361 00362 if (!(cfg = ast_config_load("amd.conf"))) { 00363 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n"); 00364 return; 00365 } 00366 00367 cat = ast_category_browse(cfg, NULL); 00368 00369 while (cat) { 00370 if (!strcasecmp(cat, "general") ) { 00371 var = ast_variable_browse(cfg, cat); 00372 while (var) { 00373 if (!strcasecmp(var->name, "initial_silence")) { 00374 dfltInitialSilence = atoi(var->value); 00375 } else if (!strcasecmp(var->name, "greeting")) { 00376 dfltGreeting = atoi(var->value); 00377 } else if (!strcasecmp(var->name, "after_greeting_silence")) { 00378 dfltAfterGreetingSilence = atoi(var->value); 00379 } else if (!strcasecmp(var->name, "silence_threshold")) { 00380 dfltSilenceThreshold = atoi(var->value); 00381 } else if (!strcasecmp(var->name, "total_analysis_time")) { 00382 dfltTotalAnalysisTime = atoi(var->value); 00383 } else if (!strcasecmp(var->name, "min_word_length")) { 00384 dfltMinimumWordLength = atoi(var->value); 00385 } else if (!strcasecmp(var->name, "between_words_silence")) { 00386 dfltBetweenWordsSilence = atoi(var->value); 00387 } else if (!strcasecmp(var->name, "maximum_number_of_words")) { 00388 dfltMaximumNumberOfWords = atoi(var->value); 00389 } else { 00390 ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n", 00391 app, cat, var->name, var->lineno); 00392 } 00393 var = var->next; 00394 } 00395 } 00396 cat = ast_category_browse(cfg, cat); 00397 } 00398 00399 ast_config_destroy(cfg); 00400 00401 if (option_verbose > 2) 00402 ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] " 00403 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n", 00404 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime, 00405 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold ); 00406 00407 return; 00408 }
static int load_module | ( | void | ) | [static] |
Definition at line 416 of file app_amd.c.
References amd_exec(), ast_register_application(), and load_config().
00417 { 00418 load_config(); 00419 return ast_register_application(app, amd_exec, synopsis, descrip); 00420 }
static int reload | ( | void | ) | [static] |
Definition at line 422 of file app_amd.c.
References load_config().
00423 { 00424 load_config(); 00425 return 0; 00426 }
static int unload_module | ( | void | ) | [static] |
Definition at line 410 of file app_amd.c.
References ast_module_user_hangup_all, and ast_unregister_application().
00411 { 00412 ast_module_user_hangup_all(); 00413 return ast_unregister_application(app); 00414 }
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 = "361d7bb937402d51e4658efb5b4d76e4" , .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] |