6db4500b64154591cf549051e48e95d75c6bef2a
[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
43 static char *app = "AMD";
44 static char *synopsis = "Attempts to detect answering machines";
45 static char *descrip =
46 "  AMD([initialSilence],[greeting],[afterGreetingSilence],[totalAnalysisTime]\n"
47 "      ,[minimumWordLength],[betweenWordsSilence],[maximumNumberOfWords]\n"
48 "      ,[silenceThreshold],[|maximumWordLength])\n"
49 "  This application attempts to detect answering machines at the beginning\n"
50 "  of outbound calls.  Simply call this application after the call\n"
51 "  has been answered (outbound only, of course).\n"
52 "  When loaded, AMD reads amd.conf and uses the parameters specified as\n"
53 "  default values. Those default values get overwritten when calling AMD\n"
54 "  with parameters.\n"
55 "- 'initialSilence' is the maximum silence duration before the greeting. If\n"
56 "   exceeded then MACHINE.\n"
57 "- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n"
58 "- 'afterGreetingSilence' is the silence after detecting a greeting.\n"
59 "   If exceeded then HUMAN.\n"
60 "- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n"
61 "   on a HUMAN or MACHINE.\n"
62 "- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n"
63 "- 'betweenWordsSilence' is the minimum duration of silence after a word to \n"
64 "   consider the audio that follows as a new word.\n"
65 "- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n"
66 "   If exceeded then MACHINE.\n"
67 "- 'silenceThreshold' is the silence threshold.\n"
68 "- 'maximumWordLength' is the maximum duration of a word to accept. If exceeded then MACHINE\n"
69 "This application sets the following channel variables upon completion:\n"
70 "    AMDSTATUS - This is the status of the answering machine detection.\n"
71 "                Possible values are:\n"
72 "                MACHINE | HUMAN | NOTSURE | HANGUP\n"
73 "    AMDCAUSE - Indicates the cause that led to the conclusion.\n"
74 "               Possible values are:\n"
75 "               TOOLONG-<%d total_time>\n"
76 "               INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
77 "               HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
78 "               MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
79 "               LONGGREETING-<%d voiceDuration>-<%d greeting>\n"
80 "               MAXWORDLENGTH-<%d consecutiveVoiceDuration>\n";
81
82 #define STATE_IN_WORD       1
83 #define STATE_IN_SILENCE    2
84
85 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
86 static int dfltInitialSilence       = 2500;
87 static int dfltGreeting             = 1500;
88 static int dfltAfterGreetingSilence = 800;
89 static int dfltTotalAnalysisTime    = 5000;
90 static int dfltMinimumWordLength    = 100;
91 static int dfltBetweenWordsSilence  = 50;
92 static int dfltMaximumNumberOfWords = 3;
93 static int dfltSilenceThreshold     = 256;
94 static int dfltMaximumWordLength    = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */
95
96 /* Set to the lowest ms value provided in amd.conf or application parameters */
97 static int dfltMaxWaitTimeForFrame  = 50;
98
99 static void isAnsweringMachine(struct ast_channel *chan, void *data)
100 {
101         int res = 0;
102         struct ast_frame *f = NULL;
103         struct ast_dsp *silenceDetector = NULL;
104         int dspsilence = 0, readFormat, framelength = 0;
105         int inInitialSilence = 1;
106         int inGreeting = 0;
107         int voiceDuration = 0;
108         int silenceDuration = 0;
109         int iTotalTime = 0;
110         int iWordsCount = 0;
111         int currentState = STATE_IN_WORD;
112         int previousState = STATE_IN_SILENCE;
113         int consecutiveVoiceDuration = 0;
114         char amdCause[256] = "", amdStatus[256] = "";
115         char *parse = ast_strdupa(data);
116
117         /* Lets set the initial values of the variables that will control the algorithm.
118            The initial values are the default ones. If they are passed as arguments
119            when invoking the application, then the default values will be overwritten
120            by the ones passed as parameters. */
121         int initialSilence       = dfltInitialSilence;
122         int greeting             = dfltGreeting;
123         int afterGreetingSilence = dfltAfterGreetingSilence;
124         int totalAnalysisTime    = dfltTotalAnalysisTime;
125         int minimumWordLength    = dfltMinimumWordLength;
126         int betweenWordsSilence  = dfltBetweenWordsSilence;
127         int maximumNumberOfWords = dfltMaximumNumberOfWords;
128         int silenceThreshold     = dfltSilenceThreshold;
129         int maximumWordLength    = dfltMaximumWordLength;
130         int maxWaitTimeForFrame  = dfltMaxWaitTimeForFrame;
131
132         AST_DECLARE_APP_ARGS(args,
133                              AST_APP_ARG(argInitialSilence);
134                              AST_APP_ARG(argGreeting);
135                              AST_APP_ARG(argAfterGreetingSilence);
136                              AST_APP_ARG(argTotalAnalysisTime);
137                              AST_APP_ARG(argMinimumWordLength);
138                              AST_APP_ARG(argBetweenWordsSilence);
139                              AST_APP_ARG(argMaximumNumberOfWords);
140                              AST_APP_ARG(argSilenceThreshold);
141                              AST_APP_ARG(argMaximumWordLength);
142         );
143
144         ast_verb(3, "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
145
146         /* Lets parse the arguments. */
147         if (!ast_strlen_zero(parse)) {
148                 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
149                 AST_STANDARD_APP_ARGS(args, parse);
150                 if (!ast_strlen_zero(args.argInitialSilence))
151                         initialSilence = atoi(args.argInitialSilence);
152                 if (!ast_strlen_zero(args.argGreeting))
153                         greeting = atoi(args.argGreeting);
154                 if (!ast_strlen_zero(args.argAfterGreetingSilence))
155                         afterGreetingSilence = atoi(args.argAfterGreetingSilence);
156                 if (!ast_strlen_zero(args.argTotalAnalysisTime))
157                         totalAnalysisTime = atoi(args.argTotalAnalysisTime);
158                 if (!ast_strlen_zero(args.argMinimumWordLength))
159                         minimumWordLength = atoi(args.argMinimumWordLength);
160                 if (!ast_strlen_zero(args.argBetweenWordsSilence))
161                         betweenWordsSilence = atoi(args.argBetweenWordsSilence);
162                 if (!ast_strlen_zero(args.argMaximumNumberOfWords))
163                         maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
164                 if (!ast_strlen_zero(args.argSilenceThreshold))
165                         silenceThreshold = atoi(args.argSilenceThreshold);
166                 if (!ast_strlen_zero(args.argMaximumWordLength))
167                         maximumWordLength = atoi(args.argMaximumWordLength);                    
168         } else {
169                 ast_debug(1, "AMD using the default parameters.\n");
170         }
171
172         /* Find lowest ms value, that will be max wait time for a frame */
173         if (maxWaitTimeForFrame > initialSilence)
174                 maxWaitTimeForFrame = initialSilence;
175         if (maxWaitTimeForFrame > greeting)
176                 maxWaitTimeForFrame = greeting;
177         if (maxWaitTimeForFrame > afterGreetingSilence)
178                 maxWaitTimeForFrame = afterGreetingSilence;
179         if (maxWaitTimeForFrame > totalAnalysisTime)
180                 maxWaitTimeForFrame = totalAnalysisTime;
181         if (maxWaitTimeForFrame > minimumWordLength)
182                 maxWaitTimeForFrame = minimumWordLength;
183         if (maxWaitTimeForFrame > betweenWordsSilence)
184                 maxWaitTimeForFrame = betweenWordsSilence;
185
186         /* Now we're ready to roll! */
187         ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
188                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
189                                 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
190                                 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);
191
192         /* Set read format to signed linear so we get signed linear frames in */
193         readFormat = chan->readformat;
194         if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
195                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
196                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
197                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
198                 return;
199         }
200
201         /* Create a new DSP that will detect the silence */
202         if (!(silenceDetector = ast_dsp_new())) {
203                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
204                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
205                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
206                 return;
207         }
208
209         /* Set silence threshold to specified value */
210         ast_dsp_set_threshold(silenceDetector, silenceThreshold);
211
212         /* Now we go into a loop waiting for frames from the channel */
213         while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
214
215                 /* If we fail to read in a frame, that means they hung up */
216                 if (!(f = ast_read(chan))) {
217                         ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan->name);
218                         ast_debug(1, "Got hangup\n");
219                         strcpy(amdStatus, "HANGUP");
220                         break;
221                 }
222
223                 if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
224                         /* If the total time exceeds the analysis time then give up as we are not too sure */
225                         if (f->frametype == AST_FRAME_VOICE)
226                                 framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
227                         else
228                                 framelength += 2 * maxWaitTimeForFrame;
229
230                         iTotalTime += framelength;
231                         if (iTotalTime >= totalAnalysisTime) {
232                                 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name );
233                                 ast_frfree(f);
234                                 strcpy(amdStatus , "NOTSURE");
235                                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
236                                 break;
237                         }
238
239                         /* Feed the frame of audio into the silence detector and see if we get a result */
240                         if (f->frametype != AST_FRAME_VOICE)
241                                 dspsilence += 2 * maxWaitTimeForFrame;
242                         else {
243                                 dspsilence = 0;
244                                 ast_dsp_silence(silenceDetector, f, &dspsilence);
245                         }
246
247                         if (dspsilence > 0) {
248                                 silenceDuration = dspsilence;
249                                 
250                                 if (silenceDuration >= betweenWordsSilence) {
251                                         if (currentState != STATE_IN_SILENCE ) {
252                                                 previousState = currentState;
253                                                 ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan->name);
254                                         }
255                                         /* Find words less than word duration */
256                                         if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
257                                                 ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan->name, consecutiveVoiceDuration);
258                                         }                                       
259                                         currentState  = STATE_IN_SILENCE;
260                                         consecutiveVoiceDuration = 0;
261                                 }
262                                 
263                                 if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
264                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
265                                                             chan->name, silenceDuration, initialSilence);
266                                         ast_frfree(f);
267                                         strcpy(amdStatus , "MACHINE");
268                                         sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
269                                         res = 1;
270                                         break;
271                                 }
272                                 
273                                 if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
274                                         ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
275                                                             chan->name, silenceDuration, afterGreetingSilence);
276                                         ast_frfree(f);
277                                         strcpy(amdStatus , "HUMAN");
278                                         sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
279                                         res = 1;
280                                         break;
281                                 }
282                                 
283                         } else {
284                                 consecutiveVoiceDuration += framelength;
285                                 voiceDuration += framelength;
286                                 
287                                 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
288                                    number of words if my previous state was Silence, which means that I moved into a word. */
289                                 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
290                                         iWordsCount++;
291                                         ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan->name, iWordsCount);
292                                         previousState = currentState;
293                                         currentState = STATE_IN_WORD;
294                                 }
295                                 if (consecutiveVoiceDuration >= maximumWordLength){
296                                         ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan->name, consecutiveVoiceDuration);
297                                         ast_frfree(f);
298                                         strcpy(amdStatus , "MACHINE");
299                                         sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
300                                         break;
301                                 }                               
302                                 if (iWordsCount >= maximumNumberOfWords) {
303                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan->name, iWordsCount);
304                                         ast_frfree(f);
305                                         strcpy(amdStatus , "MACHINE");
306                                         sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
307                                         res = 1;
308                                         break;
309                                 }
310                                 
311                                 if (inGreeting == 1 && voiceDuration >= greeting) {
312                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan->name, voiceDuration, greeting);
313                                         ast_frfree(f);
314                                         strcpy(amdStatus , "MACHINE");
315                                         sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
316                                         res = 1;
317                                         break;
318                                 }
319                                 
320                                 if (voiceDuration >= minimumWordLength ) {
321                                         if (silenceDuration > 0)
322                                                 ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan->name, silenceDuration);
323                                         silenceDuration = 0;
324                                 }
325                                 if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0){
326                                         /* Only go in here once to change the greeting flag when we detect the 1st word */
327                                         if (silenceDuration > 0)
328                                                 ast_verb(3, "AMD: Channel [%s]. Before Greeting Time:  silenceDuration: %d voiceDuration: %d\n", chan->name, silenceDuration, voiceDuration);
329                                         inInitialSilence = 0;
330                                         inGreeting = 1;
331                                 }
332                                 
333                         }
334                 }
335                 ast_frfree(f);
336         }
337         
338         if (!res) {
339                 /* It took too long to get a frame back. Giving up. */
340                 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name);
341                 strcpy(amdStatus , "NOTSURE");
342                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
343         }
344
345         /* Set the status and cause on the channel */
346         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
347         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
348
349         /* Restore channel read format */
350         if (readFormat && ast_set_read_format(chan, readFormat))
351                 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
352
353         /* Free the DSP used to detect silence */
354         ast_dsp_free(silenceDetector);
355
356         return;
357 }
358
359
360 static int amd_exec(struct ast_channel *chan, void *data)
361 {
362         isAnsweringMachine(chan, data);
363
364         return 0;
365 }
366
367 static int load_config(int reload)
368 {
369         struct ast_config *cfg = NULL;
370         char *cat = NULL;
371         struct ast_variable *var = NULL;
372         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
373
374         if (!(cfg = ast_config_load("amd.conf", config_flags))) {
375                 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
376                 return -1;
377         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
378                 return 0;
379
380         cat = ast_category_browse(cfg, NULL);
381
382         while (cat) {
383                 if (!strcasecmp(cat, "general") ) {
384                         var = ast_variable_browse(cfg, cat);
385                         while (var) {
386                                 if (!strcasecmp(var->name, "initial_silence")) {
387                                         dfltInitialSilence = atoi(var->value);
388                                 } else if (!strcasecmp(var->name, "greeting")) {
389                                         dfltGreeting = atoi(var->value);
390                                 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
391                                         dfltAfterGreetingSilence = atoi(var->value);
392                                 } else if (!strcasecmp(var->name, "silence_threshold")) {
393                                         dfltSilenceThreshold = atoi(var->value);
394                                 } else if (!strcasecmp(var->name, "total_analysis_time")) {
395                                         dfltTotalAnalysisTime = atoi(var->value);
396                                 } else if (!strcasecmp(var->name, "min_word_length")) {
397                                         dfltMinimumWordLength = atoi(var->value);
398                                 } else if (!strcasecmp(var->name, "between_words_silence")) {
399                                         dfltBetweenWordsSilence = atoi(var->value);
400                                 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
401                                         dfltMaximumNumberOfWords = atoi(var->value);
402                                 } else if (!strcasecmp(var->name, "maximum_word_length")) {
403                                         dfltMaximumWordLength = atoi(var->value);
404                                         
405                                 } else {
406                                         ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
407                                                 app, cat, var->name, var->lineno);
408                                 }
409                                 var = var->next;
410                         }
411                 }
412                 cat = ast_category_browse(cfg, cat);
413         }
414
415         ast_config_destroy(cfg);
416
417         ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
418                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
419                                 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
420                                 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);
421
422         return 0;
423 }
424
425 static int unload_module(void)
426 {
427         return ast_unregister_application(app);
428 }
429
430 static int load_module(void)
431 {
432         if (load_config(0))
433                 return AST_MODULE_LOAD_DECLINE;
434         if (ast_register_application(app, amd_exec, synopsis, descrip))
435                 return AST_MODULE_LOAD_FAILURE;
436         return AST_MODULE_LOAD_SUCCESS;
437 }
438
439 static int reload(void)
440 {
441         if (load_config(1))
442                 return AST_MODULE_LOAD_DECLINE;
443         return AST_MODULE_LOAD_SUCCESS;
444 }
445
446 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
447                 .load = load_module,
448                 .unload = unload_module,
449                 .reload = reload,
450                );