[Asterisk-code-review] ARI: External Media (...asterisk[16])

George Joseph asteriskteam at digium.com
Thu Aug 22 11:33:03 CDT 2019


George Joseph has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/12775


Change subject: ARI: External Media
......................................................................

ARI: External Media

The Channel resource has a new sub-resource "externalMedia".
This allows an application to create a channel for the sole purpose
of exchanging media with an external server.  Once created, this
channel could be placed into a bridge with existing channels to
allow the external server to inject audio into the bridge or
receive audio from the bridge.
See https://wiki.asterisk.org/wiki/display/AST/External+Media+and+ARI
for more information.

Change-Id: I9618899198880b4c650354581b50c0401b58bc46
---
A doc/CHANGES-staging/ARI.txt
M res/ari/resource_channels.c
M res/ari/resource_channels.h
M res/res_ari_channels.c
M rest-api/api-docs/channels.json
5 files changed, 407 insertions(+), 2 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/75/12775/1

diff --git a/doc/CHANGES-staging/ARI.txt b/doc/CHANGES-staging/ARI.txt
new file mode 100644
index 0000000..06ac4ab
--- /dev/null
+++ b/doc/CHANGES-staging/ARI.txt
@@ -0,0 +1,10 @@
+Subject: ARI Channels
+
+The Channel resource has a new sub-resource "externalMedia".
+This allows an application to create a channel for the sole purpose
+of exchanging media with an external server.  Once created, this
+channel could be placed into a bridge with existing channels to
+allow the external server to inject audio into the bridge or
+receive audio from the bridge.
+See https://wiki.asterisk.org/wiki/display/AST/External+Media+and+ARI
+for more information.
\ No newline at end of file
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index d415224..3aea5dd 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -2049,3 +2049,100 @@
 
 	return;
 }
+
+static void external_media_rtp_udp(struct ast_ari_channels_external_media_args *args,
+	struct ast_variable *variables,
+	struct ast_ari_response *response)
+{
+	size_t endpoint_len;
+	char *endpoint;
+
+	endpoint_len = strlen("UnicastRTP/") + strlen(args->external_host) + 1;
+	endpoint = ast_alloca(endpoint_len);
+	snprintf(endpoint, endpoint_len, "UnicastRTP/%s", args->external_host);
+
+	ari_channels_handle_originate_with_id(
+		endpoint,
+		NULL,
+		NULL,
+		0,
+		NULL,
+		args->app,
+		NULL,
+		NULL,
+		0,
+		variables,
+		args->channel_id,
+		NULL,
+		NULL,
+		args->format,
+		response);
+	ast_variables_destroy(variables);
+}
+
+#include "asterisk/config.h"
+#include "asterisk/netsock2.h"
+
+void ast_ari_channels_external_media(struct ast_variable *headers,
+	struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response)
+{
+	struct ast_variable *variables = NULL;
+	char *external_host;
+	char *host = NULL;
+	char *port = NULL;
+
+	ast_assert(response != NULL);
+
+	if (ast_strlen_zero(args->app)) {
+		ast_ari_response_error(response, 400, "Bad Request", "app cannot be empty");
+		return;
+	}
+
+	if (ast_strlen_zero(args->external_host)) {
+		ast_ari_response_error(response, 400, "Bad Request", "external_host cannot be empty");
+		return;
+	}
+
+	external_host = ast_strdupa(args->external_host);
+	if (!ast_sockaddr_split_hostport(external_host, &host, &port, PARSE_PORT_REQUIRE)) {
+		ast_ari_response_error(response, 400, "Bad Request", "external_host must be <host>:<port>");
+		return;
+	}
+
+	if (ast_strlen_zero(args->format)) {
+		ast_ari_response_error(response, 400, "Bad Request", "format cannot be empty");
+		return;
+	}
+
+	if (ast_strlen_zero(args->encapsulation)) {
+		args->encapsulation = "rtp";
+	}
+	if (ast_strlen_zero(args->transport)) {
+		args->transport = "udp";
+	}
+	if (ast_strlen_zero(args->connection_type)) {
+		args->connection_type = "client";
+	}
+	if (ast_strlen_zero(args->direction)) {
+		args->direction = "both";
+	}
+
+	if (args->variables) {
+		struct ast_json *json_variables;
+
+		ast_ari_channels_external_media_parse_body(args->variables, args);
+		json_variables = ast_json_object_get(args->variables, "variables");
+		if (json_variables
+			&& json_to_ast_variables(response, json_variables, &variables)) {
+			return;
+		}
+	}
+
+	if (strcasecmp(args->encapsulation, "rtp") == 0 && strcasecmp(args->transport, "udp") == 0) {
+		external_media_rtp_udp(args, variables, response);
+	} else {
+		ast_ari_response_error(
+			response, 501, "Not Implemented",
+			"The encapsulation and/or transport is not supported");
+	}
+}
diff --git a/res/ari/resource_channels.h b/res/ari/resource_channels.h
index 401c8a5..7c70cec 100644
--- a/res/ari/resource_channels.h
+++ b/res/ari/resource_channels.h
@@ -822,5 +822,47 @@
  * \param[out] response HTTP response
  */
 void ast_ari_channels_rtpstatistics(struct ast_variable *headers, struct ast_ari_channels_rtpstatistics_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_channels_external_media() */
+struct ast_ari_channels_external_media_args {
+	/*! The unique id to assign the channel on creation. */
+	const char *channel_id;
+	/*! Stasis Application to place channel into */
+	const char *app;
+	/*! The "variables" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { "endpoint": "SIP/Alice", "variables": { "CALLERID(name)": "Alice" } } */
+	struct ast_json *variables;
+	/*! Hostname/ip:port of external host */
+	const char *external_host;
+	/*! Payload encapsulation protocol */
+	const char *encapsulation;
+	/*! Transport protocol */
+	const char *transport;
+	/*! Connection type (client/server) */
+	const char *connection_type;
+	/*! Format to encode audio in */
+	const char *format;
+	/*! External media direction */
+	const char *direction;
+};
+/*!
+ * \brief Body parsing function for /channels/externalMedia.
+ * \param body The JSON body from which to parse parameters.
+ * \param[out] args The args structure to parse into.
+ * \retval zero on success
+ * \retval non-zero on failure
+ */
+int ast_ari_channels_external_media_parse_body(
+	struct ast_json *body,
+	struct ast_ari_channels_external_media_args *args);
+
+/*!
+ * \brief Start an External Media session.
+ *
+ * Create a channel to an External Media source/sink.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_channels_external_media(struct ast_variable *headers, struct ast_ari_channels_external_media_args *args, struct ast_ari_response *response);
 
 #endif /* _ASTERISK_RESOURCE_CHANNELS_H */
diff --git a/res/res_ari_channels.c b/res/res_ari_channels.c
index 73d1e4b..bb153c9 100644
--- a/res/res_ari_channels.c
+++ b/res/res_ari_channels.c
@@ -2807,6 +2807,128 @@
 fin: __attribute__((unused))
 	return;
 }
