From fdac31f279ea5e7d855d09af6dcafa7aba811c4c Mon Sep 17 00:00:00 2001 From: Naveen Albert Date: Mon, 15 Aug 2022 19:59:02 +0000 Subject: [PATCH] app_amd: Add option to play audio during AMD. Adds an option that will play an audio file to the party while AMD is running on the channel, so the called party does not just hear silence. ASTERISK-30179 #close Change-Id: I4af306274552b61b3d9f0883c33f698abd4699b6 --- apps/app_amd.c | 51 +++++++++++++++++++++++++++++++-- configs/samples/amd.conf.sample | 7 ++++- doc/CHANGES-staging/app_amd.txt | 5 ++++ 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 doc/CHANGES-staging/app_amd.txt diff --git a/apps/app_amd.c b/apps/app_amd.c index 39d0b79a94..fb63c7d54b 100644 --- a/apps/app_amd.c +++ b/apps/app_amd.c @@ -92,6 +92,12 @@ Is the maximum duration of a word to accept. If exceeded, then the result is detection as a MACHINE + + Is an audio file to play to the caller while AMD is in progress. + By default, no audio file is played. + If an audio file is configured in amd.conf, then that file will be used + if one is not specified here. That file may be overridden by this argument. + This application attempts to detect answering machines at the beginning @@ -155,6 +161,9 @@ static int dfltBetweenWordsSilence = 50; static int dfltMaximumNumberOfWords = 2; static int dfltSilenceThreshold = 256; static int dfltMaximumWordLength = 5000; /* Setting this to a large default so it is not used unless specify it in the configs or command line */ +static char *dfltAudioFile = NULL; + +static ast_mutex_t config_lock; /* Set to the lowest ms value provided in amd.conf or application parameters */ static int dfltMaxWaitTimeForFrame = 50; @@ -179,7 +188,7 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data) char amdCause[256] = "", amdStatus[256] = ""; char *parse = ast_strdupa(data); - /* Lets set the initial values of the variables that will control the algorithm. + /* Let's set the initial values of the variables that will control the algorithm. The initial values are the default ones. If they are passed as arguments when invoking the application, then the default values will be overwritten by the ones passed as parameters. */ @@ -193,6 +202,7 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data) int silenceThreshold = dfltSilenceThreshold; int maximumWordLength = dfltMaximumWordLength; int maxWaitTimeForFrame = dfltMaxWaitTimeForFrame; + const char *audioFile = NULL; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(argInitialSilence); @@ -204,8 +214,15 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data) AST_APP_ARG(argMaximumNumberOfWords); AST_APP_ARG(argSilenceThreshold); AST_APP_ARG(argMaximumWordLength); + AST_APP_ARG(audioFile); ); + ast_mutex_lock(&config_lock); + if (!ast_strlen_zero(dfltAudioFile)) { + audioFile = ast_strdupa(dfltAudioFile); + } + ast_mutex_unlock(&config_lock); + ast_verb(3, "AMD: %s %s %s (Fmt: %s)\n", ast_channel_name(chan), S_COR(ast_channel_caller(chan)->ani.number.valid, ast_channel_caller(chan)->ani.number.str, "(N/A)"), S_COR(ast_channel_redirecting(chan)->from.number.valid, ast_channel_redirecting(chan)->from.number.str, "(N/A)"), @@ -233,6 +250,9 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data) silenceThreshold = atoi(args.argSilenceThreshold); if (!ast_strlen_zero(args.argMaximumWordLength)) maximumWordLength = atoi(args.argMaximumWordLength); + if (!ast_strlen_zero(args.audioFile)) { + audioFile = args.audioFile; + } } else { ast_debug(1, "AMD using the default parameters.\n"); } @@ -280,6 +300,11 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data) /* Set our start time so we can tie the loop to real world time and not RTP updates */ amd_tvstart = ast_tvnow(); + /* Optional audio file to play to caller while AMD is doing its thing. */ + if (!ast_strlen_zero(audioFile)) { + ast_streamfile(chan, audioFile, ast_channel_language(chan)); + } + /* Now we go into a loop waiting for frames from the channel */ while ((res = ast_waitfor(chan, 2 * maxWaitTimeForFrame)) > -1) { int ms = 0; @@ -462,10 +487,14 @@ static void isAnsweringMachine(struct ast_channel *chan, const char *data) /* Free the DSP used to detect silence */ ast_dsp_free(silenceDetector); + /* If we were playing something to pass the time, stop it now. */ + if (!ast_strlen_zero(audioFile)) { + ast_stopstream(chan); + } + return; } - static int amd_exec(struct ast_channel *chan, const char *data) { isAnsweringMachine(chan, data); @@ -516,7 +545,16 @@ static int load_config(int reload) dfltMaximumNumberOfWords = atoi(var->value); } else if (!strcasecmp(var->name, "maximum_word_length")) { dfltMaximumWordLength = atoi(var->value); - + } else if (!strcasecmp(var->name, "playback_file")) { + ast_mutex_lock(&config_lock); + if (dfltAudioFile) { + ast_free(dfltAudioFile); + dfltAudioFile = NULL; + } + if (!ast_strlen_zero(var->value)) { + dfltAudioFile = ast_strdup(var->value); + } + ast_mutex_unlock(&config_lock); } else { ast_log(LOG_WARNING, "%s: Cat:%s. Unknown keyword %s at line %d of amd.conf\n", app, cat, var->name, var->lineno); @@ -539,6 +577,12 @@ static int load_config(int reload) static int unload_module(void) { + ast_mutex_lock(&config_lock); + if (dfltAudioFile) { + ast_free(dfltAudioFile); + } + ast_mutex_unlock(&config_lock); + ast_mutex_destroy(&config_lock); return ast_unregister_application(app); } @@ -554,6 +598,7 @@ static int unload_module(void) */ static int load_module(void) { + ast_mutex_init(&config_lock); if (load_config(0) || ast_register_application_xml(app, amd_exec)) { return AST_MODULE_LOAD_DECLINE; } diff --git a/configs/samples/amd.conf.sample b/configs/samples/amd.conf.sample index d1764b50ce..b108298331 100644 --- a/configs/samples/amd.conf.sample +++ b/configs/samples/amd.conf.sample @@ -8,6 +8,11 @@ total_analysis_time = 5000 ; Maximum time allowed for the algorithm to decide silence_threshold = 256 ; If the average level of noise in a sample does not reach ; this value, from a scale of 0 to 32767, then we will consider ; it to be silence. +;playback_file = ; Audio file to play while AMD is running, so the caller + ; does not just hear silence. Note that specifying this here + ; will apply to ALL AMD runs, so you may wish to set it + ; in the dialplan as an argument to AMD() instead. + ; Default is no audio file (not to play anything). ; Greeting ; initial_silence = 2500 ; Maximum silence duration before the greeting. @@ -19,7 +24,7 @@ greeting = 1500 ; Maximum length of a greeting. If exceeded, then the ; Word detection ; min_word_length = 100 ; Minimum duration of Voice to considered as a word -maximum_word_length = 5000 ; Maximum duration of a single Voice utterance allowed. +maximum_word_length = 5000 ; Maximum duration of a single Voice utterance allowed. between_words_silence = 50 ; Minimum duration of silence after a word to consider ; the audio what follows as a new word diff --git a/doc/CHANGES-staging/app_amd.txt b/doc/CHANGES-staging/app_amd.txt new file mode 100644 index 0000000000..ffccd8cd48 --- /dev/null +++ b/doc/CHANGES-staging/app_amd.txt @@ -0,0 +1,5 @@ +Subject: app_amd + +An audio file to play during AMD processing can +now be specified to the AMD application or configured +in the amd.conf configuration file.