6c753783d8d8744ffaa3cc25e0008f1e3df4f992
[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 <stdio.h>
35 #include <stdlib.h>
36
37 #include "asterisk/module.h"
38 #include "asterisk/lock.h"
39 #include "asterisk/options.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/dsp.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/config.h"
44 #include "asterisk/app.h"
45
46
47 static char *app = "AMD";
48 static char *synopsis = "Attempts to detect answering machines";
49 static char *descrip =
50 "  AMD([initialSilence][|greeting][|afterGreetingSilence][|totalAnalysisTime]\n"
51 "      [|minimumWordLength][|betweenWordsSilence][|maximumNumberOfWords]\n"
52 "      [|silenceThreshold])\n"
53 "  This application attempts to detect answering machines at the beginning\n"
54 "  of outbound calls.  Simply call this application after the call\n"
55 "  has been answered (outbound only, of course).\n"
56 "  When loaded, AMD reads amd.conf and uses the parameters specified as\n"
57 "  default values. Those default values get overwritten when calling AMD\n"
58 "  with parameters.\n"
59 "- 'initialSilence' is the maximum silence duration before the greeting. If\n"
60 "   exceeded then MACHINE.\n"
61 "- 'greeting' is the maximum length of a greeting. If exceeded then MACHINE.\n"
62 "- 'afterGreetingSilence' is the silence after detecting a greeting.\n"
63 "   If exceeded then HUMAN.\n"
64 "- 'totalAnalysisTime' is the maximum time allowed for the algorithm to decide\n"
65 "   on a HUMAN or MACHINE.\n"
66 "- 'minimumWordLength'is the minimum duration of Voice to considered as a word.\n"
67 "- 'betweenWordsSilence' is the minimum duration of silence after a word to \n"
68 "   consider the audio that follows as a new word.\n"
69 "- 'maximumNumberOfWords'is the maximum number of words in the greeting. \n"
70 "   If exceeded then MACHINE.\n"
71 "- 'silenceThreshold' is the silence threshold.\n"
72 "This application sets the following channel variable upon completion:\n"
73 "    AMDSTATUS - This is the status of the answering machine detection.\n"
74 "                Possible values are:\n"
75 "                MACHINE | HUMAN | NOTSURE | HANGUP\n"
76 "    AMDCAUSE - Indicates the cause that led to the conclusion.\n"
77 "               Possible values are:\n"
78 "               TOOLONG-<%d total_time>\n"
79 "               INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
80 "               HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
81 "               MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
82 "               LONGGREETING-<%d voiceDuration>-<%d greeting>\n";
83
84 #define STATE_IN_WORD       1
85 #define STATE_IN_SILENCE    2
86
87 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
88 static int dfltInitialSilence       = 2500;
89 static int dfltGreeting             = 1500;
90 static int dfltAfterGreetingSilence = 800;
91 static int dfltTotalAnalysisTime    = 5000;
92 static int dfltMinimumWordLength    = 100;
93 static int dfltBetweenWordsSilence  = 50;
94 static int dfltMaximumNumberOfWords = 3;
95 static int dfltSilenceThreshold     = 256;
96
97 static void isAnsweringMachine(struct ast_channel *chan, void *data)
98 {
99         int res = 0;
100         struct ast_frame *f = NULL;
101         struct ast_dsp *silenceDetector = NULL;
102         int dspsilence = 0, readFormat, framelength;
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] = "", amdStatus[256] = "";
113         char *parse = ast_strdupa(data);
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         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         );
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(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         } else {
163                 ast_debug(1, "AMD using the default parameters.\n");
164         }
165
166         /* Now we're ready to roll! */
167         if (option_verbose > 2)
168                 ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
169                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
170                                 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
171                                 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
172
173         /* Set read format to signed linear so we get signed linear frames in */
174         readFormat = chan->readformat;
175         if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
176                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
177                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
178                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
179                 return;
180         }
181
182         /* Create a new DSP that will detect the silence */
183         if (!(silenceDetector = ast_dsp_new())) {
184                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
185                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
186                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
187                 return;
188         }
189
190         /* Set silence threshold to specified value */
191         ast_dsp_set_threshold(silenceDetector, silenceThreshold);
192
193         /* Now we go into a loop waiting for frames from the channel */
194         while ((res = ast_waitfor(chan, totalAnalysisTime)) > -1) {
195                 /* If we fail to read in a frame, that means they hung up */
196                 if (!(f = ast_read(chan))) {
197                         if (option_verbose > 2)
198                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
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                                 if (option_verbose > 2) 
210                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
211                                 ast_frfree(f);
212                                 strcpy(amdStatus , "NOTSURE");
213                                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
214                                 break;
215                         }
216
217                         /* Feed the frame of audio into the silence detector and see if we get a result */
218                         dspsilence = 0;
219                         ast_dsp_silence(silenceDetector, f, &dspsilence);
220                         if (dspsilence) {
221                                 silenceDuration = dspsilence;
222                                 
223                                 if (silenceDuration >= betweenWordsSilence) {
224                                         if (currentState != STATE_IN_SILENCE ) {
225                                                 previousState = currentState;
226                                                 if (option_verbose > 2)
227                                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
228                                         }
229                                         currentState  = STATE_IN_SILENCE;
230                                         consecutiveVoiceDuration = 0;
231                                 }
232                                 
233                                 if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
234                                         if (option_verbose > 2)
235                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
236                                                             silenceDuration, initialSilence);
237                                         ast_frfree(f);
238                                         strcpy(amdStatus , "MACHINE");
239                                         sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
240                                         break;
241                                 }
242                                 
243                                 if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
244                                         if (option_verbose > 2)
245                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
246                                                             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                                         if (option_verbose > 2)
262                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount);
263                                         previousState = currentState;
264                                         currentState = STATE_IN_WORD;
265                                 }
266                                 
267                                 if (iWordsCount >= maximumNumberOfWords) {
268                                         if (option_verbose > 2)
269                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount);
270                                         ast_frfree(f);
271                                         strcpy(amdStatus , "MACHINE");
272                                         sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
273                                         break;
274                                 }
275                                 
276                                 if (inGreeting == 1 && voiceDuration >= greeting) {
277                                         if (option_verbose > 2)
278                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting);
279                                         ast_frfree(f);
280                                         strcpy(amdStatus , "MACHINE");
281                                         sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
282                                         break;
283                                 }
284                                 
285                                 if (voiceDuration >= minimumWordLength ) {
286                                         silenceDuration = 0;
287                                         inInitialSilence = 0;
288                                         inGreeting = 1;
289                                 }
290                                 
291                         }
292                 }
293                 ast_frfree(f);
294         }
295         
296         if (!res) {
297                 /* It took too long to get a frame back. Giving up. */
298                 if (option_verbose > 2)
299                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name);
300                 strcpy(amdStatus , "NOTSURE");
301                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
302         }
303
304         /* Set the status and cause on the channel */
305         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
306         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
307
308         /* Restore channel read format */
309         if (readFormat && ast_set_read_format(chan, readFormat))
310                 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
311
312         /* Free the DSP used to detect silence */
313         ast_dsp_free(silenceDetector);
314
315         return;
316 }
317
318
319 static int amd_exec(struct ast_channel *chan, void *data)
320 {
321         struct ast_module_user *u = NULL;
322
323         u = ast_module_user_add(chan);
324         isAnsweringMachine(chan, data);
325         ast_module_user_remove(u);
326
327         return 0;
328 }
329
330 static void load_config(void)
331 {
332         struct ast_config *cfg = NULL;
333         char *cat = NULL;
334         struct ast_variable *var = NULL;
335
336         if (!(cfg = ast_config_load("amd.conf"))) {
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, "general") ) {
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
373         ast_config_destroy(cfg);
374
375         if (option_verbose > 2)
376                 ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
377                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
378                                 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
379                                 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
380
381         return;
382 }
383
384 static int unload_module(void)
385 {
386         return ast_unregister_application(app);
387 }
388
389 static int load_module(void)
390 {
391         load_config();
392         return ast_register_application(app, amd_exec, synopsis, descrip);
393 }
394
395 static int reload(void)
396 {
397         load_config();
398         return 0;
399 }
400
401 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
402                 .load = load_module,
403                 .unload = unload_module,
404                 .reload = reload,
405                );