Wed Oct 28 13:31:17 2009

Asterisk developer's documentation


app_amd.c File Reference

Answering machine detection. More...

#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"

Include dependency graph for app_amd.c:

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, const char *data)
static void isAnsweringMachine (struct ast_channel *chan, const char *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 = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .reload = reload, }
static const char app [] = "AMD"
static struct ast_module_infoast_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


Detailed Description

Answering machine detection.

Author:
Claude Klimos (claude.klimos@aheeva.com)

Definition in file app_amd.c.


Define Documentation

#define STATE_IN_SILENCE   2

Definition at line 130 of file app_amd.c.

Referenced by isAnsweringMachine().

#define STATE_IN_WORD   1

Definition at line 129 of file app_amd.c.

Referenced by isAnsweringMachine().


Function Documentation

static void __reg_module ( void   )  [static]

Definition at line 503 of file app_amd.c.

static void __unreg_module ( void   )  [static]

Definition at line 503 of file app_amd.c.

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

Definition at line 407 of file app_amd.c.

References isAnsweringMachine().

Referenced by load_module().

00408 {
00409    isAnsweringMachine(chan, data);
00410 
00411    return 0;
00412 }

static void isAnsweringMachine ( struct ast_channel chan,
const char *  data 
) [static]

Definition at line 146 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(), ast_channel::cid, ast_callerid::cid_ani, ast_callerid::cid_rdnis, DEFAULT_SAMPLES_PER_MS, f, ast_frame::frametype, LOG_WARNING, ast_channel::name, parse(), pbx_builtin_setvar_helper(), ast_channel::readformat, STATE_IN_SILENCE, and STATE_IN_WORD.

Referenced by amd_exec().

