]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
mod_rayo: add support for speech recognizers other than pocketsphinx, fixed some...
authorChris Rienzo <chris.rienzo@grasshopper.com>
Wed, 14 Aug 2013 13:41:11 +0000 (09:41 -0400)
committerChris Rienzo <chris.rienzo@grasshopper.com>
Wed, 14 Aug 2013 13:41:11 +0000 (09:41 -0400)
13 files changed:
conf/rayo/autoload_configs/rayo.conf.xml
src/mod/event_handlers/mod_rayo/conf/autoload_configs/rayo.conf.xml
src/mod/event_handlers/mod_rayo/iks_helpers.c
src/mod/event_handlers/mod_rayo/iks_helpers.h
src/mod/event_handlers/mod_rayo/mod_rayo.c
src/mod/event_handlers/mod_rayo/rayo_components.c
src/mod/event_handlers/mod_rayo/rayo_components.h
src/mod/event_handlers/mod_rayo/rayo_elements.c
src/mod/event_handlers/mod_rayo/rayo_input_component.c
src/mod/event_handlers/mod_rayo/rayo_output_component.c
src/mod/event_handlers/mod_rayo/rayo_prompt_component.c
src/mod/event_handlers/mod_rayo/rayo_record_component.c
src/mod/event_handlers/mod_rayo/test_iks/main.c

index 0cb46d799262b21646c77b426e930ed0ab03592c..248fd47ccf5d5a64b40d58ef7037178bf98d3d15 100644 (file)
                <param name="record-file-prefix" value="$${recordings_dir}/"/>
        </record>
 
+       <!-- input component params -->
+       <input>
+               <param name="default-recognizer" value="pocketsphinx"/>
+       </input>
+
        <!-- XMPP server domain -->
        <domain name="$${rayo_domain_name}" shared-secret="ClueCon">
        <!-- use this instead if you want secure XMPP client to server connections.  Put .crt and .key file in freeswitch/certs -->
index 0cb46d799262b21646c77b426e930ed0ab03592c..248fd47ccf5d5a64b40d58ef7037178bf98d3d15 100644 (file)
                <param name="record-file-prefix" value="$${recordings_dir}/"/>
        </record>
 
+       <!-- input component params -->
+       <input>
+               <param name="default-recognizer" value="pocketsphinx"/>
+       </input>
+
        <!-- XMPP server domain -->
        <domain name="$${rayo_domain_name}" shared-secret="ClueCon">
        <!-- use this instead if you want secure XMPP client to server connections.  Put .crt and .key file in freeswitch/certs -->
index 0b5616a1f37c64e7694132c4cdf34799b643bb36..30d9d215a5c1ab35427797d0a5b4c18069eac189 100644 (file)
@@ -216,6 +216,17 @@ double iks_find_decimal_attrib(iks *xml, const char *attrib)
        return atof(iks_find_attrib_soft(xml, attrib));
 }
 
+/**
+ * Get attribute character value of node
+ * @param xml the XML node to search
+ * @param attrib the Attribute name
+ * @return the attribute value
+ */
+char iks_find_char_attrib(iks *xml, const char *attrib)
+{
+       return iks_find_attrib_soft(xml, attrib)[0];
+}
+
 /**
  * Convert iksemel XML node type to string
  * @param type the XML node type
@@ -392,6 +403,54 @@ int iks_attrib_is_decimal_between_zero_and_one(const char *value)
        return SWITCH_FALSE;
 }
 
+/**
+ * Validate dtmf digit
+ * @param value
+ * @return SWITCH_TRUE if 0-9,a,b,c,d,A,B,C,D,*,#
+ */
+int iks_attrib_is_dtmf_digit(const char *value)
+{
+       if (value && *value && strlen(value) == 1) {
+               switch (*value) {
+                       case '0':
+                       case '1':
+                       case '2':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                       case '8':
+                       case '9':
+                       case 'A':
+                       case 'a':
+                       case 'B':
+                       case 'b':
+                       case 'C':
+                       case 'c':
+                       case 'D':
+                       case 'd':
+                       case '*':
+                       case '#':
+                               return SWITCH_TRUE;
+               }
+       }
+       return SWITCH_FALSE;
+}
+
+/**
+ * @param fn to evaluate attribute
+ * @param attrib to evaluate
+ * @return true if not set or is valid
+ */
+int validate_optional_attrib(iks_attrib_validation_function fn, const char *attrib)
+{
+       if (!attrib || !*attrib) {
+               return SWITCH_TRUE;
+       }
+       return fn(attrib);
+}
+
 #define IKS_SHA256_HEX_DIGEST_LENGTH ((SHA256_DIGEST_LENGTH * 2) + 1)
 
 /**
index 90a5ca688d076d38f9945868640b1a16bed74bc0..3a7bae02e77e112ae910327338fa29c663d25905 100644 (file)
@@ -63,6 +63,7 @@ extern const char *iks_find_attrib_soft(iks *xml, const char *attrib);
 extern const char *iks_find_attrib_default(iks *xml, const char *attrib, const char *def);
 extern int iks_find_bool_attrib(iks *xml, const char *attrib);
 extern int iks_find_int_attrib(iks *xml, const char *attrib);
+extern char iks_find_char_attrib(iks *xml, const char *attrib);
 extern double iks_find_decimal_attrib(iks *xml, const char *attrib);
 extern const char *iks_node_type_to_string(int type);
 extern const char *iks_net_error_to_string(int err);
@@ -73,9 +74,12 @@ extern char *iks_server_dialback_key(const char *secret, const char *receiving_s
 /** A function to validate attribute value */
 typedef int (*iks_attrib_validation_function)(const char *);
 
