Let's also include aclocal.m4
[asterisk-bristuff.git] / apps / app_amd.c
blob52f474048105eb10bcad638588b7b4e7b6c1f643
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2003 - 2006, Aheeva Technology.
6 * Claude Klimos (claude.klimos@aheeva.com)
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.
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.
18 * A license has been granted to Digium (via disclaimer) for the use of
19 * this code.
22 #include "asterisk.h"
24 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
26 #include <stdio.h>
27 #include <stdlib.h>
29 #include "asterisk/module.h"
30 #include "asterisk/lock.h"
31 #include "asterisk/options.h"
32 #include "asterisk/channel.h"
33 #include "asterisk/dsp.h"
34 #include "asterisk/pbx.h"
35 #include "asterisk/config.h"
36 #include "asterisk/app.h"
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";
76 #define STATE_IN_WORD 1
77 #define STATE_IN_SILENCE 2
79 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
80 static int dfltInitialSilence = 2500;
81 static int dfltGreeting = 1500;
82 static int dfltAfterGreetingSilence = 800;
83 static int dfltTotalAnalysisTime = 5000;
84 static int dfltMinimumWordLength = 100;
85 static int dfltBetweenWordsSilence = 50;
86 static int dfltMaximumNumberOfWords = 3;
87 static int dfltSilenceThreshold = 256;
89 /* Set to the lowest ms value provided in amd.conf or application parameters */
90 static int dfltMaxWaitTimeForFrame = 50;
92 static void isAnsweringMachine(struct ast_channel *chan, void *data)
94 int res = 0;
95 struct ast_frame *f = NULL;
96 struct ast_dsp *silenceDetector = NULL;
97 int dspsilence = 0, readFormat, framelength = 0;
98 int inInitialSilence = 1;
99 int inGreeting = 0;
100 int voiceDuration = 0;
101 int silenceDuration = 0;
102 int iTotalTime = 0;
103 int iWordsCount = 0;
104 int currentState = STATE_IN_WORD;
105 int previousState = STATE_IN_SILENCE;
106 int consecutiveVoiceDuration = 0;
107 char amdCause[256] = "", amdStatus[256] = "";
108 char *parse = ast_strdupa(data);
110 /* Lets set the initial values of the variables that will control the algorithm.
111 The initial values are the default ones. If they are passed as arguments
112 when invoking the application, then the default values will be overwritten
113 by the ones passed as parameters. */
114 int initialSilence = dfltInitialSilence;
115 int greeting = dfltGreeting;
116 int afterGreetingSilence = dfltAfterGreetingSilence;
117 int totalAnalysisTime = dfltTotalAnalysisTime;
118 int minimumWordLength = dfltMinimumWordLength;
119 int betweenWordsSilence = dfltBetweenWordsSilence;
120 int maximumNumberOfWords = dfltMaximumNumberOfWords;
121 int silenceThreshold = dfltSilenceThreshold;
122 int maxWaitTimeForFrame = dfltMaxWaitTimeForFrame;
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);
135 if (option_verbose > 2)
136 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 /* Lets parse the arguments. */
139 if (!ast_strlen_zero(parse)) {
140 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
141 AST_STANDARD_APP_ARGS(args, parse);
142 if (!ast_strlen_zero(args.argInitialSilence))
143 initialSilence = atoi(args.argInitialSilence);
144 if (!ast_strlen_zero(args.argGreeting))
145 greeting = atoi(args.argGreeting);
146 if (!ast_strlen_zero(args.argAfterGreetingSilence))
147 afterGreetingSilence = atoi(args.argAfterGreetingSilence);
148 if (!ast_strlen_zero(args.argTotalAnalysisTime))
149 totalAnalysisTime = atoi(args.argTotalAnalysisTime);
150 if (!ast_strlen_zero(args.argMinimumWordLength))
151 minimumWordLength = atoi(args.argMinimumWordLength);
152 if (!ast_strlen_zero(args.argBetweenWordsSilence))
153 betweenWordsSilence = atoi(args.argBetweenWordsSilence);
154 if (!ast_strlen_zero(args.argMaximumNumberOfWords))
155 maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
156 if (!ast_strlen_zero(args.argSilenceThreshold))
157 silenceThreshold = atoi(args.argSilenceThreshold);
158 } else if (option_debug)
159 ast_log(LOG_DEBUG, "AMD using the default parameters.\n");
161 /* Find lowest ms value, that will be max wait time for a frame */
162 if (maxWaitTimeForFrame > initialSilence)
163 maxWaitTimeForFrame = initialSilence;
164 if (maxWaitTimeForFrame > greeting)
165 maxWaitTimeForFrame = greeting;
166 if (maxWaitTimeForFrame > afterGreetingSilence)
167 maxWaitTimeForFrame = afterGreetingSilence;
168 if (maxWaitTimeForFrame > totalAnalysisTime)
169 maxWaitTimeForFrame = totalAnalysisTime;
170 if (maxWaitTimeForFrame > minimumWordLength)
171 maxWaitTimeForFrame = minimumWordLength;
172 if (maxWaitTimeForFrame > betweenWordsSilence)
173 maxWaitTimeForFrame = betweenWordsSilence;
175 /* Now we're ready to roll! */
176 if (option_verbose > 2)
177 ast_verbose(VERBOSE_PREFIX_3 "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
178 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
179 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
180 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold );
182 /* Set read format to signed linear so we get signed linear frames in */
183 readFormat = chan->readformat;
184 if (ast_set_read_format(chan, AST_FORMAT_SLINEAR) < 0 ) {
185 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan->name );
186 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
187 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
188 return;
191 /* Create a new DSP that will detect the silence */
192 if (!(silenceDetector = ast_dsp_new())) {
193 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan->name );
194 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
195 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
196 return;
199 /* Set silence threshold to specified value */
200 ast_dsp_set_threshold(silenceDetector, silenceThreshold);
202 /* Now we go into a loop waiting for frames from the channel */
203 while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
205 /* If we fail to read in a frame, that means they hung up */
206 if (!(f = ast_read(chan))) {
207 if (option_verbose > 2)
208 ast_verbose(VERBOSE_PREFIX_3 "AMD: HANGUP\n");
209 if (option_debug)
210 ast_log(LOG_DEBUG, "Got hangup\n");
211 strcpy(amdStatus, "HANGUP");
212 break;
215 if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
216 /* If the total time exceeds the analysis time then give up as we are not too sure */
217 if (f->frametype == AST_FRAME_VOICE)
218 framelength = (ast_codec_get_samples(f) / DEFAULT_SAMPLES_PER_MS);
219 else
220 framelength += 2 * maxWaitTimeForFrame;
222 iTotalTime += framelength;
223 if (iTotalTime >= totalAnalysisTime) {
224 if (option_verbose > 2)
225 ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name );
226 ast_frfree(f);
227 strcpy(amdStatus , "NOTSURE");
228 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
229 break;
232 /* Feed the frame of audio into the silence detector and see if we get a result */
233 if (f->frametype != AST_FRAME_VOICE)
234 dspsilence += 2 * maxWaitTimeForFrame;
235 else {
236 dspsilence = 0;
237 ast_dsp_silence(silenceDetector, f, &dspsilence);
240 if (dspsilence > 0) {
241 silenceDuration = dspsilence;
243 if (silenceDuration >= betweenWordsSilence) {
244 if (currentState != STATE_IN_SILENCE ) {
245 previousState = currentState;
246 if (option_verbose > 2)
247 ast_verbose(VERBOSE_PREFIX_3 "AMD: Changed state to STATE_IN_SILENCE\n");
249 currentState = STATE_IN_SILENCE;
250 consecutiveVoiceDuration = 0;
253 if (inInitialSilence == 1 && silenceDuration >= initialSilence) {
254 if (option_verbose > 2)
255 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
256 silenceDuration, initialSilence);
257 ast_frfree(f);
258 strcpy(amdStatus , "MACHINE");
259 sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
260 res = 1;
261 break;
264 if (silenceDuration >= afterGreetingSilence && inGreeting == 1) {
265 if (option_verbose > 2)
266 ast_verbose(VERBOSE_PREFIX_3 "AMD: HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
267 silenceDuration, afterGreetingSilence);
268 ast_frfree(f);
269 strcpy(amdStatus , "HUMAN");
270 sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
271 res = 1;
272 break;
275 } else {
276 consecutiveVoiceDuration += framelength;
277 voiceDuration += framelength;
279 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
280 number of words if my previous state was Silence, which means that I moved into a word. */
281 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
282 iWordsCount++;
283 if (option_verbose > 2)
284 ast_verbose(VERBOSE_PREFIX_3 "AMD: Word detected. iWordsCount:%d\n", iWordsCount);
285 previousState = currentState;
286 currentState = STATE_IN_WORD;
289 if (iWordsCount >= maximumNumberOfWords) {
290 if (option_verbose > 2)
291 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: iWordsCount:%d\n", iWordsCount);
292 ast_frfree(f);
293 strcpy(amdStatus , "MACHINE");
294 sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
295 res = 1;
296 break;
299 if (inGreeting == 1 && voiceDuration >= greeting) {
300 if (option_verbose > 2)
301 ast_verbose(VERBOSE_PREFIX_3 "AMD: ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", voiceDuration, greeting);
302 ast_frfree(f);
303 strcpy(amdStatus , "MACHINE");
304 sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
305 res = 1;
306 break;
309 if (voiceDuration >= minimumWordLength ) {
310 silenceDuration = 0;
311 inInitialSilence = 0;
312 inGreeting = 1;
317 ast_frfree(f);
320 if (!res) {
321 /* It took too long to get a frame back. Giving up. */
322 if (option_verbose > 2)
323 ast_verbose(VERBOSE_PREFIX_3 "AMD: Channel [%s]. Too long...\n", chan->name);
324 strcpy(amdStatus , "NOTSURE");
325 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
328 /* Set the status and cause on the channel */
329 pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
330 pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
332 /* Restore channel read format */
333 if (readFormat && ast_set_read_format(chan, readFormat))
334 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", chan->name);
336 /* Free the DSP used to detect silence */
337 ast_dsp_free(silenceDetector);
339 return;
343 static int amd_exec(struct ast_channel *chan, void *data)
345 struct ast_module_user *u = NULL;
347 u = ast_module_user_add(chan);
348 isAnsweringMachine(chan, data);
349 ast_module_user_remove(u);
351 return 0;
354 static void load_config(void)
356 struct ast_config *cfg = NULL;
357 char *cat = NULL;
358 struct ast_variable *var = NULL;
360 if (!(cfg = ast_config_load("amd.conf"))) {
361 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
362 return;
365 cat = ast_category_browse(cfg, NULL);
367 while (cat) {
368 if (!strcasecmp(cat, "general") ) {
369 var = ast_variable_browse(cfg, cat);
370 while (var) {
371 if (!strcasecmp(var->name, "initial_silence")) {
372 dfltInitialSilence = atoi(var->value);
373 } else if (!strcasecmp(var->name, "greeting")) {
374 dfltGreeting = atoi(var->value);
375 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
376 dfltAfterGreetingSilence = atoi(var->value);
377 } else if (!strcasecmp(var->name, "silence_threshold")) {
378 dfltSilenceThreshold = atoi(var->value);
379 } else if (!strcasecmp(var->name, "total_analysis_time")) {
380 dfltTotalAnalysisTime = atoi(var->value);
381 } else if (!strcasecmp(var->name, "min_word_length")) {
382 dfltMinimumWordLength = atoi(var->value);
383 } else if (!strcasecmp(var->name, "between_words_silence")) {
384 dfltBetweenWordsSilence = atoi(var->value);
385 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
386 dfltMaximumNumberOfWords = atoi(var->value);
387 } else {
388 ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
389 app, cat, var->name, var->lineno);
391 var = var->next;
394 cat = ast_category_browse(cfg, cat);
397 ast_config_destroy(cfg);
399 if (option_verbose > 2)
400 ast_verbose(VERBOSE_PREFIX_3 "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
401 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] \n",
402 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
403 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold );
405 return;
408 static int unload_module(void)
410 ast_module_user_hangup_all();
411 return ast_unregister_application(app);
414 static int load_module(void)
416 load_config();
417 return ast_register_application(app, amd_exec, synopsis, descrip);
420 static int reload(void)
422 load_config();
423 return 0;
426 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
427 .load = load_module,
428 .unload = unload_module,
429 .reload = reload,