00147 {
00148    int res = 0;
00149    struct ast_frame *f = NULL;
00150    struct ast_dsp *silenceDetector = NULL;
00151    int dspsilence = 0, readFormat, framelength = 0;
00152    int inInitialSilence = 1;
00153    int inGreeting = 0;
00154    int voiceDuration = 0;
00155    int silenceDuration = 0;
00156    int iTotalTime = 0;
00157    int iWordsCount = 0;
00158    int currentState = STATE_IN_WORD;
00159    int previousState = STATE_IN_SILENCE;
00160    int consecutiveVoiceDuration = 0;
00161    char amdCause[256] = "", amdStatus[256] = "";
00162    char *parse = ast_strdupa(data);
00163 
00164    /* Lets set the initial values of the variables that will control the algorithm.
00165       The initial values are the default ones. If they are passed as arguments
00166       when invoking the application, then the default values will be overwritten
00167       by the ones passed as parameters. */
00168    int initialSilence       = dfltInitialSilence;
00169    int greeting             = dfltGreeting;
00170    int afterGreetingSilence = dfltAfterGreetingSilence;
00171    int totalAnalysisTime    = dfltTotalAnalysisTime;
00172    int minimumWordLength    = dfltMinimumWordLength;
00173    int betweenWordsSilence  = dfltBetweenWordsSilence;
00174    int maximumNumberOfWords = dfltMaximumNumberOfWords;
00175    int silenceThreshold     = dfltSilenceThreshold;
00176    int maximumWordLength    = dfltMaximumWordLength;
00177    int maxWaitTimeForFrame  = dfltMaxWaitTimeForFrame;
00178 
00179    AST_DECLARE_APP_ARGS(args,
00180       AST_APP_ARG(argInitialSilence);
00181       AST_APP_ARG(argGreeting);
00182       AST_APP_ARG(argAfterGreetingSilence);
00183       AST_APP_ARG(argTotalAnalysisTime);
00184       AST_APP_ARG(argMinimumWordLength);
00185       AST_APP_ARG(argBetweenWordsSilence);
00186       AST_APP_ARG(argMaximumNumberOfWords);
00187       AST_APP_ARG(argSilenceThreshold);
00188       AST_APP_ARG(argMaximumWordLength);
00189    );
00190 
00191    ast_verb(3, "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
00192 
00193    /* Lets parse the arguments. */
00194    if (!ast_strlen_zero(parse)) {
00195       /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
00196       AST_STANDARD_APP_ARGS(args, parse);
00197       if (!ast_strlen_zero(args.argInitialSilence))
00198          initialSilence = atoi(args.argInitialSilence);
00199       if (!ast_strlen_zero(args.argGreeting))
00200          greeting = atoi(args.argGreeting);
00201       if (!ast_strlen_zero(args.argAfterGreetingSilence))
00202          afterGreetingSilence = atoi(args.argAfterGreetingSilence);
00203       if (!ast_strlen_zero(args.argTotalAnalysisTime))
00204          totalAnalysisTime = atoi(args.argTotalAnalysisTime);
00205       if (!ast_strlen_zero(args.argMinimumWordLength))
00206          minimumWordLength = atoi(args.argMinimumWordLength);
00207       if (!ast_strlen_zero(args.argBetweenWordsSilence))
00208          betweenWordsSilence = atoi(args.argBetweenWordsSilence);
00209       if (!ast_strlen_zero(args.argMaximumNumberOfWords))
00210          maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
00211       if (!ast_strlen_zero(args.argSilenceThreshold))
00212          silenceThreshold = atoi(args.argSilenceThreshold);
00213       if (!ast_strlen_zero(args.argMaximumWordLength))
00214          maximumWordLength = atoi(args.argMaximumWordLength);
00215    } else {
00216       ast_debug(1, "AMD using the default parameters.\n");
00217    }
00218 
00219    /* Find lowest ms value, that will be max wait time for a frame */
00220    if (maxWaitTimeForFrame > initialSilence)
00221       maxWaitTimeForFrame = initialSilence;
00222    if (maxWaitTimeForFrame > greeting)
00223       maxWaitTimeForFrame = greeting;
00224    if (maxWaitTimeForFrame > afterGreetingSilence)
00225       maxWaitTimeForFrame = afterGreetingSilence;
00226    if (maxWaitTimeForFrame > totalAnalysisTime)
00227       maxWaitTimeForFrame = totalAnalysisTime;
00228    if (maxWaitTimeForFrame > minimumWordLength)
00229       maxWaitTimeForFrame = minimumWordLength;
00230    if (maxWaitTimeForFrame > betweenWordsSilence)
00231       maxWaitTimeForFrame = betweenWordsSilence;
00232 
00233    /* Now we're ready to roll! */
00234    ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00235       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
00236             initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
00237             minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);
00238 
00239    /* Set read format to signed linear so we get signed linear frames in */
00240    readFormat = chan->readformat;
00241    if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
00242       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
00243       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00244       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00245       return;
00246    }
00247 
00248    /* Create a new DSP that will detect the silence */
00249    if (!(silenceDetector = ast_dsp_new())) {
00250       ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
00251       pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
00252       pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
00253       return;
00254    }
00255 
00256    /* Set silence threshold to specified value */
00257    ast_dsp_set_threshold(silenceDetector, silenceThreshold);
00258 
00259    /* Now we go into a loop waiting for frames from the channel */
00260    while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
00261 
00262       /* If we fail to read in a frame, that means they hung up */
00263       if (!(f = ast_read(chan))) {
00264          ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name);
00265          ast_debug(1, "Got hangup\n");
00266          strcpy(amdStatus, "HANGUP");
00267          break;
00268       }
00269 
00270       if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
00271          /* If the total time exceeds the analysis time then give up as we are not too sure */
00272          if (f->frametype == AST_FRAME_VOICE)
00273             framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
00274          else
00275             framelength += 2 * maxWaitTimeForFrame;
00276 
00277          iTotalTime += framelength;
00278          if (iTotalTime >= totalAnalysisTime) {
00279             ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name );
00280             ast_frfree(f);
00281             strcpy(amdStatus , "NOTSURE");
00282             sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00283             break;
00284          }
00285 
00286          /* Feed the frame of audio into the silence detector and see if we get a result */
00287          if (f->frametype != AST_FRAME_VOICE)
00288             dspsilence += 2 * maxWaitTimeForFrame;
00289          else {
00290             dspsilence = 0;
00291             ast_dsp_silence(silenceDetector, f, &dspsilence);
00292          }
00293 
00294          if (dspsilence > 0) {
00295             silenceDuration = dspsilence;
00296             
00297             if (silenceDuration >= betweenWordsSilence) {
00298                if (currentState != STATE_IN_SILENCE ) {
00299                   previousState = currentState;
00300                   ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name);
00301                }
00302                /* Find words less than word duration */
00303                if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
00304                   ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration);
00305                }
00306                currentState  = STATE_IN_SILENCE;
00307                consecutiveVoiceDuration = 0;
00308             }
00309 
00310             if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
00311                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
00312                   chan->name, silenceDuration, initialSilence);
00313                ast_frfree(f);
00314                strcpy(amdStatus , "MACHINE");
00315                sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
00316                res = 1;
00317                break;
00318             }
00319             
00320             if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
00321                ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
00322                   chan->name, silenceDuration, afterGreetingSilence);
00323                ast_frfree(f);
00324                strcpy(amdStatus , "HUMAN");
00325                sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
00326                res = 1;
00327                break;
00328             }
00329             
00330          } else {
00331             consecutiveVoiceDuration += framelength;
00332             voiceDuration += framelength;
00333 
00334             /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
00335                number of words if my previous state was Silence, which means that I moved into a word. */
00336             if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
00337                iWordsCount++;
00338                ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount);
00339                previousState = currentState;
00340                currentState = STATE_IN_WORD;
00341             }
00342             if (consecutiveVoiceDuration >= maximumWordLength){
00343                ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration);
00344                ast_frfree(f);
00345                strcpy(amdStatus , "MACHINE");
00346                sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
00347                break;
00348             }
00349             if (iWordsCount >= maximumNumberOfWords) {
00350                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount);
00351                ast_frfree(f);
00352                strcpy(amdStatus , "MACHINE");
00353                sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
00354                res = 1;
00355                break;
00356             }
00357 
00358             if (inGreeting == 1 && voiceDuration >= greeting) {
00359                ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting);
00360                ast_frfree(f);
00361                strcpy(amdStatus , "MACHINE");
00362                sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
00363                res = 1;
00364                break;
00365             }
00366 
00367             if (voiceDuration >= minimumWordLength ) {
00368                if (silenceDuration > 0)
00369                   ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration);
00370                silenceDuration = 0;
00371             }
00372             if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) {
00373                /* Only go in here once to change the greeting flag when we detect the 1st word */
00374                if (silenceDuration > 0)
00375                   ast_verb(3, "AMD: Channel [%s]. Before Greeting Time:  silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration);
00376                inInitialSilence = 0;
00377                inGreeting = 1;
00378             }
00379             
00380          }
00381       }
00382       ast_frfree(f);
00383    }
00384    
00385    if (!res) {
00386       /* It took too long to get a frame back. Giving up. */
00387       ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name);
00388       strcpy(amdStatus , "NOTSURE");
00389       sprintf(amdCause , "TOOLONG-%d", iTotalTime);
00390    }
00391 
00392    /* Set the status and cause on the channel */
00393    pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
00394    pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
00395 
00396    /* Restore channel read format */
00397    if (readFormat && ast_set_read_format(chan, readFormat))
00398       ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
00399 
00400    /* Free the DSP used to detect silence */
00401    ast_dsp_free(silenceDetector);
00402 
00403    return;
00404 }

