Asterisk - The Open Source Telephony Project  21.4.1
test_aeap_speech.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2021, Sangoma Technologies Corporation
5  *
6  * Kevin Harwell <kharwell@sangoma.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 
19 /*** MODULEINFO
20  <depend>TEST_FRAMEWORK</depend>
21  <depend>res_aeap</depend>
22  <support_level>core</support_level>
23  ***/
24 
25 #include "asterisk.h"
26 
27 #include "asterisk/test.h"
28 #include "asterisk/module.h"
29 #include "asterisk/file.h"
30 #include "asterisk/format_cap.h"
31 #include "asterisk/http.h"
33 #include "asterisk/json.h"
34 #include "asterisk/speech.h"
35 
36 #include "asterisk/res_aeap.h"
38 
39 #define ADDR "127.0.0.1:8088"
40 
41 static int speech_test_server_setup(struct ast_json *req, struct ast_json *resp)
42 {
43  struct ast_json *params;
44 
45  if (ast_json_object_set(resp, "codecs", ast_json_ref(ast_json_object_get(req, "codecs")))) {
46  return -1;
47  }
48 
49  params = ast_json_object_get(req, "params"); /* Optional */
50  if (params && ast_json_object_set(resp, "params", ast_json_ref(params))) {
51  return -1;
52  }
53 
54  return 0;
55 }
56 
57 #define TEST_SPEECH_RESULTS_TEXT "foo"
58 #define TEST_SPEECH_RESULTS_SCORE 7
59 #define TEST_SPEECH_RESULTS_GRAMMAR "bar"
60 #define TEST_SPEECH_RESULTS_BEST 1
61 
62 static int speech_test_server_get(struct ast_json *req, struct ast_json *resp)
63 {
64  const char *param;
65  struct ast_json *json = NULL;
66 
68  if (!param) {
69  return -1;
70  }
71 
72  if (!strcmp(param, "results")) {
73  json = ast_json_pack("{s:[{s:s,s:i,s:s,s:i}]}",
74  param,
75  "text", TEST_SPEECH_RESULTS_TEXT,
76  "score", TEST_SPEECH_RESULTS_SCORE,
77  "grammar", TEST_SPEECH_RESULTS_GRAMMAR,
78  "best", TEST_SPEECH_RESULTS_BEST);
79  } else {
80  /* Assume setting */
81  json = ast_json_pack("{s:s}", param, "bar");
82  }
83 
84  if (!json || ast_json_object_set(resp, "params", json)) {
85  return -1;
86  }
87 
88  return 0;
89 }
90 
91 static int speech_test_server_set(struct ast_json *req, struct ast_json *resp)
92 {
93  if (ast_json_object_set(resp, "params", ast_json_ref(ast_json_object_get(req, "params")))) {
94  return -1;
95  }
96 
97  return 0;
98 }
99 
100 static int speech_test_server_handle_request(struct ast_websocket *ws, const void *buf, uint64_t size)
101 {
102  struct ast_json *req;
103  struct ast_json *resp;
104  const char *name;
105  char *resp_buf;
106  int res = 0;
107 
108  req = ast_json_load_buf(buf, size, NULL);
109  if (!req) {
110  ast_log(LOG_ERROR, "speech test handle request: unable to load json\n");
111  return -1;
112  }
113 
114  name = ast_json_object_string_get(req, "request");
115  if (!name) {
116  ast_log(LOG_ERROR, "speech test handle request: no name\n");
117  ast_json_unref(req);
118  return -1;
119  }
120 
121  resp = ast_json_pack("{s:s, s:s}", "response", name,
122  "id", ast_json_object_string_get(req, "id"));
123  if (!resp) {
124  ast_log(LOG_ERROR, "speech test handle request: unable to create response '%s'\n", name);
125  ast_json_unref(req);
126  return -1;
127  }
128 
129  if (!strcmp(name, "setup")) {
130  res = speech_test_server_setup(req, resp);
131  } else if (!strcmp(name, "get")) {
132  res = speech_test_server_get(req, resp);
133  } else if (!strcmp(name, "set")) {
134  res = speech_test_server_set(req, resp);
135  } else {
136  ast_log(LOG_ERROR, "speech test handle request: unsupported request '%s'\n", name);
137  return -1;
138  }
139 
140  if (res) {
141  ast_log(LOG_ERROR, "speech test handle request: unable to build response '%s'\n", name);
142  ast_json_unref(resp);
143  ast_json_unref(req);
144  return -1;
145  }
146 
147  resp_buf = ast_json_dump_string(resp);
148  ast_json_unref(resp);
149 
150  if (!resp_buf) {
151  ast_log(LOG_ERROR, "speech test handle request: unable to dump response '%s'\n", name);
152  ast_json_unref(req);
153  return -1;
154  }
155 
156  res = ast_websocket_write_string(ws, resp_buf);
157  if (res) {
158  ast_log(LOG_ERROR, "speech test handle request: unable to write response '%s'\n", name);
159  }
160 
161  ast_json_unref(req);
162  ast_free(resp_buf);
163 
164  return res;
165 }
166 
167 static void speech_test_server_cb(struct ast_websocket *ws, struct ast_variable *parameters,
168  struct ast_variable *headers)
169 {
170  int res;
171 
172  if (ast_fd_set_flags(ast_websocket_fd(ws), O_NONBLOCK)) {
173  ast_websocket_unref(ws);
174  return;
175  }
176 
177  while ((res = ast_websocket_wait_for_input(ws, -1)) > 0) {
178  char *payload;
179  uint64_t payload_len;
180  enum ast_websocket_opcode opcode;
181  int fragmented;
182 
183  if (ast_websocket_read(ws, &payload, &payload_len, &opcode, &fragmented)) {
184  ast_log(LOG_ERROR, "speech test: Read failure in server loop\n");
185  break;
186  }
187 
188  switch (opcode) {
190  ast_websocket_unref(ws);
191  return;
193  ast_websocket_write(ws, opcode, payload, payload_len);
194  break;
196  ast_debug(3, "payload=%.*s\n", (int)payload_len, payload);
197  if (speech_test_server_handle_request(ws, payload, payload_len)) {
198  ast_websocket_unref(ws);
199  return;
200  }
201  break;
202  default:
203  break;
204  }
205  }
206  ast_websocket_unref(ws);
207 }
208 
209 AST_TEST_DEFINE(res_speech_aeap_test)
210 {
211  RAII_VAR(struct ast_format_cap *, cap, NULL, ao2_cleanup);
212  RAII_VAR(struct ast_speech_result *, results, NULL, ast_speech_results_free);
213  struct ast_speech *speech = NULL;
214  enum ast_test_result_state res = AST_TEST_PASS;
215  char buf[8] = "";
216 
217  switch (cmd) {
218  case TEST_INIT:
219  info->name = __func__;
220  info->explicit_only = 0;
221  info->category = "/res/aeap/speech/";
222  info->summary = "test the speech AEAP interface";
223  info->description = info->summary;
224  return AST_TEST_NOT_RUN;
225  case TEST_EXECUTE:
226  break;
227  }
228 
229  ast_test_validate(test, !ast_websocket_add_protocol("_aeap_test_speech_", speech_test_server_cb));
230 
231  ast_test_validate(test, (cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)));
232  ast_test_validate(test, !ast_format_cap_update_by_allow_disallow(cap, "ulaw", 1));
233 
234  ast_test_validate_cleanup(test, (speech = ast_speech_new("_aeap_test_speech_", cap)), res, cleanup);
235  ast_speech_start(speech);
236  ast_test_validate_cleanup(test, !ast_speech_dtmf(speech, "1"), res, cleanup);
237  ast_test_validate_cleanup(test, !ast_speech_change(speech, "foo", "bar"), res, cleanup);
238  ast_test_validate_cleanup(test, !ast_speech_change_results_type(
239  speech, AST_SPEECH_RESULTS_TYPE_NBEST), res, cleanup);
240 
241  ast_test_validate_cleanup(test, !ast_speech_get_setting(
242  speech, "foo", buf, sizeof(buf)), res, cleanup);
243  ast_test_validate_cleanup(test, !strcmp(buf, "bar"), res, cleanup);
244 
245  ast_test_validate_cleanup(test, (results = ast_speech_results_get(speech)), res, cleanup);
246  ast_test_validate_cleanup(test, !strcmp(results->text, TEST_SPEECH_RESULTS_TEXT), res, cleanup);
247  ast_test_validate_cleanup(test, results->score == TEST_SPEECH_RESULTS_SCORE, res, cleanup);
248  ast_test_validate_cleanup(test, !strcmp(results->grammar, TEST_SPEECH_RESULTS_GRAMMAR), res, cleanup);
249  ast_test_validate_cleanup(test, results->nbest_num == TEST_SPEECH_RESULTS_BEST, res, cleanup);
250 
251 cleanup:
252  if (speech) {
253  ast_speech_destroy(speech);
254  }
255  ast_websocket_remove_protocol("_aeap_test_speech_", speech_test_server_cb);
256 
257  return res;
258 }
259 
260 static struct ast_http_server *http_server;
261 
262 static int load_module(void)
263 {
264  if (!(http_server = ast_http_test_server_get("aeap transport http server", NULL))) {
266  }
267 
268  AST_TEST_REGISTER(res_speech_aeap_test);
269 
271 }
272 
273 static int unload_module(void)
274 {
275  AST_TEST_UNREGISTER(res_speech_aeap_test);
276 
277  ast_http_test_server_discard(http_server);
278 
279  return 0;
280 }
281 
282 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Asterisk External Application Protocol Speech test(s)",
283  .support_level = AST_MODULE_SUPPORT_CORE,
284  .load = load_module,
285  .unload = unload_module,
286  .requires = "res_speech_aeap",
287 );
Asterisk External Application Protocol API.
struct ast_json * ast_json_ref(struct ast_json *value)
Increase refcount on value.
Definition: json.c:67
Asterisk main include file. File version handling, generic pbx functions.
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:612
Generic Speech Recognition API.
int ast_speech_destroy(struct ast_speech *speech)
Destroy a speech structure.
Definition: res_speech.c:251
struct ast_json * ast_json_load_buf(const char *buffer, size_t buflen, struct ast_json_error *error)
Parse buffer with known length into a JSON object or array.
Definition: json.c:585
void ast_speech_start(struct ast_speech *speech)
Indicate to the speech engine that audio is now going to start being written.
Definition: res_speech.c:122
Asterisk External Application Protocol Message API.
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
Structure for variables, used for configurations and for channel variables.
#define ast_json_dump_string(root)
Encode a JSON value to a compact string.
Definition: json.h:810
Test Framework API.
struct ast_speech_result * ast_speech_results_get(struct ast_speech *speech)
Get speech recognition results.
Definition: res_speech.c:90
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
char * grammar
Definition: speech.h:119
int ast_json_object_set(struct ast_json *object, const char *key, struct ast_json *value)
Set a field in a JSON object.
Definition: json.c:414
int ast_speech_change_results_type(struct ast_speech *speech, enum ast_speech_results_type results_type)
Change the type of results we want.
Definition: res_speech.c:308
int ast_format_cap_update_by_allow_disallow(struct ast_format_cap *cap, const char *list, int allowing)
Parse an "allow" or "deny" list and modify a format capabilities structure accordingly.
Definition: format_cap.c:320
Support for Private Asterisk HTTP Servers.
#define ast_fd_set_flags(fd, flags)
Set flags on the given file descriptor.
Definition: utils.h:1039
static void cleanup(void)
Clean up any old apps that we don't need any more.
Definition: res_stasis.c:327
Asterisk JSON abstraction layer.
#define ast_json_object_string_get(object, key)
Get a string field from a JSON object.
Definition: json.h:600
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:283
#define ast_debug(level,...)
Log a DEBUG message.
struct ast_speech_result * results
Definition: speech.h:68
Support for WebSocket connections within the Asterisk HTTP server and client WebSocket connections to...
#define ast_format_cap_alloc(flags)
Allocate a new ast_format_cap structure.
Definition: format_cap.h:49
Format Capabilities API.
Format capabilities structure, holds formats + preference order + etc.
Definition: format_cap.c:54
int AST_OPTIONAL_API_NAME() ast_websocket_write(struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t payload_size)
Write function for websocket traffic.
Structure definition for session.
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
int ast_speech_results_free(struct ast_speech_result *result)
Free a set of results.
Definition: res_speech.c:96
int ast_speech_dtmf(struct ast_speech *speech, const char *dtmf)
Signal to the engine that DTMF was received.
Definition: res_speech.c:154
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:407
struct ast_speech * ast_speech_new(const char *engine_name, const struct ast_format_cap *formats)
Create a new speech structure.
Definition: res_speech.c:181
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
Abstract JSON element (object, array, string, int, ...).
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
int ast_speech_get_setting(struct ast_speech *speech, const char *name, char *buf, size_t len)
Get an engine specific attribute.
Definition: res_speech.c:175
Asterisk module definitions.
ast_websocket_opcode
WebSocket operation codes.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
int ast_speech_change(struct ast_speech *speech, const char *name, const char *value)
Change an engine specific attribute.
Definition: res_speech.c:169
struct ast_json * ast_json_array_get(const struct ast_json *array, size_t index)
Get an element from an array.
Definition: json.c:370