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

Friendly Automation asteriskteam at digium.com
Tue Sep 10 12:01:41 CDT 2019


Friendly Automation has submitted this change and it was merged. ( https://gerrit.asterisk.org/c/asterisk/+/12776 )

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/ari_model_validators.c
M res/ari/ari_model_validators.h
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
7 files changed, 579 insertions(+), 24 deletions(-)

Approvals:
  George Joseph: Looks good to me, approved
  Friendly Automation: Approved for Submit



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/ari_model_validators.c b/res/ari/ari_model_validators.c
index 8910bbb..3d63f53 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1385,6 +1385,62 @@
 	return ast_ari_validate_dialplan_cep;
 }
 
+int ast_ari_validate_external_media(struct ast_json *json)
+{
+	int res = 1;
+	struct ast_json_iter *iter;
+	int has_channel = 0;
+
+	for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+		if (strcmp("channel", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			has_channel = 1;
+			prop_is_valid = ast_ari_validate_channel(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ExternalMedia field channel failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("local_address", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ast_ari_validate_string(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ExternalMedia field local_address failed validation\n");
+				res = 0;
+			}
+		} else
+		if (strcmp("local_port", ast_json_object_iter_key(iter)) == 0) {
+			int prop_is_valid;
+			prop_is_valid = ast_ari_validate_int(
+				ast_json_object_iter_value(iter));
+			if (!prop_is_valid) {
+				ast_log(LOG_ERROR, "ARI ExternalMedia field local_port failed validation\n");
+				res = 0;
+			}
+		} else
+		{
+			ast_log(LOG_ERROR,
+				"ARI ExternalMedia has undocumented field %s\n",
+				ast_json_object_iter_key(iter));
+			res = 0;
+		}
+	}
+
+	if (!has_channel) {
+		ast_log(LOG_ERROR, "ARI ExternalMedia missing required field channel\n");
+		res = 0;
+	}
+
+	return res;
+}
+
+ari_validator ast_ari_validate_external_media_fn(void)
+{
+	return ast_ari_validate_external_media;
+}
+
 int ast_ari_validate_rtpstat(struct ast_json *json)
 {
 	int res = 1;
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index f9285b4..53a8573 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -478,6 +478,24 @@
 ari_validator ast_ari_validate_dialplan_cep_fn(void);
 
 /*!
+ * \brief Validator for ExternalMedia.
+ *
+ * ExternalMedia session.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_external_media(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_external_media().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_external_media_fn(void);
+
+/*!
  * \brief Validator for RTPstat.
  *
  * A statistics of a RTP.
@@ -1522,6 +1540,10 @@
  * - context: string (required)
  * - exten: string (required)
  * - priority: long (required)
+ * ExternalMedia
+ * - channel: Channel (required)
+ * - local_address: string
+ * - local_port: int
  * RTPstat
  * - channel_uniqueid: string (required)
  * - local_maxjitter: double
diff --git a/res/ari/resource_channels.c b/res/ari/resource_channels.c
index 1164ab1..0fd2d69 100644
--- a/res/ari/resource_channels.c
+++ b/res/ari/resource_channels.c
@@ -1060,7 +1060,7 @@
 	return NULL;
 }
 
-static void ari_channels_handle_originate_with_id(const char *args_endpoint,
+static struct ast_channel *ari_channels_handle_originate_with_id(const char *args_endpoint,
 	const char *args_extension,
 	const char *args_context,
 	long args_priority,
@@ -1098,19 +1098,19 @@
 		|| (assignedids.uniqueid2 && AST_MAX_PUBLIC_UNIQUEID < strlen(assignedids.uniqueid2))) {
 		ast_ari_response_error(response, 400, "Bad Request",
 			"Uniqueid length exceeds maximum of %d", AST_MAX_PUBLIC_UNIQUEID);
-		return;
+		return NULL;
 	}
 
 	if (ast_strlen_zero(args_endpoint)) {
 		ast_ari_response_error(response, 400, "Bad Request",
 			"Endpoint must be specified");
-		return;
+		return NULL;
 	}
 
 	if (!ast_strlen_zero(args_originator) && !ast_strlen_zero(args_formats)) {
 		ast_ari_response_error(response, 400, "Bad Request",
 			"Originator and formats can't both be specified");
-		return;
+		return NULL;
 	}
 
 	dialtech = ast_strdupa(args_endpoint);
@@ -1122,7 +1122,7 @@
 	if (ast_strlen_zero(dialtech) || ast_strlen_zero(dialdevice)) {
 		ast_ari_response_error(response, 400, "Bad Request",
 			"Invalid endpoint specified");
-		return;
+		return NULL;
 	}
 
 	if (!ast_strlen_zero(args_app)) {
@@ -1130,7 +1130,7 @@
 
 		if (!appdata) {
 			ast_ari_response_alloc_failed(response);
-			return;
+			return NULL;
 		}
 
 		ast_str_set(&appdata, 0, "%s", args_app);
@@ -1141,7 +1141,7 @@
 		origination = ast_calloc(1, sizeof(*origination) + ast_str_size(appdata) + 1);
 		if (!origination) {
 			ast_ari_response_alloc_failed(response);
-			return;
+			return NULL;
 		}
 
 		strcpy(origination->appdata, ast_str_buffer(appdata));
@@ -1149,7 +1149,7 @@
 		origination = ast_calloc(1, sizeof(*origination) + 1);
 		if (!origination) {
 			ast_ari_response_alloc_failed(response);
-			return;
+			return NULL;
 		}
 
 		ast_copy_string(origination->context, S_OR(args_context, "default"), sizeof(origination->context));
@@ -1164,7 +1164,7 @@
 				if (ipri == -1) {
 					ast_log(AST_LOG_ERROR, "Requested label: %s can not be found in context: %s\n", args_label, args_context);
 					ast_ari_response_error(response, 404, "Not Found", "Requested label can not be found");
-					return;
+					return NULL;
 				}
 			} else {
 				ast_debug(3, "Numeric value provided for label, jumping to that priority\n");
@@ -1174,7 +1174,7 @@
 				ast_log(AST_LOG_ERROR, "Invalid priority label '%s' specified for extension %s in context: %s\n",
 						args_label, args_extension, args_context);
 				ast_ari_response_error(response, 400, "Bad Request", "Requested priority is illegal");
-				return;
+				return NULL;
 			}
 
 			/* Our priority was provided by a label */
@@ -1188,14 +1188,14 @@
 	} else {
 		ast_ari_response_error(response, 400, "Bad Request",
 			"Application or extension must be specified");
-		return;
+		return NULL;
 	}
 
 	dial = ast_dial_create();
 	if (!dial) {
 		ast_ari_response_alloc_failed(response);
 		ast_free(origination);
-		return;
+		return NULL;
 	}
 	ast_dial_set_user_data(dial, origination);
 
@@ -1203,7 +1203,7 @@
 		ast_ari_response_alloc_failed(response);
 		ast_dial_destroy(dial);
 		ast_free(origination);
-		return;
+		return NULL;
 	}
 
 	if (args_timeout > 0) {
@@ -1231,7 +1231,7 @@
 				"Provided originator channel was not found");
 			ast_dial_destroy(dial);
 			ast_free(origination);
-			return;
+			return NULL;
 		}
 	}
 