static int load_config ( int  reload  )  [static]

Definition at line 414 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_STATUS_FILEINVALID, CONFIG_STATUS_FILEUNCHANGED, ast_variable::lineno, LOG_ERROR, LOG_WARNING, ast_variable::name, ast_variable::next, THRESHOLD_SILENCE, ast_variable::value, and var.

00415 {
00416    struct ast_config *cfg = NULL;
00417    char *cat = NULL;
00418    struct ast_variable *var = NULL;
00419    struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
00420 
00421    dfltSilenceThreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
00422 
00423    if (!(cfg = ast_config_load("amd.conf", config_flags))) {
00424       ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
00425       return -1;
00426    } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
00427       return 0;
00428    } else if (cfg == CONFIG_STATUS_FILEINVALID) {
00429       ast_log(LOG_ERROR, "Config file amd.conf is in an invalid format.  Aborting.\n");
00430       return -1;
00431    }
00432 
00433    cat = ast_category_browse(cfg, NULL);
00434 
00435    while (cat) {
00436       if (!strcasecmp(cat, "general") ) {
00437          var = ast_variable_browse(cfg, cat);
00438          while (var) {
00439             if (!strcasecmp(var->name, "initial_silence")) {
00440                dfltInitialSilence = atoi(var->value);
00441             } else if (!strcasecmp(var->name, "greeting")) {
00442                dfltGreeting = atoi(var->value);
00443             } else if (!strcasecmp(var->name, "after_greeting_silence")) {
00444                dfltAfterGreetingSilence = atoi(var->value);
00445             } else if (!strcasecmp(var->name, "silence_threshold")) {
00446                dfltSilenceThreshold = atoi(var->value);
00447             } else if (!strcasecmp(var->name, "total_analysis_time")) {
00448                dfltTotalAnalysisTime = atoi(var->value);
00449             } else if (!strcasecmp(var->name, "min_word_length")) {
00450                dfltMinimumWordLength = atoi(var->value);
00451             } else if (!strcasecmp(var->name, "between_words_silence")) {
00452                dfltBetweenWordsSilence = atoi(var->value);
00453             } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
00454                dfltMaximumNumberOfWords = atoi(var->value);
00455             } else if (!strcasecmp(var->name, "maximum_word_length")) {
00456                dfltMaximumWordLength = atoi(var->value);
00457 
00458             } else {
00459                ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
00460                   app, cat, var->name, var->lineno);
00461             }
00462             var = var->next;
00463          }
00464       }
00465       cat = ast_category_browse(cfg, cat);
00466    }
00467 
00468    ast_config_destroy(cfg);
00469 
00470    ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
00471       "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
00472       dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
00473       dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);
00474 
00475    return 0;
00476 }