+extern int validate_optional_attrib(iks_attrib_validation_function fn, const char *attrib);
+
 #define ELEMENT_DECL(name) extern int VALIDATE_##name(iks *node);
 #define ELEMENT(name) int VALIDATE_##name(iks *node) { int result = 1; if (!node) return 0;
 #define ATTRIB(name, def, rule) result &= iks_attrib_is_##rule(iks_find_attrib_default(node, #name, #def));
+#define OPTIONAL_ATTRIB(name, def, rule) result &= validate_optional_attrib(iks_attrib_is_##rule, iks_find_attrib_default(node, #name, #def));
 #define STRING_ATTRIB(name, def, rule) result &= value_matches(iks_find_attrib_default(node, #name, #def), rule);
 #define ELEMENT_END return result; }
 
@@ -87,6 +91,7 @@ extern int iks_attrib_is_positive(const char *value);
 extern int iks_attrib_is_positive_or_neg_one(const char *value);
 extern int iks_attrib_is_any(const char *value);
 extern int iks_attrib_is_decimal_between_zero_and_one(const char *value);
+extern int iks_attrib_is_dtmf_digit(const char *value);
 
 #endif
 
index fffffe31eba266f906d8b1887b65c01019aea78b..69f8b2271b125a80923bfb7560361ced694b5ae0 100644 (file)
@@ -3737,6 +3737,14 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_rayo_load)
                "</grammar></input>"
                "</prompt>");
 
+       rayo_add_cmd_alias("prompt_barge_mrcp", "<prompt xmlns=\""RAYO_PROMPT_NS"\" barge-in=\"true\">"
+               "<output xmlns=\""RAYO_OUTPUT_NS"\" repeat-times=\"5\"><document content-type=\"application/ssml+xml\"><![CDATA[<speak><p>Please press a digit.</p></speak>]]></document></output>"
+               "<input xmlns=\""RAYO_INPUT_NS"\" mode=\"any\" initial-timeout=\"5000\" inter-digit-timeout=\"3000\">"
+               "<grammar content-type=\"application/srgs+xml\">"
+               "<![CDATA[<grammar mode=\"dtmf\"><rule id=\"digit\" scope=\"public\"><one-of><item>0</item><item>1</item><item>2</item><item>3</item><item>4</item><item>5</item><item>6</item><item>7</item><item>8</item><item>9</item></one-of></rule></grammar>]]>"
+               "</grammar></input>"
+               "</prompt>");
+
        rayo_add_cmd_alias("prompt_no_barge", "<prompt xmlns=\""RAYO_PROMPT_NS"\" barge-in=\"false\">"
                "<output xmlns=\""RAYO_OUTPUT_NS"\" repeat-times=\"2\"><document content-type=\"application/ssml+xml\"><![CDATA[<speak><p>Please press a digit.</p></speak>]]></document></output>"
                "<input xmlns=\""RAYO_INPUT_NS"\" mode=\"dtmf\" initial-timeout=\"5000\" inter-digit-timeout=\"3000\">"
@@ -3800,6 +3808,34 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_rayo_load)
                "<unjoin xmlns=\""RAYO_NS"\" mixer-name=\"test\"/>");
        rayo_add_cmd_alias("unjoin",
                "<unjoin xmlns=\""RAYO_NS"\"/>");
+       rayo_add_cmd_alias("input_voice_yesno_unimrcp",
+               "<input xmlns=\""RAYO_INPUT_NS"\" mode=\"voice\" recognizer=\"unimrcp\"><grammar content-type=\"application/srgs+xml\">"
+               "<![CDATA[<grammar xmlns=\"http://www.w3.org/2001/06/grammar\" "
+                       "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+                       "xsi:schemaLocation=\"http://www.w3.org/2001/06/grammar http://www.w3.org/TR/speech-grammar/grammar.xsd\" "
+                       "xml:lang=\"en-US\" version=\"1.0\">"
+               "<rule id=\"yesno\"><one-of><item>yes</item><item>no</item></one-of></rule></grammar>]]></grammar></input>");
+rayo_add_cmd_alias("input_voice_yesno_unimrcp_timeout",
+               "<input xmlns=\""RAYO_INPUT_NS"\" mode=\"voice\" recognizer=\"unimrcp\" max-silence=\"5\" initial-timeout=\"5000\"><grammar content-type=\"application/srgs+xml\">"
+               "<![CDATA[<grammar xmlns=\"http://www.w3.org/2001/06/grammar\" "
+                       "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+                       "xsi:schemaLocation=\"http://www.w3.org/2001/06/grammar http://www.w3.org/TR/speech-grammar/grammar.xsd\" "
+                       "xml:lang=\"en-US\" version=\"1.0\">"
+               "<rule id=\"yesno\"><one-of><item>yes</item><item>no</item></one-of></rule></grammar>]]></grammar></input>");
+       rayo_add_cmd_alias("input_voice_yesno_pocketsphinx",
+               "<input xmlns=\""RAYO_INPUT_NS"\" mode=\"voice\" recognizer=\"pocketsphinx\" max-silence=\"5000\" initial-timeout=\"5000\"><grammar content-type=\"application/srgs+xml\">"
+               "<![CDATA[<grammar xmlns=\"http://www.w3.org/2001/06/grammar\" "
+                       "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+                       "xsi:schemaLocation=\"http://www.w3.org/2001/06/grammar http://www.w3.org/TR/speech-grammar/grammar.xsd\" "
+                       "xml:lang=\"en-US\" version=\"1.0\">"
+               "<rule id=\"yesno\"><one-of><item>yes</item><item>no</item></one-of></rule></grammar>]]></grammar></input>");
+       rayo_add_cmd_alias("input_voice_yesno_default",
+               "<input xmlns=\""RAYO_INPUT_NS"\" mode=\"voice\"><grammar content-type=\"application/srgs+xml\">"
+               "<![CDATA[<grammar xmlns=\"http://www.w3.org/2001/06/grammar\" "
+                       "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+                       "xsi:schemaLocation=\"http://www.w3.org/2001/06/grammar http://www.w3.org/TR/speech-grammar/grammar.xsd\" "
+                       "xml:lang=\"en-US\" version=\"1.0\">"
+               "<rule id=\"yesno\"><one-of><item>yes</item><item>no</item></one-of></rule></grammar>]]></grammar></input>");
        return SWITCH_STATUS_SUCCESS;
 }
 
