Merged revisions 232355 via svnmerge from
[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                 <see-also>
120                         <ref type="application">WaitForSilence</ref>
121                         <ref type="application">WaitForNoise</ref>
122                 </see-also>
123         </application>
124
125  ***/
126
127 static const char app[] = "AMD";
128
129 #define STATE_IN_WORD       1
130 #define STATE_IN_SILENCE    2
131
132 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
133 static int dfltInitialSilence       = 2500;
134 static int dfltGreeting             = 1500;
135 static int dfltAfterGreetingSilence = 800;
136 static int dfltTotalAnalysisTime    = 5000;
137 static int dfltMinimumWordLength    = 100;
138 static int dfltBetweenWordsSilence  = 50;
139 static int dfltMaximumNumberOfWords = 3;
140 static int dfltSilenceThreshold     = 256;
141 static int dfltMaximumWordLength    = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */
142
143 /* Set to the lowest ms value provided in amd.conf or application parameters */
144 static int dfltMaxWaitTimeForFrame  = 50;
145
146 static void isAnsweringMachine(struct ast_channel *chan, const char *data)
147 {
148         int res = 0;
149         struct ast_frame *f = NULL;
150         struct ast_dsp *silenceDetector = NULL;
151         int dspsilence = 0, readFormat, framelength = 0;
152         int inInitialSilence = 1;
153         int inGreeting = 0;
154         int voiceDuration = 0;
155         int silenceDuration = 0;
156         int iTotalTime = 0;
157         int iWordsCount = 0;
158         int currentState = STATE_IN_WORD;
159         int previousState = STATE_IN_SILENCE;
160         int consecutiveVoiceDuration = 0;
161         char amdCause[256] = "", amdStatus[256] = "";
162         char *parse = ast_strdupa(data);
163
164         /* Lets set the initial values of the variables that will control the algorithm.
165            The initial values are the default ones. If they are passed as arguments
166            when invoking the application, then the default values will be overwritten
167            by the ones passed as parameters. */
168         int initialSilence       = dfltInitialSilence;
169         int greeting             = dfltGreeting;
170         int afterGreetingSilence = dfltAfterGreetingSilence;
171         int totalAnalysisTime    = dfltTotalAnalysisTime;
172         int minimumWordLength    = dfltMinimumWordLength;
173         int betweenWordsSilence  = dfltBetweenWordsSilence;
174         int maximumNumberOfWords = dfltMaximumNumberOfWords;
175         int silenceThreshold     = dfltSilenceThreshold;
176         int maximumWordLength    = dfltMaximumWordLength;
177         int maxWaitTimeForFrame  = dfltMaxWaitTimeForFrame;
178
179         AST_DECLARE_APP_ARGS(args,
180                 AST_APP_ARG(argInitialSilence);
181                 AST_APP_ARG(argGreeting);
182                 AST_APP_ARG(argAfterGreetingSilence);
183                 AST_APP_ARG(argTotalAnalysisTime);
184                 AST_APP_ARG(argMinimumWordLength);
185                 AST_APP_ARG(argBetweenWordsSilence);
186                 AST_APP_ARG(argMaximumNumberOfWords);
187                 AST_APP_ARG(argSilenceThreshold);
188                 AST_APP_ARG(argMaximumWordLength);
189         );
190
191         ast_verb(3, "AMD: %s %s %s (Fmt: %s)\n", chan->name, chan->cid.cid_ani, chan->cid.cid_rdnis, ast_getformatname(chan->readformat));
192
193         /* Lets parse the arguments. */
194         if (!ast_strlen_zero(parse)) {
195                 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
196                 AST_STANDARD_APP_ARGS(args, parse);
197                 if (!ast_strlen_zero(args.argInitialSilence))
198                         initialSilence = atoi(args.argInitialSilence);
199                 if (!ast_strlen_zero(args.argGreeting))
200                         greeting = atoi(args.argGreeting);
201                 if (!ast_strlen_zero(args.argAfterGreetingSilence))
202                         afterGreetingSilence = atoi(args.argAfterGreetingSilence);
203                 if (!ast_strlen_zero(args.argTotalAnalysisTime))
204                         totalAnalysisTime = atoi(args.argTotalAnalysisTime);
205                 if (!ast_strlen_zero(args.argMinimumWordLength))
206                         minimumWordLength = atoi(args.argMinimumWordLength);
207                 if (!ast_strlen_zero(args.argBetweenWordsSilence))
208                         betweenWordsSilence = atoi(args.argBetweenWordsSilence);
209                 if (!ast_strlen_zero(args.argMaximumNumberOfWords))
210                         maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
211                 if (!ast_strlen_zero(args.argSilenceThreshold))
212                         silenceThreshold = atoi(args.argSilenceThreshold);
213                 if (!ast_strlen_zero(args.argMaximumWordLength))
214                         maximumWordLength = atoi(args.argMaximumWordLength);
215         } else {
216                 ast_debug(1, "AMD using the default parameters.\n");
217         }
218
219         /* Find lowest ms value, that will be max wait time for a frame */
220         if (maxWaitTimeForFrame > initialSilence)
221                 maxWaitTimeForFrame = initialSilence;
222         if (maxWaitTimeForFrame > greeting)
223                 maxWaitTimeForFrame = greeting;
224         if (maxWaitTimeForFrame > afterGreetingSilence)
225                 maxWaitTimeForFrame = afterGreetingSilence;
226         if (maxWaitTimeForFrame > totalAnalysisTime)
227                 maxWaitTimeForFrame = totalAnalysisTime;
228         if (maxWaitTimeForFrame > minimumWordLength)
229                 maxWaitTimeForFrame = minimumWordLength;
230         if (maxWaitTimeForFrame > betweenWordsSilence)
231                 maxWaitTimeForFrame = betweenWordsSilence;
232
233         /* Now we're ready to roll! */
234         ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
235                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
236                                 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
237                                 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);
238
239         /* Set read format to signed linear so we get signed linear frames in */
240         readFormat = chan->readformat;
241         if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
242                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
243                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
244                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
245                 return;
246         }
247
248         /* Create a new DSP that will detect the silence */
249         if (!(silenceDetector = ast_dsp_new())) {
250                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
251                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
252                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
253                 return;
254         }
255
256         /* Set silence threshold to specified value */
257         ast_dsp_set_threshold(silenceDetector, silenceThreshold);
258
259         /* Now we go into a loop waiting for frames from the channel */
260         while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
261
262                 /* If we fail to read in a frame, that means they hung up */
263                 if (!(f = ast_read(chan))) {
264                         ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name);
265                         ast_debug(1, "Got hangup\n");
266                         strcpy(amdStatus, "HANGUP");
267                         res = 1;
268                         break;
269                 }
270
271                 if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
272                         /* If the total time exceeds the analysis time then give up as we are not too sure */
273                         if (f->frametype == AST_FRAME_VOICE)
274                                 framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
275                         else
276                                 framelength += 2 * maxWaitTimeForFrame;
277
278                         iTotalTime += framelength;
279                         if (iTotalTime >= totalAnalysisTime) {
280                                 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name );
281                                 ast_frfree(f);
282                                 strcpy(amdStatus , "NOTSURE");
283                                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
284                                 break;
285                         }
286
287                         /* Feed the frame of audio into the silence detector and see if we get a result */
288                         if (f->frametype != AST_FRAME_VOICE)
289                                 dspsilence += 2 * maxWaitTimeForFrame;
290                         else {
291                                 dspsilence = 0;
292                                 ast_dsp_silence(silenceDetector, f, &dspsilence);
293                         }
294
295                         if (dspsilence > 0) {
296                                 silenceDuration = dspsilence;
297                                 
298                                 if (silenceDuration >= betweenWordsSilence) {
299                                         if (currentState != STATE_IN_SILENCE ) {
300                                                 previousState = currentState;
301                                                 ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name);
302                                         }
303                                         /* Find words less than word duration */
304                                         if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
305                                                 ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration);
306                                         }
307                                         currentState  = STATE_IN_SILENCE;
308                                         consecutiveVoiceDuration = 0;
309                                 }
310
311                                 if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
312                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
313                                                 chan->name, silenceDuration, initialSilence);
314                                         ast_frfree(f);
315                                         strcpy(amdStatus , "MACHINE");
316                                         sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
317                                         res = 1;
318                                         break;
319                                 }
320                                 
321                                 if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
322                                         ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
323                                                 chan->name, silenceDuration, afterGreetingSilence);
324                                         ast_frfree(f);
325                                         strcpy(amdStatus , "HUMAN");
326                                         sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
327                                         res = 1;
328                                         break;
329                                 }
330                                 
331                         } else {
332                                 consecutiveVoiceDuration += framelength;
333                                 voiceDuration += framelength;
334
335                                 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
336                                    number of words if my previous state was Silence, which means that I moved into a word. */
337                                 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
338                                         iWordsCount++;
339                                         ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount);
340                                         previousState = currentState;
341                                         currentState = STATE_IN_WORD;
342                                 }
343                                 if (consecutiveVoiceDuration >= maximumWordLength){
344                                         ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration);
345                                         ast_frfree(f);
346                                         strcpy(amdStatus , "MACHINE");
347                                         sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
348                                         break;
349                                 }
350                                 if (iWordsCount >= maximumNumberOfWords) {
351                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount);
352                                         ast_frfree(f);
353                                         strcpy(amdStatus , "MACHINE");
354                                         sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
355                                         res = 1;
356                                         break;
357                                 }
358
359                                 if (inGreeting == 1 && voiceDuration >= greeting) {
360                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting);
361                                         ast_frfree(f);
362                                         strcpy(amdStatus , "MACHINE");
363                                         sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
364                                         res = 1;
365                                         break;
366                                 }
367
368                                 if (voiceDuration >= minimumWordLength ) {
369                                         if (silenceDuration > 0)
370                                                 ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration);
371                                         silenceDuration = 0;
372                                 }
373                                 if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) {
374                                         /* Only go in here once to change the greeting flag when we detect the 1st word */
375                                         if (silenceDuration > 0)
376                                                 ast_verb(3, "AMD: Channel [%s]. Before Greeting Time:  silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration);
377                                         inInitialSilence = 0;
378                                         inGreeting = 1;
379                                 }
380                                 
381                         }
382                 }
383                 ast_frfree(f);
384         }
385         
386         if (!res) {
387                 /* It took too long to get a frame back. Giving up. */
388                 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name);
389                 strcpy(amdStatus , "NOTSURE");
390                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
391         }
392
393         /* Set the status and cause on the channel */
394         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
395         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
396
397         /* Restore channel read format */
398         if (readFormat && ast_set_read_format(chan, readFormat))
399                 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
400
401         /* Free the DSP used to detect silence */
402         ast_dsp_free(silenceDetector);
403
404         return;
405 }
406
407
408 static int amd_exec(struct ast_channel *chan, const char *data)
409 {
410         isAnsweringMachine(chan, data);
411
412         return 0;
413 }
414
415 static int load_config(int reload)
416 {
417         struct ast_config *cfg = NULL;
418         char *cat = NULL;
419         struct ast_variable *var = NULL;
420         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
421
422         dfltSilenceThreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
423
424         if (!(cfg = ast_config_load("amd.conf", config_flags))) {
425                 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
426                 return -1;
427         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
428                 return 0;
429         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
430                 ast_log(LOG_ERROR, "Config file amd.conf is in an invalid format.  Aborting.\n");
431                 return -1;
432         }
433
434         cat = ast_category_browse(cfg, NULL);
435
436         while (cat) {
437                 if (!strcasecmp(cat, "general") ) {
438                         var = ast_variable_browse(cfg, cat);
439                         while (var) {
440                                 if (!strcasecmp(var->name, "initial_silence")) {
441                                         dfltInitialSilence = atoi(var->value);
442                                 } else if (!strcasecmp(var->name, "greeting")) {
443                                         dfltGreeting = atoi(var->value);
444                                 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
445                                         dfltAfterGreetingSilence = atoi(var->value);
446                                 } else if (!strcasecmp(var->name, "silence_threshold")) {
447                                         dfltSilenceThreshold = atoi(var->value);
448                                 } else if (!strcasecmp(var->name, "total_analysis_time")) {
449                                         dfltTotalAnalysisTime = atoi(var->value);
450                                 } else if (!strcasecmp(var->name, "min_word_length")) {
451                                         dfltMinimumWordLength = atoi(var->value);
452                                 } else if (!strcasecmp(var->name, "between_words_silence")) {
453                                         dfltBetweenWordsSilence = atoi(var->value);
454                                 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
455                                         dfltMaximumNumberOfWords = atoi(var->value);
456                                 } else if (!strcasecmp(var->name, "maximum_word_length")) {
457                                         dfltMaximumWordLength = atoi(var->value);
458
459                                 } else {
460                                         ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
461                                                 app, cat, var->name, var->lineno);
462                                 }
463                                 var = var->next;
464                         }
465                 }
466                 cat = ast_category_browse(cfg, cat);
467         }
468
469         ast_config_destroy(cfg);
470
471         ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
472                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
473                 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
474                 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);
475
476         return 0;
477 }
478
479 static int unload_module(void)
480 {
481         return ast_unregister_application(app);
482 }
483
484 static int load_module(void)
485 {
486         if (load_config(0))
487                 return AST_MODULE_LOAD_DECLINE;
488         if (ast_register_application_xml(app, amd_exec))
489                 return AST_MODULE_LOAD_FAILURE;
490         return AST_MODULE_LOAD_SUCCESS;
491 }
492
493 static int reload(void)
494 {
495         if (load_config(1))
496                 return AST_MODULE_LOAD_DECLINE;
497         return AST_MODULE_LOAD_SUCCESS;
498 }
499
500 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
501                 .load = load_module,
502                 .unload = unload_module,
503                 .reload = reload,
504 );