Mon Nov 24 15:34:22 2008

Asterisk developer's documentation


app_amd.c File Reference

#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 int amd_exec (struct ast_channel *chan, void *data)
 AST_MODULE_INFO (ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT,"Answering Machine Detection Application",.load=load_module,.unload=unload_module,.reload=reload,)
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 char * app = "AMD"
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 Documentation

#define STATE_IN_SILENCE   2

Definition at line 77 of file app_amd.c.

Referenced by isAnsweringMachine().

#define STATE_IN_WORD   1

Definition at line 76 of file app_amd.c.

Referenced by isAnsweringMachine().


Function Documentation

static int amd_exec ( struct ast_channel chan,
void *  data 
) [static]

Definition at line 343 of file app_amd.c.

References ast_module_user_add, ast_module_user_remove, ast_module_user::chan, and isAnsweringMachine().

Referenced by load_module().

00344 {
00345    struct ast_module_user *u = NULL;
00346 
00347    u = ast_module_user_add(chan);
00348    isAnsweringMachine(chan, data);
00349    ast_module_user_remove(u);
00350 
00351    return 0;
00352 }

AST_MODULE_INFO ( ASTERISK_GPL_KEY  ,
AST_MODFLAG_DEFAULT  ,
"Answering Machine Detection Application"  ,
load = load_module,
unload = unload_module,
reload = reload 
)

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, 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          break;
00213       }
00214 
00215       if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
00216          /* If the total time exceeds the analysis time then give up as we are not too sure */
00217          if (f->frametype == AST_FRAME_VOICE)
00218             framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
00219          else
00220             framelength += 2 * maxWaitTimeForFrame;
00221 
00222          iTotalTime += framelength;
00223          if (iTotalTime >= totalAnalysisTime) {
00224             if (option_verbose > 2) 
00225                ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
00226             ast_frfree(f);
00227             strcpy(amdStatus , "NOTSURE");
00228             sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00229             break;
00230          }
00231 
00232          /* Feed the frame of audio into the silence detector and see if we get a result */
00233          if (f->frametype != AST_FRAME_VOICE)
00234             dspsilence += 2 * maxWaitTimeForFrame;
00235          else {
00236             dspsilence = 0;
00237             ast_dsp_silence(silenceDetector, f, &dspsilence);
00238          }
00239 
00240          if (dspsilence > 0) {
00241             silenceDuration = dspsilence;
00242             
00243             if (silenceDuration >= betweenWordsSilence) {
00244                if (currentState != STATE_IN_SILENCE ) {
00245                   previousState = currentState;
00246                   if (option_verbose > 2)
00247                      ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
00248                }
00249                currentState  = STATE_IN_SILENCE;
00250                consecutiveVoiceDuration = 0;
00251             }
00252             
00253             if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
00254                if (option_verbose > 2)
00255                   ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
00256                          silenceDuration, initialSilence);
00257                ast_frfree(f);
00258                strcpy(amdStatus , "MACHINE");
00259                sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
00260                res = 1;
00261                break;
00262             }
00263             
00264             if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
00265                if (option_verbose > 2)
00266                   ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
00267                          silenceDuration, afterGreetingSilence);
00268                ast_frfree(f);
00269                strcpy(amdStatus , "HUMAN");
00270                sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
00271                res = 1;
00272                break;
00273             }
00274             
00275          } else {
00276             consecutiveVoiceDuration += framelength;
00277             voiceDuration += framelength;
00278             
00279             /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
00280                number of words if my previous state was Silence, which means that I moved into a word. */
00281             if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
00282                iWordsCount++;
00283                if (option_verbose > 2)
00284                   ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount);
00285                previousState = currentState;
00286                currentState = STATE_IN_WORD;
00287             }
00288             
00289             if (iWordsCount >= maximumNumberOfWords) {
00290                if (option_verbose > 2)
00291                   ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount);
00292                ast_frfree(f);
00293                strcpy(amdStatus , "MACHINE");
00294                sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
00295                res = 1;
00296                break;
00297             }
00298             
00299             if (inGreeting == 1 && voiceDuration >= greeting) {
00300                if (option_verbose > 2)
00301                   ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting);
00302                ast_frfree(f);
00303                strcpy(amdStatus , "MACHINE");
00304                sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
00305                res = 1;
00306                break;
00307             }
00308             
00309             if (voiceDuration >= minimumWordLength ) {
00310                silenceDuration = 0;
00311                inInitialSilence = 0;
00312                inGreeting = 1;
00313             }
00314             
00315          }
00316       }
00317       ast_frfree(f);
00318    }
00319    
00320    if (!res) {
00321       /* It took too long to get a frame back. Giving up. */
00322       if (option_verbose > 2)
00323          ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name);
00324       strcpy(amdStatus , "NOTSURE");
00325       sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00326    }
00327 
00328    /* Set the status and cause on the channel */
00329    pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
00330    pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
00331 
00332    /* Restore channel read format */
00333    if (readFormat && ast_set_read_format(chan, readFormat))
00334       ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
00335 
00336    /* Free the DSP used to detect silence */
00337    ast_dsp_free(silenceDetector);
00338 
00339    return;
00340 }

