[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