/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2005, Digium, Inc. * * Mark Spencer * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ #include #include #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" static char *tdesc = "Answering Machine Detection Application"; static char *app = "AMD"; static char *synopsis = "attempts to detect answering machines"; static char *descrip = " This application attempts to detect answering machines at the beginning\n" "of outbound calls. Simply call this application after the call\n" "has been answered (outbound only, of course).\n\n" "This application sets the following channel variable upon completion:\n" " AMDSTATUS - This is the status of the answering machine detection.\n" " Possible values are:\n" " AMD_MACHINE | AMD_PERSON | AMD_NOTSURE | AMD_HANGUP\n" " AMDCAUSE - Indicates the cause that led to the conclusion.\n" " Possible values are:\n" " AMD_TOOLONG-<%d total_time>\n" " AMD_INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n" " AMD_HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n" " AMD_MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n" " AMD_LONGGREETING-<%d voiceDuration>-<%d greeting>\n"; STANDARD_LOCAL_USER; LOCAL_USER_DECL; #define STATE_IN_WORD 1 #define STATE_IN_SILENCE 2 // Some default values for the algorithm parameters. static int initialSilence = 2500; static int greeting = 1500; static int afterGreetingSilence = 800; static int silenceThreshold = 256; static int totalAnalysisTime = 5000; static int minimumWordLength = 100; static int betweenWordsSilence = 50; static int maximumNumberOfWords = 3; static void isAnsweringMachine(struct ast_channel *chan) { int res = 0; struct ast_frame *f = NULL; struct ast_dsp *silenceDetector; /* silence detector dsp */ int dspsilence = 0; int readFormat; int inInitialSilence = 1; int inGreeting = 0; int voiceDuration = 0; int silenceDuration = 0; int iTotalTime = 0; int iWordsCount = 0; int currentState = STATE_IN_SILENCE; int previousState = STATE_IN_SILENCE; int consecutiveVoiceDuration = 0; char amdCause[256] = ""; char amdStatus[256] = ""; ast_verbose(VERBOSE_PREFIX_3 "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat ); readFormat = chan->readformat; res = ast_set_read_format(chan, AST_FORMAT_SLINEAR); if( res < 0 ) { ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name ); pbx_builtin_setvar_helper( chan , "AMDSTATUS" , "" ); pbx_builtin_setvar_helper( chan , "AMDCAUSE" , "" ); return; } silenceDetector = ast_dsp_new(); if( !silenceDetector ) { ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name ); pbx_builtin_setvar_helper( chan , "AMDSTATUS" , "" ); pbx_builtin_setvar_helper( chan , "AMDCAUSE" , "" ); return; } ast_dsp_set_threshold( silenceDetector, silenceThreshold ); while (ast_waitfor(chan, -1) > -1) { f = ast_read(chan); if( !f ) { // No Frame: Called Party Must Have Dropped ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n"); ast_log(LOG_DEBUG, "Got hangup\n"); strcpy( amdStatus , "AMD_HANGUP" ); strcpy( amdCause , "" ); break; } iTotalTime += 20; if( iTotalTime >= totalAnalysisTime ) { ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name ); ast_frfree(f); strcpy( amdStatus , "AMD_NOTSURE" ); sprintf( amdCause , "AMD_TOOLONG-%d", iTotalTime ); break; } if( f->frametype == AST_FRAME_VOICE ) { dspsilence = 0; ast_dsp_silence(silenceDetector, f, &dspsilence); if( dspsilence ) { silenceDuration = dspsilence; // ast_verbose(VERBOSE_PREFIX_3 "AMD: %d SILENCE: silenceDuration:%d afterGreetingSilence:%d inGreeting:%d\n", currentState, silenceDuration, afterGreetingSilence, inGreeting ); if( silenceDuration >= betweenWordsSilence ) { if( currentState != STATE_IN_SILENCE ) { previousState = currentState; ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n"); } currentState = STATE_IN_SILENCE; consecutiveVoiceDuration = 0; } if( inInitialSilence == 1 && silenceDuration >= initialSilence ) { ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n", silenceDuration, initialSilence ); ast_frfree(f); strcpy( amdStatus , "AMD_MACHINE" ); sprintf( amdCause , "AMD_INITIALSILENCE-%d-%d", silenceDuration, initialSilence ); break; } if( silenceDuration >= afterGreetingSilence && inGreeting == 1 ) { ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n", silenceDuration, afterGreetingSilence ); ast_frfree(f); strcpy( amdStatus , "AMD_PERSON" ); sprintf( amdCause , "AMD_HUMAN-%d-%d", silenceDuration, afterGreetingSilence ); break; } } else { consecutiveVoiceDuration += 20; voiceDuration += 20; // ast_verbose(VERBOSE_PREFIX_3 "AMD: %d VOICE: ConsecutiveVoice:%d voiceDuration:%d inGreeting:%d\n", currentState, consecutiveVoiceDuration, voiceDuration, inGreeting ); // If I have enough consecutive voice to say that I am in a Word, I can only increment the // number of words if my previous state was Silence, which means that I moved into a word. if( consecutiveVoiceDuration >= minimumWordLength ) { if( currentState == STATE_IN_SILENCE ) { iWordsCount++; ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount ); previousState = currentState; currentState = STATE_IN_WORD; } } if( iWordsCount >= maximumNumberOfWords ) { ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount ); ast_frfree(f); strcpy( amdStatus , "AMD_MACHINE" ); sprintf( amdCause , "AMD_MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords ); break; } if( inGreeting == 1 && voiceDuration >= greeting ) { ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting ); ast_frfree(f); strcpy( amdStatus , "AMD_MACHINE" ); sprintf( amdCause , "AMD_LONGGREETING-%d-%d", voiceDuration, greeting ); break; } if( voiceDuration >= minimumWordLength ) { silenceDuration = 0; inInitialSilence = 0; inGreeting = 1; } } } ast_frfree(f); } pbx_builtin_setvar_helper( chan , "AMDSTATUS" , amdStatus ); pbx_builtin_setvar_helper( chan , "AMDCAUSE" , amdCause ); // If We Started With A Valid Read Format, Return To It... if (readFormat) { res = ast_set_read_format( chan, readFormat ); if (res) ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name); } // Free The Silence Detector DSP ast_dsp_free( silenceDetector ); // Return The Result return; } static int amd_exec(struct ast_channel *chan, void *data) { struct localuser *u; LOCAL_USER_ADD(u); /* Do our thing here */ isAnsweringMachine(chan); LOCAL_USER_REMOVE(u); return 0; } static void reload_conf(void) { struct ast_config *cfg; char *cat; struct ast_variable *var; cfg = ast_config_load("amd.conf"); if (!cfg) { ast_log( LOG_NOTICE, "No Atelka config file.\n"); return; } cat = ast_category_browse(cfg, NULL); while(cat) { if( !strcasecmp(cat, "AnsweringMachineDetector") ) { var = ast_variable_browse(cfg, cat); while(var) { if (!strcasecmp(var->name, "initial_silence")) { initialSilence = atoi(var->value); } else if (!strcasecmp(var->name, "greeting")) { greeting = atoi(var->value); } else if (!strcasecmp(var->name, "after_greeting_silence")) { afterGreetingSilence = atoi(var->value); } else if (!strcasecmp(var->name, "silence_threshold")) { silenceThreshold = atoi(var->value); } else if (!strcasecmp(var->name, "total_analysis_time")) { totalAnalysisTime = atoi(var->value); } else if (!strcasecmp(var->name, "min_word_length")) { minimumWordLength = atoi(var->value); } else if (!strcasecmp(var->name, "between_words_silence")) { betweenWordsSilence = atoi(var->value); } else if (!strcasecmp(var->name, "maximum_number_of_words")) { maximumNumberOfWords = atoi(var->value); } else { ast_log( LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of akconfig.conf\n", app, cat, var->name, var->lineno); } var = var->next; } } cat = ast_category_browse(cfg, cat); } ast_config_destroy(cfg); ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] silenceThreshold [%d] totalAnalysisTime [%d]\n", initialSilence, greeting, afterGreetingSilence, silenceThreshold, totalAnalysisTime ); return; } int unload_module(void) { STANDARD_HANGUP_LOCALUSERS; return ast_unregister_application(app); } int load_module(void) { reload_conf(); return ast_register_application(app, amd_exec, synopsis, tdesc); } int reload(void) { reload_conf(); return 0; } char *description(void) { return tdesc; } int usecount(void) { int res; STANDARD_USECOUNT(res); return res; } char *key() { return ASTERISK_GPL_KEY; }