@@ -1244,7 +1244,7 @@
 			ast_dial_destroy(dial);
 			ast_free(origination);
 			ast_channel_cleanup(other);
-			return;
+			return NULL;
 		}
 
 		while ((format_name = ast_strip(strsep(&formats_copy, ",")))) {
@@ -1263,7 +1263,7 @@
 				ast_channel_cleanup(other);
 				ao2_ref(format_cap, -1);
 				ao2_cleanup(fmt);
-				return;
+				return NULL;
 			}
 			ao2_ref(fmt, -1);
 		}
@@ -1279,7 +1279,7 @@
 		ast_dial_destroy(dial);
 		ast_free(origination);
 		ast_channel_cleanup(other);
-		return;
+		return NULL;
 	}
 
 	ast_channel_cleanup(other);
@@ -1290,7 +1290,7 @@
 		ast_ari_response_alloc_failed(response);
 		ast_dial_destroy(dial);
 		ast_free(origination);
-		return;
+		return NULL;
 	}
 
 	if (!ast_strlen_zero(cid_num) || !ast_strlen_zero(cid_name)) {
@@ -1355,8 +1355,7 @@
 		ast_ari_response_ok(response, ast_channel_snapshot_to_json(snapshot, NULL));
 	}
 
-	ast_channel_unref(chan);
-	return;
+	return chan;
 }
 
 /*!
@@ -1397,6 +1396,7 @@
 	struct ast_ari_response *response)
 {
 	struct ast_variable *variables = NULL;
+	struct ast_channel *chan;
 
 	/* Parse any query parameters out of the body parameter */
 	if (args->variables) {
@@ -1410,7 +1410,7 @@
 		}
 	}
 
-	ari_channels_handle_originate_with_id(
+	chan = ari_channels_handle_originate_with_id(
 		args->endpoint,
 		args->extension,
 		args->context,
@@ -1426,6 +1426,7 @@
 		args->originator,
 		args->formats,
 		response);
+	ast_channel_cleanup(chan);
 	ast_variables_destroy(variables);
 }
 
