bf3603b3e67a9a1223fd02517b139d2acd75a8d3
[asterisk/asterisk.git] / apps / app_amd.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2003 - 2006, Aheeva Technology.
5  *
6  * Claude Klimos (claude.klimos@aheeva.com)
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  *
18  * A license has been granted to Digium (via disclaimer) for the use of
19  * this code.
20  */
21
22 /*! \file
23  *
24  * \brief Answering machine detection
25  *
26  * \author Claude Klimos (claude.klimos@aheeva.com)
27  */
28
29
30 #include "asterisk.h"
31
32 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33
34 #include "asterisk/module.h"
35 #include "asterisk/lock.h"
36 #include "asterisk/channel.h"
37 #include "asterisk/dsp.h"
38 #include "asterisk/pbx.h"
39 #include "asterisk/config.h"
40 #include "asterisk/app.h"
41
42 /*** DOCUMENTATION
43         <application name="AMD" language="en_US">
44                 <synopsis>
45                         Attempt to detect answering machines.
46                 </synopsis>
47                 <syntax>
48                         <parameter name="initialSilence" required="false">
49                                 <para>Is maximum initial silence duration before greeting.</para>
50                                 <para>If this is exceeded set as MACHINE</para>
51                         </parameter>
52                         <parameter name="greeting" required="false">
53                                 <para>is the maximum length of a greeting.</para>
54                                 <para>If this is exceeded set as MACHINE</para>
55                         </parameter>
56                         <parameter name="afterGreetingSilence" required="false">
57                                 <para>Is the silence after detecting a greeting.</para>
58                                 <para>If this is exceeded set as HUMAN</para>
59                         </parameter>
60                         <parameter name="totalAnalysis Time" required="false">
61                                 <para>Is the maximum time allowed for the algorithm</para>
62                                 <para>to decide HUMAN or MACHINE</para>
63                         </parameter>
64                         <parameter name="miniumWordLength" required="false">
65                                 <para>Is the minimum duration of Voice considered to be a word</para>
66                         </parameter>
67                         <parameter name="betweenWordSilence" required="false">
68                                 <para>Is the minimum duration of silence after a word to
69                                 consider the audio that follows to be a new word</para>
70                         </parameter>
71                         <parameter name="maximumNumberOfWords" required="false">
72                                 <para>Is the maximum number of words in a greeting</para>
73                                 <para>If this is exceeded set as MACHINE</para>
74                         </parameter>
75                         <parameter name="silenceThreshold" required="false">
76                                 <para>How long do we consider silence</para>
77                         </parameter>
78                         <parameter name="maximumWordLength" required="false">
79                                 <para>Is the maximum duration of a word to accept.</para>
80                                 <para>If exceeded set as MACHINE</para>
81                         </parameter>
82                 </syntax>
83                 <description>
84                         <para>This application attempts to detect answering machines at the beginning
85                         of outbound calls. Simply call this application after the call
86                         has been answered (outbound only, of course).</para>
87                         <para>When loaded, AMD reads amd.conf and uses the parameters specified as
88                         default values. Those default values get overwritten when the calling AMD
89                         with parameters.</para>
90                         <para>This application sets the following channel variables:</para>
91                         <variablelist>
92                                 <variable name="AMDSTATUS">
93                                         <para>This is the status of the answering machine detection</para>
94                                         <value name="MACHINE" />
95                                         <value name="HUMAN" />
96                                         <value name="NOTSURE" />
97                                         <value name="HANGUP" />
98                                 </variable>
99                                 <variable name="AMDCAUSE">
100                                         <para>Indicates the cause that led to the conclusion</para>
101                                         <value name="TOOLONG">
102                                                 Total Time.
103                                         </value>
104                                         <value name="INITIALSILENCE">
105                                                 Silence Duration - Initial Silence.
106                                         </value>
107                                         <value name="HUMAN">
108                                                 Silence Duration - afterGreetingSilence.
109                                         </value>
110                                         <value name="LONGGREETING">
111                                                 Voice Duration - Greeting.
112                                         </value>
113                                         <value name="MAXWORDLENGTH">
114                                                 Word Count - maximum number of words.
115                                         </value>        
116                                 </variable>
117                         </variablelist>
118                 </description>
119         </application>
120
121  ***/
122
123 static char *app = "AMD";
124
125 #define STATE_IN_WORD       1
126 #define STATE_IN_SILENCE    2
127
128 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
129 static int dfltInitialSilence       = 2500;
130 static int dfltGreeting             = 1500;
131 static int dfltAfterGreetingSilence = 800;
132 static int dfltTotalAnalysisTime    = 5000;
133 static int dfltMinimumWordLength    = 100;
134 static int dfltBetweenWordsSilence  = 50;
135 static int dfltMaximumNumberOfWords = 3;
136 static int dfltSilenceThreshold     = 256;
137 static int dfltMaximumWordLength    = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */
138
139 /* Set to the lowest ms value provided in amd.conf or application parameters */
140 static int dfltMaxWaitTimeForFrame  = 50;
141
142 static void isAnsweringMachine(struct ast_channel *chan, void *data)
143 {
144         int res = 0;
145         struct ast_frame *f = NULL;
146         struct ast_dsp *silenceDetector = NULL;
147         int dspsilence = 0, readFormat, framelength = 0;
148         int inInitialSilence = 1;
149         int inGreeting = 0;
150         int voiceDuration = 0;
151         int silenceDuration = 0;
152         int iTotalTime = 0;
153         int iWordsCount = 0;
154         int currentState = STATE_IN_WORD;
155         int previousState = STATE_IN_SILENCE;
156         int consecutiveVoiceDuration = 0;
157         char amdCause[256] = "", amdStatus[256] = "";
158         char *parse = ast_strdupa(data);
159
160         /* Lets set the initial values of the variables that will control the algorithm.
161            The initial values are the default ones. If they are passed as arguments
162            when invoking the application, then the default values will be overwritten
163            by the ones passed as parameters. */
164         int initialSilence       = dfltInitialSilence;
165         int greeting             = dfltGreeting;
166         int afterGreetingSilence = dfltAfterGreetingSilence;
167         int totalAnalysisTime    = dfltTotalAnalysisTime;
168         int minimumWordLength    = dfltMinimumWordLength;
169         int betweenWordsSilence  = dfltBetweenWordsSilence;
170         int maximumNumberOfWords = dfltMaximumNumberOfWords;
171         int silenceThreshold     = dfltSilenceThreshold;
172         int maximumWordLength    = dfltMaximumWordLength;
173         int maxWaitTimeForFrame  = dfltMaxWaitTimeForFrame;
174
175         AST_DECLARE_APP_ARGS(args,
176                 AST_APP_ARG(argInitialSilence);
177                 AST_APP_ARG(argGreeting);
178                 AST_APP_ARG(argAfterGreetingSilence);
179                 AST_APP_ARG(argTotalAnalysisTime);
180                 AST_APP_ARG(argMinimumWordLength);
181                 AST_APP_ARG(argBetweenWordsSilence);
182                 AST_APP_ARG(argMaximumNumberOfWords);
183                 AST_APP_ARG(argSilenceThreshold);
184                 AST_APP_ARG(argMaximumWordLength);
185         );
186
187         ast_verb(3, "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
188
189         /* Lets parse the arguments. */
190         if (!ast_strlen_zero(parse)) {
191                 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
192                 AST_STANDARD_APP_ARGS(args, parse);
193                 if (!ast_strlen_zero(args.argInitialSilence))
194                         initialSilence = atoi(args.argInitialSilence);
195                 if (!ast_strlen_zero(args.argGreeting))
196                         greeting = atoi(args.argGreeting);
197                 if (!ast_strlen_zero(args.argAfterGreetingSilence))
198                         afterGreetingSilence = atoi(args.argAfterGreetingSilence);
199                 if (!ast_strlen_zero(args.argTotalAnalysisTime))
200                         totalAnalysisTime = atoi(args.argTotalAnalysisTime);
201                 if (!ast_strlen_zero(args.argMinimumWordLength))
202                         minimumWordLength = atoi(args.argMinimumWordLength);
203                 if (!ast_strlen_zero(args.argBetweenWordsSilence))
204                         betweenWordsSilence = atoi(args.argBetweenWordsSilence);
205                 if (!ast_strlen_zero(args.argMaximumNumberOfWords))
206                         maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
207                 if (!ast_strlen_zero(args.argSilenceThreshold))
208                         silenceThreshold = atoi(args.argSilenceThreshold);
209                 if (!ast_strlen_zero(args.argMaximumWordLength))
210                         maximumWordLength = atoi(args.argMaximumWordLength);
211         } else {
212                 ast_debug(1, "AMD using the default parameters.\n");
213         }
214
215         /* Find lowest ms value, that will be max wait time for a frame */
216         if (maxWaitTimeForFrame > initialSilence)
217                 maxWaitTimeForFrame = initialSilence;
218         if (maxWaitTimeForFrame > greeting)
219                 maxWaitTimeForFrame = greeting;
220         if (maxWaitTimeForFrame > afterGreetingSilence)
221                 maxWaitTimeForFrame = afterGreetingSilence;
222         if (maxWaitTimeForFrame > totalAnalysisTime)
223                 maxWaitTimeForFrame = totalAnalysisTime;
224         if (maxWaitTimeForFrame > minimumWordLength)
225                 maxWaitTimeForFrame = minimumWordLength;
226         if (maxWaitTimeForFrame > betweenWordsSilence)
227                 maxWaitTimeForFrame = betweenWordsSilence;
228
229         /* Now we're ready to roll! */
230         ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
231                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
232                                 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
233                                 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);
234
235         /* Set read format to signed linear so we get signed linear frames in */
236         readFormat = chan->readformat;
237         if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
238                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
239                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
240                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
241                 return;
242         }
243
244         /* Create a new DSP that will detect the silence */
245         if (!(silenceDetector = ast_dsp_new())) {
246                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
247                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
248                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
249                 return;
250         }
251
252         /* Set silence threshold to specified value */
253         ast_dsp_set_threshold(silenceDetector, silenceThreshold);
254
255         /* Now we go into a loop waiting for frames from the channel */
256         while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
257
258                 /* If we fail to read in a frame, that means they hung up */
259                 if (!(f = ast_read(chan))) {
260                         ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name);
261                         ast_debug(1, "Got hangup\n");
262                         strcpy(amdStatus, "HANGUP");
263                         break;
264                 }
265
266                 if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
267                         /* If the total time exceeds the analysis time then give up as we are not too sure */
268                         if (f->frametype == AST_FRAME_VOICE)
269                                 framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
270                         else
271                                 framelength += 2 * maxWaitTimeForFrame;
272
273                         iTotalTime += framelength;
274                         if (iTotalTime >= totalAnalysisTime) {
275                                 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name );
276                                 ast_frfree(f);
277                                 strcpy(amdStatus , "NOTSURE");
278                                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
279                                 break;
280                         }
281
282                         /* Feed the frame of audio into the silence detector and see if we get a result */
283                         if (f->frametype != AST_FRAME_VOICE)
284                                 dspsilence += 2 * maxWaitTimeForFrame;
285                         else {
286                                 dspsilence = 0;
287                                 ast_dsp_silence(silenceDetector, f, &dspsilence);
288                         }
289
290                         if (dspsilence > 0) {
291                                 silenceDuration = dspsilence;
292                                 
293                                 if (silenceDuration >= betweenWordsSilence) {
294                                         if (currentState != STATE_IN_SILENCE ) {
295                                                 previousState = currentState;
296                                                 ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name);
297                                         }
298                                         /* Find words less than word duration */
299                                         if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
300                                                 ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration);
301                                         }
302                                         currentState  = STATE_IN_SILENCE;
303                                         consecutiveVoiceDuration = 0;
304                                 }
305
306                                 if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
307                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
308                                                 chan->name, silenceDuration, initialSilence);
309                                         ast_frfree(f);
310                                         strcpy(amdStatus , "MACHINE");
311                                         sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
312                                         res = 1;
313                                         break;
314                                 }
315                                 
316                                 if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
317                                         ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
318                                                 chan->name, silenceDuration, afterGreetingSilence);
319                                         ast_frfree(f);
320                                         strcpy(amdStatus , "HUMAN");
321                                         sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
322                                         res = 1;
323                                         break;
324                                 }
325                                 
326                         } else {
327                                 consecutiveVoiceDuration += framelength;
328                                 voiceDuration += framelength;
329
330                                 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
331                                    number of words if my previous state was Silence, which means that I moved into a word. */
332                                 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
333                                         iWordsCount++;
334                                         ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount);
335                                         previousState = currentState;
336                                         currentState = STATE_IN_WORD;
337                                 }
338                                 if (consecutiveVoiceDuration >= maximumWordLength){
339                                         ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration);
340                                         ast_frfree(f);
341                                         strcpy(amdStatus , "MACHINE");
342                                         sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
343                                         break;
344                                 }
345                                 if (iWordsCount >= maximumNumberOfWords) {
346                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount);
347                                         ast_frfree(f);
348                                         strcpy(amdStatus , "MACHINE");
349                                         sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
350                                         res = 1;
351                                         break;
352                                 }
353
354                                 if (inGreeting == 1 && voiceDuration >= greeting) {
355                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting);
356                                         ast_frfree(f);
357                                         strcpy(amdStatus , "MACHINE");
358                                         sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
359                                         res = 1;
360                                         break;
361                                 }
362
363                                 if (voiceDuration >= minimumWordLength ) {
364                                         if (silenceDuration > 0)
365                                                 ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration);
366                                         silenceDuration = 0;
367                                 }
368                                 if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) {
369                                         /* Only go in here once to change the greeting flag when we detect the 1st word */
370                                         if (silenceDuration > 0)
371                                                 ast_verb(3, "AMD: Channel [%s]. Before Greeting Time:  silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration);
372                                         inInitialSilence = 0;
373                                         inGreeting = 1;
374                                 }
375                                 
376                         }
377                 }
378                 ast_frfree(f);
379         }
380         
381         if (!res) {
382                 /* It took too long to get a frame back. Giving up. */
383                 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name);
384                 strcpy(amdStatus , "NOTSURE");
385                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
386         }
387
388         /* Set the status and cause on the channel */
389         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
390         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
391
392         /* Restore channel read format */
393         if (readFormat && ast_set_read_format(chan, readFormat))
394                 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
395
396         /* Free the DSP used to detect silence */
397         ast_dsp_free(silenceDetector);
398
399         return;
400 }
401
402
403 static int amd_exec(struct ast_channel *chan, void *data)
404 {
405         isAnsweringMachine(chan, data);
406
407         return 0;
408 }
409
410 static int load_config(int reload)
411 {
412         struct ast_config *cfg = NULL;
413         char *cat = NULL;
414         struct ast_variable *var = NULL;
415         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
416
417         dfltSilenceThreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
418
419         if (!(cfg = ast_config_load("amd.conf", config_flags))) {
420                 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
421                 return -1;
422         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
423                 return 0;
424         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
425                 ast_log(LOG_ERROR, "Config file amd.conf is in an invalid format.  Aborting.\n");
426                 return -1;
427         }
428
429         cat = ast_category_browse(cfg, NULL);
430
431         while (cat) {
432                 if (!strcasecmp(cat, "general") ) {
433                         var = ast_variable_browse(cfg, cat);
434                         while (var) {
435                                 if (!strcasecmp(var->name, "initial_silence")) {
436                                         dfltInitialSilence = atoi(var->value);
437                                 } else if (!strcasecmp(var->name, "greeting")) {
438                                         dfltGreeting = atoi(var->value);
439                                 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
440                                         dfltAfterGreetingSilence = atoi(var->value);
441                                 } else if (!strcasecmp(var->name, "silence_threshold")) {
442                                         dfltSilenceThreshold = atoi(var->value);
443                                 } else if (!strcasecmp(var->name, "total_analysis_time")) {
444                                         dfltTotalAnalysisTime = atoi(var->value);
445                                 } else if (!strcasecmp(var->name, "min_word_length")) {
446                                         dfltMinimumWordLength = atoi(var->value);
447                                 } else if (!strcasecmp(var->name, "between_words_silence")) {
448                                         dfltBetweenWordsSilence = atoi(var->value);
449                                 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
450                                         dfltMaximumNumberOfWords = atoi(var->value);
451                                 } else if (!strcasecmp(var->name, "maximum_word_length")) {
452                                         dfltMaximumWordLength = atoi(var->value);
453
454                                 } else {
455                                         ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
456                                                 app, cat, var->name, var->lineno);
457                                 }
458                                 var = var->next;
459                         }
460                 }
461                 cat = ast_category_browse(cfg, cat);
462         }
463
464         ast_config_destroy(cfg);
465
466         ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
467                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
468                 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
469                 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);
470
471         return 0;
472 }
473
474 static int unload_module(void)
475 {
476         return ast_unregister_application(app);
477 }
478
479 static int load_module(void)
480 {
481         if (load_config(0))
482                 return AST_MODULE_LOAD_DECLINE;
483         if (ast_register_application_xml(app, amd_exec))
484                 return AST_MODULE_LOAD_FAILURE;
485         return AST_MODULE_LOAD_SUCCESS;
486 }
487
488 static int reload(void)
489 {
490         if (load_config(1))
491                 return AST_MODULE_LOAD_DECLINE;
492         return AST_MODULE_LOAD_SUCCESS;
493 }
494
495 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
496                 .load = load_module,
497                 .unload = unload_module,
498                 .reload = reload,
499 );