Repository: asterisk/asterisk
Pull request #1138: audiosocket improvements
Author: login: florentchauveau
Labels: name: cherry-pick-checks-failed
description: Cherry-Pick checks failed
name: pre-merge-checks-passed
name: pr-submit-tests-passed
Created: 2025-03-01T16:02:57Z | Merged: 2025-03-28T19:18:15Z

- **audiosocket: fix timeout, fix dialplan app exit, server address in logs**
- **asterisk/channel.h: fix documentation for 'ast_waitfor_nandfds()'**
- **audiosocket: added support for DTMF frames**



Diff:
diff --git a/apps/app_audiosocket.c b/apps/app_audiosocket.c
index fc5983f3ff9..0580c137099 100644
--- a/apps/app_audiosocket.c
+++ b/apps/app_audiosocket.c
@@ -38,13 +38,14 @@
 #include "asterisk/module.h"
 #include "asterisk/channel.h"
 #include "asterisk/app.h"
+#include "asterisk/pbx.h"
 #include "asterisk/res_audiosocket.h"
 #include "asterisk/utils.h"
 #include "asterisk/format_cache.h"
 
 #define AST_MODULE "app_audiosocket"
 #define AUDIOSOCKET_CONFIG "audiosocket.conf"
-#define MAX_CONNECT_TIMEOUT_MSEC 2000
+#define MAX_WAIT_TIMEOUT_MSEC 2000
 
 /*** DOCUMENTATION
 	<application name="AudioSocket" language="en_US">
@@ -52,18 +53,18 @@
 			<version>18.0.0</version>
 		</since>
 		<synopsis>
-			Transmit and receive audio between channel and TCP socket
+			Transmit and receive PCM audio between a channel and a TCP socket server.
 		</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>
 			<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>
+				<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. IPv6 addresses can be specified in square brackets, like [::1]: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>Connects to the given TCP server, then transmits channel audio as 16-bit, 8KHz mono PCM over that socket (other codecs available via the channel driver interface). In turn, PCM audio is received from the socket and sent to the channel.  Only audio frames and DTMF frames will be transmitted.</para>
 			<para>Protocol is specified at https://docs.asterisk.org/Configuration/Channel-Drivers/AudioSocket/</para>
 			<para>This application does not automatically answer and should generally be preceeded by an application such as Answer() or Progress().</para>
 		</description>
@@ -72,7 +73,7 @@
 
 static const char app[] = "AudioSocket";
 
-static int audiosocket_run(struct ast_channel *chan, const char *id, const int svc);
+static int audiosocket_run(struct ast_channel *chan, const char *id, const int svc, const char *server);
 
 static int audiosocket_exec(struct ast_channel *chan, const char *data)
 {
@@ -108,67 +109,53 @@ static int audiosocket_exec(struct ast_channel *chan, const char *data)
 		return -1;
 	}
 
+	/* Save current channel audio format and force to linear PCM. */
+
 	writeFormat = ao2_bump(ast_channel_writeformat(chan));
 	readFormat = ao2_bump(ast_channel_readformat(chan));
 
 	if (ast_set_write_format(chan, ast_format_slin)) {
-		ast_log(LOG_ERROR, "Failed to set write format to SLINEAR for channel %s\n", chanName);
-		ao2_ref(writeFormat, -1);
-		ao2_ref(readFormat, -1);
-		close(s);
-		return -1;
+		ast_log(LOG_ERROR, "Failed to set write format to SLINEAR for channel '%s'\n", chanName);
+		res = -1;
+		goto end;
 	}
-	if (ast_set_read_format(chan, ast_format_slin)) {
-		ast_log(LOG_ERROR, "Failed to set read format to SLINEAR for channel %s\n", chanName);
 
-		/* Attempt to restore previous write format even though it is likely to
-		 * fail, since setting the read format did.
-		 */
-		if (ast_set_write_format(chan, writeFormat)) {
-			ast_log(LOG_ERROR, "Failed to restore write format for channel %s\n", chanName);
-		}
-		ao2_ref(writeFormat, -1);
-		ao2_ref(readFormat, -1);
-		close(s);
-		return -1;
+	if (ast_set_read_format(chan, ast_format_slin)) {
+		ast_log(LOG_ERROR, "Failed to set read format to SLINEAR for channel '%s'\n", chanName);
+		res = -1;
+		goto end;
 	}
 
-	res = audiosocket_run(chan, args.idStr, s);
-	/* On non-zero return, report failure */
-	if (res) {
-		/* Restore previous formats and close the connection */
-		if (ast_set_write_format(chan, writeFormat)) {
-			ast_log(LOG_ERROR, "Failed to restore write format for channel %s\n", chanName);
-		}
-		if (ast_set_read_format(chan, readFormat)) {
-			ast_log(LOG_ERROR, "Failed to restore read format for channel %s\n", chanName);
-		}
-		ao2_ref(writeFormat, -1);
-		ao2_ref(readFormat, -1);
-		close(s);
-		return res;
-	}
-	close(s);
+	/* Only a requested hangup or socket closure from the remote end will
+	   return a 0 value (normal exit). All other events that disrupt an
+	   active connection are treated as errors for now (non-zero). */
+
+	res = audiosocket_run(chan, args.idStr, s, args.server);
+
+end:
 
+	/* Restore previous formats and close the connection */
 	if (ast_set_write_format(chan, writeFormat)) {
-		ast_log(LOG_ERROR, "Failed to restore write format for channel %s\n", chanName);
+		ast_log(LOG_ERROR, "Failed to restore write format for channel '%s'\n", chanName);
 	}
 	if (ast_set_read_format(chan, readFormat)) {
-		ast_log(LOG_ERROR, "Failed to restore read format for channel %s\n", chanName);
+		ast_log(LOG_ERROR, "Failed to restore read format for channel '%s'\n", chanName);
 	}
 	ao2_ref(writeFormat, -1);
 	ao2_ref(readFormat, -1);
+	close(s);
 
-	return 0;
+	return res;
 }
 
