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