Sat Apr 26 2014 22:01:45

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 133 of file app_amd.c.

Referenced by isAnsweringMachine().

#define STATE_IN_WORD   1

Definition at line 132 of file app_amd.c.

Referenced by isAnsweringMachine().


Function Documentation

static void __reg_module ( void  ) [static]

Definition at line 510 of file app_amd.c.

static void __unreg_module ( void  ) [static]

Definition at line 510 of file app_amd.c.

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

Definition at line 414 of file app_amd.c.

References isAnsweringMachine().

Referenced by load_module().

{
   isAnsweringMachine(chan, data);

   return 0;
}
static void isAnsweringMachine ( struct ast_channel chan,
const char *  data 
) [static]

Definition at line 149 of file app_amd.c.

References args, AST_APP_ARG, ast_channel_caller(), ast_channel_name(), ast_channel_readformat(), ast_channel_redirecting(), 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_clear(), ast_format_copy(), AST_FORMAT_SLINEAR, AST_FRAME_CNG, AST_FRAME_NULL, AST_FRAME_VOICE, ast_frfree, ast_getformatname(), ast_log(), ast_read(), ast_set_read_format(), ast_set_read_format_by_id(), AST_STANDARD_APP_ARGS, ast_strlen_zero(), ast_verb, ast_waitfor(), DEFAULT_SAMPLES_PER_MS, dfltAfterGreetingSilence, dfltBetweenWordsSilence, dfltGreeting, dfltInitialSilence, dfltMaximumNumberOfWords, dfltMaximumWordLength, dfltMaxWaitTimeForFrame, dfltMinimumWordLength, dfltSilenceThreshold, dfltTotalAnalysisTime, f, ast_frame::frametype, ast_format::id, LOG_WARNING, parse(), pbx_builtin_setvar_helper(), S_COR, STATE_IN_SILENCE, and STATE_IN_WORD.

Referenced by amd_exec().

