res_aeap & res_speech_aeap: Add Asterisk External Application Protocol

Add framework to connect to, and read and write protocol based
messages from and to an external application using an Asterisk
External Application Protocol (AEAP). This has been divided into
several abstractions:

 1. transport - base communication layer (currently websocket only)
 2. message - AEAP description and data (currently JSON only)
 3. transaction - links/binds requests and responses
 4. aeap - transport, message, and transaction handler/manager

This patch also adds an AEAP implementation for speech to text.
Existing speech API callbacks for speech to text have been completed
making it possible for Asterisk to connect to a configured external
translator service and provide audio for STT. Results can also be
received from the external translator, and made available as speech
results in Asterisk.

Unit tests have also been created that test the AEAP framework, and
also the speech to text implementation.

ASTERISK-29726 #close

Change-Id: Iaa4b259f84aa63501e5fd2a6fb107f900b4d4ed2
This commit is contained in:
Kevin Harwell
2021-06-18 12:54:10 -05:00
committed by Friendly Automation
parent 53a3af6321
commit 272bac70dd
25 changed files with 4831 additions and 57 deletions

View File

@@ -17,29 +17,37 @@
*/
/*** MODULEINFO
<depend>res_http_websocket</depend>
<support_level>core</support_level>
***/
#include "asterisk.h"
#include "asterisk/astobj2.h"
#include "asterisk/module.h"
#include "asterisk/sorcery.h"
#include "asterisk/cli.h"
#include "asterisk/format.h"
#include "asterisk/format_cap.h"
#include "asterisk/res_aeap.h"
#include "res_aeap/general.h"
/*** DOCUMENTATION
<configInfo name="res_aeap" language="en_US">
<synopsis>Asterisk External Application Protocol (AEAP) module for Asterisk</synopsis>
<configFile name="aeap.conf">
<configObject name="server">
<synopsis>AEAP server options</synopsis>
<configObject name="client">
<synopsis>AEAP client options</synopsis>
<configOption name="type">
<synopsis>Must be of type 'server'.</synopsis>
<synopsis>Must be of type 'client'.</synopsis>
</configOption>
<configOption name="server_url">
<configOption name="url">
<synopsis>The URL of the server to connect to.</synopsis>
</configOption>
<configOption name="protocol">
<synopsis>The application protocol.</synopsis>
</configOption>
<configOption name="codecs">
<synopsis>Optional media codec(s)</synopsis>
<description><para>
@@ -56,30 +64,36 @@
/* Asterisk External Application Protocol sorcery object */
static struct ast_sorcery *aeap_sorcery;
struct aeap_server
struct ast_sorcery *ast_aeap_sorcery(void) {
return aeap_sorcery;
}
struct ast_aeap_client_config
{
SORCERY_OBJECT(details);
AST_DECLARE_STRING_FIELDS(
/*! The URL of the server to connect to */
AST_STRING_FIELD(server_url);
AST_STRING_FIELD(url);
/*! The application protocol */
AST_STRING_FIELD(protocol);
);
/*! An optional list of codecs that will be used if provided */
struct ast_format_cap *codecs;
};
static void aeap_server_destructor(void *obj)
static void client_config_destructor(void *obj)
{
struct aeap_server *cfg = obj;
struct ast_aeap_client_config *cfg = obj;
ast_string_field_free_memory(cfg);
ao2_cleanup(cfg->codecs);
}
static void *aeap_server_alloc(const char *name)
static void *client_config_alloc(const char *name)
{
struct aeap_server *cfg;
struct ast_aeap_client_config *cfg;
cfg = ast_sorcery_generic_alloc(sizeof(*cfg), aeap_server_destructor);
cfg = ast_sorcery_generic_alloc(sizeof(*cfg), client_config_destructor);
if (!cfg) {
return NULL;
}
@@ -97,32 +111,52 @@ static void *aeap_server_alloc(const char *name)
return cfg;
}
static int aeap_server_apply(const struct ast_sorcery *sorcery, void *obj)
static int client_config_apply(const struct ast_sorcery *sorcery, void *obj)
{
struct aeap_server *cfg = obj;
struct ast_aeap_client_config *cfg = obj;
if (ast_strlen_zero(cfg->server_url)) {
ast_log(LOG_ERROR, "AEAP - Server URL must be present for server '%s'\n", ast_sorcery_object_get_id(cfg));
if (ast_strlen_zero(cfg->url)) {
ast_log(LOG_ERROR, "AEAP - URL must be present for '%s'\n", ast_sorcery_object_get_id(cfg));
return -1;
}
if (!ast_begins_with(cfg->server_url, "ws")) {
ast_log(LOG_ERROR, "AEAP - Server URL must be ws or wss for server '%s'\n", ast_sorcery_object_get_id(cfg));
if (!ast_begins_with(cfg->url, "ws")) {
ast_log(LOG_ERROR, "AEAP - URL must be ws or wss for '%s'\n", ast_sorcery_object_get_id(cfg));
return -1;
}
return 0;
}
static struct aeap_server *aeap_server_get(const char *id)
const struct ast_format_cap *ast_aeap_client_config_codecs(const struct ast_aeap_client_config *cfg)
{
return ast_sorcery_retrieve_by_id(aeap_sorcery, "server", id);
return cfg->codecs;
}
static struct ao2_container *aeap_server_get_all(void)
int ast_aeap_client_config_has_protocol(const struct ast_aeap_client_config *cfg,
const char *protocol)
{
return ast_sorcery_retrieve_by_fields(aeap_sorcery, "server",
AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
return !strcmp(protocol, cfg->protocol);
}
struct ao2_container *ast_aeap_client_configs_get(const char *protocol)
{
struct ao2_container *container;
struct ast_variable *var;
var = protocol ? ast_variable_new("protocol ==", protocol, "") : NULL;
container = ast_sorcery_retrieve_by_fields(aeap_sorcery,
AEAP_CONFIG_CLIENT, AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, var);
ast_variables_destroy(var);
return container;
}
static struct ast_aeap_client_config *client_config_get(const char *id)
{
return ast_sorcery_retrieve_by_id(aeap_sorcery, AEAP_CONFIG_CLIENT, id);
}
static char *aeap_tab_complete_name(const char *word, struct ao2_container *container)
@@ -145,6 +179,8 @@ static char *aeap_tab_complete_name(const char *word, struct ao2_container *cont
}
ao2_iterator_destroy(&it);
ao2_ref(container, -1);
return NULL;
}
@@ -159,8 +195,7 @@ static int aeap_cli_show(void *obj, void *arg, int flags)
return 0;
}
options = ast_variable_list_sort(ast_sorcery_objectset_create2(
aeap_sorcery, obj, AST_HANDLER_ONLY_STRING));
options = ast_variable_list_sort(ast_sorcery_objectset_create(aeap_sorcery, obj));
if (!options) {
return 0;
}
@@ -179,20 +214,20 @@ static int aeap_cli_show(void *obj, void *arg, int flags)
return 0;
}
static char *aeap_server_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
static char *client_config_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct aeap_server *cfg;
struct ast_aeap_client_config *cfg;
switch(cmd) {
case CLI_INIT:
e->command = "aeap show server";
e->command = "aeap show client";
e->usage =
"Usage: aeap show server <id>\n"
" Show the AEAP settings for a given server\n";
"Usage: aeap show client <id>\n"
" Show the AEAP settings for a given client\n";
return NULL;
case CLI_GENERATE:
if (a->pos == 3) {
return aeap_tab_complete_name(a->word, aeap_server_get_all());
return aeap_tab_complete_name(a->word, ast_aeap_client_configs_get(NULL));
} else {
return NULL;
}
@@ -202,23 +237,23 @@ static char *aeap_server_show(struct ast_cli_entry *e, int cmd, struct ast_cli_a
return CLI_SHOWUSAGE;
}
cfg = aeap_server_get(a->argv[3]);
cfg = client_config_get(a->argv[3]);
aeap_cli_show(cfg, a, 0);
ao2_cleanup(cfg);
return CLI_SUCCESS;
}
static char *aeap_server_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
static char *client_config_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
struct ao2_container *container;
switch(cmd) {
case CLI_INIT:
e->command = "aeap show servers";
e->command = "aeap show clients";
e->usage =
"Usage: aeap show servers\n"
" Show all configured AEAP servers\n";
"Usage: aeap show clients\n"
" Show all configured AEAP clients\n";
return NULL;
case CLI_GENERATE:
return NULL;
@@ -228,9 +263,9 @@ static char *aeap_server_show_all(struct ast_cli_entry *e, int cmd, struct ast_c
return CLI_SHOWUSAGE;
}
container = aeap_server_get_all();
container = ast_aeap_client_configs_get(NULL);
if (!container || ao2_container_count(container) == 0) {
ast_cli(a->fd, "No AEAP servers found\n");
ast_cli(a->fd, "No AEAP clients found\n");
ao2_cleanup(container);
return CLI_SUCCESS;
}
@@ -242,12 +277,75 @@ static char *aeap_server_show_all(struct ast_cli_entry *e, int cmd, struct ast_c
}
static struct ast_cli_entry aeap_cli[] = {
AST_CLI_DEFINE(aeap_server_show, "Show AEAP server configuration by id"),
AST_CLI_DEFINE(aeap_server_show_all, "Show all AEAP server configurations"),
AST_CLI_DEFINE(client_config_show, "Show AEAP client configuration by id"),
AST_CLI_DEFINE(client_config_show_all, "Show all AEAP client configurations"),
};
static struct ast_aeap *aeap_create(const char *id, const struct ast_aeap_params *params,
int connect, int timeout)
{
struct ast_aeap_client_config *cfg;
struct ast_aeap *aeap;
const char *url = NULL;
const char *protocol = NULL;
cfg = client_config_get(id);
if (cfg) {
url = cfg->url;
protocol = cfg->protocol;
}
#ifdef TEST_FRAMEWORK
else if (ast_begins_with(id, "_aeap_test_")) {
url = "ws://127.0.0.1:8088/ws";
protocol = id;
}
#endif
if (!url && !protocol) {
ast_log(LOG_ERROR, "AEAP: unable to get configuration for '%s'\n", id);
return NULL;
}
aeap = connect ? ast_aeap_create_and_connect(url, params, url, protocol, timeout) :
ast_aeap_create(url, params);
ao2_cleanup(cfg);
return aeap;
}
struct ast_aeap *ast_aeap_create_by_id(const char *id, const struct ast_aeap_params *params)
{
return aeap_create(id, params, 0, 0);
}
struct ast_aeap *ast_aeap_create_and_connect_by_id(const char *id,
const struct ast_aeap_params *params, int timeout)
{
return aeap_create(id, params, 1, timeout);
}
struct ast_variable *ast_aeap_custom_fields_get(const char *id)
{
struct ast_aeap_client_config *cfg;
struct ast_variable *vars;
cfg = client_config_get(id);
if (!cfg) {
ast_log(LOG_WARNING, "AEAP: no client configuration '%s' to get fields\n", id);
return NULL;
}
vars = ast_sorcery_objectset_create(aeap_sorcery, cfg);
ao2_ref(cfg, -1);
return vars;
}
static int reload_module(void)
{
ast_sorcery_reload(aeap_sorcery);
return 0;
}
@@ -258,28 +356,35 @@ static int unload_module(void)
ast_cli_unregister_multiple(aeap_cli, ARRAY_LEN(aeap_cli));
aeap_general_finalize();
return 0;
}
static int load_module(void)
{
if (aeap_general_initialize()) {
return AST_MODULE_LOAD_DECLINE;
}
if (!(aeap_sorcery = ast_sorcery_open()))
{
ast_log(LOG_ERROR, "AEAP - failed to open sorcery\n");
return AST_MODULE_LOAD_DECLINE;
}
ast_sorcery_apply_default(aeap_sorcery, "server", "config", "aeap.conf,criteria=type=server");
ast_sorcery_apply_default(aeap_sorcery, AEAP_CONFIG_CLIENT, "config", "aeap.conf,criteria=type=client");
if (ast_sorcery_object_register(aeap_sorcery, "server", aeap_server_alloc,
NULL, aeap_server_apply)) {
ast_log(LOG_ERROR, "AEAP - failed to register server sorcery object\n");
if (ast_sorcery_object_register(aeap_sorcery, "client", client_config_alloc,
NULL, client_config_apply)) {
ast_log(LOG_ERROR, "AEAP - failed to register client sorcery object\n");
return AST_MODULE_LOAD_DECLINE;
}
ast_sorcery_object_field_register(aeap_sorcery, "server", "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register(aeap_sorcery, "server", "server_url", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct aeap_server, server_url));
ast_sorcery_object_field_register(aeap_sorcery, "server", "codecs", "", OPT_CODEC_T, 1, FLDSET(struct aeap_server, codecs));
ast_sorcery_object_field_register(aeap_sorcery, AEAP_CONFIG_CLIENT, "type", "", OPT_NOOP_T, 0, 0);
ast_sorcery_object_field_register(aeap_sorcery, AEAP_CONFIG_CLIENT, "url", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_aeap_client_config, url));
ast_sorcery_object_field_register(aeap_sorcery, AEAP_CONFIG_CLIENT, "protocol", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_aeap_client_config, protocol));
ast_sorcery_object_field_register(aeap_sorcery, AEAP_CONFIG_CLIENT, "codecs", "", OPT_CODEC_T, 1, FLDSET(struct ast_aeap_client_config, codecs));
ast_sorcery_load(aeap_sorcery);
@@ -295,4 +400,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_
.unload = unload_module,
.reload = reload_module,
.load_pri = AST_MODPRI_CHANNEL_DEPEND,
.requires = "res_http_websocket",
);