Don't reload a configuration file if nothing has changed.
[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         ast_verb(3, "AMD: %s %s %s (Fmt: %d)\n", chan->name ,chan->cid.cid_ani, chan->cid.cid_rdnis, chan->readformat);
140
141         /* Lets parse the arguments. */
142         if (!ast_strlen_zero(parse)) {
143                 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
144                 AST_STANDARD_APP_ARGS(args, parse);
145                 if (!ast_strlen_zero(args.argInitialSilence))
146                         initialSilence = atoi(args.argInitialSilence);
147                 if (!ast_strlen_zero(args.argGreeting))
148                         greeting = atoi(args.argGreeting);
149                 if (!ast_strlen_zero(args.argAfterGreetingSilence))
150                         afterGreetingSilence = atoi(args.argAfterGreetingSilence);
151                 if (!ast_strlen_zero(args.argTotalAnalysisTime))
152                         totalAnalysisTime = atoi(args.argTotalAnalysisTime);
153                 if (!ast_strlen_zero(args.argMinimumWordLength))
154                         minimumWordLength = atoi(args.argMinimumWordLength);
155                 if (!ast_strlen_zero(args.argBetweenWordsSilence))
156                         betweenWordsSilence = atoi(args.argBetweenWordsSilence);
157                 if (!ast_strlen_zero(args.argMaximumNumberOfWords))
158                         maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
159                 if (!ast_strlen_zero(args.argSilenceThreshold))
160                         silenceThreshold = atoi(args.argSilenceThreshold);
161         } else {
162                 ast_debug(1, "AMD using the default parameters.\n");
163         }
164
165         /* Now we're ready to roll! */
166         ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
167                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
168                                 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
169                                 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
170
171         /* Set read format to signed linear so we get signed linear frames in */
172         readFormat = chan->readformat;
173         if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
174                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
175                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
176                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
177                 return;
178         }
179
180         /* Create a new DSP that will detect the silence */
181         if (!(silenceDetector = ast_dsp_new())) {
182                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
183                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
184                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
185                 return;
186         }
187
188         /* Set silence threshold to specified value */
189         ast_dsp_set_threshold(silenceDetector, silenceThreshold);
190
191         /* Now we go into a loop waiting for frames from the channel */
192         while ((res = ast_waitfor(chan, totalAnalysisTime)) > -1) {
193                 /* If we fail to read in a frame, that means they hung up */
194                 if (!(f = ast_read(chan))) {
195                         ast_verb(3, "AMD: HANGUP\n");
196                         ast_debug(1, "Got hangup\n");
197                         strcpy(amdStatus, "HANGUP");
198                         break;
199                 }
200
201                 if (f->frametype == AST_FRAME_VOICE) {
202                         /* If the total time exceeds the analysis time then give up as we are not too sure */
203                         framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
204                         iTotalTime += framelength;
205                         if (iTotalTime >= totalAnalysisTime) {
206                                 if (option_verbose > 2) 
207                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
208                                 ast_frfree(f);
209                                 strcpy(amdStatus , "NOTSURE");
210                                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
211                                 break;
212                         }
213
214                         /* Feed the frame of audio into the silence detector and see if we get a result */
215                         dspsilence = 0;
216                         ast_dsp_silence(silenceDetector, f, &dspsilence);
217                         if (dspsilence) {
218                                 silenceDuration = dspsilence;
219                                 
220                                 if (silenceDuration >= betweenWordsSilence) {
221                                         if (currentState != STATE_IN_SILENCE ) {
222                                                 previousState = currentState;
223                                                 if (option_verbose > 2)
224                                                         ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
225                                         }
226                                         currentState  = STATE_IN_SILENCE;
227                                         consecutiveVoiceDuration = 0;
228                                 }
229                                 
230                                 if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
231                                         if (option_verbose > 2)
232                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
233                                                             silenceDuration, initialSilence);
234                                         ast_frfree(f);
235                                         strcpy(amdStatus , "MACHINE");
236                                         sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
237                                         break;
238                                 }
239                                 
240                                 if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
241                                         ast_verb(3, "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
242                                                             silenceDuration, afterGreetingSilence);
243                                         ast_frfree(f);
244                                         strcpy(amdStatus , "HUMAN");
245                                         sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
246                                         break;
247                                 }
248                                 
249                         } else {
250                                 consecutiveVoiceDuration += framelength;
251                                 voiceDuration += framelength;
252                                 
253                                 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
254                                    number of words if my previous state was Silence, which means that I moved into a word. */
255                                 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
256                                         iWordsCount++;
257                                         if (option_verbose > 2)
258                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount);
259                                         previousState = currentState;
260                                         currentState = STATE_IN_WORD;
261                                 }
262                                 
263                                 if (iWordsCount >= maximumNumberOfWords) {
264                                         if (option_verbose > 2)
265                                                 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount);
266                                         ast_frfree(f);
267                                         strcpy(amdStatus , "MACHINE");
268                                         sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
269                                         break;
270                                 }
271                                 
272                                 if (inGreeting == 1 && voiceDuration >= greeting) {
273                                         ast_verb(3, "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting);
274                                         ast_frfree(f);
275                                         strcpy(amdStatus , "MACHINE");
276                                         sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
277                                         break;
278                                 }
279                                 
280                                 if (voiceDuration >= minimumWordLength ) {
281                                         silenceDuration = 0;
282                                         inInitialSilence = 0;
283                                         inGreeting = 1;
284                                 }
285                                 
286                         }
287                 }
288                 ast_frfree(f);
289         }
290         
291         if (!res) {
292                 /* It took too long to get a frame back. Giving up. */
293                 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan->name);
294                 strcpy(amdStatus , "NOTSURE");
295                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
296         }
297
298         /* Set the status and cause on the channel */
299         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
300         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
301
302         /* Restore channel read format */
303         if (readFormat && ast_set_read_format(chan, readFormat))
304                 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
305
306         /* Free the DSP used to detect silence */
307         ast_dsp_free(silenceDetector);
308
309         return;
310 }
311
312
313 static int amd_exec(struct ast_channel *chan, void *data)
314 {
315         isAnsweringMachine(chan, data);
316
317         return 0;
318 }
319
320 static void load_config(int reload)
321 {
322         struct ast_config *cfg = NULL;
323         char *cat = NULL;
324         struct ast_variable *var = NULL;
325         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
326
327         if (!(cfg = ast_config_load("amd.conf", config_flags))) {
328                 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
329                 return;
330         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
331                 return;
332
333         cat = ast_category_browse(cfg, NULL);
334
335         while (cat) {
336                 if (!strcasecmp(cat, "general") ) {
337                         var = ast_variable_browse(cfg, cat);
338                         while (var) {
339                                 if (!strcasecmp(var->name, "initial_silence")) {
340                                         dfltInitialSilence = atoi(var->value);
341                                 } else if (!strcasecmp(var->name, "greeting")) {
342                                         dfltGreeting = atoi(var->value);
343                                 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
344                                         dfltAfterGreetingSilence = atoi(var->value);
345                                 } else if (!strcasecmp(var->name, "silence_threshold")) {
346                                         dfltSilenceThreshold = atoi(var->value);
347                                 } else if (!strcasecmp(var->name, "total_analysis_time")) {
348                                         dfltTotalAnalysisTime = atoi(var->value);
349                                 } else if (!strcasecmp(var->name, "min_word_length")) {
350                                         dfltMinimumWordLength = atoi(var->value);
351                                 } else if (!strcasecmp(var->name, "between_words_silence")) {
352                                         dfltBetweenWordsSilence = atoi(var->value);
353                                 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
354                                         dfltMaximumNumberOfWords = atoi(var->value);
355                                 } else {
356                                         ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
357                                                 app, cat, var->name, var->lineno);
358                                 }
359                                 var = var->next;
360                         }
361                 }
362                 cat = ast_category_browse(cfg, cat);
363         }
364
365         ast_config_destroy(cfg);
366
367         ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
368                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
369                                 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
370                                 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
371
372         return;
373 }
374
375 static int unload_module(void)
376 {
377         return ast_unregister_application(app);
378 }
379
380 static int load_module(void)
381 {
382         load_config(0);
383         return ast_register_application(app, amd_exec, synopsis, descrip);
384 }
385
386 static int reload(void)
387 {
388         load_config(1);
389         return 0;
390 }
391
392 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
393                 .load = load_module,
394                 .unload = unload_module,
395                 .reload = reload,
396                );