[Asterisk-code-review] feat: AudioSocket channel and application (...asterisk[master])

Seán C. McCord asteriskteam at digium.com
Wed Jul 17 20:05:00 CDT 2019


Seán C. McCord has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/11579


Change subject: feat: AudioSocket channel and application
......................................................................

feat: AudioSocket channel and application

This commit adds support for
[AudioSocket](https://github.com/CyCoreSystems/audiosocket), a very
simple bidirectional audio streaming protocol.  There are both channel
and application interfaces.

A description of the protocol can be found on the above referenced
GitHub page.  A short talk about the reasons and implementation can be
found on [YouTube](https://www.youtube.com/watch?v=tjduXbZZEgI), from
CommCon 2019.

ASTERISK-28484 #close

Change-Id: Ie866e6c4fa13178ec76f2a6971ad3590a3a588b5
Signed-off-by: Seán C McCord <ulexus at gmail.com>
---
A apps/app_audiosocket.c
A channels/chan_audiosocket.c
A include/asterisk/res_audiosocket.h
A res/res_audiosocket.c
A res/res_audiosocket.exports.in
5 files changed, 812 insertions(+), 0 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/79/11579/1

diff --git a/apps/app_audiosocket.c b/apps/app_audiosocket.c
new file mode 100644
index 0000000..99916b8
--- /dev/null
+++ b/apps/app_audiosocket.c
@@ -0,0 +1,180 @@
+/*
+ * app_audiosocket
+ *
+ * Copyright (C) 2018, CyCore Systems, Inc.
+ *
+ * Seán C McCord <scm at cycoresys.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief AudioSocket application -- transmit and receive audio through a TCP socket
+ *
+ * \author Seán C McCord <scm at cycoresys.com>
+ *
+ * \ingroup applications
+ */
+
+/*** MODULEINFO
+	<depend>res_audiosocket</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+#include "errno.h"
+#include <uuid/uuid.h>
+
+#include "asterisk/file.h"
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/app.h"
+#include "asterisk/uuid.h"
+#include "asterisk/res_audiosocket.h"
+#include "asterisk/utils.h"
+#include "asterisk/format_cache.h"
+
+#define AST_MODULE "app_audiosocket"
+//#define AST_MODULE_SELF "app_audiosocket"
+#define AUDIOSOCKET_CONFIG "audiosocket.conf"
+#define MAX_CONNECT_TIMEOUT_MSEC 2000
+#define CHANNEL_INPUT_TIMEOUT_MS 5000
+
+/*** DOCUMENTATION
+	<application name="AudioSocket" language="en_US">
+		<synopsis>
+			Transmit and receive audio between channel and TCP socket
+		</synopsis>
+		<syntax>
+			<parameter name="uuid" required="true">
+            <para>UUID is the universally-unique identifier of the call for the audio socket service.  This ID must conform to the string form of a standard UUID.</para>
+			</parameter>
+      </syntax>
+		<syntax>
+			<parameter name="service" required="true">
+            <para>Service is the name or IP address and port number of the audio socket service to which this call should be connected.  This should be in the form host:port, such as myserver:9019 </para>
+			</parameter>
+      </syntax>
+		<description>
+			<para>Connects to the given TCP service, then transmits channel audio over that socket.  In turn, audio is received from the socket and sent to the channel.  Only audio frames will be transmitted.</para>
+         <para>Protocol is specified at https://github.com/CyCoreSystems/audiosocket/.</para>
+			<para>This application does not automatically answer and should generally be
+			preceeded by an application such as Answer() or Progress().</para>
+		</description>
+	</application>
+ ***/
+
+static const char app[] = "AudioSocket";
+
+static int audiosocket_exec(struct ast_channel *chan, const char *data);
+static int audiosocket_run(struct ast_channel *chan, const char *id, const int svc);
+
+static int audiosocket_exec(struct ast_channel *chan, const char *data)
+{
+   char *parse;
+
+   AST_DECLARE_APP_ARGS(args,
+         AST_APP_ARG(idStr);
+         AST_APP_ARG(server);
+   );
+
+   int s = 0;
+   struct ast_uuid *id = NULL;
+
+   /* Parse and validate arguments */
+   parse = ast_strdupa(data);
+   AST_STANDARD_APP_ARGS(args, parse);
+   if (ast_strlen_zero(args.idStr)) {
+      ast_log(LOG_ERROR, "UUID is required\n");
+      return -1;
+   }
+   if ( (id = ast_str_to_uuid(args.idStr)) == NULL ) {
+      ast_log(LOG_ERROR, "UUID '%s' could not be parsed\n", args.idStr);
+      return -1;
+   }
+   if( (s = audiosocket_connect(args.server)) < 0 ) {
+      ast_log(LOG_ERROR, "failed to connect to AudioSocket\n");
+   }
+
+   audiosocket_run(chan, args.idStr, s);
+   close(s);
+
+   return 0;
+}
+
+static int audiosocket_run(struct ast_channel *chan, const char *id, const int svc) {
+
+   if (ast_set_write_format(chan, ast_format_slin)) {
+      ast_log(LOG_ERROR, "Failed to set write format to SLINEAR\n");
+      return 1;
+   }
+   if (ast_set_read_format(chan, ast_format_slin)) {
+      ast_log(LOG_ERROR, "Failed to set read format to SLINEAR\n");
+      return 1;
+   }
+
+   if (audiosocket_init(svc, id)) {
+      return 1;
+   }
+
+	while (ast_waitfor(chan, CHANNEL_INPUT_TIMEOUT_MS) > -1) {
+
+      // Check channel state
+      if( ast_channel_state(chan) != AST_STATE_UP ) {
+         return 0;
+      }
+
+		struct ast_frame *f = ast_read(chan);
+      if(!f) {
+         ast_log(LOG_WARNING, "No frame received\n");
+         return 1;
+      }
+
+      f->delivery.tv_sec = 0;
+      f->delivery.tv_usec = 0;
+      if (f->frametype == AST_FRAME_VOICE) {
+
+         // Send audio frame to audiosocket
+         if(audiosocket_send_frame(svc, f)) {
+            ast_log(LOG_ERROR, "Failed to forward channel frame to audiosocket\n");
+            ast_frfree(f);
+            return 1;
+         }
+      }
+
+      ast_frfree(f);
+
+      // Send audiosocket data to channel
+      if(!(f = audiosocket_receive_frame(svc))) {
+         ast_log(LOG_ERROR, "Failed to receive frame from audiosocket message\n");
+         return 1;
+      }
+      if(ast_write(chan, f)) {
+         ast_log(LOG_WARNING, "Failed to forward frame to channel\n");
+         return 1;
+      }
+
+	}
+	return 0;
+}
+
+static int unload_module(void)
+{
+	return ast_unregister_application(app);
+}
+
+static int load_module(void)
+{
+   return ast_register_application_xml(app, audiosocket_exec);
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "AudioSocket Application",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DRIVER,
+	.requires = "res_audiosocket",
+);
diff --git a/channels/chan_audiosocket.c b/channels/chan_audiosocket.c
new file mode 100644
index 0000000..9f6340e
--- /dev/null
+++ b/channels/chan_audiosocket.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019, CyCore Systems, Inc.
+ *
+ * Seán C McCord <scm at cycoresys.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \author Seán C McCord <scm at cycoresys.com>
+ *
+ * \brief AudioSocket Channel
+ *
+ * \ingroup channel_drivers
+ */
+
+/*** MODULEINFO
+	<depend>res_audiosocket</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+#include <uuid/uuid.h>
+
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/uuid.h"
+#include "asterisk/res_audiosocket.h"
+#include "asterisk/pbx.h"
+#include "asterisk/acl.h"
+#include "asterisk/app.h"
+#include "asterisk/causes.h"
+#include "asterisk/format_cache.h"
+
+struct audiosocket_instance {
+   int svc;
+   char id[38];
+} audiosocket_instance;
+
+/* Forward declarations */
+static struct ast_channel *audiosocket_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
+static int audiosocket_call(struct ast_channel *ast, const char *dest, int timeout);
+static int audiosocket_hangup(struct ast_channel *ast);
+static struct ast_frame *audiosocket_read(struct ast_channel *ast);
+static int audiosocket_write(struct ast_channel *ast, struct ast_frame *f);
+
+/* AudioSocket channel driver declaration */
+static struct ast_channel_tech audiosocket_channel_tech = {
+	.type = "AudioSocket",
+	.description = "AudioSocket Channel Driver",
+	.requester = audiosocket_request,
+	.call = audiosocket_call,
+	.hangup = audiosocket_hangup,
+	.read = audiosocket_read,
+	.write = audiosocket_write,
+};
+
+/*! \brief Function called when we should read a frame from the channel */
+static struct ast_frame *audiosocket_read(struct ast_channel *ast)
+{
+	struct audiosocket_instance *instance = ast_channel_tech_pvt(ast);
+
+   if (instance == NULL || instance->svc < 1) {
+      return NULL;
+   }
+   return audiosocket_receive_frame(instance->svc);
+}
+
+/*! \brief Function called when we should write a frame to the channel */
+static int audiosocket_write(struct ast_channel *ast, struct ast_frame *f)
+{
+	struct audiosocket_instance *instance = ast_channel_tech_pvt(ast);
+
+   if (instance == NULL || instance->svc < 1) {
+      return -1;
+   }
+	return audiosocket_send_frame(instance->svc, f);
+}
+
+/*! \brief Function called when we should actually call the destination */
+static int audiosocket_call(struct ast_channel *ast, const char *dest, int timeout)
+{
+	struct audiosocket_instance *instance = ast_channel_tech_pvt(ast);
+
+	ast_queue_control(ast, AST_CONTROL_ANSWER);
+
+   if (ast_set_write_format(ast, ast_format_slin)) {
+      ast_log(LOG_ERROR, "Failed to set write format to SLINEAR\n");
+      return -1;
+   }
+   if (ast_set_read_format(ast, ast_format_slin)) {
+      ast_log(LOG_ERROR, "Failed to set read format to SLINEAR\n");
+      return -1;
+   }
+
+   return audiosocket_init(instance->svc, instance->id);
+}
+
+/*! \brief Function called when we should hang the channel up */
+static int audiosocket_hangup(struct ast_channel *ast)
+{
+	struct audiosocket_instance *instance = ast_channel_tech_pvt(ast);
+
+   if(instance != NULL && instance->svc > 0) {
+      close(instance->svc);
+   }
+
+   ast_channel_tech_pvt_set(ast, NULL);
+
+	return 0;
+}
+
+/*! \brief Function called when we should prepare to call the unicast destination */
+static struct ast_channel *audiosocket_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
+{
+	char *parse;
+	struct audiosocket_instance *instance;
+	struct ast_sockaddr address;
+	struct ast_channel *chan;
+   struct ast_uuid *id = NULL;
+   int fd;
+	AST_DECLARE_APP_ARGS(args,
+		AST_APP_ARG(destination);
+		AST_APP_ARG(idStr);
+	);
+
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_ERROR, "Destination is required for the 'AudioSocket' channel\n");
+		goto failure;
+	}
+	parse = ast_strdupa(data);
+	AST_NONSTANDARD_APP_ARGS(args, parse, '/');
+
+	if (ast_strlen_zero(args.destination)) {
+		ast_log(LOG_ERROR, "Destination is required for the 'AudioSocket' channel\n");
+		goto failure;
+	}
+	if (ast_sockaddr_resolve_first_af(&address, args.destination, PARSE_PORT_REQUIRE, AST_AF_UNSPEC)) {
+		ast_log(LOG_ERROR, "Destination '%s' could not be parsed\n", args.destination);
+		goto failure;
+	}
+
+	if (ast_strlen_zero(args.idStr)) {
+		ast_log(LOG_ERROR, "UUID is required for the 'AudioSocket' channel\n");
+		goto failure;
+   }
+   if ( (id = ast_str_to_uuid(args.idStr)) == NULL ) {
+      ast_log(LOG_ERROR, "UUID '%s' could not be parsed\n", args.idStr);
+      goto failure;
+   }
+   ast_free(id);
+
+   instance = ast_calloc(1, sizeof(*instance));
+   ast_copy_string(instance->id, args.idStr, sizeof(instance->id));
+
+   //instance.id = args.idStr;
+
+	if(ast_format_cap_iscompatible_format(cap, ast_format_slin) == AST_FORMAT_CMP_NOT_EQUAL) {
+		struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
+
+		ast_log(LOG_NOTICE, "Asked to get a channel of unsupported format '%s'\n",
+			ast_format_cap_get_names(cap, &cap_buf));
+      goto failure;
+	}
+
+   if( (fd = audiosocket_connect(args.destination)) < 0 ) {
+      ast_log(LOG_ERROR, "Failed to connect to AudioSocket server at '%s'\n", args.destination);
+      goto failure;
+   }
+   instance->svc = fd;
+
+	chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
+		requestor, 0, "AudioSocket/%s-%s", args.destination, args.idStr);
+	if (!chan) {
+		goto failure;
+	}
+	ast_channel_set_fd(chan, 0, fd);
+
+	ast_channel_tech_set(chan, &audiosocket_channel_tech);
+
+	ast_channel_nativeformats_set(chan, audiosocket_channel_tech.capabilities);
+	ast_channel_set_writeformat(chan, ast_format_slin);
+	ast_channel_set_rawwriteformat(chan, ast_format_slin);
+	ast_channel_set_readformat(chan, ast_format_slin);
+	ast_channel_set_rawreadformat(chan, ast_format_slin);
+
+	ast_channel_tech_pvt_set(chan, instance);
+
+	pbx_builtin_setvar_helper(chan, "AUDIOSOCKET_UUID", args.idStr);
+	pbx_builtin_setvar_helper(chan, "AUDIOSOCKET_SERVICE", args.destination);
+
+	ast_channel_unlock(chan);
+
+	return chan;
+
+failure:
+	*cause = AST_CAUSE_FAILURE;
+	return NULL;
+}
+
+/*! \brief Function called when our module is unloaded */
+static int unload_module(void)
+{
+	ast_channel_unregister(&audiosocket_channel_tech);
+	ao2_cleanup(audiosocket_channel_tech.capabilities);
+	audiosocket_channel_tech.capabilities = NULL;
+
+	return 0;
+}
+
+/*! \brief Function called when our module is loaded */
+static int load_module(void)
+{
+	if (!(audiosocket_channel_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	ast_format_cap_append(audiosocket_channel_tech.capabilities, ast_format_slin, 0);
+
+	if (ast_channel_register(&audiosocket_channel_tech)) {
+		ast_log(LOG_ERROR, "Unable to register channel class AudioSocket");
+		ao2_ref(audiosocket_channel_tech.capabilities, -1);
+		audiosocket_channel_tech.capabilities = NULL;
+		return AST_MODULE_LOAD_DECLINE;
+	}
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "AudioSocket Channel",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DRIVER,
+	.requires = "res_audiosocket",
+);
diff --git a/include/asterisk/res_audiosocket.h b/include/asterisk/res_audiosocket.h
new file mode 100644
index 0000000..0590489
--- /dev/null
+++ b/include/asterisk/res_audiosocket.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019, CyCore Systems, Inc.
+ *
+ * Seán C McCord <scm at cycoresys.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief AudioSocket support functions
+ *
+ * \author Seán C McCord <scm at cycoresys.com>
+ *
+ */
+
+#ifndef _ASTERISK_RES_AUDIOSOCKET_H
+#define _ASTERISK_RES_AUDIOSOCKET_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+#include <uuid/uuid.h>
+
+#include "asterisk/frame.h"
+#include "asterisk/uuid.h"
+
+/*!
+ * \brief Send the initial message to an AudioSocket server
+ *
+ * \param server The server address, including port.
+ *
+ * \retval socket file descriptor for AudioSocket on success
+ * \retval -1 on error
+ */
+const int audiosocket_connect(const char *server);
+
+/*!
+ * \brief Send the initial message to an AudioSocket server
+ *
+ * \param svc The file descriptor of the network socket to the AudioSocket server.
+ * \param id The UUID to send to the AudioSocket server to uniquely identify this connection.
+ *
+ * \retval 0 on success
+ * \retval -1 on error
+ */
+const int audiosocket_init(const int svc, const char *id);
+
+/*!
+ * \brief Send an Asterisk audio frame to an AudioSocket server
+ *
+ * \param svc The file descriptor of the network socket to the AudioSocket server.
+ * \param f The Asterisk audio frame to send.
+ *
+ * \retval 0 on success
+ * \retval -1 on error
+ */
+const int audiosocket_send_frame(const int svc, const struct ast_frame *f);
+
+/*!
+ * \brief Receive an Asterisk frame from an AudioSocket server
+ *
+ * This returned object is an ao2 reference counted object.
+ *
+ * Any attribute in the returned \ref hepv3_capture_info that is a
+ * pointer should point to something that is allocated on the heap,
+ * as it will be free'd when the \ref hepv3_capture_info object is
+ * reclaimed.
+ *
+ * \param payload The payload to send to the HEP capture node
+ * \param len     Length of \ref payload
+ *
+ * \retval A \ref ast_frame on success
+ * \retval NULL on error
+ */
+struct ast_frame *audiosocket_receive_frame(const int svc);
+
+#endif /* _ASTERISK_RES_AUDIOSOCKET_H */
diff --git a/res/res_audiosocket.c b/res/res_audiosocket.c
new file mode 100644
index 0000000..c8f6e53
--- /dev/null
+++ b/res/res_audiosocket.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2019, CyCore Systems, Inc
+ *
+ * Seán C McCord <scm at cycoresys.com>
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief AudioSocket support for Asterisk
+ *
+ * \author Seán C McCord <scm at cycoresys.com>
+ *
+ */
+
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+#include "errno.h"
+#include <uuid/uuid.h>
+
+#include "asterisk/file.h"
+#include "asterisk/res_audiosocket.h"
+#include "asterisk/channel.h"
+#include "asterisk/module.h"
+#include "asterisk/uuid.h"
+#include "asterisk/format_cache.h"
+
+#define	MODULE_DESCRIPTION	"AudioSocket support functions for Asterisk"
+
+#define MAX_CONNECT_TIMEOUT_MSEC 2000
+
+static int handle_audiosocket_connection(const char *server, const struct ast_sockaddr addr, const int netsockfd);
+
+const int audiosocket_connect(const char *server) {
+   int s = -1;
+   struct ast_sockaddr *addrs;
+   int num_addrs = 0, i = 0;
+
+   if (ast_strlen_zero(server)) {
+      ast_log(LOG_ERROR, "no AudioSocket server provided");
+      return -1;
+   }
+   if (!(num_addrs = ast_sockaddr_resolve(&addrs, server, PARSE_PORT_REQUIRE, AST_AF_UNSPEC))) {
+      ast_log(LOG_ERROR, "failed to resolve AudioSocket service");
+      return -1;
+   }
+
+   /* Connect to AudioSocket service */
+   for (i = 0; i < num_addrs; i++) {
+		if (!ast_sockaddr_port(&addrs[i])) {
+         ast_log(LOG_WARNING, "no port provided");
+         continue;
+		}
+
+		if ((s = socket(addrs[i].ss.ss_family, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+			ast_log(LOG_WARNING, "unable to create socket: %s\n", strerror(errno));
+			continue;
+		}
+
+		if (ast_fd_set_flags(s, O_NONBLOCK)) {
+			close(s);
+			continue;
+		}
+
+      if (ast_connect(s, &addrs[i]) && errno == EINPROGRESS) {
+
+			if (handle_audiosocket_connection(server, addrs[i], s)) {
+				close(s);
+				continue;
+			}
+
+      } else {
+         ast_log(LOG_ERROR, "connection to %s failed with unexpected error: %s\n",
+               ast_sockaddr_stringify(&addrs[i]), strerror(errno));
+      }
+
+      break;
+   }
+   ast_free(addrs);
+
+   if (i == num_addrs) {
+      ast_log(LOG_ERROR, "failed to connect to AudioSocket service");
+      return -1;
+   }
+
+   return s;
+}
+
+/*!
+ * \internal
+ * \brief Handle the connection that was started by launch_netscript.
+ *
+ * \param server Url that we are trying to connect to.
+ * \param addr Address that host was resolved to.
+ * \param netsockfd File descriptor of socket.
+ *
+ * \retval 0 when connection is succesful.
+ * \retval 1 when there is an error.
+ */
+static int handle_audiosocket_connection(const char *server, const struct ast_sockaddr addr, const int netsockfd)
+{
+	struct pollfd pfds[1];
+	int res, conresult;
+	socklen_t reslen;
+
+	reslen = sizeof(conresult);
+
+	pfds[0].fd = netsockfd;
+	pfds[0].events = POLLOUT;
+
+	while ((res = ast_poll(pfds, 1, MAX_CONNECT_TIMEOUT_MSEC)) != 1) {
+		if (errno != EINTR) {
+			if (!res) {
+				ast_log(LOG_WARNING, "AudioSocket connection to '%s' timed out after MAX_CONNECT_TIMEOUT_MSEC (%d) milliseconds.\n",
+					server, MAX_CONNECT_TIMEOUT_MSEC);
+			} else {
+				ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", server, strerror(errno));
+			}
+
+			return -1;
+		}
+	}
+
+	if (getsockopt(pfds[0].fd, SOL_SOCKET, SO_ERROR, &conresult, &reslen) < 0) {
+		ast_log(LOG_WARNING, "Connection to %s failed with error: %s\n",
+			ast_sockaddr_stringify(&addr), strerror(errno));
+		return -1;
+	}
+
+	if (conresult) {
+		ast_log(LOG_WARNING, "Connecting to '%s' failed for url '%s': %s\n",
+			ast_sockaddr_stringify(&addr), server, strerror(conresult));
+		return -1;
+	}
+
+	return 0;
+}
+
+const int audiosocket_init(const int svc, const char *id) {
+   uuid_t uu;
+   //char idBuf[AST_UUID_STR_LEN+1];
+
+   if (ast_strlen_zero(id)) {
+      ast_log(LOG_ERROR, "No UUID for AudioSocket");
+      return -1;
+   }
+
+   if (uuid_parse(id, uu)) {
+      ast_log(LOG_ERROR, "Failed to parse UUID '%s'\n", id);
+      return -1;
+   }
+
+   usleep(100);
+   int ret = 0;
+   uint8_t *buf = ast_malloc(3+16);
+
+   buf[0] = 0x01;
+   buf[1] = 0x00;
+   buf[2] = 0x10;
+   memcpy(buf+3, uu, 16);
+
+   if (write(svc, buf, 3+16) != 3+16) {
+      ast_log(LOG_WARNING, "Failed to write data to audiosocket");
+      ret = -1;
+   }
+
+   ast_free(buf);
+   return ret;
+}
+
+const int audiosocket_send_frame(const int svc, const struct ast_frame *f) {
+
+   int ret = 0;
+   uint8_t kind = 0x10; // always 16-bit, 8kHz signed linear mono, for now
+   uint8_t *buf, *p;
+
+   buf = ast_malloc(3 + f->datalen);
+   p = buf;
+
+   *(p++) = kind;
+   *(p++) = f->datalen >> 8;
+   *(p++) = f->datalen & 0xff;
+   memcpy(p, f->data.ptr, f->datalen);
+
+   if(write(svc, buf, 3+f->datalen) != 3+f->datalen) {
+      ast_log(LOG_WARNING, "Failed to write data to audiosocket");
+      ret = -1;
+   }
+
+   ast_free(buf);
+   return ret;
+}
+
+struct ast_frame *audiosocket_receive_frame(const int svc) {
+
+   int i = 0, n = 0, ret = 0;;
+   int not_audio = 0;
+	static struct ast_frame f;
+
+   uint8_t kind;
+   uint8_t len_high;
+   uint8_t len_low;
+   uint16_t len = 0;
+   uint8_t* data;
+
+   n = read(svc, &kind, 1);
+   if (n < 0 && errno == EAGAIN) {
+      return &ast_null_frame;
+   }
+   if (n == 0) {
+      return &ast_null_frame;
+   }
+   if (n != 1) {
+      ast_log(LOG_WARNING, "Failed to read type header from audiosocket\n");
+      return NULL;
+   }
+   if (kind == 0x00) {
+      // AudioSocket ended by remote
+      return NULL;
+   }
+   if (kind != 0x10) {
+      // read but ignore non-audio message
+      ast_log(LOG_WARNING, "Received non-audio audiosocket message\n");
+      not_audio = 1;
+   }
+
+   n = read(svc, &len_high, 1);
+   if (n != 1) {
+      ast_log(LOG_WARNING, "Failed to read data length from audiosocket\n");
+      return NULL;
+   }
+   len += len_high * 256;
+   n = read(svc, &len_low, 1);
+   if (n != 1) {
+      ast_log(LOG_WARNING, "Failed to read data length from audiosocket\n");
+      return NULL;
+   }
+   len += len_low;
+
+   if (len < 1) {
+      return &ast_null_frame;
+   }
+
+   data = ast_malloc(len);
+   ret = 0;
+   n = 0;
+   i = 0;
+   while (i < len) {
+      n = read(svc, data+i, len-i);
+      if (n < 0) {
+         ast_log(LOG_ERROR, "Failed to read data from audiosocket\n");
+         ret = n;
+         break;
+      }
+      if (n == 0) {
+         ast_log(LOG_ERROR, "Insufficient data read from audiosocket\n");
+         ret = -1;
+         break;
+      }
+      i += n;
+   }
+
+   if (ret != 0) {
+      return NULL;
+   }
+
+   if(not_audio) {
+      return &ast_null_frame;
+   }
+
+	f.frametype = AST_FRAME_VOICE;
+	f.subclass.format = ast_format_slin;
+	f.src = "AudioSocket";
+	f.data.ptr = data;
+	f.datalen = len;
+	f.samples = len/2;
+   f.mallocd = AST_MALLOCD_DATA;
+
+   return &f;
+}
+
+static int load_module(void)
+{
+	ast_verb(1, "Loading AudioSocket Support module\n");
+   return AST_MODULE_LOAD_SUCCESS;
+}
+
+static int unload_module(void)
+{
+	ast_verb(1, "Unloading AudioSocket Support module\n");
+   return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "AudioSocket support",
+	.support_level = AST_MODULE_SUPPORT_CORE,
+	.load = load_module,
+	.unload = unload_module,
+	.load_pri = AST_MODPRI_CHANNEL_DEPEND,
+);
diff --git a/res/res_audiosocket.exports.in b/res/res_audiosocket.exports.in
new file mode 100644
index 0000000..dfe0e4a
--- /dev/null
+++ b/res/res_audiosocket.exports.in
@@ -0,0 +1,9 @@
+{
+	global:
+		LINKER_SYMBOL_PREFIXaudiosocket_connect;
+		LINKER_SYMBOL_PREFIXaudiosocket_init;
+		LINKER_SYMBOL_PREFIXaudiosocket_send_frame;
+		LINKER_SYMBOL_PREFIX*audiosocket_receive_frame;
+	local:
+		*;
+};

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/11579
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Change-Id: Ie866e6c4fa13178ec76f2a6971ad3590a3a588b5
Gerrit-Change-Number: 11579
Gerrit-PatchSet: 1
Gerrit-Owner: Seán C. McCord <ulexus at gmail.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20190717/db75975a/attachment-0001.html>


More information about the asterisk-code-review mailing list