Merge "res_musiconhold: Start playlist after initial announcement"
[asterisk/asterisk.git] / apps / app_talkdetect.c
old mode 100755 (executable)
new mode 100644 (file)
index 1157ac3..fbe2ea7
 /*
- * Asterisk -- A telephony toolkit for Linux.
+ * Asterisk -- An open source telephony toolkit.
  *
- * Playback a file with audio detect
- * 
- * Copyright (C) 2004, Digium, Inc.
+ * Copyright (C) 1999 - 2005, Digium, Inc.
  *
  * Mark Spencer <markster@digium.com>
  *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
  * This program is free software, distributed under the terms of
- * the GNU General Public License
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
  */
-#include <asterisk/lock.h>
-#include <asterisk/file.h>
-#include <asterisk/logger.h>
-#include <asterisk/channel.h>
-#include <asterisk/pbx.h>
-#include <asterisk/module.h>
-#include <asterisk/translate.h>
-#include <asterisk/utils.h>
-#include <asterisk/dsp.h>
-#include <string.h>
-#include <stdlib.h>
-#include <pthread.h>
-
-static char *tdesc = "Playback with Talk Detection";
 
-static char *app = "BackgroundDetect";
+/*! \file
+ *
+ * \brief Playback a file with audio detect
+ *
+ * \author Mark Spencer <markster@digium.com>
+ * 
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+       <support_level>core</support_level>
+ ***/
 
-static char *synopsis = "Background a file with talk detect";
+#include "asterisk.h"
 
-static char *descrip = 
-"  BackgroundDetect(filename[|sil[|min|[max]]]):  Plays  back  a  given\n"
-"filename, waiting for interruption from a given digit (the digit must\n"
-"start the beginning of a valid extension, or it will be ignored).\n"
-"During the playback of the file, audio is monitored in the receive\n"
-"direction, and if a period of non-silence which is greater than 'min' ms\n"
-"yet less than 'max' ms is followed by silence for at least 'sil' ms then\n"
-"the audio playback is aborted and processing jumps to the 'talk' extension\n"
-"if available.  If unspecified, sil, min, and max default to 1000, 100, and\n"
-"infinity respectively.  Returns -1 on hangup, and 0 on successful playback\n"
-"completion with no exit conditions.\n";
+#include "asterisk/lock.h"
+#include "asterisk/file.h"
+#include "asterisk/channel.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/translate.h"
+#include "asterisk/utils.h"
+#include "asterisk/dsp.h"
+#include "asterisk/app.h"
+#include "asterisk/format.h"
+#include "asterisk/format_cache.h"
 
-STANDARD_LOCAL_USER;
+/*** DOCUMENTATION
+       <application name="BackgroundDetect" language="en_US">
+               <synopsis>
+                       Background a file with talk detect.
+               </synopsis>
+               <syntax>
+                       <parameter name="filename" required="true" />
+                       <parameter name="sil">
+                               <para>If not specified, defaults to <literal>1000</literal>.</para>
+                       </parameter>
+                       <parameter name="min">
+                               <para>If not specified, defaults to <literal>100</literal>.</para>
+                       </parameter>
+                       <parameter name="max">
+                               <para>If not specified, defaults to <literal>infinity</literal>.</para>
+                       </parameter>
+                       <parameter name="analysistime">
+                               <para>If not specified, defaults to <literal>infinity</literal>.</para>
+                       </parameter>
+               </syntax>
+               <description>
+                       <para>Plays back <replaceable>filename</replaceable>, waiting for interruption from a given digit (the digit
+                       must start the beginning of a valid extension, or it will be ignored). During
+                       the playback of the file, audio is monitored in the receive direction, and if
+                       a period of non-silence which is greater than <replaceable>min</replaceable> ms yet less than
+                       <replaceable>max</replaceable> ms is followed by silence for at least <replaceable>sil</replaceable> ms,
+                       which occurs during the first <replaceable>analysistime</replaceable> ms, then the audio playback is
+                       aborted and processing jumps to the <replaceable>talk</replaceable> extension, if available.</para>
+               </description>
+       </application>
+ ***/
 
