Add missing
[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;
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
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         ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
185                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
186                                 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
187                                 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
188
189         readFormat = chan->readformat;
190         res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
191         if (res < 0 ) {
192                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
193                 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
194                 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
195                 return;
196         }
197
198         silenceDetector = ast_dsp_new();
199         if (!silenceDetector ) {
200                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
201                 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , "" );
202                 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , "" );
203                 return;
204         }
205         ast_dsp_set_threshold(silenceDetector, silenceThreshold );
206
207         while (ast_waitfor(chan, -1) > -1)
208         {
209                 f = ast_read(chan);
210                 if (!f ) {
211                         /* No Frame: Called Party Must Have Dropped */
212                         ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
213                         ast_log(LOG_DEBUG, "Got hangup\n");
214                         strcpy(amdStatus , "HANGUP" );
215                         strcpy(amdCause , "" );
216                         break;
217                 }
218                 framelength = (ast_codec_get_samples(f) / 8);
219                 iTotalTime += framelength;
220                 if (iTotalTime >= totalAnalysisTime ) {
221                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
222                         ast_frfree(f);
223                         strcpy(amdStatus , "NOTSURE" );
224                         sprintf(amdCause , "TOOLONG-%d", iTotalTime );
225                         break;
226                 }
227                 if (f->frametype == AST_FRAME_VOICE ) {
228                         dspsilence = 0;
229                         ast_dsp_silence(silenceDetector, f, &dspsilence);
230                         if (dspsilence ) {
231                                 silenceDuration = dspsilence;
232                                 /* ast_verbose(VERBOSE_PREFIX_3 "AMD: %d SILENCE: silenceDuration:%d afterGreetingSilence:%d inGreeting:%d\n", currentState, silenceDuration, afterGreetingSilence, inGreeting ); */
233                                 if (silenceDuration >= betweenWordsSilence ) {
234                                         if (currentState != STATE_IN_SILENCE ) {
235                                                 previousState = currentState;
236                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
237                                         }
238                                         currentState  = STATE_IN_SILENCE;
239                                         consecutiveVoiceDuration = 0;
240                                 }
241                                 if (inInitialSilence == 1  && silenceDuration >= initialSilence ) {
242                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
243                                                         silenceDuration, initialSilence );
244                                         ast_frfree(f);
245                                         strcpy(amdStatus , "MACHINE" );
246                                         sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence );
247                                         break;
248                                 }
249
250                                 if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1 ) {
251                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
252                                                         silenceDuration, afterGreetingSilence );
253                                         ast_frfree(f);
254                                         strcpy(amdStatus , "HUMAN" );
255                                         sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence );
256                                         break;
257                                 }
258                         } else {
259                                 consecutiveVoiceDuration += framelength;
260                                 voiceDuration += framelength;
261                                 /* ast_verbose(VERBOSE_PREFIX_3 "AMD: %d VOICE: ConsecutiveVoice:%d voiceDuration:%d inGreeting:%d\n", currentState, consecutiveVoiceDuration, voiceDuration, inGreeting ); */
262
263                                 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
264                                         number of words if my previous state was Silence, which means that I moved into a word. */
265                                 if (consecutiveVoiceDuration >= minimumWordLength ) {
266                                         if (currentState == STATE_IN_SILENCE ) {
267                                                 iWordsCount++;
268                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount );
269                                                 previousState = currentState;
270                                                 currentState = STATE_IN_WORD;
271                                         }
272                                 }
273
274                                 if (iWordsCount >= maximumNumberOfWords ) {
275                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount );
276                                         ast_frfree(f);
277                                         strcpy(amdStatus , "MACHINE" );
278                                         sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords );
279                                         break;
280                                 }
281
282                                 if (inGreeting == 1  &&  voiceDuration >= greeting ) {
283                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n",
284                                                         voiceDuration, greeting );
285                                         ast_frfree(f);
286                                         strcpy(amdStatus , "MACHINE" );
287                                         sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting );
288                                         break;
289                                 }
290                                 if (voiceDuration >= minimumWordLength ) {
291                                         silenceDuration = 0;
292                                         inInitialSilence = 0;
293                                         inGreeting = 1;
294                                 }
295                         }
296                 }
297                 ast_frfree(f);
298         }
299
300         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus );
301         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause );
302
303         /* If We Started With A Valid Read Format, Return To It... */
304         if (readFormat) {
305                 res = ast_set_read_format(chan, readFormat );
306                 if (res)
307                         ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
308         }
309
310         /* Free The Silence Detector DSP */
311         ast_dsp_free(silenceDetector );
312
313         return;
314 }
315
316
317 static int amd_exec(struct ast_channel *chan, void *data)
318 {
319         struct localuser *u;
320
321         LOCAL_USER_ADD(u);
322         isAnsweringMachine(chan, data);
323         LOCAL_USER_REMOVE(u);
324
325         return 0;
326 }
327
328 static void load_config(void)
329 {
330         struct ast_config *cfg;
331         char *cat;
332         struct ast_variable *var;
333
334         cfg = ast_config_load("amd.conf");
335
336         if (!cfg) {
337                 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
338                 return;
339         }
340
341         cat = ast_category_browse(cfg, NULL);
342
343         while (cat) {
344                 if (!strcasecmp(cat, "amd") ) {
345                         var = ast_variable_browse(cfg, cat);
346                         while (var) {
347                                 if (!strcasecmp(var->name, "initial_silence")) {
348                                         dfltInitialSilence = atoi(var->value);
349                                 } else if (!strcasecmp(var->name, "greeting")) {
350                                         dfltGreeting = atoi(var->value);
351                                 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
352                                         dfltAfterGreetingSilence = atoi(var->value);
353                                 } else if (!strcasecmp(var->name, "silence_threshold")) {
354                                         dfltSilenceThreshold = atoi(var->value);
355                                 } else if (!strcasecmp(var->name, "total_analysis_time")) {
356                                         dfltTotalAnalysisTime = atoi(var->value);
357                                 } else if (!strcasecmp(var->name, "min_word_length")) {
358                                         dfltMinimumWordLength = atoi(var->value);
359                                 } else if (!strcasecmp(var->name, "between_words_silence")) {
360                                         dfltBetweenWordsSilence = atoi(var->value);
361                                 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
362                                         dfltMaximumNumberOfWords = atoi(var->value);
363                                 } else {
364                                         ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
365                                                 app, cat, var->name, var->lineno);
366                                 }
367                                 var = var->next;
368                         }
369                 }
370                 cat = ast_category_browse(cfg, cat);
371         }
372         ast_config_destroy(cfg);
373
374         ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
375                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
376                                 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
377                                 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
378
379         return;
380 }
381
382 int unload_module(void)
383 {
384         STANDARD_HANGUP_LOCALUSERS;
385         return ast_unregister_application(app);
386 }
387
388 int load_module(void)
389 {
390         load_config();
391         return ast_register_application(app, amd_exec, synopsis, descrip);
392 }
393
394 int reload(void)
395 {
396         load_config();
397         return 0;
398 }
399
400 char *description(void)
401 {
402         return tdesc;
403 }
404
405 int usecount(void)
406 {
407         int res;
408         STANDARD_USECOUNT(res);
409         return res;
410 }
411
412 char *key()
413 {
414         return ASTERISK_GPL_KEY;
415 }
416