+int ast_ari_channels_external_media_parse_body(
+	struct ast_json *body,
+	struct ast_ari_channels_external_media_args *args)
+{
+	struct ast_json *field;
+	/* Parse query parameters out of it */
+	field = ast_json_object_get(body, "channelId");
+	if (field) {
+		args->channel_id = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "app");
+	if (field) {
+		args->app = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "external_host");
+	if (field) {
+		args->external_host = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "encapsulation");
+	if (field) {
+		args->encapsulation = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "transport");
+	if (field) {
+		args->transport = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "connection_type");
+	if (field) {
+		args->connection_type = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "format");
+	if (field) {
+		args->format = ast_json_string_get(field);
+	}
+	field = ast_json_object_get(body, "direction");
+	if (field) {
+		args->direction = ast_json_string_get(field);
+	}
+	return 0;
+}
+
+/*!
+ * \brief Parameter parsing callback for /channels/externalMedia.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_channels_external_media_cb(
+	struct ast_tcptls_session_instance *ser,
+	struct ast_variable *get_params, struct ast_variable *path_vars,
+	struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
+{
+	struct ast_ari_channels_external_media_args args = {};
+	struct ast_variable *i;
+#if defined(AST_DEVMODE)
+	int is_valid;
+	int code;
+#endif /* AST_DEVMODE */
+
+	for (i = get_params; i; i = i->next) {
+		if (strcmp(i->name, "channelId") == 0) {
+			args.channel_id = (i->value);
+		} else
+		if (strcmp(i->name, "app") == 0) {
+			args.app = (i->value);
+		} else
+		if (strcmp(i->name, "external_host") == 0) {
+			args.external_host = (i->value);
+		} else
+		if (strcmp(i->name, "encapsulation") == 0) {
+			args.encapsulation = (i->value);
+		} else
+		if (strcmp(i->name, "transport") == 0) {
+			args.transport = (i->value);
+		} else
+		if (strcmp(i->name, "connection_type") == 0) {
+			args.connection_type = (i->value);
+		} else
+		if (strcmp(i->name, "format") == 0) {
+			args.format = (i->value);
+		} else
+		if (strcmp(i->name, "direction") == 0) {
+			args.direction = (i->value);
+		} else
+		{}
+	}
+	args.variables = body;
+	ast_ari_channels_external_media(headers, &args, response);
+#if defined(AST_DEVMODE)
+	code = response->response_code;
+
+	switch (code) {
+	case 0: /* Implementation is still a stub, or the code wasn't set */
+		is_valid = response->message == NULL;
+		break;
+	case 500: /* Internal Server Error */
+	case 501: /* Not Implemented */
+	case 400: /* Invalid parameters */
+	case 409: /* Channel is not in a Stasis application; Channel is already bridged */
+		is_valid = 1;
+		break;
+	default:
+		if (200 <= code && code <= 299) {
+			is_valid = ast_ari_validate_channel(
+				response->message);
+		} else {
+			ast_log(LOG_ERROR, "Invalid error response %d for /channels/externalMedia\n", code);
+			is_valid = 0;
+		}
+	}
+
+	if (!is_valid) {
+		ast_log(LOG_ERROR, "Response validation failed for /channels/externalMedia\n");
+		ast_ari_response_error(response, 500,
+			"Internal Server Error", "Response validation failed");
+	}
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+	return;
+}
 
 /*! \brief REST handler for /api-docs/channels.json */
 static struct stasis_rest_handlers channels_create = {
@@ -3000,14 +3122,23 @@
 	.children = { &channels_channelId_continue,&channels_channelId_move,&channels_channelId_redirect,&channels_channelId_answer,&channels_channelId_ring,&channels_channelId_dtmf,&channels_channelId_mute,&channels_channelId_hold,&channels_channelId_moh,&channels_channelId_silence,&channels_channelId_play,&channels_channelId_record,&channels_channelId_variable,&channels_channelId_snoop,&channels_channelId_dial,&channels_channelId_rtp_statistics, }
 };
 /*! \brief REST handler for /api-docs/channels.json */
