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