set properties for new files (i need to get this documented)
[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 "                AMD_MACHINE | AMD_PERSON | AMD_NOTSURE | AMD_HANGUP\n"
64 "    AMDCAUSE - Indicates the cause that led to the conclusion.\n"
65 "               Possible values are:\n"
66 "               AMD_TOOLONG-<%d total_time>\n"
67 "               AMD_INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
68 "               AMD_HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
69 "               AMD_MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
70 "               AMD_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
100         int inInitialSilence         = 1;
101         int inGreeting               = 0;
102         int voiceDuration            = 0;
103         int silenceDuration          = 0;
104         int iTotalTime               = 0;
105         int iWordsCount              = 0;
106         int currentState             = STATE_IN_SILENCE;
107         int previousState            = STATE_IN_SILENCE;
108         int consecutiveVoiceDuration = 0;
109         char amdCause[256]           = "";
110         char amdStatus[256]          = "";
111
112         /* Lets set the initial values of the variables that will control the algorithm.
113            The initial values are the default ones. If they are passed as arguments
114            when invoking the application, then the default values will be overwritten
115            by the ones passed as parameters. */
116         int initialSilence       = dfltInitialSilence;
117         int greeting             = dfltGreeting;
118         int afterGreetingSilence = dfltAfterGreetingSilence;
119         int totalAnalysisTime    = dfltTotalAnalysisTime;
120         int minimumWordLength    = dfltMinimumWordLength;
121         int betweenWordsSilence  = dfltBetweenWordsSilence;
122         int maximumNumberOfWords = dfltMaximumNumberOfWords;
123         int silenceThreshold     = dfltSilenceThreshold;
124
125         char *parse;
126         AST_DECLARE_APP_ARGS(args,
127                              AST_APP_ARG(argInitialSilence);
128                              AST_APP_ARG(argGreeting);
129                              AST_APP_ARG(argAfterGreetingSilence);
130                              AST_APP_ARG(argTotalAnalysisTime);
131                              AST_APP_ARG(argMinimumWordLength);
132                              AST_APP_ARG(argBetweenWordsSilence);
133                              AST_APP_ARG(argMaximumNumberOfWords);
134                              AST_APP_ARG(argSilenceThreshold);
135         );
136
137         ast_verbose(VERBOSE_PREFIX_3 "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
138
139         /* Lets parse the arguments. */
140         if (ast_strlen_zero(data)) {
141                 ast_log(LOG_NOTICE, "AMD using the default parameters.\n");
142         } else {
143                 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
144                 if (!(parse = ast_strdupa(data))) {
145                         ast_log(LOG_WARNING, "Memory allocation failure\n");
146                         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
147                         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
148                         return;
149                 }
150
151                 AST_STANDARD_APP_ARGS(args, parse);
152
153                 if (!ast_strlen_zero(args.argInitialSilence)) {
154                         initialSilence = atoi(args.argInitialSilence);
155                 }
156                 if (!ast_strlen_zero(args.argGreeting)) {
157                         greeting = atoi(args.argGreeting);
158                 }
159                 if (!ast_strlen_zero(args.argAfterGreetingSilence)) {
160                         afterGreetingSilence = atoi(args.argAfterGreetingSilence);
161                 }
162                 if (!ast_strlen_zero(args.argTotalAnalysisTime)) {
163                         totalAnalysisTime = atoi(args.argTotalAnalysisTime);
164                 }
165                 if (!ast_strlen_zero(args.argMinimumWordLength)) {
166                         minimumWordLength = atoi(args.argMinimumWordLength);
167                 }
168                 if (!ast_strlen_zero(args.argBetweenWordsSilence)) {
169                         betweenWordsSilence = atoi(args.argBetweenWordsSilence);
170                 }
171                 if (!ast_strlen_zero(args.argMaximumNumberOfWords)) {
172                         maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
173                 }
174                 if (!ast_strlen_zero(args.argSilenceThreshold)) {
175                         silenceThreshold = atoi(args.argSilenceThreshold);
176                 }
177         }
178
179         /* Now we're ready to roll! */
180
181         ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
182                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
183                                 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
184                                 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
185
186         readFormat = chan->readformat;
187         res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
188         if (res < 0 ) {
189                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
190                 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
191                 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
192                 return;
193         }
194
195         silenceDetector = ast_dsp_new();
196         if (!silenceDetector ) {
197                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
198                 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
199                 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
200                 return;
201         }
202         ast_dsp_set_threshold(silenceDetector, silenceThreshold );
203
204         while (ast_waitfor(chan, -1) > -1)
205         {
206                 f = ast_read(chan);
207                 if (!f ) {
208                         /* No Frame: Called Party Must Have Dropped */
209                         ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
210                         ast_log(LOG_DEBUG, "Got hangup\n");
211                         strcpy(amdStatus , "AMD_HANGUP" );
212                         strcpy(amdCause , "" );
213                         break;
214                 }
215                 iTotalTime += 20;
216                 if (iTotalTime >= totalAnalysisTime ) {
217                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
218                         ast_frfree(f);
219                         strcpy(amdStatus , "AMD_NOTSURE" );
220                         sprintf(amdCause , "AMD_TOOLONG-%d", iTotalTime );
221                         break;
222                 }
223                 if (f->frametype == AST_FRAME_VOICE ) {
224                         dspsilence = 0;
225                         ast_dsp_silence(silenceDetector, f, &dspsilence);
226                         if (dspsilence ) {
227                                 silenceDuration = dspsilence;
228                                 /* ast_verbose(VERBOSE_PREFIX_3 "AMD: %d SILENCE: silenceDuration:%d afterGreetingSilence:%d inGreeting:%d\n", currentState, silenceDuration, afterGreetingSilence, inGreeting ); */
229                                 if (silenceDuration >= betweenWordsSilence ) {
230                                         if (currentState != STATE_IN_SILENCE ) {
231                                                 previousState = currentState;
232                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
233                                         }
234                                         currentState  = STATE_IN_SILENCE;
235                                         consecutiveVoiceDuration = 0;
236                                 }
237                                 if (inInitialSilence == 1  && silenceDuration >= initialSilence ) {
238                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
239                                                         silenceDuration, initialSilence );
240                                         ast_frfree(f);
241                                         strcpy(amdStatus , "AMD_MACHINE" );
242                                         sprintf(amdCause , "AMD_INITIALSILENCE-%d-%d", silenceDuration, initialSilence );
243                                         break;
244                                 }
245
246                                 if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1 ) {
247                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
248                                                         silenceDuration, afterGreetingSilence );
249                                         ast_frfree(f);
250                                         strcpy(amdStatus , "AMD_PERSON" );
251                                         sprintf(amdCause , "AMD_HUMAN-%d-%d", silenceDuration, afterGreetingSilence );
252                                         break;
253                                 }
254                         } else {
255                                 consecutiveVoiceDuration += 20;
256                                 voiceDuration += 20;
257                                 /* ast_verbose(VERBOSE_PREFIX_3 "AMD: %d VOICE: ConsecutiveVoice:%d voiceDuration:%d inGreeting:%d\n", currentState, consecutiveVoiceDuration, voiceDuration, inGreeting ); */
258
259                                 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
260                                         number of words if my previous state was Silence, which means that I moved into a word. */
261                                 if (consecutiveVoiceDuration >= minimumWordLength ) {
262                                         if (currentState == STATE_IN_SILENCE ) {
263                                                 iWordsCount++;
264                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount );
265                                                 previousState = currentState;
266                                                 currentState = STATE_IN_WORD;
267                                         }
268                                 }
269
270                                 if (iWordsCount >= maximumNumberOfWords ) {
271                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount );
272                                         ast_frfree(f);
273                                         strcpy(amdStatus , "AMD_MACHINE" );
274                                         sprintf(amdCause , "AMD_MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords );
275                                         break;
276                                 }
277
278                                 if (inGreeting == 1  &&  voiceDuration >= greeting ) {
279                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n",
280                                                         voiceDuration, greeting );
281                                         ast_frfree(f);
282                                         strcpy(amdStatus , "AMD_MACHINE" );
283                                         sprintf(amdCause , "AMD_LONGGREETING-%d-%d", voiceDuration, greeting );
284                                         break;
285                                 }
286                                 if (voiceDuration >= minimumWordLength ) {
287                                         silenceDuration = 0;
288                                         inInitialSilence = 0;
289                                         inGreeting = 1;
290                                 }
291                         }
292                 }
293                 ast_frfree(f);
294         }
295
296         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus );
297         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause );
298
299         /* If We Started With A Valid Read Format, Return To It... */
300         if (readFormat) {
301                 res = ast_set_read_format(chan, readFormat );
302                 if (res)
303                         ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
304         }
305
306         /* Free The Silence Detector DSP */
307         ast_dsp_free(silenceDetector );
308
309         return;
310 }
311
312
313 static int amd_exec(struct ast_channel *chan, void *data)
314 {
315         struct localuser *u;
316
317         LOCAL_USER_ADD(u);
318         isAnsweringMachine(chan, data);
319         LOCAL_USER_REMOVE(u);
320
321         return 0;
322 }
323
324 static void load_config(void)
325 {
326         struct ast_config *cfg;
327         char *cat;
328         struct ast_variable *var;
329
330         cfg = ast_config_load("amd.conf");
331
332         if (!cfg) {
333                 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
334                 return;
335         }
336
337         cat = ast_category_browse(cfg, NULL);
338
339         while (cat) {
340                 if (!strcasecmp(cat, "amd") ) {
341                         var = ast_variable_browse(cfg, cat);
342                         while (var) {
343                                 if (!strcasecmp(var->name, "initial_silence")) {
344                                         dfltInitialSilence = atoi(var->value);
345                                 } else if (!strcasecmp(var->name, "greeting")) {
346                                         dfltGreeting = atoi(var->value);
347                                 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
348                                         dfltAfterGreetingSilence = atoi(var->value);
349                                 } else if (!strcasecmp(var->name, "silence_threshold")) {
350                                         dfltSilenceThreshold = atoi(var->value);
351                                 } else if (!strcasecmp(var->name, "total_analysis_time")) {
352                                         dfltTotalAnalysisTime = atoi(var->value);
353                                 } else if (!strcasecmp(var->name, "min_word_length")) {
354                                         dfltMinimumWordLength = atoi(var->value);
355                                 } else if (!strcasecmp(var->name, "between_words_silence")) {
356                                         dfltBetweenWordsSilence = atoi(var->value);
357                                 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
358                                         dfltMaximumNumberOfWords = atoi(var->value);
359                                 } else {
360                                         ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
361                                                 app, cat, var->name, var->lineno);
362                                 }
363                                 var = var->next;
364                         }
365                 }
366                 cat = ast_category_browse(cfg, cat);
367         }
368         ast_config_destroy(cfg);
369
370         ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
371                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
372                                 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
373                                 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
374
375         return;
376 }
377
378 int unload_module(void)
379 {
380         STANDARD_HANGUP_LOCALUSERS;
381         return ast_unregister_application(app);
382 }
383
384 int load_module(void)
385 {
386         load_config();
387         return ast_register_application(app, amd_exec, synopsis, descrip);
388 }
389
390 int reload(void)
391 {
392         load_config();
393         return 0;
394 }
395
396 char *description(void)
397 {
398         return tdesc;
399 }
400
401 int usecount(void)
402 {
403         int res;
404         STANDARD_USECOUNT(res);
405         return res;
406 }
407
408 char *key()
409 {
410         return ASTERISK_GPL_KEY;
411 }
412