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