1 /*
2 * mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
3 * Copyright (C) 2013-2015, Grasshopper
4 *
5 * Version: MPL 1.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * The Original Code is mod_rayo for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
18 *
19 * The Initial Developer of the Original Code is Grasshopper
20 * Portions created by the Initial Developer are Copyright (C)
21 * the Initial Developer. All Rights Reserved.
22 *
23 * Contributor(s):
24 * Chris Rienzo <chris.rienzo@grasshopper.com>
25 *
26 * rayo_input_component.c -- Rayo input component implementation
27 *
28 */
29 #include "rayo_components.h"
30 #include "rayo_cpa_component.h"
31 #include "rayo_elements.h"
32 #include "srgs.h"
33 #include "nlsml.h"
34
35 #define MAX_DTMF 256
36
37 #define INPUT_MATCH_TAG "match"
38 #define INPUT_MATCH INPUT_MATCH_TAG, RAYO_INPUT_COMPLETE_NS
39 #define INPUT_NOINPUT "noinput", RAYO_INPUT_COMPLETE_NS
40 #define INPUT_NOMATCH "nomatch", RAYO_INPUT_COMPLETE_NS
41
42 #define RAYO_INPUT_COMPONENT_PRIVATE_VAR "__rayo_input_component"
43
44 struct input_handler;
45
46 static struct {
47 /** grammar parser */
48 struct srgs_parser *parser;
49 /** default recognizer to use if none specified */
50 const char *default_recognizer;
51 } globals;
52
53 /**
54 * Input component state
55 */
56 struct input_component {
57 /** component base class */
58 struct rayo_component base;
59 /** true if speech detection */
60 int speech_mode;
61 /** Number of collected digits */
62 int num_digits;
63 /** Terminating digit */
64 char term_digit;
65 /** The collected digits */
66 char digits[MAX_DTMF + 1];
67 /** grammar to match */
68 struct srgs_grammar *grammar;
69 /** time when last digit was received */
70 switch_time_t last_digit_time;
71 /** timeout before first digit is received */
72 int initial_timeout;
73 /** maximum silence allowed */
74 int max_silence;
75 /** minimum speech detection confidence */
76 double min_confidence;
77 /** sensitivity to background noise */
78 double sensitivity;
79 /** timeout after first digit is received */
80 int inter_digit_timeout;
81 /** stop flag */
82 int stop;
83 /** true if input timers started */
84 int start_timers;
85 /** true if event fired for first digit / start of speech */
86 int barge_event;
87 /** optional language to use */
88 const char *language;
89 /** optional recognizer to use */
90 const char *recognizer;
91 /** global data */
92 struct input_handler *handler;
93 };
94
95 #define INPUT_COMPONENT(x) ((struct input_component *)x)
96
97 /**
98 * Call input state
99 */
100 struct input_handler {
101 /** media bug to monitor frames / control input lifecycle */
102 switch_media_bug_t *bug;
103 /** active voice input component */
104 struct input_component *voice_component;
105 /** active dtmf input components */
106 switch_hash_t *dtmf_components;
107 /** synchronizes media bug and dtmf callbacks */
108 switch_mutex_t *mutex;
109 /** last recognizer used */
110 const char *last_recognizer;
111 };
112
113 /**
114 * @param digit1 to match
115 * @param digit2 to match
116 * @return true if matching
117 */
digit_test(char digit1,char digit2)118 static int digit_test(char digit1, char digit2)
119 {
120 return digit1 && digit2 && tolower(digit1) == tolower(digit2);
121 }
122
123 /**
124 * Send match event to client
125 */
send_match_event(struct rayo_component * component,iks * result)126 static void send_match_event(struct rayo_component *component, iks *result)
127 {
128 iks *event = rayo_component_create_complete_event(RAYO_COMPONENT(component), INPUT_MATCH);
129 iks *match = iks_find(iks_find(event, "complete"), INPUT_MATCH_TAG);
130 iks_insert_attrib(match, "content-type", "application/nlsml+xml");
131 iks_insert_cdata(match, iks_string(iks_stack(result), result), 0);
132 rayo_component_send_complete_event(component, event);
133 }
134
135 /**
136 * Send barge-in event to client
137 */
send_barge_event(struct rayo_component * component)138 static void send_barge_event(struct rayo_component *component)
139 {
140 iks *event = iks_new("presence");
141 iks *x;
142 iks_insert_attrib(event, "from", RAYO_JID(component));
143 iks_insert_attrib(event, "to", component->client_jid);
144 x = iks_insert(event, "start-of-input");
145 iks_insert_attrib(x, "xmlns", RAYO_INPUT_NS);
146 RAYO_SEND_REPLY(component, component->client_jid, event);
147 }
148
149 /**
150 * Check if dtmf component has timed out
151 */
dtmf_component_check_timeout(struct input_component * component,switch_core_session_t * session)152 static switch_status_t dtmf_component_check_timeout(struct input_component *component, switch_core_session_t *session)
153 {
154 /* check for stopped component */
155 if (component->stop) {
156 rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_STOP);
157
158 /* let handler know component is done */
159 return SWITCH_STATUS_FALSE;
160 }
161
162 /* check for timeout */
163 if (component->start_timers) {
164 int elapsed_ms = (switch_micro_time_now() - component->last_digit_time) / 1000;
165 if (component->num_digits && component->inter_digit_timeout > 0 && elapsed_ms > component->inter_digit_timeout) {
166 enum srgs_match_type match;
167 const char *interpretation = NULL;
168
169 /* we got some input, check for match */
170 match = srgs_grammar_match(component->grammar, component->digits, &interpretation);
171 if (match == SMT_MATCH || match == SMT_MATCH_END) {
172 iks *result = nlsml_create_dtmf_match(component->digits, interpretation);
173 /* notify of match */
174 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "MATCH = %s\n", component->digits);
175 send_match_event(RAYO_COMPONENT(component), result);
176 iks_delete(result);
177 } else {
178 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "inter-digit-timeout\n");
179 rayo_component_send_complete(RAYO_COMPONENT(component), INPUT_NOMATCH);
180 }
181
182 /* let handler know component is done */
183 return SWITCH_STATUS_FALSE;
184 } else if (!component->num_digits && component->initial_timeout > 0 && elapsed_ms > component->initial_timeout) {
185 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "initial-timeout\n");
186 rayo_component_send_complete(RAYO_COMPONENT(component), INPUT_NOINPUT);
187
188 /* let handler know component is done */
189 return SWITCH_STATUS_FALSE;
190 }
191 }
192 return SWITCH_STATUS_SUCCESS;
193 }
194
195 /**
196 * Process DTMF press for a specific component
197 * @param component to receive DTMF
198 * @param session
199 * @param dtmf
200 * @param direction
201 * @return SWITCH_STATUS_FALSE if component is done
202 */
dtmf_component_on_dtmf(struct input_component * component,switch_core_session_t * session,const switch_dtmf_t * dtmf,switch_dtmf_direction_t direction)203 static switch_status_t dtmf_component_on_dtmf(struct input_component *component, switch_core_session_t *session, const switch_dtmf_t *dtmf, switch_dtmf_direction_t direction)
204 {
205 int is_term_digit = 0;
206 enum srgs_match_type match;
207 const char *interpretation = NULL;
208
209 is_term_digit = digit_test(component->term_digit, dtmf->digit);
210
211 if (!is_term_digit) {
212 component->digits[component->num_digits] = dtmf->digit;
213 component->num_digits++;
214 component->digits[component->num_digits] = '\0';
215 component->last_digit_time = switch_micro_time_now();
216 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Collected digits = \"%s\"\n", component->digits);
217 } else {
218 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Collected term digit = \"%c\"\n", dtmf->digit);
219 }
220
221 match = srgs_grammar_match(component->grammar, component->digits, &interpretation);
222
223 if (is_term_digit) {
224 /* finalize result if terminating digit was pressed */
225 if (match == SMT_MATCH_PARTIAL) {
226 match = SMT_NO_MATCH;
227 } else if (match == SMT_MATCH) {
228 match = SMT_MATCH_END;
229 }
230 } else if (component->num_digits >= MAX_DTMF) {
231 /* maximum digits collected and still not a definitive match */
232 if (match != SMT_MATCH_END) {
233 match = SMT_NO_MATCH;
234 }
235 }
236
237 switch (match) {
238 case SMT_MATCH:
239 case SMT_MATCH_PARTIAL: {
240 /* need more digits */
241 if (component->num_digits == 1) {
242 send_barge_event(RAYO_COMPONENT(component));
243 }
244 break;
245 }
246 case SMT_NO_MATCH: {
247 /* notify of no-match and remove input component */
248 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "NO MATCH = %s\n", component->digits);
249 rayo_component_send_complete(RAYO_COMPONENT(component), INPUT_NOMATCH);
250
251 /* let handler know component is done */
252 return SWITCH_STATUS_FALSE;
253 }
254 case SMT_MATCH_END: {
255 iks *result = nlsml_create_dtmf_match(component->digits, interpretation);
256 /* notify of match and remove input component */
257 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "MATCH = %s\n", component->digits);
258 send_match_event(RAYO_COMPONENT(component), result);
259 iks_delete(result);
260
261 /* let handler know component is done */
262 return SWITCH_STATUS_FALSE;
263 }
264 }
265
266 /* still need more input */
267 return SWITCH_STATUS_SUCCESS;
268 }
269
270 /**
271 * Process DTMF press on call
272 */
input_handler_on_dtmf(switch_core_session_t * session,const switch_dtmf_t * dtmf,switch_dtmf_direction_t direction)273 static switch_status_t input_handler_on_dtmf(switch_core_session_t *session, const switch_dtmf_t *dtmf, switch_dtmf_direction_t direction)
274 {
275 switch_channel_t *channel = switch_core_session_get_channel(session);
276 struct input_handler *handler = (struct input_handler *)switch_channel_get_private(channel, RAYO_INPUT_COMPONENT_PRIVATE_VAR);
277
278 if (handler) {
279 switch_event_t *components_to_remove = NULL;
280 switch_hash_index_t *hi;
281
282 switch_mutex_lock(handler->mutex);
283
284 /* check input on each component */
285 for (hi = switch_core_hash_first(handler->dtmf_components); hi; hi = switch_core_hash_next(&hi)) {
286 const void *jid;
287 void *component;
288 switch_core_hash_this(hi, &jid, NULL, &component);
289 if (dtmf_component_on_dtmf(INPUT_COMPONENT(component), session, dtmf, direction) != SWITCH_STATUS_SUCCESS) {
290 if (!components_to_remove) {
291 switch_event_create_subclass(&components_to_remove, SWITCH_EVENT_CLONE, NULL);
292 }
293 switch_event_add_header_string(components_to_remove, SWITCH_STACK_BOTTOM, "done", RAYO_JID(component));
294 }
295 }
296
297 /* remove any finished components */
298 if (components_to_remove) {
299 switch_event_header_t *component_to_remove = NULL;
300 for (component_to_remove = components_to_remove->headers; component_to_remove; component_to_remove = component_to_remove->next) {
301 switch_core_hash_delete(handler->dtmf_components, component_to_remove->value);
302 }
303 switch_event_destroy(&components_to_remove);
304 }
305
306 switch_mutex_unlock(handler->mutex);
307 }
308 return SWITCH_STATUS_SUCCESS;
309 }
310
311 /**
312 * Monitor for input
313 */
input_handler_bug_callback(switch_media_bug_t * bug,void * user_data,switch_abc_type_t type)314 static switch_bool_t input_handler_bug_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
315 {
316 switch_core_session_t *session = switch_core_media_bug_get_session(bug);
317 struct input_handler *handler = (struct input_handler *)user_data;
318 switch_hash_index_t *hi;
319
320 switch_mutex_lock(handler->mutex);
321
322 switch(type) {
323 case SWITCH_ABC_TYPE_INIT: {
324 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Adding DTMF callback\n");
325 switch_core_event_hook_add_recv_dtmf(session, input_handler_on_dtmf);
326 break;
327 }
328 case SWITCH_ABC_TYPE_READ_REPLACE: {
329 switch_frame_t *rframe = switch_core_media_bug_get_read_replace_frame(bug);
330 switch_event_t *components_to_remove = NULL;
331
332 /* check timeout/stop on each component */
333 for (hi = switch_core_hash_first(handler->dtmf_components); hi; hi = switch_core_hash_next(&hi)) {
334 const void *jid;
335 void *component;
336 switch_core_hash_this(hi, &jid, NULL, &component);
337 if (dtmf_component_check_timeout(INPUT_COMPONENT(component), session) != SWITCH_STATUS_SUCCESS) {
338 if (!components_to_remove) {
339 switch_event_create_subclass(&components_to_remove, SWITCH_EVENT_CLONE, NULL);
340 }
341 switch_event_add_header_string(components_to_remove, SWITCH_STACK_BOTTOM, "done", RAYO_JID(component));
342 }
343 }
344
345 /* remove any finished components */
346 if (components_to_remove) {
347 switch_event_header_t *component_to_remove = NULL;
348 for (component_to_remove = components_to_remove->headers; component_to_remove; component_to_remove = component_to_remove->next) {
349 switch_core_hash_delete(handler->dtmf_components, component_to_remove->value);
350 }
351 switch_event_destroy(&components_to_remove);
352 }
353
354 switch_core_media_bug_set_read_replace_frame(bug, rframe);
355 break;
356 }
357 case SWITCH_ABC_TYPE_CLOSE:
358 /* complete all components */
359 for (hi = switch_core_hash_first(handler->dtmf_components); hi; hi = switch_core_hash_next(&hi)) {
360 const void *jid;
361 void *component;
362 switch_core_hash_this(hi, &jid, NULL, &component);
363 rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_HANGUP);
364 }
365 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Removing DTMF callback\n");
366 switch_core_event_hook_remove_recv_dtmf(session, input_handler_on_dtmf);
367 switch_core_hash_destroy(&handler->dtmf_components);
368 break;
369 default:
370 break;
371 }
372 switch_mutex_unlock(handler->mutex);
373 return SWITCH_TRUE;
374 }
375
376 /**
377 * Validate input request
378 * @param input request to validate
379 * @param error message
380 * @return 0 if error, 1 if valid
381 */
validate_call_input(iks * input,const char ** error)382 static int validate_call_input(iks *input, const char **error)
383 {
384 iks *grammar;
385 const char *content_type;
386 int has_grammar = 0;
387 int use_mrcp = 0;
388
389 /* validate input attributes */
390 if (!VALIDATE_RAYO_INPUT(input)) {
391 *error = "Bad <input> attrib value";
392 return 0;
393 }
394
395 use_mrcp = !strncmp("unimrcp", iks_find_attrib(input, "recognizer") ? iks_find_attrib(input, "recognizer") : globals.default_recognizer, 7);
396
397 /* validate grammar elements */
398 for (grammar = iks_find(input, "grammar"); grammar; grammar = iks_next_tag(grammar)) {
399 /* is this a grammar? */
400 if (strcmp("grammar", iks_name(grammar))) {
401 continue;
402 }
403 content_type = iks_find_attrib(grammar, "content-type");
404 if (zstr(content_type)) {
405 /* grammar URL */
406 if (zstr(iks_find_attrib(grammar, "url"))) {
407 *error = "url or content-type must be set";
408 return 0;
409 } else if (!use_mrcp) {
410 *error = "url only supported with unimrcp recognizer";
411 return 0;
412 }
413 } else {
414 /* inline grammar / only support srgs */
415 if (!zstr(iks_find_attrib(grammar, "url"))) {
416 *error = "url not allowed with content-type";
417 return 0;
418 } else if (strcmp("application/srgs+xml", content_type) && strcmp("text/plain", content_type)) {
419 *error = "Unsupported content type";
420 return 0;
421 }
422
423 /* missing inline grammar body */
424 if (zstr(iks_find_cdata(input, "grammar"))) {
425 *error = "Grammar content is missing";
426 return 0;
427 }
428 }
429 has_grammar = 1;
430 }
431
432 if (!has_grammar) {
433 *error = "Missing <grammar>";
434 return 0;
435 }
436
437 return 1;
438 }
439
setup_grammars_pocketsphinx(struct input_component * component,switch_core_session_t * session,iks * input,const struct xmpp_error ** stanza_error,const char ** error_detail)440 static char *setup_grammars_pocketsphinx(struct input_component *component, switch_core_session_t *session, iks *input, const struct xmpp_error **stanza_error, const char **error_detail)
441 {
442 const char *jsgf_path;
443 switch_stream_handle_t grammar = { 0 };
444 SWITCH_STANDARD_STREAM(grammar);
445
446 /* transform SRGS grammar to JSGF */
447 if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) {
448 *stanza_error = STANZA_ERROR_BAD_REQUEST;
449 *error_detail = "Failed to parse grammar body";
450 switch_safe_free(grammar.data);
451 return NULL;
452 }
453
454 jsgf_path = srgs_grammar_to_jsgf_file(component->grammar, SWITCH_GLOBAL_dirs.grammar_dir, "gram");
455 if (!jsgf_path) {
456 *stanza_error = STANZA_ERROR_BAD_REQUEST;
457 *error_detail = "Grammar conversion to JSGF error";
458 switch_safe_free(grammar.data);
459 return NULL;
460 }
461
462 /* build pocketsphinx grammar string */
463 grammar.write_function(&grammar,
464 "{start-input-timers=%s,no-input-timeout=%d,speech-timeout=%d,confidence-threshold=%d}%s",
465 component->start_timers ? "true" : "false",
466 component->initial_timeout,
467 component->max_silence,
468 (int)ceil(component->min_confidence * 100.0),
469 jsgf_path);
470
471 return (char *)grammar.data;
472 }
473
setup_grammars_unimrcp(struct input_component * component,switch_core_session_t * session,iks * input,const struct xmpp_error ** stanza_error,const char ** error_detail)474 static char *setup_grammars_unimrcp(struct input_component *component, switch_core_session_t *session, iks *input, const struct xmpp_error **stanza_error, const char **error_detail)
475 {
476 iks *grammar_tag;
477 switch_asr_handle_t *ah;
478 switch_stream_handle_t grammar_uri_list = { 0 };
479 SWITCH_STANDARD_STREAM(grammar_uri_list);
480
481 /* unlock handler mutex, otherwise deadlock will happen when switch_ivr_detect_speech_init adds a new media bug */
482 switch_mutex_unlock(component->handler->mutex);
483 ah = switch_core_session_alloc(session, sizeof(*ah));
484 if (switch_ivr_detect_speech_init(session, component->recognizer, "", ah) != SWITCH_STATUS_SUCCESS) {
485 switch_mutex_lock(component->handler->mutex);
486 *stanza_error = STANZA_ERROR_INTERNAL_SERVER_ERROR;
487 *error_detail = "Failed to initialize recognizer";
488 switch_safe_free(grammar_uri_list.data);
489 return NULL;
490 }
491 switch_mutex_lock(component->handler->mutex);
492
493 /* handle input config */
494 switch_core_asr_text_param(ah, "start-input-timers", component->start_timers ? "true" : "false");
495 switch_core_asr_text_param(ah, "confidence-threshold", switch_core_sprintf(RAYO_POOL(component), "%f", component->min_confidence));
496 switch_core_asr_text_param(ah, "sensitivity-level", switch_core_sprintf(RAYO_POOL(component), "%f", component->sensitivity));
497 if (component->initial_timeout > 0) {
498 switch_core_asr_text_param(ah, "no-input-timeout", switch_core_sprintf(RAYO_POOL(component), "%d", component->initial_timeout));
499 }
500 if (component->max_silence > 0) {
501 switch_core_asr_text_param(ah, "speech-complete-timeout", switch_core_sprintf(RAYO_POOL(component), "%d", component->max_silence));
502 switch_core_asr_text_param(ah, "speech-incomplete-timeout", switch_core_sprintf(RAYO_POOL(component), "%d", component->max_silence));
503 }
504 if (!zstr(component->language)) {
505 switch_core_asr_text_param(ah, "speech-language", component->language);
506 }
507 if (!strcmp(iks_find_attrib_soft(input, "mode"), "any") || !strcmp(iks_find_attrib_soft(input, "mode"), "dtmf")) {
508 /* set dtmf params */
509 if (component->inter_digit_timeout > 0) {
510 switch_core_asr_text_param(ah, "dtmf-interdigit-timeout", switch_core_sprintf(RAYO_POOL(component), "%d", component->inter_digit_timeout));
511 }
512 if (component->term_digit) {
513 switch_core_asr_text_param(ah, "dtmf-term-char", switch_core_sprintf(RAYO_POOL(component), "%c", component->term_digit));
514 }
515 }
516
517 /* override input configs w/ custom headers */
518 {
519 iks *header = NULL;
520 for (header = iks_find(input, "header"); header; header = iks_next_tag(header)) {
521 if (!strcmp("header", iks_name(header))) {
522 const char *name = iks_find_attrib_soft(header, "name");
523 const char *value = iks_find_attrib_soft(header, "value");
524 if (!zstr(name) && !zstr(value)) {
525 switch_core_asr_text_param(ah, (char *)name, value);
526 }
527 }
528 }
529 }
530
531 switch_core_asr_text_param(ah, "start-recognize", "false");
532 switch_core_asr_text_param(ah, "define-grammar", "true");
533 for (grammar_tag = iks_find(input, "grammar"); grammar_tag; grammar_tag = iks_next_tag(grammar_tag)) {
534 const char *grammar_name;
535 iks *grammar_cdata;
536 const char *grammar;
537
538 /* is this a grammar? */
539 if (strcmp("grammar", iks_name(grammar_tag))) {
540 continue;
541 }
542
543 if (!zstr(iks_find_attrib_soft(grammar_tag, "content-type"))) {
544 /* get the srgs contained in this grammar */
545 if (!(grammar_cdata = iks_child(grammar_tag)) || iks_type(grammar_cdata) != IKS_CDATA) {
546 *stanza_error = STANZA_ERROR_BAD_REQUEST;
547 *error_detail = "Missing grammar";
548 switch_safe_free(grammar_uri_list.data);
549 return NULL;
550 }
551 grammar = switch_core_sprintf(RAYO_POOL(component), "inline:%s", iks_cdata(grammar_cdata));
552 } else {
553 /* Grammar is at a URL */
554 grammar = iks_find_attrib_soft(grammar_tag, "url");
555 if (zstr(grammar)) {
556 *stanza_error = STANZA_ERROR_BAD_REQUEST;
557 *error_detail = "Missing grammar";
558 switch_safe_free(grammar_uri_list.data);
559 return NULL;
560 }
561 if (strncasecmp(grammar, "http", 4) && strncasecmp(grammar, "file", 4)) {
562 *stanza_error = STANZA_ERROR_BAD_REQUEST;
563 *error_detail = "Bad URL";
564 switch_safe_free(grammar_uri_list.data);
565 return NULL;
566 }
567 }
568 grammar_name = switch_core_sprintf(RAYO_POOL(component), "grammar-%d", rayo_actor_seq_next(RAYO_ACTOR(component)));
569
570 /* DEFINE-GRAMMAR */
571 /* unlock handler mutex, otherwise deadlock will happen if switch_ivr_detect_speech_load_grammar removes the media bug */
572 switch_mutex_unlock(component->handler->mutex);
573 if (switch_ivr_detect_speech_load_grammar(session, grammar, grammar_name) != SWITCH_STATUS_SUCCESS) {
574 switch_mutex_lock(component->handler->mutex);
575 *stanza_error = STANZA_ERROR_INTERNAL_SERVER_ERROR;
576 *error_detail = "Failed to load grammar";
577 switch_safe_free(grammar_uri_list.data);
578 return NULL;
579 }
580 switch_mutex_lock(component->handler->mutex);
581
582 /* add grammar to uri-list */
583 grammar_uri_list.write_function(&grammar_uri_list, "session:%s\r\n", grammar_name);
584 }
585 switch_core_asr_text_param(ah, "start-recognize", "true");
586 switch_core_asr_text_param(ah, "define-grammar", "false");
587
588 return (char *)grammar_uri_list.data;
589 }
590
setup_grammars_unknown(struct input_component * component,switch_core_session_t * session,iks * input,const struct xmpp_error ** stanza_error,const char ** error_detail)591 static char *setup_grammars_unknown(struct input_component *component, switch_core_session_t *session, iks *input, const struct xmpp_error **stanza_error, const char **error_detail)
592 {
593 switch_stream_handle_t grammar = { 0 };
594 SWITCH_STANDARD_STREAM(grammar);
595 grammar.write_function(&grammar, "%s", iks_find_cdata(input, "grammar"));
596 return (char *)grammar.data;
597 }
598
599 /**
600 * Start call input on voice resource
601 */
start_call_voice_input(struct input_component * component,switch_core_session_t * session,iks * input,iks * iq,int barge_in)602 static iks *start_call_voice_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, int barge_in)
603 {
604 struct input_handler *handler = component->handler;
605 char *grammar = NULL;
606 const struct xmpp_error *stanza_error = NULL;
607 const char *error_detail = NULL;
608
609 if (component->speech_mode && handler->voice_component) {
610 /* don't allow multi voice input */
611 RAYO_RELEASE(component);
612 RAYO_DESTROY(component);
613 return iks_new_error_detailed(iq, STANZA_ERROR_CONFLICT, "Multiple voice input is not allowed");
614 }
615
616 handler->voice_component = component;
617
618 if (zstr(component->recognizer)) {
619 component->recognizer = globals.default_recognizer;
620 }
621
622 /* if recognition engine is different, we can't handle this request */
623 if (!zstr(handler->last_recognizer) && strcmp(component->recognizer, handler->last_recognizer)) {
624 handler->voice_component = NULL;
625 RAYO_RELEASE(component);
626 RAYO_DESTROY(component);
627 return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Must use the same recognizer for the entire call");
628 } else if (zstr(handler->last_recognizer)) {
629 handler->last_recognizer = switch_core_session_strdup(session, component->recognizer);
630 }
631
632 if (!strcmp(component->recognizer, "pocketsphinx")) {
633 grammar = setup_grammars_pocketsphinx(component, session, input, &stanza_error, &error_detail);
634 } else if (!strncmp(component->recognizer, "unimrcp", strlen("unimrcp"))) {
635 grammar = setup_grammars_unimrcp(component, session, input, &stanza_error, &error_detail);
636 } else {
637 grammar = setup_grammars_unknown(component, session, input, &stanza_error, &error_detail);
638 }
639
640 if (!grammar) {
641 handler->voice_component = NULL;
642 RAYO_RELEASE(component);
643 RAYO_DESTROY(component);
644 return iks_new_error_detailed(iq, stanza_error, error_detail);
645 }
646
647 /* acknowledge command */
648 rayo_component_send_start(RAYO_COMPONENT(component), iq);
649
650 /* start speech detection */
651 switch_channel_set_variable(switch_core_session_get_channel(session), "fire_asr_events", "true");
652 /* unlock handler mutex, otherwise deadlock will happen if switch_ivr_detect_speech adds a media bug */
653 switch_mutex_unlock(handler->mutex);
654 if (switch_ivr_detect_speech(session, component->recognizer, grammar, "mod_rayo_grammar", "", NULL) != SWITCH_STATUS_SUCCESS) {
655 switch_mutex_lock(handler->mutex);
656 handler->voice_component = NULL;
657 rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_ERROR);
658 } else {
659 switch_mutex_lock(handler->mutex);
660 }
661 switch_safe_free(grammar);
662
663 return NULL;
664 }
665
666 /**
667 * Start call input on DTMF resource
668 */
start_call_dtmf_input(struct input_component * component,switch_core_session_t * session,iks * input,iks * iq,int barge_in)669 static iks *start_call_dtmf_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, int barge_in)
670 {
671 /* parse the grammar */
672 if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) {
673 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Failed to parse grammar body\n");
674 RAYO_RELEASE(component);
675 RAYO_DESTROY(component);
676 return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Failed to parse grammar body");
677 }
678
679 component->last_digit_time = switch_micro_time_now();
680
681 /* acknowledge command */
682 rayo_component_send_start(RAYO_COMPONENT(component), iq);
683
684 /* start dtmf input detection */
685 switch_core_hash_insert(component->handler->dtmf_components, RAYO_JID(component), component);
686
687 return NULL;
688 }
689
690 /**
691 * Start call input for the given component
692 * @param component the input or prompt component
693 * @param session the session
694 * @param input the input request
695 * @param iq the original input/prompt request
696 */
start_call_input(struct input_component * component,switch_core_session_t * session,iks * input,iks * iq,int barge_in)697 static iks *start_call_input(struct input_component *component, switch_core_session_t *session, iks *input, iks *iq, int barge_in)
698 {
699 iks *result = NULL;
700
701 /* set up input component for new detection */
702 struct input_handler *handler = (struct input_handler *)switch_channel_get_private(switch_core_session_get_channel(session), RAYO_INPUT_COMPONENT_PRIVATE_VAR);
703 if (!handler) {
704 /* create input component */
705 handler = switch_core_session_alloc(session, sizeof(*handler));
706 switch_mutex_init(&handler->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
707 switch_core_hash_init(&handler->dtmf_components);
708 switch_channel_set_private(switch_core_session_get_channel(session), RAYO_INPUT_COMPONENT_PRIVATE_VAR, handler);
709 handler->last_recognizer = "";
710
711 /* fire up media bug to monitor lifecycle */
712 if (switch_core_media_bug_add(session, "rayo_input_component", NULL, input_handler_bug_callback, handler, 0, SMBF_READ_REPLACE, &handler->bug) != SWITCH_STATUS_SUCCESS) {
713 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Failed to create input handler media bug\n");
714 RAYO_RELEASE(component);
715 RAYO_DESTROY(component);
716 return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create input handler media bug");
717 }
718 }
719
720 switch_mutex_lock(handler->mutex);
721
722 if (!handler->dtmf_components) {
723 /* handler bug was destroyed */
724 switch_mutex_unlock(handler->mutex);
725 switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Input handler media bug is closed\n");
726 RAYO_RELEASE(component);
727 RAYO_DESTROY(component);
728 return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Input handler media bug is closed\n");
729 }
730
731 component->grammar = NULL;
732 component->num_digits = 0;
733 component->digits[0] = '\0';
734 component->stop = 0;
735 component->initial_timeout = iks_find_int_attrib(input, "initial-timeout");
736 component->inter_digit_timeout = iks_find_int_attrib(input, "inter-digit-timeout");
737 component->max_silence = iks_find_int_attrib(input, "max-silence");
738 component->min_confidence = iks_find_decimal_attrib(input, "min-confidence");
739 component->sensitivity = iks_find_decimal_attrib(input, "sensitivity");
740 component->barge_event = iks_find_bool_attrib(input, "barge-event");
741 component->start_timers = iks_find_bool_attrib(input, "start-timers");
742 component->term_digit = iks_find_char_attrib(input, "terminator");
743 component->recognizer = switch_core_strdup(RAYO_POOL(component), iks_find_attrib_soft(input, "recognizer"));
744 component->language = switch_core_strdup(RAYO_POOL(component), iks_find_attrib_soft(input, "language"));
745 component->handler = handler;
746 component->speech_mode = strcmp(iks_find_attrib_soft(input, "mode"), "dtmf");
747
748 if (component->speech_mode) {
749 result = start_call_voice_input(component, session, input, iq, barge_in);
750 } else {
751 result = start_call_dtmf_input(component, session, input, iq, barge_in);
752 }
753
754 switch_mutex_unlock(handler->mutex);
755
756 return result;
757 }
758
759 /**
760 * Create input component id for session.
761 * @param session requesting component
762 * @param input request
763 * @return the ID
764 */
create_input_component_id(switch_core_session_t * session,iks * input)765 static char *create_input_component_id(switch_core_session_t *session, iks *input)
766 {
767 const char *mode = "unk";
768 if (input) {
769 mode = iks_find_attrib_soft(input, "mode");
770 if (!strcmp(mode, "dtmf")) {
771 return NULL;
772 }
773 if (!strcmp(mode, "any")) {
774 mode = "voice";
775 }
776 }
777 return switch_core_session_sprintf(session, "%s-input-%s", switch_core_session_get_uuid(session), mode);
778 }
779
780 /**
781 * Release any resources consumed by this input component
782 */
input_component_cleanup(struct rayo_actor * component)783 static void input_component_cleanup(struct rayo_actor *component)
784 {
785 if (INPUT_COMPONENT(component)->speech_mode) {
786 switch_core_session_t *session = switch_core_session_locate(component->parent->id);
787 if (session) {
788 switch_ivr_stop_detect_speech(session);
789 switch_core_session_rwunlock(session);
790 }
791 }
792 }
793
794 /**
795 * Start execution of input component
796 */
start_call_input_component(struct rayo_actor * call,struct rayo_message * msg,void * session_data)797 static iks *start_call_input_component(struct rayo_actor *call, struct rayo_message *msg, void *session_data)
798 {
799 iks *iq = msg->payload;
800 switch_core_session_t *session = (switch_core_session_t *)session_data;
801 iks *input = iks_find(iq, "input");
802 char *component_id = create_input_component_id(session, input);
803 switch_memory_pool_t *pool = NULL;
804 struct input_component *input_component = NULL;
805 const char *error = NULL;
806
807 /* Start CPA */
808 if (!strcmp(iks_find_attrib_soft(input, "mode"), "cpa")) {
809 return rayo_cpa_component_start(call, msg, session_data);
810 }
811
812 /* start input */
813 if (!validate_call_input(input, &error)) {
814 return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, error);
815 }
816
817 switch_core_new_memory_pool(&pool);
818 input_component = switch_core_alloc(pool, sizeof(*input_component));
819 input_component = INPUT_COMPONENT(rayo_component_init_cleanup(RAYO_COMPONENT(input_component), pool, RAT_CALL_COMPONENT, "input", component_id, call, iks_find_attrib(iq, "from"), input_component_cleanup));
820 if (!input_component) {
821 switch_core_destroy_memory_pool(&pool);
822 return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Failed to create input entity");
823 }
824 return start_call_input(input_component, session, input, iq, 0);
825 }
826
827 /**
828 * Stop execution of input component
829 */
stop_call_input_component(struct rayo_actor * component,struct rayo_message * msg,void * data)830 static iks *stop_call_input_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
831 {
832 iks *iq = msg->payload;
833 struct input_component *input_component = INPUT_COMPONENT(component);
834
835 if (input_component && !input_component->stop) {
836 switch_core_session_t *session = switch_core_session_locate(component->parent->id);
837 if (session) {
838 switch_mutex_lock(input_component->handler->mutex);
839 input_component->stop = 1;
840 if (input_component->speech_mode) {
841 switch_mutex_unlock(input_component->handler->mutex);
842 switch_ivr_stop_detect_speech(session);
843 switch_mutex_lock(input_component->handler->mutex);
844 rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_STOP);
845 }
846 switch_mutex_unlock(input_component->handler->mutex);
847 switch_core_session_rwunlock(session);
848 }
849 }
850 return iks_new_iq_result(iq);
851 }
852
853 /**
854 * Start input component timers
855 */
start_timers_call_input_component(struct rayo_actor * component,struct rayo_message * msg,void * data)856 static iks *start_timers_call_input_component(struct rayo_actor *component, struct rayo_message *msg, void *data)
857 {
858 iks *iq = msg->payload;
859 struct input_component *input_component = INPUT_COMPONENT(component);
860 if (input_component) {
861 switch_core_session_t *session = switch_core_session_locate(component->parent->id);
862 if (session) {
863 switch_mutex_lock(input_component->handler->mutex);
864 if (input_component->speech_mode) {
865 switch_mutex_unlock(input_component->handler->mutex);
866 switch_ivr_detect_speech_start_input_timers(session);
867 switch_mutex_lock(input_component->handler->mutex);
868 } else {
869 input_component->last_digit_time = switch_micro_time_now();
870 input_component->start_timers = 1;
871 }
872 switch_mutex_unlock(input_component->handler->mutex);
873 switch_core_session_rwunlock(session);
874 }
875 }
876 return iks_new_iq_result(iq);
877 }
878
879 /**
880 * Get text / error from result
881 */
get_detected_speech_result_text(cJSON * result_json,double * confidence,const char ** error_text)882 static const char *get_detected_speech_result_text(cJSON *result_json, double *confidence, const char **error_text)
883 {
884 const char *result_text = NULL;
885 const char *text = cJSON_GetObjectCstr(result_json, "text");
886 if (confidence) {
887 *confidence = 0.0;
888 }
889 if (!zstr(text)) {
890 cJSON *json_confidence = cJSON_GetObjectItem(result_json, "confidence");
891 if (json_confidence && json_confidence->valuedouble > 0.0) {
892 *confidence = json_confidence->valuedouble;
893 } else {
894 *confidence = 0.99;
895 }
896 result_text = text;
897 } else if (error_text) {
898 *error_text = cJSON_GetObjectCstr(result_json, "error");
899 }
900 return result_text;
901 }
902
903 /**
904 * Handle speech detection event
905 */
on_detected_speech_event(switch_event_t * event)906 static void on_detected_speech_event(switch_event_t *event)
907 {
908 const char *speech_type = switch_event_get_header(event, "Speech-Type");
909 char *event_str = NULL;
910 const char *uuid = switch_event_get_header(event, "Unique-ID");
911 switch_event_serialize(event, &event_str, SWITCH_FALSE);
912 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s\n", event_str);
913 if (!speech_type || !uuid) {
914 return;
915 }
916
917 if (!strcasecmp("detected-speech", speech_type)) {
918 char *component_id = switch_mprintf("%s-input-voice", uuid);
919 struct rayo_component *component = RAYO_COMPONENT_LOCATE(component_id);
920
921 switch_safe_free(component_id);
922 if (component) {
923 const char *result = switch_event_get_body(event);
924
925 switch_mutex_lock(INPUT_COMPONENT(component)->handler->mutex);
926 INPUT_COMPONENT(component)->handler->voice_component = NULL;
927 switch_mutex_unlock(INPUT_COMPONENT(component)->handler->mutex);
928
929 if (zstr(result)) {
930 rayo_component_send_complete(component, INPUT_NOMATCH);
931 } else {
932 if (result[0] == '{') {
933 // internal FS JSON format
934 cJSON *json_result = cJSON_Parse(result);
935 if (json_result) {
936 // examine result to determine what happened
937 double confidence = 0.0;
938 const char *error_text = NULL;
939 const char *result_text = NULL;
940 result_text = get_detected_speech_result_text(json_result, &confidence, &error_text);
941 if (!zstr(result_text)) {
942 // got result... send as NLSML
943 iks *result = nlsml_create_match(result_text, NULL, "speech", (int)(confidence * 100.0));
944 /* notify of match */
945 switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "MATCH = %s\n", result_text);
946 send_match_event(RAYO_COMPONENT(component), result);
947 iks_delete(result);
948 } else if (zstr(error_text)) {
949 // unknown error
950 switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_WARNING, "No matching text nor error in result: %s!\n", result);
951 rayo_component_send_complete(component, INPUT_NOMATCH);
952 } else if (!strcmp(error_text, "no_input")) {
953 // no input error
954 rayo_component_send_complete(component, INPUT_NOINPUT);
955 } else if (!strcmp(error_text, "no_match")) {
956 // no match error
957 rayo_component_send_complete(component, INPUT_NOMATCH);
958 } else {
959 // generic error
960 iks *response = rayo_component_create_complete_event(component, COMPONENT_COMPLETE_ERROR);
961 iks *error = NULL;
962 if ((error = iks_find(response, "complete"))) {
963 if ((error = iks_find(error, "error"))) {
964 iks_insert_cdata(error, error_text, strlen(error_text));
965 }
966 }
967 rayo_component_send_complete_event(component, response);
968 }
969 cJSON_Delete(json_result);
970 } else {
971 // failed to parse JSON result
972 switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_WARNING, "Failed to parse JSON result: %s!\n", result);
973 rayo_component_send_complete(component, INPUT_NOMATCH);
974 }
975 } else if (strchr(result, '<')) {
976 /* got an XML result */
977 enum nlsml_match_type match_type = nlsml_parse(result, uuid);
978 switch (match_type) {
979 case NMT_NOINPUT:
980 rayo_component_send_complete(component, INPUT_NOINPUT);
981 break;
982 case NMT_MATCH: {
983 iks *result_xml = nlsml_normalize(result);
984 send_match_event(RAYO_COMPONENT(component), result_xml);
985 iks_delete(result_xml);
986 break;
987 }
988 case NMT_BAD_XML:
989 switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_WARNING, "Failed to parse NLSML result: %s!\n", result);
990 rayo_component_send_complete(component, INPUT_NOMATCH);
991 break;
992 case NMT_NOMATCH:
993 rayo_component_send_complete(component, INPUT_NOMATCH);
994 break;
995 default:
996 switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_CRIT, "Unknown NLSML match type: %i, %s!\n", match_type, result);
997 rayo_component_send_complete(component, INPUT_NOMATCH);
998 break;
999 }
1000 } else if (strstr(result, "002")) {
1001 /* Completion-Cause: 002 no-input-timeout */
1002 rayo_component_send_complete(component, INPUT_NOINPUT);
1003 } else if (strstr(result, "004") || strstr(result, "005") || strstr(result, "006") || strstr(result, "009") || strstr(result, "010")) {
1004 /* Completion-Cause: 004 gram-load-failure */
1005 /* Completion-Cause: 005 gram-comp-failure */
1006 /* Completion-Cause: 006 error */
1007 /* Completion-Cause: 009 uri-failure */
1008 /* Completion-Cause: 010 language-unsupported */
1009 iks *response = rayo_component_create_complete_event(component, COMPONENT_COMPLETE_ERROR);
1010 const char *error_reason = switch_event_get_header(event, "ASR-Completion-Reason");
1011 if (!zstr(error_reason)) {
1012 iks *error;
1013 if ((error = iks_find(response, "complete"))) {
1014 if ((error = iks_find(error, "error"))) {
1015 iks_insert_cdata(error, error_reason, strlen(error_reason));
1016 }
1017 }
1018 }
1019 rayo_component_send_complete_event(component, response);
1020 } else {
1021 /* assume no match */
1022 /* Completion-Cause: 001 no-match */
1023 /* Completion-Cause: 003 recognition-timeout */
1024 /* Completion-Cause: 007 speech-too-early */
1025 /* Completion-Cause: 008 too-much-speech-timeout */
1026 rayo_component_send_complete(component, INPUT_NOMATCH);
1027 }
1028 }
1029 RAYO_RELEASE(component);
1030 }
1031 } else if (!strcasecmp("begin-speaking", speech_type)) {
1032 char *component_id = switch_mprintf("%s-input-voice", uuid);
1033 struct rayo_component *component = RAYO_COMPONENT_LOCATE(component_id);
1034 switch_safe_free(component_id);
1035 if (component && INPUT_COMPONENT(component)->barge_event) {
1036 send_barge_event(component);
1037 }
1038 RAYO_RELEASE(component);
1039 } else if (!strcasecmp("closed", speech_type)) {
1040 char *component_id = switch_mprintf("%s-input-voice", uuid);
1041 struct rayo_component *component = RAYO_COMPONENT_LOCATE(component_id);
1042 switch_safe_free(component_id);
1043 if (component) {
1044 char *channel_state = switch_event_get_header(event, "Channel-State");
1045 switch_mutex_lock(INPUT_COMPONENT(component)->handler->mutex);
1046 INPUT_COMPONENT(component)->handler->voice_component = NULL;
1047 switch_mutex_unlock(INPUT_COMPONENT(component)->handler->mutex);
1048 switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Recognizer closed\n");
1049 if (channel_state && !strcmp("CS_HANGUP", channel_state)) {
1050 rayo_component_send_complete(component, COMPONENT_COMPLETE_HANGUP);
1051 } else {
1052 /* shouldn't get here... */
1053 rayo_component_send_complete(component, COMPONENT_COMPLETE_ERROR);
1054 }
1055 RAYO_RELEASE(component);
1056 }
1057 }
1058 switch_safe_free(event_str);
1059 }
1060
1061 /**
1062 * Process module XML configuration
1063 * @param pool memory pool to allocate from
1064 * @param config_file to use
1065 * @return SWITCH_STATUS_SUCCESS on successful configuration
1066 */
do_config(switch_memory_pool_t * pool,const char * config_file)1067 static switch_status_t do_config(switch_memory_pool_t *pool, const char *config_file)
1068 {
1069 switch_xml_t cfg, xml;
1070
1071 /* set defaults */
1072 globals.default_recognizer = "pocketsphinx";
1073
1074 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Configuring module\n");
1075 if (!(xml = switch_xml_open_cfg(config_file, &cfg, NULL))) {
1076 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", config_file);
1077 return SWITCH_STATUS_TERM;
1078 }
1079
1080 /* get params */
1081 {
1082 switch_xml_t settings = switch_xml_child(cfg, "input");
1083 if (settings) {
1084 switch_xml_t param;
1085 for (param = switch_xml_child(settings, "param"); param; param = param->next) {
1086 const char *var = switch_xml_attr_soft(param, "name");
1087 const char *val = switch_xml_attr_soft(param, "value");
1088 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "param: %s = %s\n", var, val);
1089 if (!strcasecmp(var, "default-recognizer")) {
1090 if (!zstr(val)) {
1091 globals.default_recognizer = switch_core_strdup(pool, val);
1092 }
1093 } else {
1094 switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unsupported param: %s\n", var);
1095 }
1096 }
1097 }
1098 }
1099
1100 switch_xml_free(xml);
1101
1102 return SWITCH_STATUS_SUCCESS;
1103 }
1104
1105 /**
1106 * Initialize input component
1107 * @param module_interface
1108 * @param pool memory pool to allocate from
1109 * @param config_file to use
1110 * @return SWITCH_STATUS_SUCCESS if successful
1111 */
rayo_input_component_load(switch_loadable_module_interface_t ** module_interface,switch_memory_pool_t * pool,const char * config_file)1112 switch_status_t rayo_input_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
1113 {
1114 if (do_config(pool, config_file) != SWITCH_STATUS_SUCCESS) {
1115 return SWITCH_STATUS_TERM;
1116 }
1117
1118 srgs_init();
1119 nlsml_init();
1120
1121 globals.parser = srgs_parser_new(NULL);
1122
1123 rayo_actor_command_handler_add(RAT_CALL, "", "set:"RAYO_INPUT_NS":input", start_call_input_component);
1124 rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "input", "set:"RAYO_EXT_NS":stop", stop_call_input_component);
1125 rayo_actor_command_handler_add(RAT_CALL_COMPONENT, "input", "set:"RAYO_INPUT_NS":start-timers", start_timers_call_input_component);
1126 switch_event_bind("rayo_input_component", SWITCH_EVENT_DETECTED_SPEECH, SWITCH_EVENT_SUBCLASS_ANY, on_detected_speech_event, NULL);
1127
1128 return rayo_cpa_component_load(module_interface, pool, config_file);
1129 }
1130
1131 /**
1132 * Shutdown input component
1133 * @return SWITCH_STATUS_SUCCESS if successful
1134 */
rayo_input_component_shutdown(void)1135 switch_status_t rayo_input_component_shutdown(void)
1136 {
1137 switch_event_unbind_callback(on_detected_speech_event);
1138
1139 if (globals.parser) {
1140 srgs_parser_destroy(globals.parser);
1141 }
1142 srgs_destroy();
1143 nlsml_destroy();
1144
1145 rayo_cpa_component_shutdown();
1146
1147 return SWITCH_STATUS_SUCCESS;
1148 }
1149
1150 /* For Emacs:
1151 * Local Variables:
1152 * mode:c
1153 * indent-tabs-mode:t
1154 * tab-width:4
1155 * c-basic-offset:4
1156 * End:
1157 * For VIM:
1158 * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet
1159 */
1160
1161