@@ -1434,6 +1435,7 @@
 	struct ast_ari_response *response)
 {
 	struct ast_variable *variables = NULL;
+	struct ast_channel *chan;
 
 	/* Parse any query parameters out of the body parameter */
 	if (args->variables) {
@@ -1447,7 +1449,7 @@
 		}
 	}
 
-	ari_channels_handle_originate_with_id(
+	chan = ari_channels_handle_originate_with_id(
 		args->endpoint,
 		args->extension,
 		args->context,
@@ -1463,6 +1465,7 @@
 		args->originator,
 		args->formats,
 		response);
+	ast_channel_cleanup(chan);
 	ast_variables_destroy(variables);
 }
 
@@ -2053,3 +2056,148 @@
 
 	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;
+	struct ast_channel *chan;
+	struct ast_json *json_chan;
+	struct varshead *vars;
+
+	endpoint_len = strlen("UnicastRTP/") + strlen(args->external_host) + 1;
+	endpoint = ast_alloca(endpoint_len);
+	snprintf(endpoint, endpoint_len, "UnicastRTP/%s", args->external_host);
+
+	chan = 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);
+
+	if (!chan) {
+		return;
+	}
+
+	/*
+	 * At this point, response->message contains a channel object so we
+	 * need to save it then create a new ExternalMedia object and put the
+	 * channel in it.
+	 */
+	json_chan = response->message;
+	response->message = ast_json_object_create();
+	if (!response->message) {
+		ast_channel_unref(chan);
+		ast_json_unref(json_chan);
+		ast_ari_response_alloc_failed(response);
+		return;
+	}
+
+	ast_json_object_set(response->message, "channel", json_chan);
+	/*
+	 * At the time the channel snapshot was taken the channel variables might
+	 * not have been set so we try to grab them directly from the channel.
+	 */
+	ast_channel_lock(chan);
+	vars = ast_channel_varshead(chan);
+	if (vars && !AST_LIST_EMPTY(vars)) {
+		struct ast_var_t *variables;
+
+		/* Put them all on the channel object */
+		ast_json_object_set(json_chan, "channelvars", ast_json_channel_vars(vars));
+		/* Grab out the local address and port */
+		AST_LIST_TRAVERSE(vars, variables, entries) {
+			if (!strcmp("UNICASTRTP_LOCAL_ADDRESS", ast_var_name(variables))) {
+				ast_json_object_set(response->message, "local_address",
+					ast_json_string_create(ast_var_value(variables)));
+			}
+			else if (!strcmp("UNICASTRTP_LOCAL_PORT", ast_var_name(variables))) {
+				ast_json_object_set(response->message, "local_port",
+					ast_json_integer_create(strtol(ast_var_value(variables), NULL, 10)));
+			}
+		}
+	}
+	ast_channel_unlock(chan);
+	ast_channel_unref(chan);
+}
+
+#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 8aefb40..49a3882 100644
--- a/res/ari/resource_channels.h
+++ b/res/ari/resource_channels.h
@@ -824,5 +824,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 825d4c2..27d9deb 100644
--- a/res/res_ari_channels.c
+++ b/res/res_ari_channels.c
@@ -2814,6 +2814,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_external_media(
+				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 = {
@@ -3007,14 +3129,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 7741269..53e6e9d 100644
--- a/rest-api/api-docs/channels.json
+++ b/rest-api/api-docs/channels.json
@@ -1755,6 +1755,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": "ExternalMedia",
+					"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": {
@@ -2041,6 +2166,27 @@
 					"description": "Channel variables"
 				}
 			}
+		},
+		"ExternalMedia": {
+			"id": "ExternalMedia",
+			"description": "ExternalMedia session.",
+			"properties": {
+				"channel": {
+					"required": true,
+					"type": "Channel",
+					"description": "The Asterisk channel representing the external media"
+				},
+				"local_address": {
+					"required": false,
+					"type": "string",
+					"description": "The local ip address used" 
+				},
+				"local_port": {
+					"required": false,
+					"type": "int",
+					"description": "The local ip port used" 
+				}
+			}
 		}
 	}
 }

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

Gerrit-Project: asterisk
Gerrit-Branch: 17
Gerrit-Change-Id: I9618899198880b4c650354581b50c0401b58bc46
Gerrit-Change-Number: 12776
Gerrit-PatchSet: 6
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Friendly Automation
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Kevin Harwell <kharwell at digium.com>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20190910/52e781cd/attachment-0001.html>


More information about the asterisk-code-review mailing list