Fix a problem where if the channel was hungup during detection, the application...
[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  * Disclaimed to Digium
9  *
10  * See http://www.asterisk.org for more information about
11  * the Asterisk project. Please do not directly contact
12  * any of the maintainers of this project for assistance;
13  * the project provides a web site, mailing lists and IRC
14  * channels for your use.
15  *
16  * This program is free software, distributed under the terms of
17  * the GNU General Public License Version 2. See the LICENSE file
18  * at the top of the source tree.
19  */
20
21 #include <stdio.h>
22 #include <stdlib.h>
23
24 #include "asterisk.h"
25  
26 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
27
28 #include "asterisk/module.h"
29 #include "asterisk/lock.h"
30 #include "asterisk/options.h"
31 #include "asterisk/channel.h"
32 #include "asterisk/dsp.h"
33 #include "asterisk/pbx.h"
34 #include "asterisk/config.h"
35 #include "asterisk/app.h"
36
37
38 static char *tdesc = "Answering Machine Detection Application";
39 static char *app = "AMD";
40 static char *synopsis = "Attempts to detect answering machines";
41 static char *descrip =
42 "  AMD([initialSilence][|greeting][|afterGreetingSilence][|totalAnalysisTime]\n"
43 "      [|minimumWordLength][|betweenWordsSilence][|maximumNumberOfWords]\n"
44 "      [|silenceThreshold])\n"
45 "  This application attempts to detect answering machines at the beginning\n"
46 "  of outbound calls.  Simply call this application after the call\n"
47 "  has been answered (outbound only, of course).\n"
48 "  When loaded, AMD reads amd.conf and uses the parameters specified as\n"
49 "  default values. Those default values get overwritten when calling AMD\n"
50 "  with parameters.\n"
51 "- 'initialSilence' is the maximum silence duration before the greeting. If\n"
52 "   exceeded then MACHINE.\n"
53 "- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n"
54 "- 'afterGreetingSilence' is the silence after detecting a greeting.\n"
55 "   If exceeded then HUMAN.\n"
56 "- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n"
57 "   on a HUMAN or MACHINE.\n"
58 "- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n"
59 "- 'betweenWordsSilence' is the minimum duration of silence after a word to \n"
60 "   consider the audio that follows as a new word.\n"
61 "- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n"
62 "   If exceeded then MACHINE.\n"
63 "- 'silenceThreshold' is the silence threshold.\n"
64 "This application sets the following channel variable upon completion:\n"
65 "    AMDSTATUS - This is the status of the answering machine detection.\n"
66 "                Possible values are:\n"
67 "                MACHINE | HUMAN | NOTSURE | HANGUP\n"
68 "    AMDCAUSE - Indicates the cause that led to the conclusion.\n"
69 "               Possible values are:\n"
70 "               TOOLONG-<%d total_time>\n"
71 "               INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
72 "               HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
73 "               MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
74 "               LONGGREETING-<%d voiceDuration>-<%d greeting>\n";
75
76
77 LOCAL_USER_DECL;
78
79 #define STATE_IN_WORD       1
80 #define STATE_IN_SILENCE    2
81
82 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
83 static int dfltInitialSilence       = 2500;
84 static int dfltGreeting             = 1500;
85 static int dfltAfterGreetingSilence = 800;
86 static int dfltTotalAnalysisTime    = 5000;
87 static int dfltMinimumWordLength    = 100;
88 static int dfltBetweenWordsSilence  = 50;
89 static int dfltMaximumNumberOfWords = 3;
90 static int dfltSilenceThreshold     = 256;
91
92 static void isAnsweringMachine(struct ast_channel *chan, void *data)
93 {
94         int res = 0, ret = 0;
95
96         struct ast_frame *f = NULL;
97
98         struct ast_dsp *silenceDetector;         /* silence detector dsp */
99         int dspsilence = 0;
100         int readFormat;
101         int framelength;
102
103         int inInitialSilence         = 1;
104         int inGreeting               = 0;
105         int voiceDuration            = 0;
106         int silenceDuration          = 0;
107         int iTotalTime               = 0;
108         int iWordsCount              = 0;
109         int currentState             = STATE_IN_SILENCE;
110         int previousState            = STATE_IN_SILENCE;
111         int consecutiveVoiceDuration = 0;
112         char amdCause[256]           = "";
113         char amdStatus[256]          = "";
114
115         /* Lets set the initial values of the variables that will control the algorithm.
116            The initial values are the default ones. If they are passed as arguments
117            when invoking the application, then the default values will be overwritten
118            by the ones passed as parameters. */
119         int initialSilence       = dfltInitialSilence;
120         int greeting             = dfltGreeting;
121         int afterGreetingSilence = dfltAfterGreetingSilence;
122         int totalAnalysisTime    = dfltTotalAnalysisTime;
123         int minimumWordLength    = dfltMinimumWordLength;
124         int betweenWordsSilence  = dfltBetweenWordsSilence;
125         int maximumNumberOfWords = dfltMaximumNumberOfWords;
126         int silenceThreshold     = dfltSilenceThreshold;
127
128         char *parse;
129         AST_DECLARE_APP_ARGS(args,
130                              AST_APP_ARG(argInitialSilence);
131                              AST_APP_ARG(argGreeting);
132                              AST_APP_ARG(argAfterGreetingSilence);
133                              AST_APP_ARG(argTotalAnalysisTime);
134                              AST_APP_ARG(argMinimumWordLength);
135                              AST_APP_ARG(argBetweenWordsSilence);
136                              AST_APP_ARG(argMaximumNumberOfWords);
137                              AST_APP_ARG(argSilenceThreshold);
138         );
139         if (option_verbose > 2)
140                 ast_verbose(VERBOSE_PREFIX_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(data)) {
144                 ast_log(LOG_NOTICE, "AMD using the default parameters.\n");
145         } else {
146                 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
147                 if (!(parse = ast_strdupa(data))) {
148                         ast_log(LOG_WARNING, "Memory allocation failure\n");
149                         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
150                         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
151                         return;
152                 }
153
154                 AST_STANDARD_APP_ARGS(args, parse);
155
156                 if (!ast_strlen_zero(args.argInitialSilence)) {
157                         initialSilence = atoi(args.argInitialSilence);
158                 }
159                 if (!ast_strlen_zero(args.argGreeting)) {
160                         greeting = atoi(args.argGreeting);
161                 }
162                 if (!ast_strlen_zero(args.argAfterGreetingSilence)) {
163                         afterGreetingSilence = atoi(args.argAfterGreetingSilence);
164                 }
165                 if (!ast_strlen_zero(args.argTotalAnalysisTime)) {
166                         totalAnalysisTime = atoi(args.argTotalAnalysisTime);
167                 }
168                 if (!ast_strlen_zero(args.argMinimumWordLength)) {
169                         minimumWordLength = atoi(args.argMinimumWordLength);
170                 }
171                 if (!ast_strlen_zero(args.argBetweenWordsSilence)) {
172                         betweenWordsSilence = atoi(args.argBetweenWordsSilence);
173                 }
174                 if (!ast_strlen_zero(args.argMaximumNumberOfWords)) {
175                         maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
176                 }
177                 if (!ast_strlen_zero(args.argSilenceThreshold)) {
178                         silenceThreshold = atoi(args.argSilenceThreshold);
179                 }
180         }
181
182         /* Now we're ready to roll! */
183         
184         if (option_verbose > 2)
185                 ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
186                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
187                                 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
188                                 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
189
190         readFormat = chan->readformat;
191         res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
192         if (res < 0 ) {
193                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
194                 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
195                 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
196                 return;
197         }
198
199         silenceDetector = ast_dsp_new();
200         if (!silenceDetector ) {
201                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
202                 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
203                 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
204                 return;
205         }
206         ast_dsp_set_threshold(silenceDetector, silenceThreshold );
207
208         while ((ret = ast_waitfor(chan, totalAnalysisTime)))
209         {
210                 if (ret < 0) {
211                         /* No Frame: Called Party Must Have Dropped */
212                         if (option_verbose > 2)
213                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
214                         if (option_debug)
215                                 ast_log(LOG_DEBUG, "Got hangup\n");
216                         strcpy(amdStatus , "HANGUP" );
217                         strcpy(amdCause , "" );
218                         break;
219                 }
220                 f = ast_read(chan);
221                 if (!f ) {
222                         /* No Frame: Called Party Must Have Dropped */
223                         if (option_verbose > 2)
224                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
225                         if (option_debug)
226                                 ast_log(LOG_DEBUG, "Got hangup\n");
227                         strcpy(amdStatus , "HANGUP" );
228                         strcpy(amdCause , "" );
229                         break;
230                 }
231                 if (f->frametype == AST_FRAME_VOICE ) {
232                         framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
233                         iTotalTime += framelength;
234                         if (iTotalTime >= totalAnalysisTime ) {
235                                 if (option_verbose > 2) 
236                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
237                                 ast_frfree(f);
238                                 strcpy(amdStatus , "NOTSURE" );
239                                 sprintf(amdCause , "TOOLONG-%d", iTotalTime );
240                                 break;
241                         }
242                         dspsilence = 0;
243                         ast_dsp_silence(silenceDetector, f, &dspsilence);
244                         if (dspsilence ) {
245                                 silenceDuration = dspsilence;
246                                 if (silenceDuration >= betweenWordsSilence ) {
247                                         if (currentState != STATE_IN_SILENCE ) {
248                                                 previousState = currentState;
249                                                 if (option_verbose > 2)
250                                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
251                                         }
252                                         currentState  = STATE_IN_SILENCE;
253                                         consecutiveVoiceDuration = 0;
254                                 }
255                                 if (inInitialSilence == 1  && silenceDuration >= initialSilence ) {
256                                         if (option_verbose > 2)
257                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
258                                                         silenceDuration, initialSilence );
259                                         ast_frfree(f);
260                                         strcpy(amdStatus , "MACHINE" );
261                                         sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence );
262                                         break;
263                                 }
264
265                                 if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1 ) {
266                                         if (option_verbose > 2)
267                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
268                                                         silenceDuration, afterGreetingSilence );
269                                         ast_frfree(f);
270                                         strcpy(amdStatus , "HUMAN" );
271                                         sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence );
272                                         break;
273                                 }
274                         } else {
275                                 consecutiveVoiceDuration += framelength;
276                                 voiceDuration += framelength;
277
278                                 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
279                                         number of words if my previous state was Silence, which means that I moved into a word. */
280                                 if (consecutiveVoiceDuration >= minimumWordLength ) {
281                                         if (currentState == STATE_IN_SILENCE ) {
282                                                 iWordsCount++;
283                                                 if (option_verbose > 2)
284                                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount );
285                                                 previousState = currentState;
286                                                 currentState = STATE_IN_WORD;
287                                         }
288                                 }
289
290                                 if (iWordsCount >= maximumNumberOfWords ) {
291                                         if (option_verbose > 2)
292                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount );
293                                         ast_frfree(f);
294                                         strcpy(amdStatus , "MACHINE" );
295                                         sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords );
296                                         break;
297                                 }
298
299                                 if (inGreeting == 1  &&  voiceDuration >= greeting ) {
300                                         if (option_verbose > 2)
301                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting);
302                                          ast_frfree(f);
303                                         strcpy(amdStatus , "MACHINE" );
304                                         sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting );
305                                         break;
306                                 }
307                                 if (voiceDuration >= minimumWordLength ) {
308                                         silenceDuration = 0;
309                                         inInitialSilence = 0;
310                                         inGreeting = 1;
311                                 }
312                         }
313                 }
314                 ast_frfree(f);
315         }
316         if (!ret) {
317                 /* It took too long to get a frame back. Giving up. */
318                 if (option_verbose > 2)
319                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
320                 strcpy(amdStatus , "NOTSURE" );
321                 sprintf(amdCause , "TOOLONG-%d", iTotalTime );
322         }
323
324         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus );
325         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause );
326
327         /* If We Started With A Valid Read Format, Return To It... */
328         if (readFormat && chan->_state == AST_STATE_UP) {
329                 res = ast_set_read_format(chan, readFormat );
330                 if (res)
331                         ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
332         }
333
334         /* Free The Silence Detector DSP */
335         ast_dsp_free(silenceDetector );
336
337         return;
338 }
339
340
341 static int amd_exec(struct ast_channel *chan, void *data)
342 {
343         struct localuser *u;
344
345         LOCAL_USER_ADD(u);
346         isAnsweringMachine(chan, data);
347         LOCAL_USER_REMOVE(u);
348
349         return 0;
350 }
351
352 static void load_config(void)
353 {
354         struct ast_config *cfg;
355         char *cat;
356         struct ast_variable *var;
357
358         cfg = ast_config_load("amd.conf");
359
360         if (!cfg) {
361                 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
362                 return;
363         }
364
365         cat = ast_category_browse(cfg, NULL);
366
367         while (cat) {
368                 if (!strcasecmp(cat, "amd") ) {
369                         var = ast_variable_browse(cfg, cat);
370                         while (var) {
371                                 if (!strcasecmp(var->name, "initial_silence")) {
372                                         dfltInitialSilence = atoi(var->value);
373                                 } else if (!strcasecmp(var->name, "greeting")) {
374                                         dfltGreeting = atoi(var->value);
375                                 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
376                                         dfltAfterGreetingSilence = atoi(var->value);
377                                 } else if (!strcasecmp(var->name, "silence_threshold")) {
378                                         dfltSilenceThreshold = atoi(var->value);
379                                 } else if (!strcasecmp(var->name, "total_analysis_time")) {
380                                         dfltTotalAnalysisTime = atoi(var->value);
381                                 } else if (!strcasecmp(var->name, "min_word_length")) {
382                                         dfltMinimumWordLength = atoi(var->value);
383                                 } else if (!strcasecmp(var->name, "between_words_silence")) {
384                                         dfltBetweenWordsSilence = atoi(var->value);
385                                 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
386                                         dfltMaximumNumberOfWords = atoi(var->value);
387                                 } else {
388                                         ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
389                                                 app, cat, var->name, var->lineno);
390                                 }
391                                 var = var->next;
392                         }
393                 }
394                 cat = ast_category_browse(cfg, cat);
395         }
396         ast_config_destroy(cfg);
397
398         if (option_verbose > 2)
399                 ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
400                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
401                                 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
402                                 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
403
404         return;
405 }
406
407 int unload_module(void)
408 {
409         STANDARD_HANGUP_LOCALUSERS;
410         return ast_unregister_application(app);
411 }
412
413 int load_module(void)
414 {
415         load_config();
416         return ast_register_application(app, amd_exec, synopsis, descrip);
417 }
418
419 int reload(void)
420 {
421         load_config();
422         return 0;
423 }
424
425 char *description(void)
426 {
427         return tdesc;
428 }
429
430 int usecount(void)
431 {
432         int res;
433         STANDARD_USECOUNT(res);
434         return res;
435 }
436
437 char *key()
438 {
439         return ASTERISK_GPL_KEY;
440 }
441