{
   int res = 0;
   struct ast_frame *f = NULL;
   struct ast_dsp *silenceDetector = NULL;
   int dspsilence = 0, framelength = 0;
   struct ast_format readFormat;
   int inInitialSilence = 1;
   int inGreeting = 0;
   int voiceDuration = 0;
   int silenceDuration = 0;
   int iTotalTime = 0;
   int iWordsCount = 0;
   int currentState = STATE_IN_WORD;
   int consecutiveVoiceDuration = 0;
   char amdCause[256] = "", amdStatus[256] = "";
   char *parse = ast_strdupa(data);

   /* Lets set the initial values of the variables that will control the algorithm.
      The initial values are the default ones. If they are passed as arguments
      when invoking the application, then the default values will be overwritten
      by the ones passed as parameters. */
   int initialSilence       = dfltInitialSilence;
   int greeting             = dfltGreeting;
   int afterGreetingSilence = dfltAfterGreetingSilence;
   int totalAnalysisTime    = dfltTotalAnalysisTime;
   int minimumWordLength    = dfltMinimumWordLength;
   int betweenWordsSilence  = dfltBetweenWordsSilence;
   int maximumNumberOfWords = dfltMaximumNumberOfWords;
   int silenceThreshold     = dfltSilenceThreshold;
   int maximumWordLength    = dfltMaximumWordLength;
   int maxWaitTimeForFrame  = dfltMaxWaitTimeForFrame;

   AST_DECLARE_APP_ARGS(args,
      AST_APP_ARG(argInitialSilence);
      AST_APP_ARG(argGreeting);
      AST_APP_ARG(argAfterGreetingSilence);
      AST_APP_ARG(argTotalAnalysisTime);
      AST_APP_ARG(argMinimumWordLength);
      AST_APP_ARG(argBetweenWordsSilence);
      AST_APP_ARG(argMaximumNumberOfWords);
      AST_APP_ARG(argSilenceThreshold);
      AST_APP_ARG(argMaximumWordLength);
   );

   ast_format_clear(&readFormat);
   ast_verb(3, "AMD: %s %s %s (Fmt: %s)\n", ast_channel_name(chan),
      S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str, "(N/A)"),
      S_COR(ast_channel_redirecting(chan)->from.number.valid, ast_channel_redirecting(chan)->from.number.str, "(N/A)"),
      ast_getformatname(ast_channel_readformat(chan)));

   /* Lets parse the arguments. */
   if (!ast_strlen_zero(parse)) {
      /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
      AST_STANDARD_APP_ARGS(args, parse);
      if (!ast_strlen_zero(args.argInitialSilence))
         initialSilence = atoi(args.argInitialSilence);
      if (!ast_strlen_zero(args.argGreeting))
         greeting = atoi(args.argGreeting);
      if (!ast_strlen_zero(args.argAfterGreetingSilence))
         afterGreetingSilence = atoi(args.argAfterGreetingSilence);
      if (!ast_strlen_zero(args.argTotalAnalysisTime))
         totalAnalysisTime = atoi(args.argTotalAnalysisTime);
      if (!ast_strlen_zero(args.argMinimumWordLength))
         minimumWordLength = atoi(args.argMinimumWordLength);
      if (!ast_strlen_zero(args.argBetweenWordsSilence))
         betweenWordsSilence = atoi(args.argBetweenWordsSilence);
      if (!ast_strlen_zero(args.argMaximumNumberOfWords))
         maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
      if (!ast_strlen_zero(args.argSilenceThreshold))
         silenceThreshold = atoi(args.argSilenceThreshold);
      if (!ast_strlen_zero(args.argMaximumWordLength))
         maximumWordLength = atoi(args.argMaximumWordLength);
   } else {
      ast_debug(1, "AMD using the default parameters.\n");
   }

   /* Find lowest ms value, that will be max wait time for a frame */
   if (maxWaitTimeForFrame > initialSilence)
      maxWaitTimeForFrame = initialSilence;
   if (maxWaitTimeForFrame > greeting)
      maxWaitTimeForFrame = greeting;
   if (maxWaitTimeForFrame > afterGreetingSilence)
      maxWaitTimeForFrame = afterGreetingSilence;
   if (maxWaitTimeForFrame > totalAnalysisTime)
      maxWaitTimeForFrame = totalAnalysisTime;
   if (maxWaitTimeForFrame > minimumWordLength)
      maxWaitTimeForFrame = minimumWordLength;
   if (maxWaitTimeForFrame > betweenWordsSilence)
      maxWaitTimeForFrame = betweenWordsSilence;

   /* Now we're ready to roll! */
   ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
      "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
            initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
            minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);

   /* Set read format to signed linear so we get signed linear frames in */
   ast_format_copy(&readFormat, ast_channel_readformat(chan));
   if (ast_set_read_format_by_id(chan, AST_FORMAT_SLINEAR) < 0 ) {
      ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", ast_channel_name(chan));
      pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
      pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
      return;
   }

   /* Create a new DSP that will detect the silence */
   if (!(silenceDetector = ast_dsp_new())) {
      ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", ast_channel_name(chan));
      pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
      pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
      return;
   }

   /* Set silence threshold to specified value */
   ast_dsp_set_threshold(silenceDetector, silenceThreshold);

   /* Now we go into a loop waiting for frames from the channel */
   while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {

      /* If we fail to read in a frame, that means they hung up */
      if (!(f = ast_read(chan))) {
         ast_verb(3, "AMD: Channel [%s]. HANGUP\n", ast_channel_name(chan));
         ast_debug(1, "Got hangup\n");
         strcpy(amdStatus, "HANGUP");
         res = 1;
         break;
      }

      if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
         /* If the total time exceeds the analysis time then give up as we are not too sure */
         if (f->frametype == AST_FRAME_VOICE) {
            framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
         } else {
            framelength = 2 * maxWaitTimeForFrame;
         }

         iTotalTime += framelength;
         if (iTotalTime >= totalAnalysisTime) {
            ast_verb(3, "AMD: Channel [%s]. Too long...\n", ast_channel_name(chan));
            ast_frfree(f);
            strcpy(amdStatus , "NOTSURE");
            sprintf(amdCause , "TOOLONG-%d", iTotalTime);
            break;
         }

         /* Feed the frame of audio into the silence detector and see if we get a result */
         if (f->frametype != AST_FRAME_VOICE)
            dspsilence += 2 * maxWaitTimeForFrame;
         else {
            dspsilence = 0;
            ast_dsp_silence(silenceDetector, f, &dspsilence);
         }

         if (dspsilence > 0) {
            silenceDuration = dspsilence;
            
            if (silenceDuration >= betweenWordsSilence) {
               if (currentState != STATE_IN_SILENCE ) {
                  ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", ast_channel_name(chan));
               }
               /* Find words less than word duration */
               if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
                  ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", ast_channel_name(chan), consecutiveVoiceDuration);
               }
               currentState  = STATE_IN_SILENCE;
               consecutiveVoiceDuration = 0;
            }

            if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
               ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
                  ast_channel_name(chan), silenceDuration, initialSilence);
               ast_frfree(f);
               strcpy(amdStatus , "MACHINE");
               sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
               res = 1;
               break;
            }
            
            if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
               ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
                  ast_channel_name(chan), silenceDuration, afterGreetingSilence);
               ast_frfree(f);
               strcpy(amdStatus , "HUMAN");
               sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
               res = 1;
               break;
            }
            
         } else {
            consecutiveVoiceDuration += framelength;
            voiceDuration += framelength;

            /* 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 && currentState == STATE_IN_SILENCE) {
               iWordsCount++;
               ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", ast_channel_name(chan), iWordsCount);
               currentState = STATE_IN_WORD;
            }
            if (consecutiveVoiceDuration >= maximumWordLength){
               ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", ast_channel_name(chan), consecutiveVoiceDuration);
               ast_frfree(f);
               strcpy(amdStatus , "MACHINE");
               sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
               break;
            }
            if (iWordsCount >= maximumNumberOfWords) {
               ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", ast_channel_name(chan), iWordsCount);
               ast_frfree(f);
               strcpy(amdStatus , "MACHINE");
               sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
               res = 1;
               break;
            }

            if (inGreeting == 1 && voiceDuration >= greeting) {
               ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", ast_channel_name(chan), voiceDuration, greeting);
               ast_frfree(f);
               strcpy(amdStatus , "MACHINE");
               sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
               res = 1;
               break;
            }

            if (voiceDuration >= minimumWordLength ) {
               if (silenceDuration > 0)
                  ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", ast_channel_name(chan), silenceDuration);
               silenceDuration = 0;
            }
            if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) {
               /* Only go in here once to change the greeting flag when we detect the 1st word */
               if (silenceDuration > 0)
                  ast_verb(3, "AMD: Channel [%s]. Before Greeting Time:  silenceDuration: %d voiceDuration: %d\n", ast_channel_name(chan), silenceDuration, voiceDuration);
               inInitialSilence = 0;
               inGreeting = 1;
            }
            
         }
      }
      ast_frfree(f);
   }
   
   if (!res) {
      /* It took too long to get a frame back. Giving up. */
      ast_verb(3, "AMD: Channel [%s]. Too long...\n", ast_channel_name(chan));
      strcpy(amdStatus , "NOTSURE");
      sprintf(amdCause , "TOOLONG-%d", iTotalTime);
   }

   /* Set the status and cause on the channel */
   pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
   pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);

   /* Restore channel read format */
   if (readFormat.id && ast_set_read_format(chan, &readFormat))
      ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", ast_channel_name(chan));

   /* Free the DSP used to detect silence */
   ast_dsp_free(silenceDetector);

   return;
}
static int load_config ( int  reload) [static]

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

