[Asterisk-code-review] Add XAGI dialplan application (...asterisk[16])
Dan Jenkins
asteriskteam at digium.com
Wed Jul 31 11:03:56 CDT 2019
Dan Jenkins has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/11657
Change subject: Add XAGI dialplan application
......................................................................
Add XAGI dialplan application
This enablesusto be able to pull audio out of asterisk and push it
in using file descriptors and a new dialplan application
Co-authored-by: Torrey Searle <tsearle at gmail.com>
Change-Id: I32a656294ef28a4c4a98f0afb880e52952aa1eec
---
M include/asterisk/agi.h
M res/res_agi.c
2 files changed, 227 insertions(+), 64 deletions(-)
git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/57/11657/1
diff --git a/include/asterisk/agi.h b/include/asterisk/agi.h
index 568cd5d..73c7d48 100644
--- a/include/asterisk/agi.h
+++ b/include/asterisk/agi.h
@@ -34,6 +34,7 @@
typedef struct agi_state {
int fd; /*!< FD for general output */
int audio; /*!< FD for audio output */
+ int audio_in; /* FD for audio output */
int ctrl; /*!< FD for input control */
unsigned int fast:1; /*!< flag for fast agi or not */
struct ast_speech *speech; /*!< Speech structure for speech recognition */
diff --git a/res/res_agi.c b/res/res_agi.c
index d88d511..02eb015 100644
--- a/res/res_agi.c
+++ b/res/res_agi.c
@@ -1211,6 +1211,7 @@
<ref type="manager">AGI</ref>
<ref type="managerEvent">AsyncAGIStart</ref>
<ref type="managerEvent">AsyncAGIEnd</ref>
+ <ref type="application">XAGI</ref>
<ref type="application">EAGI</ref>
<ref type="application">DeadAGI</ref>
<ref type="filename">asterisk.conf</ref>
@@ -1234,6 +1235,29 @@
</description>
<see-also>
<ref type="application">AGI</ref>
+ <ref type="application">XGI</ref>
+ <ref type="application">DeadAGI</ref>
+ </see-also>
+ </application>
+ <application name="XAGI" language="en_US">
+ <synopsis>
+ Executes an XAGI compliant application.
+ </synopsis>
+ <syntax>
+ <xi:include xpointer="xpointer(/docs/application[@name='AGI']/syntax/parameter[@name='command'])" />
+ <xi:include xpointer="xpointer(/docs/application[@name='AGI']/syntax/parameter[@name='args'])" />
+ </syntax>
+ <description>
+ <para>Using 'XAGI' provides enhanced AGI, with incoming and outgoing audio available out of band
+ on file descriptor 3 and 4. In all other respects, it behaves in the same fashion as
+ AGI/EAGI. See the documentation for the <literal>AGI</literal> dialplan application for
+ more information on invoking AGI on a channel.</para>
+ <para>This application sets the following channel variable upon completion:</para>
+ <xi:include xpointer="xpointer(/docs/application[@name='AGI']/description/variablelist)" />
+ </description>
+ <see-also>
+ <ref type="application">AGI</ref>
+ <ref type="application">EGI</ref>
<ref type="application">DeadAGI</ref>
</see-also>
</application>
@@ -1260,6 +1284,7 @@
</description>
<see-also>
<ref type="application">AGI</ref>
+ <ref type="application">XAGI</ref>
<ref type="application">EAGI</ref>
</see-also>
</application>
@@ -1387,6 +1412,8 @@
static char *app = "AGI";
+static char *xapp = "XAGI";
+
static char *eapp = "EAGI";
static char *deadapp = "DeadAGI";
@@ -2191,10 +2218,10 @@
return AGI_RESULT_FAILURE;
}
-static enum agi_result launch_script(struct ast_channel *chan, char *script, int argc, char *argv[], int *fds, int *efd, int *opid)
+static enum agi_result launch_script(struct ast_channel *chan, char *script, int argc, char *argv[], int *fds, int *efd, int *efd2, int *opid)
{
char tmp[256];
- int pid, toast[2], fromast[2], audio[2], res;
+ int pid, toast[2], fromast[2], audio[2], audio2[2], res;
struct stat st;
if (!strncasecmp(script, "agi://", 6)) {
@@ -2251,6 +2278,31 @@
}
}
+ if (efd2) {
+ if (pipe(audio2)) {
+ ast_log(LOG_WARNING, "unable to create audio pipe: %s\n", strerror(errno));
+ close(fromast[0]);
+ close(fromast[1]);
+ close(toast[0]);
+ close(toast[1]);
+ return AGI_RESULT_FAILURE;
+ }
+
+ res = ast_fd_set_flags(audio2[1], O_NONBLOCK);
+ if (res < 0) {
+ ast_log(LOG_WARNING, "unable to set audio pipe parameters: %s\n", strerror(errno));
+ close(fromast[0]);
+ close(fromast[1]);
+ close(toast[0]);
+ close(toast[1]);
+ close(audio[0]);
+ close(audio[1]);
+ close(audio2[0]);
+ close(audio2[1]);
+ return AGI_RESULT_FAILURE;
+ }
+ }
+
if ((pid = ast_safe_fork(1)) < 0) {
ast_log(LOG_WARNING, "Failed to fork(): %s\n", strerror(errno));
return AGI_RESULT_FAILURE;
@@ -2280,8 +2332,13 @@
else
close(STDERR_FILENO + 1);
+ if (efd2)
+ dup2(audio2[1], STDERR_FILENO + 2);
+ else
+ close(STDERR_FILENO + 2);
+
/* Close everything but stdin/out/error */
- ast_close_fds_above_n(STDERR_FILENO + 1);
+ ast_close_fds_above_n(STDERR_FILENO + 2);
/* Execute script */
/* XXX argv should be deprecated in favor of passing agi_argX paramaters */
@@ -2298,12 +2355,17 @@
fds[1] = fromast[1];
if (efd)
*efd = audio[1];
+
+ if (efd2)
+ *efd2 = audio2[0];
/* close what we're not using in the parent */
close(toast[1]);
close(fromast[0]);
if (efd)
close(audio[0]);
+ if (efd2)
+ close(audio2[1]);
*opid = pid;
return AGI_RESULT_SUCCESS;
@@ -2340,7 +2402,7 @@
ast_agi_send(fd, chan, "agi_context: %s\n", ast_channel_context(chan));
ast_agi_send(fd, chan, "agi_extension: %s\n", ast_channel_exten(chan));
ast_agi_send(fd, chan, "agi_priority: %d\n", ast_channel_priority(chan));
- ast_agi_send(fd, chan, "agi_enhanced: %s\n", enhanced ? "1.0" : "0.0");
+ ast_agi_send(fd, chan, "agi_enhanced: %d%s\n", enhanced, ".0");
/* User information */
ast_agi_send(fd, chan, "agi_accountcode: %s\n", ast_channel_accountcode(chan) ? ast_channel_accountcode(chan) : "");
@@ -4111,8 +4173,13 @@
int needhup = 0;
enum agi_result returnstatus = AGI_RESULT_SUCCESS;
struct ast_frame *f;
+ struct ast_frame fr;
char buf[AGI_BUF_LEN];
+ char audiobuf[320];
char *res = NULL;
+ int audiobytes;
+ int fds[2];
+ int enhanced = 0;
FILE *readf;
/* how many times we'll retry if ast_waitfor_nandfs will return without either
channel or file descriptor in case select is interrupted by a system call (EINTR) */
@@ -4123,6 +4190,7 @@
int exit_on_hangup;
/*! Running in an interception routine is like DeadAGI mode. No touchy the channel frames. */
int in_intercept = ast_channel_get_intercept_mode();
+ struct timeval next_xagi_read = ast_tvnow();
ast_channel_lock(chan);
sighup_str = pbx_builtin_getvar_helper(chan, "AGISIGHUP");
@@ -4140,8 +4208,18 @@
}
setlinebuf(readf);
- setup_env(chan, request, agi->fd, (agi->audio > -1), argc, argv);
+ if (agi->audio > -1) {
+ enhanced = 1;
+ }
+ if (agi->audio_in > -1) {
+ enhanced++;
+ }
+ setup_env(chan, request, agi->fd, enhanced, argc, argv);
+ fds[0] = agi->ctrl;
+ fds[1] = agi->audio_in;
+
for (;;) {
+ int timeout_expected = 0;
if (needhup) {
needhup = 0;
dead = 1;
@@ -4160,7 +4238,15 @@
if (dead || in_intercept) {
c = ast_waitfor_nandfds(&chan, 0, &agi->ctrl, 1, NULL, &outfd, &ms);
} else if (!ast_check_hangup(chan)) {
- c = ast_waitfor_nandfds(&chan, 1, &agi->ctrl, 1, NULL, &outfd, &ms);
+ struct timeval now = ast_tvnow();
+ int delta = ast_tvdiff_ms(next_xagi_read, now);
+ if(delta <= 0) {
+ c = ast_waitfor_nandfds(&chan, 1, fds, 2, NULL, &outfd, &ms);
+ } else {
+ /* don't check if audio is available until the next timeout */
+ c = ast_waitfor_nandfds(&chan, 1, &agi->ctrl, 1, NULL, &outfd, &delta);
+ timeout_expected = 1;
+ }
} else {
/*
* Read the channel control queue until it is dry so we can
@@ -4188,65 +4274,81 @@
ast_frfree(f);
}
} else if (outfd > -1) {
- size_t len = sizeof(buf);
- size_t buflen = 0;
- enum agi_result cmd_status;
+ if ((agi->audio_in > -1) && (outfd == agi->audio_in)) {
+ audiobytes = read(agi->audio_in, audiobuf, sizeof(audiobuf));
+ next_xagi_read = ast_tvadd(ast_tvnow(), ast_tv(0,20000));
+ if (audiobytes > 0) {
+ memset(&fr, 0, sizeof(fr));
+ fr.frametype = AST_FRAME_VOICE;
+ fr.subclass.format = ast_format_slin;
+ fr.datalen = audiobytes;
+ fr.data.ptr = audiobuf;
+ fr.samples = audiobytes/2;
+ fr.offset = AST_FRIENDLY_OFFSET;
- retry = AGI_NANDFS_RETRY;
- buf[0] = '\0';
-
- while (len > 1) {
- res = fgets(buf + buflen, len, readf);
- if (feof(readf))
- break;
- if (ferror(readf) && ((errno != EINTR) && (errno != EAGAIN)))
- break;
- if (res != NULL && !agi->fast)
- break;
- buflen = strlen(buf);
- if (buflen && buf[buflen - 1] == '\n')
- break;
- len = sizeof(buf) - buflen;
- if (agidebug)
- ast_verbose("AGI Rx << temp buffer %s - errno %s\nNo \\n received, checking again.\n", buf, strerror(errno));
- }
-
- if (!buf[0]) {
- /* Program terminated */
- ast_verb(3, "<%s>AGI Script %s completed, returning %d\n", ast_channel_name(chan), request, returnstatus);
- if (pid > 0)
- waitpid(pid, status, 0);
- /* No need to kill the pid anymore, since they closed us */
- pid = -1;
- break;
- }
-
- /* Special case for inability to execute child process */
- if (*buf && strncasecmp(buf, "failure", 7) == 0) {
- returnstatus = AGI_RESULT_FAILURE;
- break;
- }
-
- /* get rid of trailing newline, if any */
- buflen = strlen(buf);
- if (buflen && buf[buflen - 1] == '\n') {
- buf[buflen - 1] = '\0';
- }
-
- if (agidebug)
- ast_verbose("<%s>AGI Rx << %s\n", ast_channel_name(chan), buf);
- cmd_status = agi_handle_command(chan, agi, buf, dead || in_intercept);
- switch (cmd_status) {
- case AGI_RESULT_FAILURE:
- if (dead || in_intercept || !ast_check_hangup(chan)) {
- /* The failure was not because of a hangup. */
- returnstatus = AGI_RESULT_FAILURE;
+ ast_write(chan, &fr);
}
- break;
- default:
- break;
+ } else {
+ size_t len = sizeof(buf);
+ size_t buflen = 0;
+ enum agi_result cmd_status;
+
+ retry = AGI_NANDFS_RETRY;
+ buf[0] = '\0';
+
+ while (len > 1) {
+ res = fgets(buf + buflen, len, readf);
+ if (feof(readf))
+ break;
+ if (ferror(readf) && ((errno != EINTR) && (errno != EAGAIN)))
+ break;
+ if (res != NULL && !agi->fast)
+ break;
+ buflen = strlen(buf);
+ if (buflen && buf[buflen - 1] == '\n')
+ break;
+ len = sizeof(buf) - buflen;
+ if (agidebug)
+ ast_verbose("AGI Rx << temp buffer %s - errno %s\nNo \\n received, checking again.\n", buf, strerror(errno));
+ }
+
+ if (!buf[0]) {
+ /* Program terminated */
+ ast_verb(3, "<%s>AGI Script %s completed, returning %d\n", ast_channel_name(chan), request, returnstatus);
+ if (pid > 0)
+ waitpid(pid, status, 0);
+ /* No need to kill the pid anymore, since they closed us */
+ pid = -1;
+ break;
+ }
+
+ /* Special case for inability to execute child process */
+ if (*buf && strncasecmp(buf, "failure", 7) == 0) {
+ returnstatus = AGI_RESULT_FAILURE;
+ break;
+ }
+
+ /* get rid of trailing newline, if any */
+ buflen = strlen(buf);
+ if (buflen && buf[buflen - 1] == '\n') {
+ buf[buflen - 1] = '\0';
+ }
+
+ if (agidebug)
+ ast_verbose("<%s>AGI Rx << %s\n", ast_channel_name(chan), buf);
+ cmd_status = agi_handle_command(chan, agi, buf, dead || in_intercept);
+ switch (cmd_status) {
+ case AGI_RESULT_FAILURE:
+ if (dead || in_intercept || !ast_check_hangup(chan)) {
+ /* The failure was not because of a hangup. */
+ returnstatus = AGI_RESULT_FAILURE;
+ }
+ break;
+ default:
+ break;
+ }
}
- } else {
+ } else if (!timeout_expected) {
if (--retry <= 0) {
ast_log(LOG_WARNING, "No channel, no fd?\n");
returnstatus = AGI_RESULT_FAILURE;
@@ -4489,7 +4591,7 @@
{
enum agi_result res;
char *buf;
- int fds[2], efd = -1, pid = -1;
+ int fds[2], efd = -1, efd2 = -1, pid = -1;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(arg)[MAX_ARGS];
);
@@ -4512,7 +4614,7 @@
return -1;
}
#endif
- res = launch_script(chan, args.argv[0], args.argc, args.argv, fds, enhanced ? &efd : NULL, &pid);
+ res = launch_script(chan, args.argv[0], args.argc, args.argv, fds, enhanced ? &efd : NULL, (enhanced == 2) ? &efd2 : NULL, &pid);
/* Async AGI do not require run_agi(), so just proceed if normal AGI
or Fast AGI are setup with success. */
if (res == AGI_RESULT_SUCCESS || res == AGI_RESULT_SUCCESS_FAST) {
@@ -4520,6 +4622,7 @@
agi.fd = fds[1];
agi.ctrl = fds[0];
agi.audio = efd;
+ agi.audio_in = efd2;
agi.fast = (res == AGI_RESULT_SUCCESS_FAST) ? 1 : 0;
res = run_agi(chan, args.argv[0], &agi, pid, &status, dead, args.argc, args.argv);
/* If the fork'd process returns non-zero, set AGISTATUS to FAILURE */
@@ -4529,6 +4632,8 @@
close(fds[1]);
if (efd > -1)
close(efd);
+ if (efd2 > -1)
+ close(efd2);
}
ast_safe_fork_cleanup();
@@ -4602,6 +4707,61 @@
return res;
}
+static int xagi_exec(struct ast_channel *chan, const char *data)
+{
+ int res;
+ struct ast_format *readformat;
+ struct ast_format *writeformat;
+ struct ast_format *requested_format = NULL;
+ const char *requested_format_name;
+
+ if (ast_check_hangup(chan)) {
+ ast_log(LOG_ERROR, "EAGI cannot be run on a dead/hungup channel, please use AGI.\n");
+ return 0;
+ }
+
+ requested_format_name = pbx_builtin_getvar_helper(chan, "EAGI_AUDIO_FORMAT");
+ if (requested_format_name) {
+ requested_format = ast_format_cache_get(requested_format_name);
+ if (requested_format) {
+ ast_verb(3, "<%s> Setting EAGI audio pipe format to %s\n",
+ ast_channel_name(chan), ast_format_get_name(requested_format));
+ } else {
+ ast_log(LOG_ERROR, "Could not find requested format: %s\n", requested_format_name);
+ }
+ }
+
+ readformat = ao2_bump(ast_channel_readformat(chan));
+ if (ast_set_read_format(chan, requested_format ?: ast_format_slin)) {
+ ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", ast_channel_name(chan));
+ ao2_cleanup(requested_format);
+ ao2_cleanup(readformat);
+ return -1;
+ }
+ writeformat = ao2_bump(ast_channel_writeformat(chan));
+ if (ast_set_read_format(chan, requested_format ?: ast_format_slin)) {
+ ast_log(LOG_WARNING, "Unable to set channel '%s' to linear mode\n", ast_channel_name(chan));
+ ao2_cleanup(requested_format);
+ ao2_cleanup(writeformat);
+ return -1;
+ }
+ res = agi_exec_full(chan, data, 2, 0);
+ if (!res) {
+ if (ast_set_read_format(chan, readformat)) {
+ ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", ast_channel_name(chan),
+ ast_format_get_name(readformat));
+ }
+ if (ast_set_read_format(chan, writeformat)) {
+ ast_log(LOG_WARNING, "Unable to restore channel '%s' to format %s\n", ast_channel_name(chan),
+ ast_format_get_name(writeformat));
+ }
+ }
+ ao2_cleanup(requested_format);
+ ao2_cleanup(readformat);
+ ao2_cleanup(writeformat);
+ return res;
+}
+
static int deadagi_exec(struct ast_channel *chan, const char *data)
{
ast_log(LOG_WARNING, "DeadAGI has been deprecated, please use AGI in all cases!\n");
@@ -4666,6 +4826,7 @@
ast_cli_unregister_multiple(cli_agi, ARRAY_LEN(cli_agi));
ast_agi_unregister_multiple(commands, ARRAY_LEN(commands));
ast_unregister_application(eapp);
+ ast_unregister_application(xapp);
ast_unregister_application(deadapp);
ast_manager_unregister("AGI");
ast_unregister_application(app);
@@ -4687,6 +4848,7 @@
err |= ast_agi_register_multiple(ast_module_info->self, commands, ARRAY_LEN(commands));
err |= ast_register_application_xml(deadapp, deadagi_exec);
err |= ast_register_application_xml(eapp, eagi_exec);
+ err |= ast_register_application_xml(xapp, xagi_exec);
err |= ast_manager_register_xml("AGI", EVENT_FLAG_AGI, action_add_agi_cmd);
err |= ast_register_application_xml(app, agi_exec);
--
To view, visit https://gerrit.asterisk.org/c/asterisk/+/11657
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings
Gerrit-Project: asterisk
Gerrit-Branch: 16
Gerrit-Change-Id: I32a656294ef28a4c4a98f0afb880e52952aa1eec
Gerrit-Change-Number: 11657
Gerrit-PatchSet: 1
Gerrit-Owner: Dan Jenkins <dan at nimbleape.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20190731/2b776b7c/attachment-0001.html>
More information about the asterisk-code-review
mailing list