index 54e241d03616811ca2f5d2455812b330a469f46a..d8a8854f527f50cf82de6e5d89284b7580caea4e 100644 (file)
@@ -227,15 +227,10 @@ void rayo_component_api_execute_async(struct rayo_component *component, const ch
  */
 switch_status_t rayo_components_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
 {
-       rayo_input_component_load();
-       rayo_output_component_load(module_interface, pool);
-       rayo_prompt_component_load();
-       rayo_record_component_load(pool, config_file);
-
-       if (rayo_input_component_load() != SWITCH_STATUS_SUCCESS ||
-               rayo_output_component_load(module_interface, pool) != SWITCH_STATUS_SUCCESS ||
-               rayo_prompt_component_load() != SWITCH_STATUS_SUCCESS ||
-               rayo_record_component_load(pool, config_file) != SWITCH_STATUS_SUCCESS) {
+       if (rayo_input_component_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS ||
+               rayo_output_component_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS ||
+               rayo_prompt_component_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS ||
+               rayo_record_component_load(module_interface, pool, config_file) != SWITCH_STATUS_SUCCESS) {
                return SWITCH_STATUS_TERM;
        }
        return SWITCH_STATUS_SUCCESS;
index 71891ea3d629f8eabfafa66dad3db8cbb5ce26be..6e93dfbc433f27c5daafcfd58dd93eba48351eff 100644 (file)
 #define COMPONENT_COMPLETE_HANGUP "hangup", RAYO_EXT_COMPLETE_NS
 
 extern switch_status_t rayo_components_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
-extern switch_status_t rayo_input_component_load(void);
-extern switch_status_t rayo_output_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool);
-extern switch_status_t rayo_prompt_component_load(void);
-extern switch_status_t rayo_record_component_load(switch_memory_pool_t *pool, const char *config_file);
+extern switch_status_t rayo_input_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
+extern switch_status_t rayo_output_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
+extern switch_status_t rayo_prompt_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
+extern switch_status_t rayo_record_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file);
 
 extern switch_status_t rayo_components_shutdown(void);
 extern switch_status_t rayo_input_component_shutdown(void);
index 89e31c7d66a13b39e82d99988c3306de3369b3bc..34577a9492dde949c99d278a2f52fc62cb60b370 100644 (file)
@@ -33,7 +33,7 @@
  */
 ELEMENT(RAYO_INPUT)
        STRING_ATTRIB(mode, any, "any,dtmf,voice")
-       ATTRIB(terminator,, any)
+       OPTIONAL_ATTRIB(terminator,, dtmf_digit)
        ATTRIB(recognizer,, any)
        ATTRIB(language, en-US, any)
        ATTRIB(initial-timeout, -1, positive_or_neg_one)
index 9e10d1b7a55716695c75e3812768a16114470516..3bf3b6dfb6677e727799d4e163d9037cc2dec66f 100644 (file)
@@ -45,6 +45,8 @@ struct input_handler;
 static struct {
        /** grammar parser */
        struct srgs_parser *parser;
+       /** default recognizer to use if none specified */
+       const char *default_recognizer;
 } globals;
 
 /**
@@ -57,8 +59,8 @@ struct input_component {
        int speech_mode;
        /** Number of collected digits */
        int num_digits;
-       /** Terminating digits */
-       int term_digit_mask;
+       /** Terminating digit */
+       char term_digit;
        /** The collected digits */
        char digits[MAX_DTMF + 1];
        /** grammar to match */
@@ -70,7 +72,9 @@ struct input_component {
        /** maximum silence allowed */
        int max_silence;
        /** minimum speech detection confidence */
-       int min_confidence;
+       double min_confidence;
+       /** sensitivity to background noise */
+       double sensitivity;
        /** timeout after first digit is received */
        int inter_digit_timeout;
        /** stop flag */
@@ -79,6 +83,10 @@ struct input_component {
        int start_timers;
        /** true if event fired for first digit / start of speech */
        int barge_event;
+       /** optional language to use */
+       const char *language;
+       /** optional recognizer to use */
+       const char *recognizer;
        /** global data */
        struct input_handler *handler;
 };
@@ -91,77 +99,24 @@ struct input_component {
 struct input_handler {
        /** media bug to monitor frames / control input lifecycle */
        switch_media_bug_t *bug;
-       /** active input component - TODO multiple inputs */
-       struct input_component *component;
+       /** active voice input component */
+       struct input_component *voice_component;
+       /** active dtmf input component */
+       struct input_component *dtmf_component;
        /** synchronizes media bug and dtmf callbacks */
        switch_mutex_t *mutex;
+       /** last recognizer used */
+       const char *last_recognizer;
 };
 
 /**
- * @return digit mask
- */
-static int get_digit_mask(char digit)
-{
-       switch(digit) {
-               case '0': return 1;
-               case '1': return 1 << 1;
-               case '2': return 1 << 2;
-               case '3': return 1 << 3;
-               case '4': return 1 << 4;
-               case '5': return 1 << 5;
-               case '6': return 1 << 6;
-               case '7': return 1 << 7;
-               case '8': return 1 << 8;
-               case '9': return 1 << 9;
-               case 'A':
-               case 'a': return 1 << 10;
-               case 'B':
-               case 'b': return 1 << 11;
-               case 'C':
-               case 'c': return 1 << 12;
-               case 'D':
-               case 'd': return 1 << 13;
-               case '#': return 1 << 14;
-               case '*': return 1 << 15;
-       }
-       return 0;
-}
-
-/**
- * @param digit_mask to check
- * @param digit to look for
- * @return true if set
- */
-static int digit_mask_test(int digit_mask, char digit)
-{
-       return digit_mask & get_digit_mask(digit);
-}
-
-/**
- * @param digit_mask to set digit in
- * @param digit to set
- * @return the digit mask with the set digit
+ * @param digit1 to match
+ * @param digit2 to match
+ * @return true if matching
  */
-static int digit_mask_set(int digit_mask, char digit)
+static int digit_test(char digit1, char digit2)
 {
-       return digit_mask | get_digit_mask(digit);
-}
-
-/**
- * @param digit_mask to set digits in
- * @param digits to add to mask
- * @return the digit mask with the set digits
- */
-static int digit_mask_set_from_digits(int digit_mask, const char *digits)
-{
-       if (!zstr(digits)) {
-               int digits_len = strlen(digits);
-               int i;
-               for (i = 0; i < digits_len; i++) {
-                       digit_mask = digit_mask_set(digit_mask, digits[i]);
-               }
-       }
-       return digit_mask;
+       return digit1 && digit2 && tolower(digit1) == tolower(digit2);
 }
 
 /**
@@ -205,15 +160,14 @@ static switch_status_t input_component_on_dtmf(switch_core_session_t *session, c
 
                switch_mutex_lock(handler->mutex);
 
-               component = handler->component;
+               component = handler->dtmf_component;
                /* additional paranoia check */
                if (!component) {
-                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Received DTMF without active input component\n");
                        switch_mutex_unlock(handler->mutex);
                        return SWITCH_STATUS_SUCCESS;
                }
 
-               is_term_digit = digit_mask_test(component->term_digit_mask, dtmf->digit);
+               is_term_digit = digit_test(component->term_digit, dtmf->digit);
 
                if (!is_term_digit) {
                        component->digits[component->num_digits] = dtmf->digit;
@@ -247,7 +201,7 @@ static switch_status_t input_component_on_dtmf(switch_core_session_t *session, c
                        }
                        case SMT_NO_MATCH: {
                                /* notify of no-match and remove input component */
-                               handler->component = NULL;
+                               handler->dtmf_component = NULL;
                                switch_core_media_bug_remove(session, &handler->bug);
                                switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "NO MATCH = %s\n", component->digits);
                                rayo_component_send_complete(RAYO_COMPONENT(component), INPUT_NOMATCH);
@@ -256,7 +210,7 @@ static switch_status_t input_component_on_dtmf(switch_core_session_t *session, c
                        case SMT_MATCH_END: {
                                iks *result = nlsml_create_dtmf_match(component->digits);
                                /* notify of match and remove input component */
-                               handler->component = NULL;
+                               handler->dtmf_component = NULL;
                                switch_core_media_bug_remove(session, &handler->bug);
                                switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "MATCH = %s\n", component->digits);
                                send_match_event(RAYO_COMPONENT(component), result);
@@ -279,7 +233,7 @@ static switch_bool_t input_component_bug_callback(switch_media_bug_t *bug, void
        struct input_component *component;
 
        switch_mutex_lock(handler->mutex);
-       component = handler->component;
+       component = handler->dtmf_component;
 
        switch(type) {
                case SWITCH_ABC_TYPE_INIT: {
@@ -294,7 +248,7 @@ static switch_bool_t input_component_bug_callback(switch_media_bug_t *bug, void
                                int elapsed_ms = (switch_micro_time_now() - component->last_digit_time) / 1000;
                                if (component->num_digits && component->inter_digit_timeout > 0 && elapsed_ms > component->inter_digit_timeout) {
                                        enum srgs_match_type match;
-                                       handler->component = NULL;
+                                       handler->dtmf_component = NULL;
                                        switch_core_media_bug_set_flag(bug, SMBF_PRUNE);
 
                                        /* we got some input, check for match */
@@ -310,7 +264,7 @@ static switch_bool_t input_component_bug_callback(switch_media_bug_t *bug, void
                                                rayo_component_send_complete(RAYO_COMPONENT(component), INPUT_NOMATCH);
                                        }
                                } else if (!component->num_digits && component->initial_timeout > 0 && elapsed_ms > component->initial_timeout) {
-                                       handler->component = NULL;
+                                       handler->dtmf_component = NULL;
                                        switch_core_media_bug_set_flag(bug, SMBF_PRUNE);
                                        switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "initial-timeout\n");
                                        rayo_component_send_complete(RAYO_COMPONENT(component), INPUT_NOINPUT);
@@ -323,10 +277,10 @@ static switch_bool_t input_component_bug_callback(switch_media_bug_t *bug, void
                        /* check for hangup */
                        if (component) {
                                if (component->stop) {
-                                       handler->component = NULL;
+                                       handler->dtmf_component = NULL;
                                        rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_STOP);
                                } else {
-                                       handler->component = NULL;
+                                       handler->dtmf_component = NULL;
                                        rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_HANGUP);
                                }
                        }
@@ -396,34 +350,53 @@ static iks *start_call_input(struct input_component *component, switch_core_sess
                handler = switch_core_session_alloc(session, sizeof(*handler));
                switch_mutex_init(&handler->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
                switch_channel_set_private(switch_core_session_get_channel(session), RAYO_INPUT_COMPONENT_PRIVATE_VAR, handler);
+               handler->last_recognizer = "";
+       }
+
+       /* TODO break up this function by mode... dtmf/voice/fax/etc */
+       component->speech_mode = strcmp(iks_find_attrib_soft(input, "mode"), "dtmf");
+       if (component->speech_mode && handler->voice_component) {
+               /* don't allow multi voice input */
+               return iks_new_error_detailed(iq, STANZA_ERROR_CONFLICT, "Multiple voice input is not allowed");
        }
-       handler->component = component;
+       if (!component->speech_mode && handler->dtmf_component) {
+               /* don't allow multi dtmf input */
+               return iks_new_error_detailed(iq, STANZA_ERROR_CONFLICT, "Multiple dtmf input is not allowed");
+       }
+
+       if (component->speech_mode) {
+               handler->voice_component = component;
+       } else {
+               handler->dtmf_component = component;
+       }
+
+       component->grammar = NULL;
        component->num_digits = 0;
        component->digits[0] = '\0';
        component->stop = 0;
-       component->speech_mode = 0;
        component->initial_timeout = iks_find_int_attrib(input, "initial-timeout");
        component->inter_digit_timeout = iks_find_int_attrib(input, "inter-digit-timeout");
        component->max_silence = iks_find_int_attrib(input, "max-silence");
-       component->min_confidence = (int)ceil(iks_find_decimal_attrib(input, "min-confidence") * 100.0);
+       component->min_confidence = iks_find_decimal_attrib(input, "min-confidence");
+       component->sensitivity = iks_find_decimal_attrib(input, "sensitivity");
        component->barge_event = iks_find_bool_attrib(input, "barge-event");
        component->start_timers = iks_find_bool_attrib(input, "start-timers");
-       /* TODO this should just be a single digit terminator? */
-       component->term_digit_mask = digit_mask_set_from_digits(0, iks_find_attrib_soft(input, "terminator"));
-       /* TODO recognizer ignored */
-       /* TODO language ignored */
+       component->term_digit = iks_find_char_attrib(input, "terminator");
+       component->recognizer = iks_find_attrib(input, "recognizer");
+       component->language = iks_find_attrib(input, "language");
        component->handler = handler;
 
-       /* parse the grammar */
-       if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) {
-               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Failed to parse grammar body\n");
-               RAYO_UNLOCK(component);
-               RAYO_DESTROY(component);
-               return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Failed to parse grammar body");
-       }
-
        /* is this voice or dtmf srgs grammar? */
-       if (!strcasecmp("dtmf", iks_find_attrib_soft(input, "mode"))) {
+       if (!component->speech_mode) {
+
+               /* parse the grammar */
+               if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) {
+                       switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Failed to parse grammar body\n");
+                       RAYO_UNLOCK(component);
+                       RAYO_DESTROY(component);
+                       return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Failed to parse grammar body");
+               }
+
                component->last_digit_time = switch_micro_time_now();
 
                /* acknowledge command */
@@ -431,38 +404,124 @@ static iks *start_call_input(struct input_component *component, switch_core_sess
 
                /* start dtmf input detection */
                if (switch_core_media_bug_add(session, "rayo_input_component", NULL, input_component_bug_callback, handler, 0, SMBF_READ_REPLACE, &handler->bug) != SWITCH_STATUS_SUCCESS) {
+                       handler->dtmf_component = NULL;
                        rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_ERROR);
                }
        } else {
-               char *grammar = NULL;
-               const char *jsgf_path;
-               component->speech_mode = 1;
-               jsgf_path = srgs_grammar_to_jsgf_file(component->grammar, SWITCH_GLOBAL_dirs.grammar_dir, "gram");
-               if (!jsgf_path) {
+               switch_stream_handle_t grammar = { 0 };
+               SWITCH_STANDARD_STREAM(grammar);
+
+               if (zstr(component->recognizer)) {
+                       component->recognizer = globals.default_recognizer;
+               }
+
+               /* if recognition engine is different, we can't handle this request */
+               if (!zstr(handler->last_recognizer) && strcmp(component->recognizer, handler->last_recognizer)) {
+                       handler->voice_component = NULL;
                        RAYO_UNLOCK(component);
                        RAYO_DESTROY(component);
-                       return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Grammar error");
+                       return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Must use the same recognizer for the entire call");
+               }
+               handler->last_recognizer = switch_core_session_strdup(session, component->recognizer);
+
+               if (!strcmp(component->recognizer, "pocketsphinx")) {
+                       const char *jsgf_path;
+
+                       /* transform SRGS grammar to JSGF */
+                       if (!(component->grammar = srgs_parse(globals.parser, iks_find_cdata(input, "grammar")))) {
+                               switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Failed to parse grammar body\n");
+                               handler->voice_component = NULL;
+                               RAYO_UNLOCK(component);
+                               RAYO_DESTROY(component);
+                               return iks_new_error_detailed(iq, STANZA_ERROR_BAD_REQUEST, "Failed to parse grammar body");
+                       }
+                       jsgf_path = srgs_grammar_to_jsgf_file(component->grammar, SWITCH_GLOBAL_dirs.grammar_dir, "gram");
+                       if (!jsgf_path) {
+                               handler->voice_component = NULL;
+                               RAYO_UNLOCK(component);
+                               RAYO_DESTROY(component);
+                               return iks_new_error_detailed(iq, STANZA_ERROR_INTERNAL_SERVER_ERROR, "Grammar conversion to JSGF error");
+                       }
+
+                       /* build pocketsphinx grammar string */
+                       grammar.write_function(&grammar,
+                               "{start-input-timers=%s,no-input-timeout=%d,speech-timeout=%d,confidence-threshold=%d}%s",
+                               component->start_timers ? "true" : "false",
+                               component->initial_timeout,
+                               component->max_silence,
+                               (int)ceil(component->min_confidence * 100.0),
+                               jsgf_path);
+               } else if (!strncmp(component->recognizer, "unimrcp", strlen("unimrcp"))) {
+                       /* send inline grammar to unimrcp */
+                       grammar.write_function(&grammar, "{start-input-timers=%s,confidence-threshold=%f,sensitivity-level=%f",
+                                                                       component->start_timers ? "true" : "false",
+                                                                       component->min_confidence,
+                                                                       component->sensitivity);
+
+                       if (component->initial_timeout > 0) {
+                               grammar.write_function(&grammar, ",no-input-timeout=%d",
+                                       component->initial_timeout);
+                       }
+
+                       if (component->max_silence > 0) {
+                               grammar.write_function(&grammar, ",speech-complete-timeout=%d,speech-incomplete-timeout=%d",
+                                       component->max_silence,
+                                       component->max_silence);
+                       }
+
+                       if (!zstr(component->language)) {
+                               grammar.write_function(&grammar, ",speech-language=%s", component->language);
+                       }
+
+                       if (!strcmp(iks_find_attrib_soft(input, "mode"), "any")) {
+                               /* set dtmf params */
+                               if (component->inter_digit_timeout > 0) {
+                                       grammar.write_function(&grammar, ",dtmf-interdigit-timeout=%d", component->inter_digit_timeout);
+                               }
+                               if (component->term_digit) {
+                                       grammar.write_function(&grammar, ",dtmf-term-char=%c", component->term_digit);
+                               }
+                       }
+
+                       grammar.write_function(&grammar, "}inline:%s", iks_find_cdata(input, "grammar"));
+               } else {
+                       /* passthrough to unknown ASR module */
+                       grammar.write_function(&grammar, "%s", iks_find_cdata(input, "grammar"));
                }
 
                /* acknowledge command */
                rayo_component_send_start(RAYO_COMPONENT(component), iq);
 
-               /* TODO configurable speech detection - different engines, grammar passthrough, dtmf handled by recognizer */
-               grammar = switch_mprintf("{no-input-timeout=%s,speech-timeout=%s,start-input-timers=%s,confidence-threshold=%d}%s",
-                       component->initial_timeout, component->max_silence,
-                       component->start_timers ? "true" : "false",
-                       component->min_confidence, jsgf_path);
                /* start speech detection */
                switch_channel_set_variable(switch_core_session_get_channel(session), "fire_asr_events", "true");
-               if (switch_ivr_detect_speech(session, "pocketsphinx", grammar, "mod_rayo_grammar", "", NULL) != SWITCH_STATUS_SUCCESS) {
+               if (switch_ivr_detect_speech(session, component->recognizer, grammar.data, "mod_rayo_grammar", "", NULL) != SWITCH_STATUS_SUCCESS) {
+                       handler->voice_component = NULL;
                        rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_ERROR);
                }
-               switch_safe_free(grammar);
+               switch_safe_free(grammar.data);
        }
 
        return NULL;
 }
 
+/**
+ * Create input component id for session.
+ * @param session requesting component
+ * @param input request
+ * @return the ID
+ */
+static char *create_input_component_id(switch_core_session_t *session, iks *input)
+{
+       const char *mode = "unk";
+       if (input) {
+               mode = iks_find_attrib_soft(input, "mode");
+               if (!strcmp(mode, "any")) {
+                       mode = "voice";
+               }
+       }
+       return switch_core_session_sprintf(session, "%s-input-%s", switch_core_session_get_uuid(session), mode);
+}
+
 /**
  * Start execution of input component
  */
@@ -470,10 +529,10 @@ static iks *start_call_input_component(struct rayo_actor *call, struct rayo_mess
 {
        iks *iq = msg->payload;
        switch_core_session_t *session = (switch_core_session_t *)session_data;
-       char *component_id = switch_mprintf("%s-input", switch_core_session_get_uuid(session));
+       iks *input = iks_find(iq, "input");
+       char *component_id = create_input_component_id(session, input);
        switch_memory_pool_t *pool = NULL;
        struct input_component *input_component = NULL;
-       iks *input = iks_find(iq, "input");
        const char *error = NULL;
 
        if (!validate_call_input(input, &error)) {
@@ -484,7 +543,6 @@ static iks *start_call_input_component(struct rayo_actor *call, struct rayo_mess
        switch_core_new_memory_pool(&pool);
        input_component = switch_core_alloc(pool, sizeof(*input_component));
        rayo_component_init(RAYO_COMPONENT(input_component), pool, RAT_CALL_COMPONENT, "input", component_id, call, iks_find_attrib(iq, "from"));
-       switch_safe_free(component_id);
 
        /* start input */
        return start_call_input(input_component, session, iks_find(iq, "input"), iq, NULL, 0);
@@ -530,7 +588,6 @@ static iks *start_timers_call_input_component(struct rayo_actor *component, stru
                        switch_mutex_lock(input_component->handler->mutex);
                        if (input_component->speech_mode) {
                                switch_ivr_detect_speech_start_input_timers(session);
-                               rayo_component_send_complete(RAYO_COMPONENT(component), COMPONENT_COMPLETE_STOP);
                        } else {
                                input_component->last_digit_time = switch_micro_time_now();
                                input_component->start_timers = 1;
@@ -549,53 +606,63 @@ static void on_detected_speech_event(switch_event_t *event)
 {
        const char *speech_type = switch_event_get_header(event, "Speech-Type");
        char *event_str = NULL;
+       const char *uuid = switch_event_get_header(event, "Unique-ID");
        switch_event_serialize(event, &event_str, SWITCH_FALSE);
        switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s\n", event_str);
-       if (!speech_type) {
+       if (!speech_type || !uuid) {
                return;
        }
+
        if (!strcasecmp("detected-speech", speech_type)) {
-               const char *uuid = switch_event_get_header(event, "Unique-ID");
-               char *component_id = switch_mprintf("%s-input", uuid);
+               char *component_id = switch_mprintf("%s-input-voice", uuid);
                struct rayo_component *component = RAYO_COMPONENT_LOCATE(component_id);
+
                switch_safe_free(component_id);
                if (component) {
                        const char *result = switch_event_get_body(event);
                        switch_mutex_lock(INPUT_COMPONENT(component)->handler->mutex);
-                       INPUT_COMPONENT(component)->handler->component = NULL;
+                       INPUT_COMPONENT(component)->handler->voice_component = NULL;
                        switch_mutex_unlock(INPUT_COMPONENT(component)->handler->mutex);
                        if (zstr(result)) {
                                rayo_component_send_complete(component, INPUT_NOMATCH);
                        } else {
-                               enum nlsml_match_type match_type = nlsml_parse(result, uuid);
-                               switch (match_type) {
-                               case NMT_NOINPUT:
+                               if (strchr(result, '<')) {
+                                       /* got an XML result */
+                                       enum nlsml_match_type match_type = nlsml_parse(result, uuid);
+                                       switch (match_type) {
+                                       case NMT_NOINPUT:
+                                               rayo_component_send_complete(component, INPUT_NOINPUT);
+                                               break;
+                                       case NMT_MATCH: {
+                                               iks *result_xml = nlsml_normalize(result);
+                                               send_match_event(RAYO_COMPONENT(component), result_xml);
+                                               iks_delete(result_xml);
+                                               break;
+                                       }
+                                       case NMT_BAD_XML:
+                                               switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_WARNING, "Failed to parse NLSML result: %s!\n", result);
+                                               rayo_component_send_complete(component, INPUT_NOMATCH);
+                                               break;
+                                       case NMT_NOMATCH:
+                                               rayo_component_send_complete(component, INPUT_NOMATCH);
+                                               break;
+                                       default:
+                                               switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_CRIT, "Unknown NLSML match type: %i, %s!\n", match_type, result);
+                                               rayo_component_send_complete(component, INPUT_NOMATCH);
+                                               break;
+                                       }
+                               } else if (strstr(result, "002")) {
+                                       /* Completion-Cause: 002 no-input-timeout */
                                        rayo_component_send_complete(component, INPUT_NOINPUT);
-                                       break;
-                               case NMT_MATCH: {
-                                       iks *result_xml = nlsml_normalize(result);
-                                       send_match_event(RAYO_COMPONENT(component), result_xml);
-                                       iks_delete(result_xml);
-                                       break;
-                               }
-                               case NMT_BAD_XML:
-                                       switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_WARNING, "Failed to parse NLSML result: %s!\n", result);
-                                       rayo_component_send_complete(component, INPUT_NOMATCH);
-                                       break;
-                               case NMT_NOMATCH:
-                                       rayo_component_send_complete(component, INPUT_NOMATCH);
-                                       break;
-                               default:
-                                       switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_CRIT, "Unknown NLSML match type: %i, %s!\n", match_type, result);
+                               } else {
+                                       /* assume no match */
                                        rayo_component_send_complete(component, INPUT_NOMATCH);
-                                       break;
                                }
                        }
                        RAYO_UNLOCK(component);
                }
        } else if (!strcasecmp("begin-speaking", speech_type)) {
-               const char *uuid = switch_event_get_header(event, "Unique-ID");
-               char *component_id = switch_mprintf("%s-input", uuid);
+               char *component_id = switch_mprintf("%s-input-voice", uuid);
                struct rayo_component *component = RAYO_COMPONENT_LOCATE(component_id);
                switch_safe_free(component_id);
                if (component && INPUT_COMPONENT(component)->barge_event) {
@@ -603,14 +670,13 @@ static void on_detected_speech_event(switch_event_t *event)
                }
                RAYO_UNLOCK(component);
        } else if (!strcasecmp("closed", speech_type)) {
-               const char *uuid = switch_event_get_header(event, "Unique-ID");
-               char *component_id = switch_mprintf("%s-input", uuid);
+               char *component_id = switch_mprintf("%s-input-voice", uuid);
                struct rayo_component *component = RAYO_COMPONENT_LOCATE(component_id);
                switch_safe_free(component_id);
                if (component) {
                        char *channel_state = switch_event_get_header(event, "Channel-State");
                        switch_mutex_lock(INPUT_COMPONENT(component)->handler->mutex);
-                       INPUT_COMPONENT(component)->handler->component = NULL;
+                       INPUT_COMPONENT(component)->handler->voice_component = NULL;
                        switch_mutex_unlock(INPUT_COMPONENT(component)->handler->mutex);
                        switch_log_printf(SWITCH_CHANNEL_UUID_LOG(uuid), SWITCH_LOG_DEBUG, "Recognizer closed\n");
                        if (channel_state && !strcmp("CS_HANGUP", channel_state)) {
@@ -625,12 +691,63 @@ static void on_detected_speech_event(switch_event_t *event)
        switch_safe_free(event_str);
 }
 
+/**
+ * Process module XML configuration
+ * @param pool memory pool to allocate from
+ * @param config_file to use
+ * @return SWITCH_STATUS_SUCCESS on successful configuration
+ */
+static switch_status_t do_config(switch_memory_pool_t *pool, const char *config_file)
+{
+       switch_xml_t cfg, xml;
+
+       /* set defaults */
+       globals.default_recognizer = "pocketsphinx";
+
+       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Configuring module\n");
+       if (!(xml = switch_xml_open_cfg(config_file, &cfg, NULL))) {
+               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", config_file);
+               return SWITCH_STATUS_TERM;
+       }
+
+       /* get params */
+       {
+               switch_xml_t settings = switch_xml_child(cfg, "input");
+               if (settings) {
+                       switch_xml_t param;
+                       for (param = switch_xml_child(settings, "param"); param; param = param->next) {
+                               const char *var = switch_xml_attr_soft(param, "name");
+                               const char *val = switch_xml_attr_soft(param, "value");
+                               switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "param: %s = %s\n", var, val);
+                               if (!strcasecmp(var, "default-recognizer")) {
+                                       if (!zstr(val)) {
+                                               globals.default_recognizer = switch_core_strdup(pool, val);
+                                       }
+                               } else {
+                                       switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Unsupported param: %s\n", var);
+                               }
+                       }
+               }
+       }
+
+       switch_xml_free(xml);
+
+       return SWITCH_STATUS_SUCCESS;
+}
+
 /**
  * Initialize input component
+ * @param module_interface
+ * @param pool memory pool to allocate from
+ * @param config_file to use
  * @return SWITCH_STATUS_SUCCESS if successful
  */
-switch_status_t rayo_input_component_load(void)
+switch_status_t rayo_input_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
 {
+       if (do_config(pool, config_file) != SWITCH_STATUS_SUCCESS) {
+               return SWITCH_STATUS_TERM;
+       }
+
        srgs_init();
        nlsml_init();
 
index f92d994a6453991414470d281dd2abb95c0b164a..e85a12cece4f142a7dfc5b1e63ca1ec34771f6cb 100644 (file)
@@ -1092,9 +1092,12 @@ static char *fileman_supported_formats[] = { "fileman", NULL };
 
 /**
  * Initialize output component
+ * @param module_interface
+ * @param pool memory pool to allocate from
+ * @param config_file to use
  * @return SWITCH_STATUS_SUCCESS if successful
  */
-switch_status_t rayo_output_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool)
+switch_status_t rayo_output_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
 {
        switch_api_interface_t *api_interface;
        switch_file_interface_t *file_interface;
index 47dbc46df32c0c1b9f176c610273001f78059b1f..f48e7d02d29ef7c21a06ea965448699b2408bbbf 100644 (file)
@@ -620,9 +620,12 @@ static iks *forward_output_component_request(struct rayo_actor *prompt, struct r
 
 /**
  * Initialize prompt component
+ * @param module_interface
+ * @param pool memory pool to allocate from
+ * @param config_file to use
  * @return SWITCH_STATUS_SUCCESS if successful
  */
-switch_status_t rayo_prompt_component_load(void)
+switch_status_t rayo_prompt_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
 {
        /* Prompt is a convenience component that wraps <input> and <output> */
        rayo_actor_command_handler_add(RAT_CALL, "", "set:"RAYO_PROMPT_NS":prompt", start_call_prompt_component);
index 2db5b30771fdc0f3bfcd2f5d84fa5b67127f5f5f..601d8cb3aa352c50129b85f149cec3d2d35f3737 100644 (file)
@@ -479,11 +479,12 @@ static switch_status_t do_config(switch_memory_pool_t *pool, const char *config_
 
 /**
  * Initialize record component
+ * @param module_interface
  * @param pool memory pool to allocate from
  * @param config_file to use
  * @return SWITCH_STATUS_SUCCESS if successful
  */
-switch_status_t rayo_record_component_load(switch_memory_pool_t *pool, const char *config_file)
+switch_status_t rayo_record_component_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool, const char *config_file)
 {
        if (do_config(pool, config_file) != SWITCH_STATUS_SUCCESS) {
                return SWITCH_STATUS_TERM;
index 699f40267ef63be4b7082c3254656cd9d6fdc7ea..09a368dc4b8f735fb825776151783001b38b234f 100644 (file)
@@ -145,6 +145,27 @@ static void test_dialback_key(void)
        ASSERT_NULL(iks_server_dialback_key("s3cr3tf0rd14lb4ck", "xmpp.example.com", "example.org", NULL));
 }
 
+static void test_validate_dtmf(void)
+{
+       ASSERT_EQUALS(SWITCH_TRUE, iks_attrib_is_dtmf_digit("1"));
+       ASSERT_EQUALS(SWITCH_TRUE, iks_attrib_is_dtmf_digit("A"));
+       ASSERT_EQUALS(SWITCH_TRUE, iks_attrib_is_dtmf_digit("a"));
+       ASSERT_EQUALS(SWITCH_TRUE, iks_attrib_is_dtmf_digit("D"));
+       ASSERT_EQUALS(SWITCH_TRUE, iks_attrib_is_dtmf_digit("d"));
+       ASSERT_EQUALS(SWITCH_TRUE, iks_attrib_is_dtmf_digit("*"));
+       ASSERT_EQUALS(SWITCH_TRUE, iks_attrib_is_dtmf_digit("#"));
+       ASSERT_EQUALS(SWITCH_FALSE, iks_attrib_is_dtmf_digit("E"));
+       ASSERT_EQUALS(SWITCH_FALSE, iks_attrib_is_dtmf_digit(NULL));
+       ASSERT_EQUALS(SWITCH_FALSE, iks_attrib_is_dtmf_digit(""));
+       ASSERT_EQUALS(SWITCH_FALSE, iks_attrib_is_dtmf_digit("11"));
+       ASSERT_EQUALS(SWITCH_TRUE, validate_optional_attrib(iks_attrib_is_dtmf_digit, "A"));
+       ASSERT_EQUALS(SWITCH_TRUE, validate_optional_attrib(iks_attrib_is_dtmf_digit, "1"));
+       ASSERT_EQUALS(SWITCH_FALSE, validate_optional_attrib(iks_attrib_is_dtmf_digit, "Z"));
+       ASSERT_EQUALS(SWITCH_FALSE, validate_optional_attrib(iks_attrib_is_dtmf_digit, "11"));
+       ASSERT_EQUALS(SWITCH_TRUE, validate_optional_attrib(iks_attrib_is_dtmf_digit, NULL));
+       ASSERT_EQUALS(SWITCH_TRUE, validate_optional_attrib(iks_attrib_is_dtmf_digit, ""));
+}
+
 /**
  * main program
  */
@@ -159,5 +180,6 @@ int main(int argc, char **argv)
        TEST(test_rayo_test_srgs);
        TEST(test_iks_helper_value_matches);
        TEST(test_dialback_key);
+       TEST(test_validate_dtmf);
        return 0;
 }