Referenced by load_module(), and reload().

{
   struct ast_config *cfg = NULL;
   char *cat = NULL;
   struct ast_variable *var = NULL;
   struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };

   dfltSilenceThreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);

   if (!(cfg = ast_config_load("amd.conf", config_flags))) {
      ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
      return -1;
   } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
      return 0;
   } else if (cfg == CONFIG_STATUS_FILEINVALID) {
      ast_log(LOG_ERROR, "Config file amd.conf is in an invalid format.  Aborting.\n");
      return -1;
   }

   cat = ast_category_browse(cfg, NULL);

   while (cat) {
      if (!strcasecmp(cat, "general") ) {
         var = ast_variable_browse(cfg, cat);
         while (var) {
            if (!strcasecmp(var->name, "initial_silence")) {
               dfltInitialSilence = atoi(var->value);
            } else if (!strcasecmp(var->name, "greeting")) {
               dfltGreeting = atoi(var->value);
            } else if (!strcasecmp(var->name, "after_greeting_silence")) {
               dfltAfterGreetingSilence = atoi(var->value);
            } else if (!strcasecmp(var->name, "silence_threshold")) {
               dfltSilenceThreshold = atoi(var->value);
            } else if (!strcasecmp(var->name, "total_analysis_time")) {
               dfltTotalAnalysisTime = atoi(var->value);
            } else if (!strcasecmp(var->name, "min_word_length")) {
               dfltMinimumWordLength = atoi(var->value);
            } else if (!strcasecmp(var->name, "between_words_silence")) {
               dfltBetweenWordsSilence = atoi(var->value);
            } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
               dfltMaximumNumberOfWords = atoi(var->value);
            } else if (!strcasecmp(var->name, "maximum_word_length")) {
               dfltMaximumWordLength = atoi(var->value);

            } else {
               ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
                  app, cat, var->name, var->lineno);
            }
            var = var->next;
         }
      }
      cat = ast_category_browse(cfg, cat);
   }

   ast_config_destroy(cfg);

   ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
      "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
      dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
      dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);

   return 0;
}
static int reload ( void  ) [static]
static int unload_module ( void  ) [static]