+static struct stasis_rest_handlers channels_externalMedia = {
+	.path_segment = "externalMedia",
+	.callbacks = {
+		[AST_HTTP_POST] = ast_ari_channels_external_media_cb,
+	},
+	.num_children = 0,
+	.children = {  }
+};
+/*! \brief REST handler for /api-docs/channels.json */
 static struct stasis_rest_handlers channels = {
 	.path_segment = "channels",
 	.callbacks = {
 		[AST_HTTP_GET] = ast_ari_channels_list_cb,
 		[AST_HTTP_POST] = ast_ari_channels_originate_cb,
 	},
-	.num_children = 2,
-	.children = { &channels_create,&channels_channelId, }
+	.num_children = 3,
+	.children = { &channels_create,&channels_channelId,&channels_externalMedia, }
 };
 
 static int unload_module(void)
diff --git a/rest-api/api-docs/channels.json b/rest-api/api-docs/channels.json
index 4ea5f56..366fec8 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -1748,6 +1748,131 @@
 					]
 				}
 			]
+		},
+		{
+			"path": "/channels/externalMedia",
+			"description": "Create a channel to an External Media source/sink.",
+			"operations": [
+				{
+					"httpMethod": "POST",
+					"summary": "Start an External Media session.",
+					"notes": "Create a channel to an External Media source/sink.",
+					"nickname": "externalMedia",
+					"responseClass": "Channel",
+					"parameters": [
+						{
+							"name": "channelId",
+							"description": "The unique id to assign the channel on creation.",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "app",
+							"description": "Stasis Application to place channel into",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "variables",
+							"description": "The \"variables\" key in the body object holds variable key/value pairs to set on the channel on creation. Other keys in the body object are interpreted as query parameters. Ex. { \"endpoint\": \"SIP/Alice\", \"variables\": { \"CALLERID(name)\": \"Alice\" } }",
+							"paramType": "body",
+							"required": false,
+							"dataType": "containers",
+							"allowMultiple": false
+						},
+						{
+							"name": "external_host",
+							"description": "Hostname/ip:port of external host",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "encapsulation",
+							"description": "Payload encapsulation protocol",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string",
+							"defaultValue": "rtp",
+							"allowableValues": {
+								"valueType": "LIST",
+								"values": [
+									"rtp"
+								]
+							}
+						},
+						{
+							"name": "transport",
+							"description": "Transport protocol",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string",
+							"defaultValue": "udp",
+							"allowableValues": {
+								"valueType": "LIST",
+								"values": [
+									"udp"
+								]
+							}
+						},
+						{
+							"name": "connection_type",
+							"description": "Connection type (client/server)",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string",
+							"defaultValue": "client",
+							"allowableValues": {
+								"valueType": "LIST",
+								"values": [
+									"client"
+								]
+							}
+						},
+						{
+							"name": "format",
+							"description": "Format to encode audio in",
+							"paramType": "query",
+							"required": true,
+							"allowMultiple": false,
+							"dataType": "string"
+						},
+						{
+							"name": "direction",
+							"description": "External media direction",
+							"paramType": "query",
+							"required": false,
+							"allowMultiple": false,
+							"dataType": "string",
+							"defaultValue": "both",
+							"allowableValues": {
+								"valueType": "LIST",
+								"values": [
+									"both"
+								]
+							}
+						}
+					],
+					"errorResponses": [
+						{
+							"code": 400,
+							"reason": "Invalid parameters"
+						},
+						{
+							"code": 409,
+							"reason": "Channel is not in a Stasis application; Channel is already bridged"
+						}
+					]
+				}
+			]
 		}
 	],
 	"models": {

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

Gerrit-Project: asterisk
Gerrit-Branch: 16
Gerrit-Change-Id: I9618899198880b4c650354581b50c0401b58bc46
Gerrit-Change-Number: 12775
Gerrit-PatchSet: 1
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20190822/46a07013/attachment-0001.html>


More information about the asterisk-code-review mailing list