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
24 * \brief Answering machine detection
26 * \author Claude Klimos (claude.klimos@aheeva.com)
32 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
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"
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],[|maximumWordLength])\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"
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 "- 'maximumWordLength' is the maximum duration of a word to accept. If exceeded then MACHINE\n"
69 "This application sets the following channel variables upon completion:\n"
70 " AMDSTATUS - This is the status of the answering machine detection.\n"
71 " Possible values are:\n"
72 " MACHINE | HUMAN | NOTSURE | HANGUP\n"
73 " AMDCAUSE - Indicates the cause that led to the conclusion.\n"
74 " Possible values are:\n"
75 " TOOLONG-<%d total_time>\n"
76 " INITIALSILENCE-<%d silenceDuration>-<%d initialSilence>\n"
77 " HUMAN-<%d silenceDuration>-<%d afterGreetingSilence>\n"
78 " MAXWORDS-<%d wordsCount>-<%d maximumNumberOfWords>\n"
79 " LONGGREETING-<%d voiceDuration>-<%d greeting>\n"
80 " MAXWORDLENGTH-<%d consecutiveVoiceDuration>\n";
82 #define STATE_IN_WORD 1
83 #define STATE_IN_SILENCE 2
85 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
86 static int dfltInitialSilence
= 2500;
87 static int dfltGreeting
= 1500;
88 static int dfltAfterGreetingSilence
= 800;
89 static int dfltTotalAnalysisTime
= 5000;
90 static int dfltMinimumWordLength
= 100;
91 static int dfltBetweenWordsSilence
= 50;
92 static int dfltMaximumNumberOfWords
= 3;
93 static int dfltSilenceThreshold
= 256;
94 static int dfltMaximumWordLength
= 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */
96 /* Set to the lowest ms value provided in amd.conf or application parameters */
97 static int dfltMaxWaitTimeForFrame
= 50;
99 static void isAnsweringMachine(struct ast_channel
*chan
, void *data
)
102 struct ast_frame
*f
= NULL
;
103 struct ast_dsp
*silenceDetector
= NULL
;
104 int dspsilence
= 0, readFormat
, framelength
= 0;
105 int inInitialSilence
= 1;
107 int voiceDuration
= 0;
108 int silenceDuration
= 0;
111 int currentState
= STATE_IN_WORD
;
112 int previousState
= STATE_IN_SILENCE
;
113 int consecutiveVoiceDuration
= 0;
114 char amdCause
[256] = "", amdStatus
[256] = "";
115 char *parse
= ast_strdupa(data
);
117 /* Lets set the initial values of the variables that will control the algorithm.
118 The initial values are the default ones. If they are passed as arguments
119 when invoking the application, then the default values will be overwritten
120 by the ones passed as parameters. */
121 int initialSilence
= dfltInitialSilence
;
122 int greeting
= dfltGreeting
;
123 int afterGreetingSilence
= dfltAfterGreetingSilence
;
124 int totalAnalysisTime
= dfltTotalAnalysisTime
;
125 int minimumWordLength
= dfltMinimumWordLength
;
126 int betweenWordsSilence
= dfltBetweenWordsSilence
;
127 int maximumNumberOfWords
= dfltMaximumNumberOfWords
;
128 int silenceThreshold
= dfltSilenceThreshold
;
129 int maximumWordLength
= dfltMaximumWordLength
;
130 int maxWaitTimeForFrame
= dfltMaxWaitTimeForFrame
;
132 AST_DECLARE_APP_ARGS(args
,
133 AST_APP_ARG(argInitialSilence
);
134 AST_APP_ARG(argGreeting
);
135 AST_APP_ARG(argAfterGreetingSilence
);
136 AST_APP_ARG(argTotalAnalysisTime
);
137 AST_APP_ARG(argMinimumWordLength
);
138 AST_APP_ARG(argBetweenWordsSilence
);
139 AST_APP_ARG(argMaximumNumberOfWords
);
140 AST_APP_ARG(argSilenceThreshold
);
141 AST_APP_ARG(argMaximumWordLength
);
144 ast_verb(3, "AMD: %s %s %s (Fmt: %d)\n", chan
->name
,chan
->cid
.cid_ani
, chan
->cid
.cid_rdnis
, chan
->readformat
);
146 /* Lets parse the arguments. */
147 if (!ast_strlen_zero(parse
)) {
148 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
149 AST_STANDARD_APP_ARGS(args
, parse
);
150 if (!ast_strlen_zero(args
.argInitialSilence
))
151 initialSilence
= atoi(args
.argInitialSilence
);
152 if (!ast_strlen_zero(args
.argGreeting
))
153 greeting
= atoi(args
.argGreeting
);
154 if (!ast_strlen_zero(args
.argAfterGreetingSilence
))
155 afterGreetingSilence
= atoi(args
.argAfterGreetingSilence
);
156 if (!ast_strlen_zero(args
.argTotalAnalysisTime
))
157 totalAnalysisTime
= atoi(args
.argTotalAnalysisTime
);
158 if (!ast_strlen_zero(args
.argMinimumWordLength
))
159 minimumWordLength
= atoi(args
.argMinimumWordLength
);
160 if (!ast_strlen_zero(args
.argBetweenWordsSilence
))
161 betweenWordsSilence
= atoi(args
.argBetweenWordsSilence
);
162 if (!ast_strlen_zero(args
.argMaximumNumberOfWords
))
163 maximumNumberOfWords
= atoi(args
.argMaximumNumberOfWords
);
164 if (!ast_strlen_zero(args
.argSilenceThreshold
))
165 silenceThreshold
= atoi(args
.argSilenceThreshold
);
166 if (!ast_strlen_zero(args
.argMaximumWordLength
))
167 maximumWordLength
= atoi(args
.argMaximumWordLength
);
169 ast_debug(1, "AMD using the default parameters.\n");
172 /* Find lowest ms value, that will be max wait time for a frame */
173 if (maxWaitTimeForFrame
> initialSilence
)
174 maxWaitTimeForFrame
= initialSilence
;
175 if (maxWaitTimeForFrame
> greeting
)
176 maxWaitTimeForFrame
= greeting
;
177 if (maxWaitTimeForFrame
> afterGreetingSilence
)
178 maxWaitTimeForFrame
= afterGreetingSilence
;
179 if (maxWaitTimeForFrame
> totalAnalysisTime
)
180 maxWaitTimeForFrame
= totalAnalysisTime
;
181 if (maxWaitTimeForFrame
> minimumWordLength
)
182 maxWaitTimeForFrame
= minimumWordLength
;
183 if (maxWaitTimeForFrame
> betweenWordsSilence
)
184 maxWaitTimeForFrame
= betweenWordsSilence
;
186 /* Now we're ready to roll! */
187 ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
188 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
189 initialSilence
, greeting
, afterGreetingSilence
, totalAnalysisTime
,
190 minimumWordLength
, betweenWordsSilence
, maximumNumberOfWords
, silenceThreshold
, maximumWordLength
);
192 /* Set read format to signed linear so we get signed linear frames in */
193 readFormat
= chan
->readformat
;
194 if (ast_set_read_format(chan
, AST_FORMAT_SLINEAR
) < 0 ) {
195 ast_log(LOG_WARNING
, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", chan
->name
);
196 pbx_builtin_setvar_helper(chan
, "AMDSTATUS", "");
197 pbx_builtin_setvar_helper(chan
, "AMDCAUSE", "");
201 /* Create a new DSP that will detect the silence */
202 if (!(silenceDetector
= ast_dsp_new())) {
203 ast_log(LOG_WARNING
, "AMD: Channel [%s]. Unable to create silence detector :(\n", chan
->name
);
204 pbx_builtin_setvar_helper(chan
, "AMDSTATUS", "");
205 pbx_builtin_setvar_helper(chan
, "AMDCAUSE", "");
209 /* Set silence threshold to specified value */
210 ast_dsp_set_threshold(silenceDetector
, silenceThreshold
);
212 /* Now we go into a loop waiting for frames from the channel */
213 while ((res
= ast_waitfor(chan
, 2 * maxWaitTimeForFrame
)) > -1) {
215 /* If we fail to read in a frame, that means they hung up */
216 if (!(f
= ast_read(chan
))) {
217 ast_verb(3, "AMD: Channel [%s]. HANGUP\n", chan
->name
);
218 ast_debug(1, "Got hangup\n");
219 strcpy(amdStatus
, "HANGUP");
223 if (f
->frametype
== AST_FRAME_VOICE
|| f
->frametype
== AST_FRAME_NULL
|| f
->frametype
== AST_FRAME_CNG
) {
224 /* If the total time exceeds the analysis time then give up as we are not too sure */
225 if (f
->frametype
== AST_FRAME_VOICE
)
226 framelength
= (ast_codec_get_samples(f
) / DEFAULT_SAMPLES_PER_MS
);
228 framelength
+= 2 * maxWaitTimeForFrame
;
230 iTotalTime
+= framelength
;
231 if (iTotalTime
>= totalAnalysisTime
) {
232 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan
->name
);
234 strcpy(amdStatus
, "NOTSURE");
235 sprintf(amdCause
, "TOOLONG-%d", iTotalTime
);
239 /* Feed the frame of audio into the silence detector and see if we get a result */
240 if (f
->frametype
!= AST_FRAME_VOICE
)
241 dspsilence
+= 2 * maxWaitTimeForFrame
;
244 ast_dsp_silence(silenceDetector
, f
, &dspsilence
);
247 if (dspsilence
> 0) {
248 silenceDuration
= dspsilence
;
250 if (silenceDuration
>= betweenWordsSilence
) {
251 if (currentState
!= STATE_IN_SILENCE
) {
252 previousState
= currentState
;
253 ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", chan
->name
);
255 /* Find words less than word duration */
256 if (consecutiveVoiceDuration
< minimumWordLength
&& consecutiveVoiceDuration
> 0){
257 ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", chan
->name
, consecutiveVoiceDuration
);
259 currentState
= STATE_IN_SILENCE
;
260 consecutiveVoiceDuration
= 0;
263 if (inInitialSilence
== 1 && silenceDuration
>= initialSilence
) {
264 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
265 chan
->name
, silenceDuration
, initialSilence
);
267 strcpy(amdStatus
, "MACHINE");
268 sprintf(amdCause
, "INITIALSILENCE-%d-%d", silenceDuration
, initialSilence
);
273 if (silenceDuration
>= afterGreetingSilence
&& inGreeting
== 1) {
274 ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
275 chan
->name
, silenceDuration
, afterGreetingSilence
);
277 strcpy(amdStatus
, "HUMAN");
278 sprintf(amdCause
, "HUMAN-%d-%d", silenceDuration
, afterGreetingSilence
);
284 consecutiveVoiceDuration
+= framelength
;
285 voiceDuration
+= framelength
;
287 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
288 number of words if my previous state was Silence, which means that I moved into a word. */
289 if (consecutiveVoiceDuration
>= minimumWordLength
&& currentState
== STATE_IN_SILENCE
) {
291 ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", chan
->name
, iWordsCount
);
292 previousState
= currentState
;
293 currentState
= STATE_IN_WORD
;
295 if (consecutiveVoiceDuration
>= maximumWordLength
){
296 ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", chan
->name
, consecutiveVoiceDuration
);
298 strcpy(amdStatus
, "MACHINE");
299 sprintf(amdCause
, "MAXWORDLENGTH-%d", consecutiveVoiceDuration
);
302 if (iWordsCount
>= maximumNumberOfWords
) {
303 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", chan
->name
, iWordsCount
);
305 strcpy(amdStatus
, "MACHINE");
306 sprintf(amdCause
, "MAXWORDS-%d-%d", iWordsCount
, maximumNumberOfWords
);
311 if (inGreeting
== 1 && voiceDuration
>= greeting
) {
312 ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", chan
->name
, voiceDuration
, greeting
);
314 strcpy(amdStatus
, "MACHINE");
315 sprintf(amdCause
, "LONGGREETING-%d-%d", voiceDuration
, greeting
);
320 if (voiceDuration
>= minimumWordLength
) {
321 if (silenceDuration
> 0)
322 ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", chan
->name
, silenceDuration
);
325 if (consecutiveVoiceDuration
>= minimumWordLength
&& inGreeting
== 0) {
326 /* Only go in here once to change the greeting flag when we detect the 1st word */
327 if (silenceDuration
> 0)
328 ast_verb(3, "AMD: Channel [%s]. Before Greeting Time: silenceDuration: %d voiceDuration: %d\n", chan
->name
, silenceDuration
, voiceDuration
);
329 inInitialSilence
= 0;
339 /* It took too long to get a frame back. Giving up. */
340 ast_verb(3, "AMD: Channel [%s]. Too long...\n", chan
->name
);
341 strcpy(amdStatus
, "NOTSURE");
342 sprintf(amdCause
, "TOOLONG-%d", iTotalTime
);
345 /* Set the status and cause on the channel */
346 pbx_builtin_setvar_helper(chan
, "AMDSTATUS" , amdStatus
);
347 pbx_builtin_setvar_helper(chan
, "AMDCAUSE" , amdCause
);
349 /* Restore channel read format */
350 if (readFormat
&& ast_set_read_format(chan
, readFormat
))
351 ast_log(LOG_WARNING
, "AMD: Unable to restore read format on '%s'\n", chan
->name
);
353 /* Free the DSP used to detect silence */
354 ast_dsp_free(silenceDetector
);
360 static int amd_exec(struct ast_channel
*chan
, void *data
)
362 isAnsweringMachine(chan
, data
);
367 static int load_config(int reload
)
369 struct ast_config
*cfg
= NULL
;
371 struct ast_variable
*var
= NULL
;
372 struct ast_flags config_flags
= { reload
? CONFIG_FLAG_FILEUNCHANGED
: 0 };
374 dfltSilenceThreshold
= ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE
);
376 if (!(cfg
= ast_config_load("amd.conf", config_flags
))) {
377 ast_log(LOG_ERROR
, "Configuration file amd.conf missing.\n");
379 } else if (cfg
== CONFIG_STATUS_FILEUNCHANGED
)
382 cat
= ast_category_browse(cfg
, NULL
);
385 if (!strcasecmp(cat
, "general") ) {
386 var
= ast_variable_browse(cfg
, cat
);
388 if (!strcasecmp(var
->name
, "initial_silence")) {
389 dfltInitialSilence
= atoi(var
->value
);
390 } else if (!strcasecmp(var
->name
, "greeting")) {
391 dfltGreeting
= atoi(var
->value
);
392 } else if (!strcasecmp(var
->name
, "after_greeting_silence")) {
393 dfltAfterGreetingSilence
= atoi(var
->value
);
394 } else if (!strcasecmp(var
->name
, "silence_threshold")) {
395 dfltSilenceThreshold
= atoi(var
->value
);
396 } else if (!strcasecmp(var
->name
, "total_analysis_time")) {
397 dfltTotalAnalysisTime
= atoi(var
->value
);
398 } else if (!strcasecmp(var
->name
, "min_word_length")) {
399 dfltMinimumWordLength
= atoi(var
->value
);
400 } else if (!strcasecmp(var
->name
, "between_words_silence")) {
401 dfltBetweenWordsSilence
= atoi(var
->value
);
402 } else if (!strcasecmp(var
->name
, "maximum_number_of_words")) {
403 dfltMaximumNumberOfWords
= atoi(var
->value
);
404 } else if (!strcasecmp(var
->name
, "maximum_word_length")) {
405 dfltMaximumWordLength
= atoi(var
->value
);
408 ast_log(LOG_WARNING
, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
409 app
, cat
, var
->name
, var
->lineno
);
414 cat
= ast_category_browse(cfg
, cat
);
417 ast_config_destroy(cfg
);
419 ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
420 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
421 dfltInitialSilence
, dfltGreeting
, dfltAfterGreetingSilence
, dfltTotalAnalysisTime
,
422 dfltMinimumWordLength
, dfltBetweenWordsSilence
, dfltMaximumNumberOfWords
, dfltSilenceThreshold
, dfltMaximumWordLength
);
427 static int unload_module(void)
429 return ast_unregister_application(app
);
432 static int load_module(void)
435 return AST_MODULE_LOAD_DECLINE
;
436 if (ast_register_application(app
, amd_exec
, synopsis
, descrip
))
437 return AST_MODULE_LOAD_FAILURE
;
438 return AST_MODULE_LOAD_SUCCESS
;
441 static int reload(void)
444 return AST_MODULE_LOAD_DECLINE
;
445 return AST_MODULE_LOAD_SUCCESS
;
448 AST_MODULE_INFO(ASTERISK_GPL_KEY
, AST_MODFLAG_DEFAULT
, "Answering Machine Detection Application",
450 .unload
= unload_module
,