static int load_module ( void   )  [static]

static int reload ( void   )  [static]

Definition at line 492 of file app_amd.c.

References AST_MODULE_LOAD_DECLINE, AST_MODULE_LOAD_SUCCESS, and load_config().

00493 {
00494    if (load_config(1))
00495       return AST_MODULE_LOAD_DECLINE;
00496    return AST_MODULE_LOAD_SUCCESS;
00497 }

static int unload_module ( void   )  [static]

Definition at line 478 of file app_amd.c.

References ast_unregister_application().

00479 {
00480    return ast_unregister_application(app);
00481 }


Variable Documentation

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 = AST_BUILDOPT_SUM, .load = load_module, .unload = unload_module, .reload = reload, } [static]

Definition at line 503 of file app_amd.c.

const char app[] = "AMD" [static]

Definition at line 127 of file app_amd.c.

Definition at line 503 of file app_amd.c.

int dfltAfterGreetingSilence = 800 [static]

Definition at line 135 of file app_amd.c.

int dfltBetweenWordsSilence = 50 [static]

Definition at line 138 of file app_amd.c.

int dfltGreeting = 1500 [static]

Definition at line 134 of file app_amd.c.

int dfltInitialSilence = 2500 [static]

Definition at line 133 of file app_amd.c.

int dfltMaximumNumberOfWords = 3 [static]

Definition at line 139 of file app_amd.c.

int dfltMaximumWordLength = 5000 [static]

Definition at line 141 of file app_amd.c.

int dfltMaxWaitTimeForFrame = 50 [static]

Definition at line 144 of file app_amd.c.

int dfltMinimumWordLength = 100 [static]

Definition at line 137 of file app_amd.c.

int dfltSilenceThreshold = 256 [static]

Definition at line 140 of file app_amd.c.

int dfltTotalAnalysisTime = 5000 [static]

Definition at line 136 of file app_amd.c.


Generated on Wed Oct 28 13:31:17 2009 for Asterisk - the Open Source PBX by  doxygen 1.5.6