media formats: re-architect handling of media for performance improvements
[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  * \ingroup applications
29  */
30
31 /*! \li \ref app_amd.c uses the configuration file \ref amd.conf
32  * \addtogroup configuration_file Configuration Files
33  */
34
35 /*! 
36  * \page amd.conf amd.conf
37  * \verbinclude amd.conf.sample
38  */
39
40 /*** MODULEINFO
41         <support_level>extended</support_level>
42  ***/
43
44 #include "asterisk.h"
45
46 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
47
48 #include "asterisk/module.h"
49 #include "asterisk/lock.h"
50 #include "asterisk/channel.h"
51 #include "asterisk/dsp.h"
52 #include "asterisk/pbx.h"
53 #include "asterisk/config.h"
54 #include "asterisk/app.h"
55 #include "asterisk/format_cache.h"
56
57 /*** DOCUMENTATION
58         <application name="AMD" language="en_US">
59                 <synopsis>
60                         Attempt to detect answering machines.
61                 </synopsis>
62                 <syntax>
63                         <parameter name="initialSilence" required="false">
64                                 <para>Is maximum initial silence duration before greeting.</para>
65                                 <para>If this is exceeded set as MACHINE</para>
66                         </parameter>
67                         <parameter name="greeting" required="false">
68                                 <para>is the maximum length of a greeting.</para>
69                                 <para>If this is exceeded set as MACHINE</para>
70                         </parameter>
71                         <parameter name="afterGreetingSilence" required="false">
72                                 <para>Is the silence after detecting a greeting.</para>
73                                 <para>If this is exceeded set as HUMAN</para>
74                         </parameter>
75                         <parameter name="totalAnalysis Time" required="false">
76                                 <para>Is the maximum time allowed for the algorithm</para>
77                                 <para>to decide HUMAN or MACHINE</para>
78                         </parameter>
79                         <parameter name="miniumWordLength" required="false">
80                                 <para>Is the minimum duration of Voice considered to be a word</para>
81                         </parameter>
82                         <parameter name="betweenWordSilence" required="false">
83                                 <para>Is the minimum duration of silence after a word to
84                                 consider the audio that follows to be a new word</para>
85                         </parameter>
86                         <parameter name="maximumNumberOfWords" required="false">
87                                 <para>Is the maximum number of words in a greeting</para>
88                                 <para>If this is exceeded set as MACHINE</para>
89                         </parameter>
90                         <parameter name="silenceThreshold" required="false">
91                                 <para>How long do we consider silence</para>
92                         </parameter>
93                         <parameter name="maximumWordLength" required="false">
94                                 <para>Is the maximum duration of a word to accept.</para>
95                                 <para>If exceeded set as MACHINE</para>
96                         </parameter>
97                 </syntax>
98                 <description>
99                         <para>This application attempts to detect answering machines at the beginning
100                         of outbound calls. Simply call this application after the call
101                         has been answered (outbound only, of course).</para>
102                         <para>When loaded, AMD reads amd.conf and uses the parameters specified as
103                         default values. Those default values get overwritten when the calling AMD
104                         with parameters.</para>
105                         <para>This application sets the following channel variables:</para>
106                         <variablelist>
107                                 <variable name="AMDSTATUS">
108                                         <para>This is the status of the answering machine detection</para>
109                                         <value name="MACHINE" />
110                                         <value name="HUMAN" />
111                                         <value name="NOTSURE" />
112                                         <value name="HANGUP" />
113                                 </variable>
114                                 <variable name="AMDCAUSE">
115                                         <para>Indicates the cause that led to the conclusion</para>
116                                         <value name="TOOLONG">
117                                                 Total Time.
118                                         </value>
119                                         <value name="INITIALSILENCE">
120                                                 Silence Duration - Initial Silence.
121                                         </value>
122                                         <value name="HUMAN">
123                                                 Silence Duration - afterGreetingSilence.
124                                         </value>
125                                         <value name="LONGGREETING">
126                                                 Voice Duration - Greeting.
127                                         </value>
128                                         <value name="MAXWORDLENGTH">
129                                                 Word Count - maximum number of words.
130                                         </value>        
131                                 </variable>
132                         </variablelist>
133                 </description>
134                 <see-also>
135                         <ref type="application">WaitForSilence</ref>
136                         <ref type="application">WaitForNoise</ref>
137                 </see-also>
138         </application>
139
140  ***/
141
142 static const char app[] = "AMD";
143
144 #define STATE_IN_WORD       1
145 #define STATE_IN_SILENCE    2
146
147 /* Some default values for the algorithm parameters. These defaults will be overwritten from amd.conf */
148 static int dfltInitialSilence       = 2500;
149 static int dfltGreeting             = 1500;
150 static int dfltAfterGreetingSilence = 800;
151 static int dfltTotalAnalysisTime    = 5000;
152 static int dfltMinimumWordLength    = 100;
153 static int dfltBetweenWordsSilence  = 50;
154 static int dfltMaximumNumberOfWords = 3;
155 static int dfltSilenceThreshold     = 256;
156 static int dfltMaximumWordLength    = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */
157
158 /* Set to the lowest ms value provided in amd.conf or application parameters */
159 static int dfltMaxWaitTimeForFrame  = 50;
160
161 static void isAnsweringMachine(struct ast_channel *chan, const char *data)
162 {
163         int res = 0;
164         struct ast_frame *f = NULL;
165         struct ast_dsp *silenceDetector = NULL;
166         int dspsilence = 0, framelength = 0;
167         RAII_VAR(struct ast_format *, readFormat, NULL, ao2_cleanup);
168         int inInitialSilence = 1;
169         int inGreeting = 0;
170         int voiceDuration = 0;
171         int silenceDuration = 0;
172         int iTotalTime = 0;
173         int iWordsCount = 0;
174         int currentState = STATE_IN_WORD;
175         int consecutiveVoiceDuration = 0;
176         char amdCause[256] = "", amdStatus[256] = "";
177         char *parse = ast_strdupa(data);
178
179         /* Lets set the initial values of the variables that will control the algorithm.
180            The initial values are the default ones. If they are passed as arguments
181            when invoking the application, then the default values will be overwritten
182            by the ones passed as parameters. */
183         int initialSilence       = dfltInitialSilence;
184         int greeting             = dfltGreeting;
185         int afterGreetingSilence = dfltAfterGreetingSilence;
186         int totalAnalysisTime    = dfltTotalAnalysisTime;
187         int minimumWordLength    = dfltMinimumWordLength;
188         int betweenWordsSilence  = dfltBetweenWordsSilence;
189         int maximumNumberOfWords = dfltMaximumNumberOfWords;
190         int silenceThreshold     = dfltSilenceThreshold;
191         int maximumWordLength    = dfltMaximumWordLength;
192         int maxWaitTimeForFrame  = dfltMaxWaitTimeForFrame;
193
194         AST_DECLARE_APP_ARGS(args,
195                 AST_APP_ARG(argInitialSilence);
196                 AST_APP_ARG(argGreeting);
197                 AST_APP_ARG(argAfterGreetingSilence);
198                 AST_APP_ARG(argTotalAnalysisTime);
199                 AST_APP_ARG(argMinimumWordLength);
200                 AST_APP_ARG(argBetweenWordsSilence);
201                 AST_APP_ARG(argMaximumNumberOfWords);
202                 AST_APP_ARG(argSilenceThreshold);
203                 AST_APP_ARG(argMaximumWordLength);
204         );
205
206         ast_verb(3, "AMD: %s %s %s (Fmt: %s)\n", ast_channel_name(chan),
207                 S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str, "(N/A)"),
208                 S_COR(ast_channel_redirecting(chan)->from.number.valid, ast_channel_redirecting(chan)->from.number.str, "(N/A)"),
209                 ast_format_get_name(ast_channel_readformat(chan)));
210
211         /* Lets parse the arguments. */
212         if (!ast_strlen_zero(parse)) {
213                 /* Some arguments have been passed. Lets parse them and overwrite the defaults. */
214                 AST_STANDARD_APP_ARGS(args, parse);
215                 if (!ast_strlen_zero(args.argInitialSilence))
216                         initialSilence = atoi(args.argInitialSilence);
217                 if (!ast_strlen_zero(args.argGreeting))
218                         greeting = atoi(args.argGreeting);
219                 if (!ast_strlen_zero(args.argAfterGreetingSilence))
220                         afterGreetingSilence = atoi(args.argAfterGreetingSilence);
221                 if (!ast_strlen_zero(args.argTotalAnalysisTime))
222                         totalAnalysisTime = atoi(args.argTotalAnalysisTime);
223                 if (!ast_strlen_zero(args.argMinimumWordLength))
224                         minimumWordLength = atoi(args.argMinimumWordLength);
225                 if (!ast_strlen_zero(args.argBetweenWordsSilence))
226                         betweenWordsSilence = atoi(args.argBetweenWordsSilence);
227                 if (!ast_strlen_zero(args.argMaximumNumberOfWords))
228                         maximumNumberOfWords = atoi(args.argMaximumNumberOfWords);
229                 if (!ast_strlen_zero(args.argSilenceThreshold))
230                         silenceThreshold = atoi(args.argSilenceThreshold);
231                 if (!ast_strlen_zero(args.argMaximumWordLength))
232                         maximumWordLength = atoi(args.argMaximumWordLength);
233         } else {
234                 ast_debug(1, "AMD using the default parameters.\n");
235         }
236
237         /* Find lowest ms value, that will be max wait time for a frame */
238         if (maxWaitTimeForFrame > initialSilence)
239                 maxWaitTimeForFrame = initialSilence;
240         if (maxWaitTimeForFrame > greeting)
241                 maxWaitTimeForFrame = greeting;
242         if (maxWaitTimeForFrame > afterGreetingSilence)
243                 maxWaitTimeForFrame = afterGreetingSilence;
244         if (maxWaitTimeForFrame > totalAnalysisTime)
245                 maxWaitTimeForFrame = totalAnalysisTime;
246         if (maxWaitTimeForFrame > minimumWordLength)
247                 maxWaitTimeForFrame = minimumWordLength;
248         if (maxWaitTimeForFrame > betweenWordsSilence)
249                 maxWaitTimeForFrame = betweenWordsSilence;
250
251         /* Now we're ready to roll! */
252         ast_verb(3, "AMD: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
253                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d] \n",
254                                 initialSilence, greeting, afterGreetingSilence, totalAnalysisTime,
255                                 minimumWordLength, betweenWordsSilence, maximumNumberOfWords, silenceThreshold, maximumWordLength);
256
257         /* Set read format to signed linear so we get signed linear frames in */
258         readFormat = ao2_bump(ast_channel_readformat(chan));
259         if (ast_set_read_format(chan, ast_format_slin) < 0 ) {
260                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to set to linear mode, giving up\n", ast_channel_name(chan));
261                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
262                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
263                 return;
264         }
265
266         /* Create a new DSP that will detect the silence */
267         if (!(silenceDetector = ast_dsp_new())) {
268                 ast_log(LOG_WARNING, "AMD: Channel [%s]. Unable to create silence detector :(\n", ast_channel_name(chan));
269                 pbx_builtin_setvar_helper(chan , "AMDSTATUS", "");
270                 pbx_builtin_setvar_helper(chan , "AMDCAUSE", "");
271                 return;
272         }
273
274         /* Set silence threshold to specified value */
275         ast_dsp_set_threshold(silenceDetector, silenceThreshold);
276
277         /* Now we go into a loop waiting for frames from the channel */
278         while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) {
279
280                 /* If we fail to read in a frame, that means they hung up */
281                 if (!(f = ast_read(chan))) {
282                         ast_verb(3, "AMD: Channel [%s]. HANGUP\n", ast_channel_name(chan));
283                         ast_debug(1, "Got hangup\n");
284                         strcpy(amdStatus, "HANGUP");
285                         res = 1;
286                         break;
287                 }
288
289                 if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_NULL || f->frametype == AST_FRAME_CNG) {
290                         /* If the total time exceeds the analysis time then give up as we are not too sure */
291                         if (f->frametype == AST_FRAME_VOICE) {
292                                 framelength = (ast_codec_samples_count(f) / DEFAULT_SAMPLES_PER_MS);
293                         } else {
294                                 framelength = 2 * maxWaitTimeForFrame;
295                         }
296
297                         iTotalTime += framelength;
298                         if (iTotalTime >= totalAnalysisTime) {
299                                 ast_verb(3, "AMD: Channel [%s]. Too long...\n", ast_channel_name(chan));
300                                 ast_frfree(f);
301                                 strcpy(amdStatus , "NOTSURE");
302                                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
303                                 break;
304                         }
305
306                         /* Feed the frame of audio into the silence detector and see if we get a result */
307                         if (f->frametype != AST_FRAME_VOICE)
308                                 dspsilence += 2 * maxWaitTimeForFrame;
309                         else {
310                                 dspsilence = 0;
311                                 ast_dsp_silence(silenceDetector, f, &dspsilence);
312                         }
313
314                         if (dspsilence > 0) {
315                                 silenceDuration = dspsilence;
316                                 
317                                 if (silenceDuration >= betweenWordsSilence) {
318                                         if (currentState != STATE_IN_SILENCE ) {
319                                                 ast_verb(3, "AMD: Channel [%s]. Changed state to STATE_IN_SILENCE\n", ast_channel_name(chan));
320                                         }
321                                         /* Find words less than word duration */
322                                         if (consecutiveVoiceDuration < minimumWordLength && consecutiveVoiceDuration > 0){
323                                                 ast_verb(3, "AMD: Channel [%s]. Short Word Duration: %d\n", ast_channel_name(chan), consecutiveVoiceDuration);
324                                         }
325                                         currentState  = STATE_IN_SILENCE;
326                                         consecutiveVoiceDuration = 0;
327                                 }
328
329                                 if (inInitialSilence == 1  && silenceDuration >= initialSilence) {
330                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: silenceDuration:%d initialSilence:%d\n",
331                                                 ast_channel_name(chan), silenceDuration, initialSilence);
332                                         ast_frfree(f);
333                                         strcpy(amdStatus , "MACHINE");
334                                         sprintf(amdCause , "INITIALSILENCE-%d-%d", silenceDuration, initialSilence);
335                                         res = 1;
336                                         break;
337                                 }
338                                 
339                                 if (silenceDuration >= afterGreetingSilence  &&  inGreeting == 1) {
340                                         ast_verb(3, "AMD: Channel [%s]. HUMAN: silenceDuration:%d afterGreetingSilence:%d\n",
341                                                 ast_channel_name(chan), silenceDuration, afterGreetingSilence);
342                                         ast_frfree(f);
343                                         strcpy(amdStatus , "HUMAN");
344                                         sprintf(amdCause , "HUMAN-%d-%d", silenceDuration, afterGreetingSilence);
345                                         res = 1;
346                                         break;
347                                 }
348                                 
349                         } else {
350                                 consecutiveVoiceDuration += framelength;
351                                 voiceDuration += framelength;
352
353                                 /* If I have enough consecutive voice to say that I am in a Word, I can only increment the
354                                    number of words if my previous state was Silence, which means that I moved into a word. */
355                                 if (consecutiveVoiceDuration >= minimumWordLength && currentState == STATE_IN_SILENCE) {
356                                         iWordsCount++;
357                                         ast_verb(3, "AMD: Channel [%s]. Word detected. iWordsCount:%d\n", ast_channel_name(chan), iWordsCount);
358                                         currentState = STATE_IN_WORD;
359                                 }
360                                 if (consecutiveVoiceDuration >= maximumWordLength){
361                                         ast_verb(3, "AMD: Channel [%s]. Maximum Word Length detected. [%d]\n", ast_channel_name(chan), consecutiveVoiceDuration);
362                                         ast_frfree(f);
363                                         strcpy(amdStatus , "MACHINE");
364                                         sprintf(amdCause , "MAXWORDLENGTH-%d", consecutiveVoiceDuration);
365                                         break;
366                                 }
367                                 if (iWordsCount >= maximumNumberOfWords) {
368                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: iWordsCount:%d\n", ast_channel_name(chan), iWordsCount);
369                                         ast_frfree(f);
370                                         strcpy(amdStatus , "MACHINE");
371                                         sprintf(amdCause , "MAXWORDS-%d-%d", iWordsCount, maximumNumberOfWords);
372                                         res = 1;
373                                         break;
374                                 }
375
376                                 if (inGreeting == 1 && voiceDuration >= greeting) {
377                                         ast_verb(3, "AMD: Channel [%s]. ANSWERING MACHINE: voiceDuration:%d greeting:%d\n", ast_channel_name(chan), voiceDuration, greeting);
378                                         ast_frfree(f);
379                                         strcpy(amdStatus , "MACHINE");
380                                         sprintf(amdCause , "LONGGREETING-%d-%d", voiceDuration, greeting);
381                                         res = 1;
382                                         break;
383                                 }
384
385                                 if (voiceDuration >= minimumWordLength ) {
386                                         if (silenceDuration > 0)
387                                                 ast_verb(3, "AMD: Channel [%s]. Detected Talk, previous silence duration: %d\n", ast_channel_name(chan), silenceDuration);
388                                         silenceDuration = 0;
389                                 }
390                                 if (consecutiveVoiceDuration >= minimumWordLength && inGreeting == 0) {
391                                         /* Only go in here once to change the greeting flag when we detect the 1st word */
392                                         if (silenceDuration > 0)
393                                                 ast_verb(3, "AMD: Channel [%s]. Before Greeting Time:  silenceDuration: %d voiceDuration: %d\n", ast_channel_name(chan), silenceDuration, voiceDuration);
394                                         inInitialSilence = 0;
395                                         inGreeting = 1;
396                                 }
397                                 
398                         }
399                 }
400                 ast_frfree(f);
401         }
402         
403         if (!res) {
404                 /* It took too long to get a frame back. Giving up. */
405                 ast_verb(3, "AMD: Channel [%s]. Too long...\n", ast_channel_name(chan));
406                 strcpy(amdStatus , "NOTSURE");
407                 sprintf(amdCause , "TOOLONG-%d", iTotalTime);
408         }
409
410         /* Set the status and cause on the channel */
411         pbx_builtin_setvar_helper(chan , "AMDSTATUS" , amdStatus);
412         pbx_builtin_setvar_helper(chan , "AMDCAUSE" , amdCause);
413
414         /* Restore channel read format */
415         if (readFormat && ast_set_read_format(chan, readFormat))
416                 ast_log(LOG_WARNING, "AMD: Unable to restore read format on '%s'\n", ast_channel_name(chan));
417
418         /* Free the DSP used to detect silence */
419         ast_dsp_free(silenceDetector);
420
421         return;
422 }
423
424
425 static int amd_exec(struct ast_channel *chan, const char *data)
426 {
427         isAnsweringMachine(chan, data);
428
429         return 0;
430 }
431
432 static int load_config(int reload)
433 {
434         struct ast_config *cfg = NULL;
435         char *cat = NULL;
436         struct ast_variable *var = NULL;
437         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
438
439         dfltSilenceThreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
440
441         if (!(cfg = ast_config_load("amd.conf", config_flags))) {
442                 ast_log(LOG_ERROR, "Configuration file amd.conf missing.\n");
443                 return -1;
444         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
445                 return 0;
446         } else if (cfg == CONFIG_STATUS_FILEINVALID) {
447                 ast_log(LOG_ERROR, "Config file amd.conf is in an invalid format.  Aborting.\n");
448                 return -1;
449         }
450
451         cat = ast_category_browse(cfg, NULL);
452
453         while (cat) {
454                 if (!strcasecmp(cat, "general") ) {
455                         var = ast_variable_browse(cfg, cat);
456                         while (var) {
457                                 if (!strcasecmp(var->name, "initial_silence")) {
458                                         dfltInitialSilence = atoi(var->value);
459                                 } else if (!strcasecmp(var->name, "greeting")) {
460                                         dfltGreeting = atoi(var->value);
461                                 } else if (!strcasecmp(var->name, "after_greeting_silence")) {
462                                         dfltAfterGreetingSilence = atoi(var->value);
463                                 } else if (!strcasecmp(var->name, "silence_threshold")) {
464                                         dfltSilenceThreshold = atoi(var->value);
465                                 } else if (!strcasecmp(var->name, "total_analysis_time")) {
466                                         dfltTotalAnalysisTime = atoi(var->value);
467                                 } else if (!strcasecmp(var->name, "min_word_length")) {
468                                         dfltMinimumWordLength = atoi(var->value);
469                                 } else if (!strcasecmp(var->name, "between_words_silence")) {
470                                         dfltBetweenWordsSilence = atoi(var->value);
471                                 } else if (!strcasecmp(var->name, "maximum_number_of_words")) {
472                                         dfltMaximumNumberOfWords = atoi(var->value);
473                                 } else if (!strcasecmp(var->name, "maximum_word_length")) {
474                                         dfltMaximumWordLength = atoi(var->value);
475
476                                 } else {
477                                         ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n",
478                                                 app, cat, var->name, var->lineno);
479                                 }
480                                 var = var->next;
481                         }
482                 }
483                 cat = ast_category_browse(cfg, cat);
484         }
485
486         ast_config_destroy(cfg);
487
488         ast_verb(3, "AMD defaults: initialSilence [%d] greeting [%d] afterGreetingSilence [%d] "
489                 "totalAnalysisTime [%d] minimumWordLength [%d] betweenWordsSilence [%d] maximumNumberOfWords [%d] silenceThreshold [%d] maximumWordLength [%d]\n",
490                 dfltInitialSilence, dfltGreeting, dfltAfterGreetingSilence, dfltTotalAnalysisTime,
491                 dfltMinimumWordLength, dfltBetweenWordsSilence, dfltMaximumNumberOfWords, dfltSilenceThreshold, dfltMaximumWordLength);
492
493         return 0;
494 }
495
496 static int unload_module(void)
497 {
498         return ast_unregister_application(app);
499 }
500
501 /*!
502  * \brief Load the module
503  *
504  * Module loading including tests for configuration or dependencies.
505  * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
506  * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
507  * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the 
508  * configuration file or other non-critical problem return 
509  * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
510  */
511 static int load_module(void)
512 {
513         if (load_config(0) || ast_register_application_xml(app, amd_exec)) {
514                 return AST_MODULE_LOAD_DECLINE;
515         }
516
517         return AST_MODULE_LOAD_SUCCESS;
518 }
519
520 static int reload(void)
521 {
522         if (load_config(1))
523                 return AST_MODULE_LOAD_DECLINE;
524         return AST_MODULE_LOAD_SUCCESS;
525 }
526
527 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Answering Machine Detection Application",
528                 .load = load_module,
529                 .unload = unload_module,
530                 .reload = reload,
531 );