-static int audiosocket_run(struct ast_channel *chan, const char *id, int svc)
+static int audiosocket_run(struct ast_channel *chan, const char *id, int svc, const char *server)
 {
 	const char *chanName;
 	struct ast_channel *targetChan;
-	int ms = 0;
+	int ms = MAX_WAIT_TIMEOUT_MSEC;
 	int outfd = -1;
 	struct ast_frame *f;
+	int hangup;
 
 	if (!chan || ast_channel_state(chan) != AST_STATE_UP) {
 		ast_log(LOG_ERROR, "Channel is %s\n", chan ? "not answered" : "missing");
@@ -183,39 +170,56 @@ static int audiosocket_run(struct ast_channel *chan, const char *id, int svc)
 	chanName = ast_channel_name(chan);
 
 	while (1) {
-		ms = -1;
+		/* Timeout is hard-coded currently, could be made into an
+		   argument if needed, but 2 seconds seems like a realistic
+		   time range to give. */
 		targetChan = ast_waitfor_nandfds(&chan, 1, &svc, 1, NULL, &outfd, &ms);
+		ms = MAX_WAIT_TIMEOUT_MSEC;
+
 		if (targetChan) {
+			/* Receive frame from connected channel. */
 			f = ast_read(chan);
 			if (!f) {
+				ast_log(LOG_WARNING, "Failed to receive frame from channel '%s' connected to AudioSocket server '%s'", chanName, server);
 				return -1;
 			}
 
-			if (f->frametype == AST_FRAME_VOICE) {
-				/* Send audio frame to audiosocket */
+			if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_DTMF) {
+				/* Send audio frame or DTMF frame to audiosocket */
 				if (ast_audiosocket_send_frame(svc, f)) {
-					ast_log(LOG_ERROR, "Failed to forward channel frame from %s to AudioSocket\n",
-						chanName);
+					ast_log(LOG_WARNING, "Failed to forward frame from channel '%s' to AudioSocket server '%s'\n",
+						chanName, server);
 					ast_frfree(f);
 					return -1;
 				}
 			}
 			ast_frfree(f);
-		}
 
-		if (outfd >= 0) {
-			f = ast_audiosocket_receive_frame(svc);
+		} else if (outfd >= 0) {
+			/* Receive audio frame from audiosocket. */
+			f = ast_audiosocket_receive_frame_with_hangup(svc, &hangup);
+			if (hangup) {
+				/* Graceful termination, no frame to free. */
+				return 0;
+			}
 			if (!f) {
-				ast_log(LOG_ERROR, "Failed to receive frame from AudioSocket message for"
-					"channel %s\n", chanName);
+				ast_log(LOG_WARNING, "Failed to receive frame from AudioSocket server '%s'"
+					" connected to channel '%s'\n", server, chanName);
 				return -1;
 			}
+			/* Send audio frame to connected channel. */
 			if (ast_write(chan, f)) {
-				ast_log(LOG_WARNING, "Failed to forward frame to channel %s\n", chanName);
+				ast_log(LOG_WARNING, "Failed to forward frame from AudioSocket server '%s' to channel '%s'\n", server, chanName);
 				ast_frfree(f);
 				return -1;
 			}
 			ast_frfree(f);
+
+		} else {
+			/* Neither the channel nor audio socket had activity
+			   before timeout. Assume connection was lost. */
+			ast_log(LOG_ERROR, "Reached timeout after %d ms of no activity on AudioSocket connection between '%s' and '%s'\n", MAX_WAIT_TIMEOUT_MSEC, chanName, server);
+			return -1;
 		}
 	}
 	return 0;
diff --git a/channels/chan_audiosocket.c b/channels/chan_audiosocket.c
index 1c9c0fb656f..e69cc773322 100644
--- a/channels/chan_audiosocket.c
+++ b/channels/chan_audiosocket.c
@@ -30,6 +30,7 @@
 	<support_level>extended</support_level>
  ***/
 
+
 #include "asterisk.h"
 #include <uuid/uuid.h>
 
@@ -45,8 +46,9 @@
 #define FD_OUTPUT 1	/* A fd of -1 means an error, 0 is stdin */
 
 struct audiosocket_instance {
-	int svc;	/* The file descriptor for the AudioSocket instance */
-	char id[38];	/* The UUID identifying this AudioSocket instance */
+	int svc;	  /* The file descriptor for the AudioSocket instance */
+	char server[50];  /* The IP/port of the server the instance is connected to */
+	char id[38];	  /* The UUID identifying this AudioSocket instance */
 } audiosocket_instance;
 
 /* Forward declarations */
@@ -57,6 +59,7 @@ static int audiosocket_call(struct ast_channel *ast, const char *dest, int timeo
 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);
+static int audiosocket_send_dtmf(struct ast_channel *ast, char digit, unsigned int duration);
 
 /* AudioSocket channel driver declaration */
 static struct ast_channel_tech audiosocket_channel_tech = {
@@ -67,19 +70,29 @@ static struct ast_channel_tech audiosocket_channel_tech = {
 	.hangup = audiosocket_hangup,
 	.read = audiosocket_read,
 	.write = audiosocket_write,
+	.send_digit_end = audiosocket_send_dtmf,
 };
 
 /*! \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;
+	struct ast_frame *frame;
+	int hangup;
+
 
 	/* The channel should always be present from the API */
 	instance = ast_channel_tech_pvt(ast);
 	if (instance == NULL || instance->svc < FD_OUTPUT) {
 		return NULL;
 	}
-	return ast_audiosocket_receive_frame(instance->svc);
+
+	frame = ast_audiosocket_receive_frame_with_hangup(instance->svc, &hangup);
+	if (!frame && !hangup) {
+		ast_log(LOG_ERROR, "Failed to receive frame from AudioSocket server '%s'"
+			" connected to channel '%s'\n", instance->server, ast_channel_name(ast));
+	}
+	return frame;
 }
 
 /*! \brief Function called when we should write a frame to the channel */
@@ -92,7 +105,37 @@ static int audiosocket_write(struct ast_channel *ast, struct ast_frame *f)
 	if (instance == NULL || instance->svc < 1) {
 		return -1;
 	}
-	return ast_audiosocket_send_frame(instance->svc, f);
+
+	if (ast_audiosocket_send_frame(instance->svc, f)) {
+		ast_log(LOG_ERROR, "Failed to forward frame from channel '%s' to AudioSocket server '%s'\n",
+				ast_channel_name(ast), instance->server);
+		return -1;
+	}
+	return 0;
+}
+
+/*! \brief Function called when we should write a DTMF frame to the channel */
+static int audiosocket_send_dtmf(struct ast_channel *ast, char digit, unsigned int duration)
+{
+	struct audiosocket_instance *instance;
+	struct ast_frame f;
+
+	/* The channel should always be present from the API */
+	instance = ast_channel_tech_pvt(ast);
+	if (instance == NULL || instance->svc < 1) {
+		return -1;
+	}
+
+	f.frametype = AST_FRAME_DTMF;
+	f.subclass.integer = digit;
+	f.len = duration;
+
+	if (ast_audiosocket_send_frame(instance->svc, &f)) {
+		ast_log(LOG_ERROR, "Failed to forward DTMF frame from channel '%s' to AudioSocket server '%s'\n",
+				ast_channel_name(ast), instance->server);
+		return -1;
+	}
+	return 0;
 }
 
 /*! \brief Function called when we should actually call the destination */
@@ -222,6 +265,8 @@ static struct ast_channel *audiosocket_request(const char *type,
 	if ((fd = ast_audiosocket_connect(args.destination, NULL)) < 0) {
 		goto failure;
 	}
+	/* Make a copy of the server address for display in error messages. */
+	ast_copy_string(instance->server, args.destination, sizeof(instance->server));
 	instance->svc = fd;
 
 	chan = ast_channel_alloc(1, AST_STATE_DOWN, "", "", "", "", "", assignedids,
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 46dc1a63889..f0658220913 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -2035,10 +2035,12 @@ int ast_safe_sleep_conditional(struct ast_channel *chan, int ms, int (*cond)(voi
  * \param nfds the number of fds to wait upon
  * \param exception exception flag
  * \param outfd fd that had activity on it
- * \param ms how long the wait was
+ * \param ms On invocation, max wait time. Upon returning, how long the wait
+ * actually was (in/out parameter).
  * \details
  * Big momma function here.  Wait for activity on any of the n channels, or any of the nfds
- * file descriptors.
+ * file descriptors. -1 can be passed as the ms timeout to wait forever, 0 to
+ * return instantly if theres no activity immediantely available.
  * \return Returns the channel with activity, or NULL on error or if an FD
  * came first.  If the FD came first, it will be returned in outfd, otherwise, outfd
  * will be -1
diff --git a/include/asterisk/res_audiosocket.h b/include/asterisk/res_audiosocket.h
index 0357bcdced4..d26d7b939e8 100644
--- a/include/asterisk/res_audiosocket.h
+++ b/include/asterisk/res_audiosocket.h
@@ -36,6 +36,25 @@ extern "C" {
 #include "asterisk/frame.h"
 #include "asterisk/uuid.h"
 
+
+enum ast_audiosocket_msg_kind {
+	/*! \brief Message indicates the channel should be hung up, direction: Sent only. */
+	AST_AUDIOSOCKET_KIND_HANGUP = 0x00,
+
+	/*! \brief Message contains the connection's UUID, direction: Received only. */
+	AST_AUDIOSOCKET_KIND_UUID   = 0x01,
+
+	/*! \brief Message contains a DTMF digit, direction: Received only. */
+	AST_AUDIOSOCKET_KIND_DTMF   = 0x03,
+
+	/*! \brief Messages contains audio data, direction: Sent and received. */
+	AST_AUDIOSOCKET_KIND_AUDIO  = 0x10,
+
+	/*! \brief An Asterisk-side error occurred, direction: Received only. */
+	AST_AUDIOSOCKET_KIND_ERROR  = 0xFF,
+};
+
+
 /*!
  * \brief Send the initial message to an AudioSocket server
  *
@@ -84,4 +103,22 @@ const int ast_audiosocket_send_frame(const int svc, const struct ast_frame *f);
  */
 struct ast_frame *ast_audiosocket_receive_frame(const int svc);
 
+/*!
+ * \brief Receive an Asterisk frame from an AudioSocket server
+ *
+ * This returned object is a pointer to an Asterisk frame which must be
+ * manually freed by the caller.
+ *
+ * \param svc The file descriptor of the network socket to the AudioSocket
+ * server.
+ * \param hangup Will be true if the AudioSocket server requested the channel
+ * be hung up, otherwise false. Used as an out-parameter only, pass NULL if
+ * not needed. The function return value will always be NULL when true.
+ *
+ * \retval A \ref ast_frame on success
+ * \retval NULL on error or when the hungup parameter is true.
+ */
+struct ast_frame *ast_audiosocket_receive_frame_with_hangup(const int svc,
+	int *const hangup);
+
 #endif /* _ASTERISK_RES_AUDIOSOCKET_H */
diff --git a/res/res_audiosocket.c b/res/res_audiosocket.c
index 9863f07b2a6..3c4682df7f0 100644
--- a/res/res_audiosocket.c
+++ b/res/res_audiosocket.c
@@ -31,6 +31,7 @@
 #include "asterisk.h"
 #include "errno.h"
 #include <uuid/uuid.h>
+#include <arpa/inet.h>  /* For byte-order conversion. */
 
 #include "asterisk/file.h"
 #include "asterisk/res_audiosocket.h"
@@ -82,7 +83,7 @@ static int handle_audiosocket_connection(const char *server,
 	}
 
 	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_log(LOG_WARNING, "Connection to '%s' failed with error: %s\n",
 			ast_sockaddr_stringify(&addr), strerror(errno));
 		return -1;
 	}
@@ -104,7 +105,7 @@ const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
 
 	if (chan && ast_autoservice_start(chan) < 0) {
 		ast_log(LOG_WARNING, "Failed to start autoservice for channel "
-			"%s\n", ast_channel_name(chan));
+			"'%s'\n", ast_channel_name(chan));
 		goto end;
 	}
 
@@ -115,7 +116,7 @@ const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
 
 	if (!(num_addrs = ast_sockaddr_resolve(&addrs, server, PARSE_PORT_REQUIRE,
 		AST_AF_UNSPEC))) {
-		ast_log(LOG_ERROR, "Failed to resolve AudioSocket service using %s - "
+		ast_log(LOG_ERROR, "Failed to resolve AudioSocket service using '%s' - "
 			"requires a valid hostname and port\n", server);
 		goto end;
 	}
@@ -127,7 +128,7 @@ const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
 			/* If there's no port, other addresses should have the
 			 * same problem. Stop here.
 			 */
-			ast_log(LOG_ERROR, "No port provided for %s\n",
+			ast_log(LOG_ERROR, "No port provided for '%s'\n",
 				ast_sockaddr_stringify(&addrs[i]));
 			s = -1;
 			goto end;
@@ -135,7 +136,7 @@ const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
 
 		if ((s = ast_socket_nonblock(addrs[i].ss.ss_family, SOCK_STREAM,
 			IPPROTO_TCP)) < 0) {
-			ast_log(LOG_WARNING, "Unable to create socket: %s\n", strerror(errno));
+			ast_log(LOG_WARNING, "Unable to create socket: '%s'\n", strerror(errno));
 			continue;
 		}
 
@@ -147,7 +148,7 @@ const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
 			}
 
 		} else {
-			ast_log(LOG_ERROR, "Connection to %s failed with unexpected error: %s\n",
+			ast_log(LOG_ERROR, "Connection to '%s' failed with unexpected error: %s\n",
 				ast_sockaddr_stringify(&addrs[i]), strerror(errno));
 			close(s);
 			s = -1;
@@ -162,7 +163,7 @@ const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
 	}
 
 	if (chan && ast_autoservice_stop(chan) < 0) {
-		ast_log(LOG_WARNING, "Failed to stop autoservice for channel %s\n",
+		ast_log(LOG_WARNING, "Failed to stop autoservice for channel '%s'\n",
 		ast_channel_name(chan));
 		close(s);
 		return -1;
@@ -193,13 +194,13 @@ const int ast_audiosocket_init(const int svc, const char *id)
 		return -1;
 	}
 
-	buf[0] = 0x01;
+	buf[0] = AST_AUDIOSOCKET_KIND_UUID;
 	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\n");
+		ast_log(LOG_WARNING, "Failed to write data to AudioSocket because: %s\n", strerror(errno));
 		ret = -1;
 	}
 
@@ -208,81 +209,93 @@ const int ast_audiosocket_init(const int svc, const char *id)
 
 const int ast_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 *p;
-	uint8_t buf[3 + f->datalen];
-
-	p = buf;
+	int datalen = f->datalen;
+	if (f->frametype == AST_FRAME_DTMF) {
+		datalen = 1;
+	}
 
-	*(p++) = kind;
-	*(p++) = f->datalen >> 8;
-	*(p++) = f->datalen & 0xff;
-	memcpy(p, f->data.ptr, f->datalen);
+	{
+		uint8_t buf[3 + datalen];
+		uint16_t *length = (uint16_t *) &buf[1];
+
+		/* Audio format is 16-bit, 8kHz signed linear mono for dialplan app,
+			depends on agreed upon audio codec for channel driver interface. */
+		switch (f->frametype) {
+			case AST_FRAME_VOICE:
+				buf[0] = AST_AUDIOSOCKET_KIND_AUDIO;
+				*length = htons(datalen);
+				memcpy(&buf[3], f->data.ptr, datalen);
+				break;
+			case AST_FRAME_DTMF:
+				buf[0] = AST_AUDIOSOCKET_KIND_DTMF;
+				buf[3] = (uint8_t) f->subclass.integer;
+				*length = htons(1);
+				break;
+			default:
+				ast_log(LOG_ERROR, "Unsupported frame type %d for AudioSocket\n", f->frametype);
+				return -1;
+		}
 
-	if (write(svc, buf, 3 + f->datalen) != 3 + f->datalen) {
-		ast_log(LOG_WARNING, "Failed to write data to AudioSocket\n");
-		ret = -1;
+		if (write(svc, buf, 3 + datalen) != 3 + datalen) {
+			ast_log(LOG_WARNING, "Failed to write data to AudioSocket because: %s\n", strerror(errno));
+			return -1;
+		}
 	}
 
-	return ret;
+	return 0;
 }
 
 struct ast_frame *ast_audiosocket_receive_frame(const int svc)
 {
+	return ast_audiosocket_receive_frame_with_hangup(svc, NULL);
+}
 
-	int i = 0, n = 0, ret = 0, not_audio = 0;
+struct ast_frame *ast_audiosocket_receive_frame_with_hangup(const int svc,
+	int *const hangup)
+{
+	int i = 0, n = 0, ret = 0;
 	struct ast_frame f = {
 		.frametype = AST_FRAME_VOICE,
 		.subclass.format = ast_format_slin,
 		.src = "AudioSocket",
 		.mallocd = AST_MALLOCD_DATA,
 	};
-	uint8_t kind;
-	uint8_t len_high;
-	uint8_t len_low;
-	uint16_t len = 0;
+	uint8_t header[3];
+	uint8_t *kind = &header[0];
+	uint16_t *length = (uint16_t *) &header[1];
 	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 (hangup) {
+		*hangup = 0;
 	}
-	if (n != 1) {
-		ast_log(LOG_WARNING, "Failed to read type header from AudioSocket\n");
-		return NULL;
-	}
-	if (kind == 0x00) {
-		/* AudioSocket ended by remote */
+
+	n = read(svc, &header, 3);
+	if (n == -1) {
+		ast_log(LOG_WARNING, "Failed to read header from AudioSocket because: %s\n", strerror(errno));
 		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");
+	if (n == 0 || *kind == AST_AUDIOSOCKET_KIND_HANGUP) {
+		/* Socket closure or requested hangup. */
+		if (hangup) {
+			*hangup = 1;
+		}
 		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");
+
+	if (*kind != AST_AUDIOSOCKET_KIND_AUDIO) {
+		ast_log(LOG_ERROR, "Received AudioSocket message other than hangup or audio, refer to protocol specification for valid message types\n");
 		return NULL;
 	}
-	len += len_low;
 
-	if (len < 1) {
-		return &ast_null_frame;
+	/* Swap endianess of length if needed. */
+	*length = ntohs(*length);
+	if (*length < 1) {
+		ast_log(LOG_ERROR, "Invalid message length received from AudioSocket server. \n");
+		return NULL;
 	}
 
-	data = ast_malloc(len);
+	data = ast_malloc(*length);
 	if (!data) {
 		ast_log(LOG_ERROR, "Failed to allocate for data from AudioSocket\n");
 		return NULL;
@@ -291,15 +304,15 @@ struct ast_frame *ast_audiosocket_receive_frame(const int svc)
 	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;
+	while (i < *length) {
+		n = read(svc, data + i, *length - i);
+		if (n == -1) {
+			ast_log(LOG_ERROR, "Failed to read payload from AudioSocket: %s\n", strerror(errno));
+			ret = -1;
 			break;
 		}
 		if (n == 0) {
-			ast_log(LOG_ERROR, "Insufficient data read from AudioSocket\n");
+			ast_log(LOG_ERROR, "Insufficient payload read from AudioSocket\n");
 			ret = -1;
 			break;
 		}
@@ -311,14 +324,9 @@ struct ast_frame *ast_audiosocket_receive_frame(const int svc)
 		return NULL;
 	}
 
-	if (not_audio) {
-		ast_free(data);
-		return &ast_null_frame;
-	}
-
 	f.data.ptr = data;
-	f.datalen = len;
-	f.samples = len / 2;
+	f.datalen = *length;
+	f.samples = *length / 2;
 
 	/* The frame steals data, so it doesn't need to be freed here */
 	return ast_frisolate(&f);


Reviews:
user: login: jcolp
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-19T09:07:32Z
user: login: florentchauveau
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:14:33Z
user: login: florentchauveau
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:17:43Z
user: login: jcolp
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T10:20:50Z
user: login: florentchauveau
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T12:05:02Z
user: login: jcolp
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-20T08:53:20Z
user: login: jcolp
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-28T08:45:27Z

Review comments:
diff_hunk: @@ -67,32 +70,78 @@ static struct ast_channel_tech audiosocket_channel_tech = {
 	.hangup = audiosocket_hangup,
 	.read = audiosocket_read,
 	.write = audiosocket_write,
+	.send_digit_end = audiosocket_send_dtmf,
 };
 
 /*! \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;
+	struct ast_frame *frame;
+	struct ast_channel *bridgedChan;
+	int hangup;
+
 
 	/* The channel should always be present from the API */
 	instance = ast_channel_tech_pvt(ast);
 	if (instance == NULL || instance->svc < FD_OUTPUT) {
 		return NULL;
 	}
-	return ast_audiosocket_receive_frame(instance->svc);
+
+	frame = ast_audiosocket_receive_frame(instance->svc, &hangup);
+	if (!frame && !hangup) {
+		bridgedChan = ast_channel_bridge_peer(ast);
path: channels/chan_audiosocket.c
user: login: jcolp
body: This leaks the bridge peer channel, this is also not commonly how the logging works, instead it just uses the channel name and not the bridged name, which is actually what your log message is doing
created_at: 2025-03-19T08:59:36Z
author_association: MEMBER
diff_hunk: @@ -38,41 +38,42 @@
 #include "asterisk/module.h"
 #include "asterisk/channel.h"
 #include "asterisk/app.h"
+#include "asterisk/pbx.h"
 #include "asterisk/res_audiosocket.h"
 #include "asterisk/utils.h"
 #include "asterisk/format_cache.h"
 
 #define AST_MODULE "app_audiosocket"
 #define AUDIOSOCKET_CONFIG "audiosocket.conf"
-#define MAX_CONNECT_TIMEOUT_MSEC 2000
+#define MAX_WAIT_TIMEOUT_MSEC 2000
 
 /*** DOCUMENTATION
 	<application name="AudioSocket" language="en_US">
 		<since>
 			<version>18.0.0</version>
 		</since>
 		<synopsis>
-			Transmit and receive audio between channel and TCP socket
+			Transmit and receive PCM audio between a channel and a TCP socket server.
 		</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>
 			<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>
+				<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. IPv6 addresses can be specified in square brackets, like [::1]: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://docs.asterisk.org/Configuration/Channel-Drivers/AudioSocket/</para>
+			<para>Connects to the given TCP server, then transmits channel audio as 16-bit, 8KHz mono PCM over that socket (other codecs available via the channel driver interface). In turn, PCM audio is received from the socket and sent to the channel.  Only audio frames and DTMF frames will be transmitted.</para>
+			<para>Protocol is specified at https://wiki.asterisk.org/wiki/display/AST/AudioSocket</para>
path: apps/app_audiosocket.c
user: login: jcolp
body: You've undone a prior change to update this link
created_at: 2025-03-19T08:59:56Z
author_association: MEMBER
diff_hunk: @@ -108,67 +109,56 @@ static int audiosocket_exec(struct ast_channel *chan, const char *data)
 		return -1;
 	}
 
+	/* Save current channel audio format and force to linear PCM. */
+
 	writeFormat = ao2_bump(ast_channel_writeformat(chan));
 	readFormat = ao2_bump(ast_channel_readformat(chan));
 
 	if (ast_set_write_format(chan, ast_format_slin)) {
-		ast_log(LOG_ERROR, "Failed to set write format to SLINEAR for channel %s\n", chanName);
-		ao2_ref(writeFormat, -1);
-		ao2_ref(readFormat, -1);
-		close(s);
-		return -1;
+		ast_log(LOG_ERROR, "Failed to set write format to SLINEAR for channel '%s'\n", chanName);
+		res = -1;
+		goto end;
 	}
-	if (ast_set_read_format(chan, ast_format_slin)) {
-		ast_log(LOG_ERROR, "Failed to set read format to SLINEAR for channel %s\n", chanName);
 
-		/* Attempt to restore previous write format even though it is likely to
-		 * fail, since setting the read format did.
-		 */
-		if (ast_set_write_format(chan, writeFormat)) {
-			ast_log(LOG_ERROR, "Failed to restore write format for channel %s\n", chanName);
-		}
-		ao2_ref(writeFormat, -1);
-		ao2_ref(readFormat, -1);
-		close(s);
-		return -1;
+	if (ast_set_read_format(chan, ast_format_slin)) {
+		ast_log(LOG_ERROR, "Failed to set read format to SLINEAR for channel '%s'\n", chanName);
+		res = -1;
+		goto end;
 	}
 
-	res = audiosocket_run(chan, args.idStr, s);
-	/* On non-zero return, report failure */
-	if (res) {
-		/* Restore previous formats and close the connection */
-		if (ast_set_write_format(chan, writeFormat)) {
-			ast_log(LOG_ERROR, "Failed to restore write format for channel %s\n", chanName);
-		}
-		if (ast_set_read_format(chan, readFormat)) {
-			ast_log(LOG_ERROR, "Failed to restore read format for channel %s\n", chanName);
-		}
-		ao2_ref(writeFormat, -1);
-		ao2_ref(readFormat, -1);
-		close(s);
-		return res;
-	}
-	close(s);
+	pbx_builtin_setvar_helper(chan, "AUDIOSOCKET_UUID", args.idStr);
path: apps/app_audiosocket.c
user: login: jcolp
body: These have just gotten added but I don't see their existence documented anywhere, what are they for?
created_at: 2025-03-19T09:01:16Z
author_association: MEMBER
diff_hunk: @@ -183,39 +173,56 @@ static int audiosocket_run(struct ast_channel *chan, const char *id, int svc)
 	chanName = ast_channel_name(chan);
 
 	while (1) {
-		ms = -1;
+		/* Timeout is hard-coded currently, could be made into an
+		   argument if needed, but 2 seconds seems like a realistic
+		   time range to give. */
 		targetChan = ast_waitfor_nandfds(&chan, 1, &svc, 1, NULL, &outfd, &ms);
+		ms = MAX_WAIT_TIMEOUT_MSEC;
+
 		if (targetChan) {
+			/* Receive audio frame from connected channel. */
 			f = ast_read(chan);
 			if (!f) {
+				ast_log(LOG_WARNING, "Failed to receive frame from channel '%s' connected to AudioSocket server '%s'", chanName, server);
 				return -1;
 			}
 
-			if (f->frametype == AST_FRAME_VOICE) {
+			if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_DTMF) {
 				/* Send audio frame to audiosocket */
path: apps/app_audiosocket.c
user: login: jcolp
body: This is no longer accurate, and now this is supporting DTMF but not documented elsewhere as such
created_at: 2025-03-19T09:02:04Z
author_association: MEMBER
diff_hunk: @@ -183,39 +173,56 @@ static int audiosocket_run(struct ast_channel *chan, const char *id, int svc)
 	chanName = ast_channel_name(chan);
 
 	while (1) {
-		ms = -1;
+		/* Timeout is hard-coded currently, could be made into an
+		   argument if needed, but 2 seconds seems like a realistic
+		   time range to give. */
 		targetChan = ast_waitfor_nandfds(&chan, 1, &svc, 1, NULL, &outfd, &ms);
+		ms = MAX_WAIT_TIMEOUT_MSEC;
+
 		if (targetChan) {
+			/* Receive audio frame from connected channel. */
path: apps/app_audiosocket.c
user: login: jcolp
body: This receives all frames from connected channel, not just audio
created_at: 2025-03-19T09:02:36Z
author_association: MEMBER
diff_hunk: @@ -67,32 +70,78 @@ static struct ast_channel_tech audiosocket_channel_tech = {
 	.hangup = audiosocket_hangup,
 	.read = audiosocket_read,
 	.write = audiosocket_write,
+	.send_digit_end = audiosocket_send_dtmf,
 };
 
 /*! \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;
+	struct ast_frame *frame;
+	struct ast_channel *bridgedChan;
+	int hangup;
+
 
 	/* The channel should always be present from the API */
 	instance = ast_channel_tech_pvt(ast);
 	if (instance == NULL || instance->svc < FD_OUTPUT) {
 		return NULL;
 	}
-	return ast_audiosocket_receive_frame(instance->svc);
+
+	frame = ast_audiosocket_receive_frame(instance->svc, &hangup);
+	if (!frame && !hangup) {
+		bridgedChan = ast_channel_bridge_peer(ast);
+		ast_log(LOG_ERROR, "Failed to receive frame from AudioSocket server '%s'"
+			" connected to channel '%s'\n", instance->server, bridgedChan ? ast_channel_name(ast) : "Unknown");
+	}
+	return frame;
 }
 
 /*! \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;
+	struct ast_channel *bridgedChan;
 
 	/* The channel should always be present from the API */
 	instance = ast_channel_tech_pvt(ast);
 	if (instance == NULL || instance->svc < 1) {
 		return -1;
 	}
-	return ast_audiosocket_send_frame(instance->svc, f);
+
+	if (ast_audiosocket_send_frame(instance->svc, f)) {
+		bridgedChan = ast_channel_bridge_peer(ast);
path: channels/chan_audiosocket.c
user: login: jcolp
body: This leaks the bridged channel
created_at: 2025-03-19T09:03:20Z
author_association: MEMBER
diff_hunk: @@ -67,32 +70,78 @@ static struct ast_channel_tech audiosocket_channel_tech = {
 	.hangup = audiosocket_hangup,
 	.read = audiosocket_read,
 	.write = audiosocket_write,
+	.send_digit_end = audiosocket_send_dtmf,
 };
 
 /*! \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;
+	struct ast_frame *frame;
+	struct ast_channel *bridgedChan;
+	int hangup;
+
 
 	/* The channel should always be present from the API */
 	instance = ast_channel_tech_pvt(ast);
 	if (instance == NULL || instance->svc < FD_OUTPUT) {
 		return NULL;
 	}
-	return ast_audiosocket_receive_frame(instance->svc);
+
+	frame = ast_audiosocket_receive_frame(instance->svc, &hangup);
+	if (!frame && !hangup) {
+		bridgedChan = ast_channel_bridge_peer(ast);
+		ast_log(LOG_ERROR, "Failed to receive frame from AudioSocket server '%s'"
+			" connected to channel '%s'\n", instance->server, bridgedChan ? ast_channel_name(ast) : "Unknown");
+	}
+	return frame;
 }
 
 /*! \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;
+	struct ast_channel *bridgedChan;
 
 	/* The channel should always be present from the API */
 	instance = ast_channel_tech_pvt(ast);
 	if (instance == NULL || instance->svc < 1) {
 		return -1;
 	}
-	return ast_audiosocket_send_frame(instance->svc, f);
+
+	if (ast_audiosocket_send_frame(instance->svc, f)) {
+		bridgedChan = ast_channel_bridge_peer(ast);
+		ast_log(LOG_ERROR, "Failed to forward frame from channel '%s' to AudioSocket server '%s'\n",
+				bridgedChan ? ast_channel_name(bridgedChan) : "Unknown", instance->server);
+		return -1;
+	}
+	return 0;
+}
+
+/*! \brief Function called when we should write a DTMF frame to the channel */
+static int audiosocket_send_dtmf(struct ast_channel *ast, char digit, unsigned int duration)
+{
+	struct audiosocket_instance *instance;
+	struct ast_channel *bridgedChan;
+	struct ast_frame f;
+
+	/* The channel should always be present from the API */
+	instance = ast_channel_tech_pvt(ast);
+	if (instance == NULL || instance->svc < 1) {
+		return -1;
+	}
+
+	f.frametype = AST_FRAME_DTMF;
+	f.subclass.integer = digit;
+	f.len = duration;
+
+	if (ast_audiosocket_send_frame(instance->svc, &f)) {
+		bridgedChan = ast_channel_bridge_peer(ast);
path: channels/chan_audiosocket.c
user: login: jcolp
body: This leaks the bridged channel
created_at: 2025-03-19T09:04:10Z
author_association: MEMBER
diff_hunk: @@ -77,11 +96,15 @@ const int ast_audiosocket_send_frame(const int svc, const struct ast_frame *f);
  * This returned object is a pointer to an Asterisk frame which must be
  * manually freed by the caller.
  *
- * \param svc The file descriptor of the network socket to the AudioSocket server.
+ * \param svc The file descriptor of the network socket to the AudioSocket
+ * server.
+ * \param hangup Will be true if the AudioSocket server requested the channel
+ * be hung up, otherwise false. Used as an out-parameter only, pass NULL if
+ * not needed. The function return value will always be NULL when true.
  *
  * \retval A \ref ast_frame on success
- * \retval NULL on error
+ * \retval NULL on error or when the hungup parameter is true.
  */
-struct ast_frame *ast_audiosocket_receive_frame(const int svc);
+struct ast_frame *ast_audiosocket_receive_frame(const int svc, int *const hangup);
path: include/asterisk/res_audiosocket.h
user: login: jcolp
body: This should be a second API call to maintain ABI compatibility
created_at: 2025-03-19T09:05:51Z
author_association: MEMBER
diff_hunk: @@ -36,6 +36,25 @@ extern "C" {
 #include "asterisk/frame.h"
 #include "asterisk/uuid.h"
 
+
+enum ast_audiosocket_msg_kind {
+	/*! \brief Message indicates the channel should be hung up, direction: Sent only. */
+	AST_AUDIOSOCKET_KIND_HANGUP = 0x00,
+
+	/*! \brief Message contains the connection's UUID, direction: Received only. */
+	AST_AUDIOSOCKET_KIND_UUID   = 0x01,
+
+	/*! \brief Message contains a DTMF digit, direction: Received only. */
+	AST_AUDIOSOCKET_KIND_DTMF   = 0x03,
path: include/asterisk/res_audiosocket.h
user: login: jcolp
body: Where is this documented? are you going to update the documentation page?
created_at: 2025-03-19T09:07:08Z
author_association: MEMBER
diff_hunk: @@ -183,39 +173,56 @@ static int audiosocket_run(struct ast_channel *chan, const char *id, int svc)
 	chanName = ast_channel_name(chan);
 
 	while (1) {
-		ms = -1;
+		/* Timeout is hard-coded currently, could be made into an
+		   argument if needed, but 2 seconds seems like a realistic
+		   time range to give. */
 		targetChan = ast_waitfor_nandfds(&chan, 1, &svc, 1, NULL, &outfd, &ms);
+		ms = MAX_WAIT_TIMEOUT_MSEC;
+
 		if (targetChan) {
+			/* Receive audio frame from connected channel. */
 			f = ast_read(chan);
 			if (!f) {
+				ast_log(LOG_WARNING, "Failed to receive frame from channel '%s' connected to AudioSocket server '%s'", chanName, server);
 				return -1;
 			}
 
-			if (f->frametype == AST_FRAME_VOICE) {
+			if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_DTMF) {
 				/* Send audio frame to audiosocket */
path: apps/app_audiosocket.c
user: login: florentchauveau
body: hello @jcolp,

could you please elaborate on "this is no longer accurate"? 

the app used to filter on AST_FRAME_VOICE frames, I just added DTMF frames. The app description has been updated to specify DTMF frames are forwarded too.
created_at: 2025-03-19T10:14:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,6 +36,25 @@ extern "C" {
 #include "asterisk/frame.h"
 #include "asterisk/uuid.h"
 
+
+enum ast_audiosocket_msg_kind {
+	/*! \brief Message indicates the channel should be hung up, direction: Sent only. */
+	AST_AUDIOSOCKET_KIND_HANGUP = 0x00,
+
+	/*! \brief Message contains the connection's UUID, direction: Received only. */
+	AST_AUDIOSOCKET_KIND_UUID   = 0x01,
+
+	/*! \brief Message contains a DTMF digit, direction: Received only. */
+	AST_AUDIOSOCKET_KIND_DTMF   = 0x03,
path: include/asterisk/res_audiosocket.h
user: login: florentchauveau
body: This is documented here: https://github.com/CyCoreSystems/audiosocket/pull/24/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5

We are waiting for this PR to be merged to merge the audiosocket PR as well. The audiosocket maintainer has pre-approved the PR.

Sure, I'll update [asterisk/documentation](https://github.com/asterisk/documentation)  too.
created_at: 2025-03-19T10:17:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -183,39 +173,56 @@ static int audiosocket_run(struct ast_channel *chan, const char *id, int svc)
 	chanName = ast_channel_name(chan);
 
 	while (1) {
-		ms = -1;
+		/* Timeout is hard-coded currently, could be made into an
+		   argument if needed, but 2 seconds seems like a realistic
+		   time range to give. */
 		targetChan = ast_waitfor_nandfds(&chan, 1, &svc, 1, NULL, &outfd, &ms);
+		ms = MAX_WAIT_TIMEOUT_MSEC;
+
 		if (targetChan) {
+			/* Receive audio frame from connected channel. */
 			f = ast_read(chan);
 			if (!f) {
+				ast_log(LOG_WARNING, "Failed to receive frame from channel '%s' connected to AudioSocket server '%s'", chanName, server);
 				return -1;
 			}
 
-			if (f->frametype == AST_FRAME_VOICE) {
+			if (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_DTMF) {
 				/* Send audio frame to audiosocket */
path: apps/app_audiosocket.c
user: login: jcolp
body: The comment says "Send audio frame to audiosocket"
created_at: 2025-03-19T10:20:50Z
author_association: MEMBER
diff_hunk: @@ -36,6 +36,25 @@ extern "C" {
 #include "asterisk/frame.h"
 #include "asterisk/uuid.h"
 
+
+enum ast_audiosocket_msg_kind {
+	/*! \brief Message indicates the channel should be hung up, direction: Sent only. */
+	AST_AUDIOSOCKET_KIND_HANGUP = 0x00,
+
+	/*! \brief Message contains the connection's UUID, direction: Received only. */
+	AST_AUDIOSOCKET_KIND_UUID   = 0x01,
+
+	/*! \brief Message contains a DTMF digit, direction: Received only. */
+	AST_AUDIOSOCKET_KIND_DTMF   = 0x03,
path: include/asterisk/res_audiosocket.h
user: login: florentchauveau
body: I have requested the PR here: https://github.com/asterisk/documentation/pull/131
created_at: 2025-03-19T12:05:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -222,6 +265,9 @@ static struct ast_channel *audiosocket_request(const char *type,
 	if ((fd = ast_audiosocket_connect(args.destination, NULL)) < 0) {
 		goto failure;
 	}
+	/* Make a copy of the server address for display in error messages. */
+	ast_copy_string(instance->server, args.destination, sizeof(instance->server));
+	// TODO: Use `strcpy()` instead?
path: channels/chan_audiosocket.c
user: login: jcolp
body: ast_copy_string is safer, this TODO can be removed
created_at: 2025-03-20T08:52:44Z
author_association: MEMBER

PR comments:
user: login: sangoma-oss-cla[bot]
created_at: 2025-03-01T16:03:11Z
body: [![CLA assistant check](https://oss-cla.sangoma.com/pull/badge/signed)](https://oss-cla.sangoma.com/asterisk/asterisk?pullRequest=1138) <br/>All committers have signed the CLA.
author_association: NONE
user: login: florentchauveau
created_at: 2025-03-01T16:05:09Z
body: multiple-commits: standalone
author_association: CONTRIBUTOR
user: login: florentchauveau
created_at: 2025-03-01T16:05:40Z
body: cherry-pick-to: 20
cherry-pick-to: 21
cherry-pick-to: 22

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-01T16:10:14Z
body: Workflow PRCheck failed
[master-build](https://github.com/asterisk/asterisk/actions/runs/13606478548/job/38038592120): FAILED: Build

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-01T16:22:33Z
body: Workflow PRCheck failed
[master-build](https://github.com/asterisk/asterisk/actions/runs/13606588608/job/38038814325): FAILED: Build

author_association: NONE
user: login: seanbright
created_at: 2025-03-01T16:48:57Z
body: @florentchauveau FYI this is not reviewable until NormHarrison has signed the CLA.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-01T16:49:39Z
body: Workflow PRCheck completed successfully

author_association: NONE
user: login: florentchauveau
created_at: 2025-03-01T17:02:19Z
body: > @florentchauveau FYI this is not reviewable until NormHarrison has signed the CLA.

@NormHarrison 🙏
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-19T12:03:42Z
body: Workflow PRCheck completed successfully

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-19T12:33:37Z
body: Workflow PRCheck completed successfully

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-20T11:00:16Z
body: Workflow PRCheck completed successfully

author_association: NONE
user: login: s20r
created_at: 2025-03-21T05:10:41Z
body: Hello, while I haven't been involved with these changes in quite awhile, I saw this was making progress thanks to @florentchauveau's efforts, and if its alright I had had a few additional questions/comments about the requested changes.

1. Its good to know the behavior of `ast_channel_bridge_peer()`, at the time I was not aware that it returned referenced counted data that needed to be decremented afterwards. I know that the asterisk logging standards generally display the "local" channel name instead of the name of the bridged channel, but when using audiosocket as a channel driver the local channel name becomes `AUDIOSOCKET/<UUID>`. My thinking at the time was that, the audiosocket channel isn't really an normal "channel entity" within Asterisk, but more so a proxy or intermediary for external communication (and most often in one direction). Calls don't really "come" from audiosocket so to speak, they are always made from within Asterisk out to a server (the UUID is also usually auto-generated via `${UUID()}` and is always sent to the server). In short, I didn't really feel like a random UUID was that informative within the context of Asterisk's usual logging output from channel drivers, so I thought it would be more useful to use the channel name which its connected to (which was also what the grammar of the log message was written for). Whichever is best is up to the core maintainers of course, I just wanted to provide an explanation of the original reasoning.

2. On the topic of misunderstood or missed implementation details, are there any missing or improper calls to `ast_channel_lock()` or `ast_channel_unlock()`? (Mostly since I noticed that the removed calls to `ast_channel_bridge_peer()` would have required them, but I thought it may be worth asking in case any other functions needed them too).

3. Regarding the addition of the `AUDIOSOCKET_UUID` and `AUDIOSOCKET_SERVICE` channel variables, these were both actually apart of the original commit by [CyCoreSystems in 2019](https://github.com/CyCoreSystems/audiosocket/commit/3a007c195aaeccf9a8514839bd89430efb979da9#diff-d7ebb16f47e34e4f89444c2d0fc7b9ce2fc27272214396c8a902bb5b38f75516R192), but they were only set by the channel driver variant, I am not sure why the dialplan application variant did not set them, it may have just been an oversight. However, it doesn't seem like they were ever added to the documentation in either case. In regular dialplan usage of Audiosocket, they aren't "critical" so to speak, in that they only store information that the user already provides during the initial call (the UUID and the server's IP address and port respectively). But in the context of Audiosocket being invoked externally, like through an AMI `Originate` (or any place where setting a channel variable isnt as simple), it is a lot more convenient to have them set by the Asterisk modules themselves in case they are needed at some point later.

(Many thanks again to @florentchauveau for putting in the effort to merge these changes, and thank you @jcolp for reviewing them)
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-28T15:57:21Z
body: Workflow PRCPCheck failed
[20-ari1](https://github.com/asterisk/asterisk/actions/runs/14131751182/job/39593956661): FAILED TEST: rest_api/bridges/softmix_unhold
[22-pjs1](https://github.com/asterisk/asterisk/actions/runs/14131751182/job/39593963106): FAILED TEST: channels/pjsip/geolocation/incoming/basic
[22-pjs1](https://github.com/asterisk/asterisk/actions/runs/14131751182/job/39593963106): FAILED TEST: channels/pjsip/geolocation/incoming/profile_precedence
[22-pjs1](https://github.com/asterisk/asterisk/actions/runs/14131751182/job/39593963106): FAILED TEST: channels/pjsip/qualify/qualify_timeout

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-28T19:18:24Z
body: Successfully merged to branch master and cherry-picked to ["20","21","22"]
author_association: NONE
user: login: waitforit99
created_at: 2025-04-10T19:02:59Z
body: Hey @florentchauveau , thank you very much for these updates! Can you please provide with some instructions how to manually apply this patch, if doable at all? Thank you!
author_association: NONE