-LOCAL_USER_DECL;
+static char *app = "BackgroundDetect";
 
-static int background_detect_exec(struct ast_channel *chan, void *data)
+static int background_detect_exec(struct ast_channel *chan, const char *data)
 {
        int res = 0;
-       struct localuser *u;
-       char tmp[256];
-       char *options;
-       char *stringp;
+       char *tmp;
        struct ast_frame *fr;
-       int notsilent=0;
-       struct timeval start = { 0, 0}, end = {0, 0};
+       int notsilent = 0;
+       struct timeval start = { 0, 0 };
+       struct timeval detection_start = { 0, 0 };
        int sil = 1000;
        int min = 100;
        int max = -1;
+       int analysistime = -1;
+       int continue_analysis = 1;
        int x;
-       int origrformat=0;
-       struct ast_dsp *dsp;
-       if (!data || ast_strlen_zero((char *)data)) {
+       RAII_VAR(struct ast_format *, origrformat, NULL, ao2_cleanup);
+       struct ast_dsp *dsp = NULL;
+       AST_DECLARE_APP_ARGS(args,
+               AST_APP_ARG(filename);
+               AST_APP_ARG(silence);
+               AST_APP_ARG(min);
+               AST_APP_ARG(max);
+               AST_APP_ARG(analysistime);
+       );
+
+       if (ast_strlen_zero(data)) {
                ast_log(LOG_WARNING, "BackgroundDetect requires an argument (filename)\n");
                return -1;
        }
-       strncpy(tmp, (char *)data, sizeof(tmp)-1);
-       stringp=tmp;
-       strsep(&stringp, "|");
-       options = strsep(&stringp, "|");
-       if (options) {
-               if ((sscanf(options, "%d", &x) == 1) && (x > 0))
-                       sil = x;
-               options = strsep(&stringp, "|");
-               if (options) {
-                       if ((sscanf(options, "%d", &x) == 1) && (x > 0))
-                               min = x;
-                       options = strsep(&stringp, "|");
-                       if (options) {
-                               if ((sscanf(options, "%d", &x) == 1) && (x > 0))
-                                       max = x;
-                       }
-               }
+
+       tmp = ast_strdupa(data);
+       AST_STANDARD_APP_ARGS(args, tmp);
+
+       if (!ast_strlen_zero(args.silence) && (sscanf(args.silence, "%30d", &x) == 1) && (x > 0)) {
+               sil = x;
        }
-       ast_log(LOG_DEBUG, "Preparing detect of '%s', sil=%d,min=%d,max=%d\n", 
-                                               tmp, sil, min, max);
-       LOCAL_USER_ADD(u);
-       if (chan->_state != AST_STATE_UP) {
-               /* Otherwise answer unless we're supposed to send this while on-hook */
-               res = ast_answer(chan);
+       if (!ast_strlen_zero(args.min) && (sscanf(args.min, "%30d", &x) == 1) && (x > 0)) {
+               min = x;
        }
-       if (!res) {
-               origrformat = chan->readformat;
-               if ((res = ast_set_read_format(chan, AST_FORMAT_SLINEAR))) 
-                       ast_log(LOG_WARNING, "Unable to set read format to linear!\n");
+       if (!ast_strlen_zero(args.max) && (sscanf(args.max, "%30d", &x) == 1) && (x > 0)) {
+               max = x;
        }
-       if (!(dsp = ast_dsp_new())) {
-               ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
-               res = -1;
+       if (!ast_strlen_zero(args.analysistime) && (sscanf(args.analysistime, "%30d", &x) == 1) && (x > 0)) {
+               analysistime = x;
        }
-       if (!res) {
+
+       ast_debug(1, "Preparing detect of '%s', sil=%d, min=%d, max=%d, analysistime=%d\n", args.filename, sil, min, max, analysistime);
+       do {
+               if (ast_channel_state(chan) != AST_STATE_UP) {
+                       if ((res = ast_answer(chan))) {
+                               break;
+                       }
+               }
+
+               origrformat = ao2_bump(ast_channel_readformat(chan));
+               if ((ast_set_read_format(chan, ast_format_slin))) {
+                       ast_log(LOG_WARNING, "Unable to set read format to linear!\n");
+                       res = -1;
+                       break;
+               }
+
+               if (!(dsp = ast_dsp_new())) {
+                       ast_log(LOG_WARNING, "Unable to allocate DSP!\n");
+                       res = -1;
+                       break;
+               }
                ast_stopstream(chan);
-               res = ast_streamfile(chan, tmp, chan->language);
-               if (!res) {
-                       while(chan->stream) {
-                               res = ast_sched_wait(chan->sched);
-                               if ((res < 0) && !chan->timingfunc) {
-                                       res = 0;
-                                       break;
+               if (ast_streamfile(chan, tmp, ast_channel_language(chan))) {
+                       ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", ast_channel_name(chan), (char *)data);
+                       break;
+               }
+               detection_start = ast_tvnow();
+               while (ast_channel_stream(chan)) {
+                       res = ast_sched_wait(ast_channel_sched(chan));
+                       if ((res < 0) && !ast_channel_timingfunc(chan)) {
+                               res = 0;
+                               break;
+                       }
+                       if (res < 0) {
+                               res = 1000;
+                       }
+                       res = ast_waitfor(chan, res);
+                       if (res < 0) {
+                               ast_log(LOG_WARNING, "Waitfor failed on %s\n", ast_channel_name(chan));
+                               break;
+                       } else if (res > 0) {
+                               fr = ast_read(chan);
+                               if (continue_analysis && analysistime >= 0) {
+                                       /* If we have a limit for the time to analyze voice
+                                        * frames and the time has not expired */
+                                       if (ast_tvdiff_ms(ast_tvnow(), detection_start) >= analysistime) {
+                                               continue_analysis = 0;
+                                               ast_verb(3, "BackgroundDetect: Talk analysis time complete on %s.\n", ast_channel_name(chan));
+                                       }
                                }
-                               if (res < 0)
-                                       res = 1000;
-                               res = ast_waitfor(chan, res);
-                               if (res < 0) {
-                                       ast_log(LOG_WARNING, "Waitfor failed on %s\n", chan->name);
+                               
+                               if (!fr) {
+                                       res = -1;
                                        break;
-                               } else if (res > 0) {
-                                       fr = ast_read(chan);
-                                       if (!fr) {
-                                               res = -1;
+                               } else if (fr->frametype == AST_FRAME_DTMF) {
+                                       char t[2];
+                                       t[0] = fr->subclass.integer;
+                                       t[1] = '\0';
+                                       if (ast_canmatch_extension(chan, ast_channel_context(chan), t, 1,
+                                               S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
+                                               /* They entered a valid  extension, or might be anyhow */
+                                               res = fr->subclass.integer;
+                                               ast_frfree(fr);
                                                break;
-                                       } else if (fr->frametype == AST_FRAME_DTMF) {
-                                               char t[2];
-                                               t[0] = fr->subclass;
-                                               t[1] = '\0';
-                                               if (ast_canmatch_extension(chan, chan->context, t, 1, chan->callerid)) {
-                                                       /* They entered a valid  extension, or might be anyhow */
-                                                       res = fr->subclass;
-                                                       ast_frfree(fr);
-                                                       break;
-                                               }
-                                       } else if (fr->frametype == AST_FRAME_VOICE) {
-                                               int totalsilence;
-                                               int ms;
-                                               res = ast_dsp_silence(dsp, fr, &totalsilence);
-                                               if (res && (totalsilence > sil)) {
-                                                       /* We've been quiet a little while */
-                                                       if (notsilent) {
-                                                               /* We had heard some talking */
-                                                               gettimeofday(&end, NULL);
-                                                               ms = (end.tv_sec - start.tv_sec) * 1000;
-                                                               ms += (end.tv_usec - start.tv_usec) / 1000;
-                                                               ms -= sil;
-                                                               if (ms < 0)
-                                                                       ms = 0;
-                                                               if ((ms > min) && ((max < 0) || (ms < max))) {
-                                                                       ast_log(LOG_DEBUG, "Found qualified token of %d ms\n", ms);
-                                                                       if (ast_exists_extension(chan, chan->context, "talk", 1, chan->callerid)) {
-                                                                               strncpy(chan->exten, "talk", sizeof(chan->exten) -1 );
-                                                                               chan->priority = 0;
-                                                                       }
-                                                                       res = 0;
-                                                                       ast_frfree(fr);
-                                                                       break;
-                                                               } else
-                                                                       ast_log(LOG_DEBUG, "Found unqualified token of %d ms\n", ms);
-                                                               notsilent = 0;
-                                                       }
-                                               } else {
-                                                       if (!notsilent) {
-                                                               /* Heard some audio, mark the begining of the token */
-                                                               gettimeofday(&start, NULL);
-                                                               ast_log(LOG_DEBUG, "Start of voice token!\n");
-                                                               notsilent = 1;
+                                       }
+                               } else if ((fr->frametype == AST_FRAME_VOICE) &&
+                               (ast_format_cmp(fr->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) && continue_analysis) {
+                                       int totalsilence;
+                                       int ms;
+                                       res = ast_dsp_silence(dsp, fr, &totalsilence);
+                                       if (res && (totalsilence > sil)) {
+                                               /* We've been quiet a little while */
+                                               if (notsilent) {
+                                                       /* We had heard some talking */
+                                                       ms = ast_tvdiff_ms(ast_tvnow(), start);
+                                                       ms -= sil;
+                                                       if (ms < 0)
+                                                               ms = 0;
+                                                       if ((ms > min) && ((max < 0) || (ms < max))) {
+                                                               char ms_str[12];
+                                                               ast_debug(1, "Found qualified token of %d ms\n", ms);
+
+                                                               /* Save detected talk time (in milliseconds) */ 
+                                                               snprintf(ms_str, sizeof(ms_str), "%d", ms);     
+                                                               pbx_builtin_setvar_helper(chan, "TALK_DETECTED", ms_str);
+
+                                                               ast_goto_if_exists(chan, ast_channel_context(chan), "talk", 1);
+                                                               res = 0;
+                                                               ast_frfree(fr);
+                                                               break;
+                                                       } else {
+                                                               ast_debug(1, "Found unqualified token of %d ms\n", ms);
                                                        }
+                                                       notsilent = 0;
+                                               }
+                                       } else {
+                                               if (!notsilent) {
+                                                       /* Heard some audio, mark the begining of the token */
+                                                       start = ast_tvnow();
+                                                       ast_debug(1, "Start of voice token!\n");
+                                                       notsilent = 1;
                                                }
-                                               
                                        }
-                                       ast_frfree(fr);
                                }
+                               ast_frfree(fr);
                        }
-                       ast_stopstream(chan);
-               } else {
-                       ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
-                       res = 0;
+                       ast_sched_runq(ast_channel_sched(chan));
                }
-       }
+               ast_stopstream(chan);
+       } while (0);
+
        if (res > -1) {
                if (origrformat && ast_set_read_format(chan, origrformat)) {
                        ast_log(LOG_WARNING, "Failed to restore read format for %s to %s\n", 
-                               chan->name, ast_getformatname(origrformat));
+                               ast_channel_name(chan), ast_format_get_name(origrformat));
                }
        }
-       if (dsp)
+       if (dsp) {
                ast_dsp_free(dsp);
-       LOCAL_USER_REMOVE(u);
+       }
        return res;
 }
 
-int unload_module(void)
+static int unload_module(void)
 {
-       STANDARD_HANGUP_LOCALUSERS;
        return ast_unregister_application(app);
 }
 
-int load_module(void)
+static int load_module(void)
 {
-       return ast_register_application(app, background_detect_exec, synopsis, descrip);
+       return ast_register_application_xml(app, background_detect_exec);
 }
 
-char *description(void)
-{
-       return tdesc;
-}
+AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Playback with Talk Detection");
 
-int usecount(void)
-{
-       int res;
-       STANDARD_USECOUNT(res);
-       return res;
-}
-
-char *key()
-{
-       return ASTERISK_GPL_KEY;
-}