Definition at line 485 of file app_amd.c.

References ast_unregister_application().


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 510 of file app_amd.c.

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

Definition at line 130 of file app_amd.c.

Definition at line 510 of file app_amd.c.

int dfltAfterGreetingSilence = 800 [static]

Definition at line 138 of file app_amd.c.

Referenced by isAnsweringMachine().

int dfltBetweenWordsSilence = 50 [static]

Definition at line 141 of file app_amd.c.

Referenced by isAnsweringMachine().

int dfltGreeting = 1500 [static]

Definition at line 137 of file app_amd.c.

Referenced by isAnsweringMachine().

int dfltInitialSilence = 2500 [static]

Definition at line 136 of file app_amd.c.

Referenced by isAnsweringMachine().

int dfltMaximumNumberOfWords = 3 [static]

Definition at line 142 of file app_amd.c.

Referenced by isAnsweringMachine().

int dfltMaximumWordLength = 5000 [static]

Definition at line 144 of file app_amd.c.

Referenced by isAnsweringMachine().

int dfltMaxWaitTimeForFrame = 50 [static]

Definition at line 147 of file app_amd.c.

Referenced by isAnsweringMachine().

int dfltMinimumWordLength = 100 [static]

Definition at line 140 of file app_amd.c.

Referenced by isAnsweringMachine().

int dfltSilenceThreshold = 256 [static]

Definition at line 143 of file app_amd.c.

Referenced by isAnsweringMachine().

int dfltTotalAnalysisTime = 5000 [static]

Definition at line 139 of file app_amd.c.

Referenced by isAnsweringMachine().