static void load_config ( void   )  [static]

Definition at line 354 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.

00355 {
00356    struct ast_config *cfg = NULL;
00357    char *cat = NULL;
00358    struct ast_variable *var = NULL;
00359 
00360    if (!(cfg = ast_config_load("amd.conf"))) {
00361       ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
00362       return;
00363    }
00364 
00365    cat = ast_category_browse(cfg, NULL);
00366 
00367    while (cat) {
00368       if (!strcasecmp(cat, "general") ) {
00369          var = ast_variable_browse(cfg, cat);
00370          while (var) {
00371             if (!strcasecmp(var->name, "initial_silence")) {
00372                dfltInitialSilence = atoi(var->value);
00373             } else if (!strcasecmp(var->name, "greeting")) {
00374                dfltGreeting = atoi(var->value);
00375             } else if (!strcasecmp(var->name, "after_greeting_silence")) {
00376                dfltAfterGreetingSilence = atoi(var->value);
00377             } else if (!strcasecmp(var->name, "silence_threshold")) {
00378                dfltSilenceThreshold = atoi(var->value);
00379             } else if (!strcasecmp(var->name, "total_analysis_time")) {
00380                dfltTotalAnalysisTime = atoi(var->value);
00381             } else if (!strcasecmp(var->name, "min_word_length")) {
00382                dfltMinimumWordLength = atoi(var->value);
00383             } else if (!strcasecmp(var->name, "between_words_silence")) {
00384                dfltBetweenWordsSilence = atoi(var->value);
00385             } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
00386                dfltMaximumNumberOfWords = atoi(var->value);
00387             } else {
00388                ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
00389                   app, cat, var->name, var->lineno);
00390             }
00391             var = var->next;
00392          }
00393       }
00394       cat = ast_category_browse(cfg, cat);
00395    }
00396 
00397    ast_config_destroy(cfg);
00398 
00399    if (option_verbose > 2)
00400       ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00401       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
00402             dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
00403             dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
00404 
00405    return;
00406 }

static int load_module ( void   )  [static]

Definition at line 414 of file app_amd.c.

References amd_exec(), ast_register_application(), and load_config().

00415 {
00416    load_config();
00417    return ast_register_application(app, amd_exec, synopsis, descrip);
00418 }

static int reload ( void   )  [static]

Definition at line 420 of file app_amd.c.

References load_config().

00421 {
00422    load_config();
00423    return 0;
00424 }

static int unload_module ( void   )  [static]

Definition at line 408 of file app_amd.c.

References ast_module_user_hangup_all, and ast_unregister_application().

00409 {
00410    ast_module_user_hangup_all();
00411    return ast_unregister_application(app);
00412 }


Variable Documentation

char* app = "AMD" [static]

Definition at line 39 of file app_amd.c.

char* descrip [static]

Definition at line 41 of file app_amd.c.

int dfltAfterGreetingSilence = 800 [static]

Definition at line 82 of file app_amd.c.

int dfltBetweenWordsSilence = 50 [static]

Definition at line 85 of file app_amd.c.

int dfltGreeting = 1500 [static]

Definition at line 81 of file app_amd.c.

int dfltInitialSilence = 2500 [static]

Definition at line 80 of file app_amd.c.

int dfltMaximumNumberOfWords = 3 [static]

Definition at line 86 of file app_amd.c.

int dfltMaxWaitTimeForFrame = 50 [static]

Definition at line 90 of file app_amd.c.

int dfltMinimumWordLength = 100 [static]

Definition at line 84 of file app_amd.c.

int dfltSilenceThreshold = 256 [static]

Definition at line 87 of file app_amd.c.

int dfltTotalAnalysisTime = 5000 [static]

Definition at line 83 of file app_amd.c.

char* synopsis = "Attempts to detect answering machines" [static]

Definition at line 40 of file app_amd.c.


Generated on Mon Nov 24 15:34:22 2008 for Asterisk - the Open Source PBX by  doxygen 1.4.7