[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