diff --git a/src/mod/asr_tts/mod_lumenvox/Makefile b/src/mod/asr_tts/mod_lumenvox/Makefile new file mode 100644 index 0000000000..73e22c5c34 --- /dev/null +++ b/src/mod/asr_tts/mod_lumenvox/Makefile @@ -0,0 +1,18 @@ +LDFLAGS += -L/opt/lumenvox/engine_7.0/lib -llv_lvspeechport +CFLAGS += -fpermissive -Wno-deprecated -Wno-conversion -fpermissive -Wno-unused -Wno-comment -Wno-sign-compare -Wno-conversion -Wno-reorder -I/opt/lumenvox/engine_7.0/include +LINKER=g++ +CC=g++ + +all: depends $(MODNAME).$(DYNAMIC_LIB_EXTEN) + +depends: + +$(MODNAME).$(DYNAMIC_LIB_EXTEN): $(MODNAME).cpp + $(CC) $(CFLAGS) -fPIC -c $(MODNAME).cpp -o $(MODNAME).o + $(LINKER) $(SOLINK) -o $(MODNAME).$(DYNAMIC_LIB_EXTEN) $(MODNAME).o $(LDFLAGS) + +clean: + rm -fr *.$(DYNAMIC_LIB_EXTEN) *.o *~ + +install: + cp -f $(MODNAME).$(DYNAMIC_LIB_EXTEN) $(PREFIX)/mod diff --git a/src/mod/asr_tts/mod_lumenvox/mod_lumenvox.cpp b/src/mod/asr_tts/mod_lumenvox/mod_lumenvox.cpp new file mode 100644 index 0000000000..969f395aa4 --- /dev/null +++ b/src/mod/asr_tts/mod_lumenvox/mod_lumenvox.cpp @@ -0,0 +1,459 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005/2006, Anthony Minessale II + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * + * mod_lumenvox.c -- Lumenvox Interface + * + * + */ +#ifdef __ICC +#pragma warning (disable:188) +#endif + +#include +#include +#include + +static const char modname[] = "mod_lumenvox"; + +typedef enum { + LVFLAG_HAS_TEXT = (1 << 0), + LVFLAG_BARGE = (1 << 1), + LVFLAG_READY = (1 << 2) +} lvflag_t; + +typedef struct { + LVSpeechPort port; + uint32_t flags; + int sound_format; + int channel; + switch_mutex_t *flag_mutex; +} lumenvox_t; + +static void log_callback(const char* message, void* userdata) +{ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s\n", message); +} + +static const char *state_names[] = { + "NOT_READY", + "READY", + "BARGE_IN", + "END_SPEECH", + "STOPPED", + "BARGE_IN_TIMEOUT", + "END_SPEECH_TIMEOUT", + "BEEP" +}; + +std::ostream& operator << (std::ostream& os ,const LVSemanticData& Data) +{ + int i; + LVSemanticObject Obj; + switch (Data.Type()) + { + case SI_TYPE_BOOL: + os << Data.GetBool(); + break; + case SI_TYPE_INT: + os << Data.GetInt(); + break; + case SI_TYPE_DOUBLE: + os << Data.GetDouble(); + break; + case SI_TYPE_STRING: + os << Data.GetString(); + break; + case SI_TYPE_OBJECT: + Obj = Data.GetSemanticObject(); + for (i = 0; i < Obj.NumberOfProperties(); ++i) + { + os << "<" << Obj.PropertyName(i) << ">"; + os << Obj.PropertyValue(i); + os << ""; + } + break; + case SI_TYPE_ARRAY: + for (i = 0; i < Data.GetSemanticArray().Size(); ++i) + { + os << Data.GetArray().At(i); + } + break; + } + return os; +} + +//============================================================================================== +// code to plug LVInterpretation into any standard stream +std::ostream& operator << (std::ostream& os, const LVInterpretation& Interp) +{ + os << ""; + os << ""; + os << Interp.ResultData(); + os << ""; + os << ""; + os << Interp.InputSentence(); + os << ""; + os << ""; + return os; +} + + +static void state_change_callback(long new_state, unsigned long total_bytes, + unsigned long recorded_bytes, void *user_data) +{ + lumenvox_t *lv = (lumenvox_t *) user_data; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "State: [%s] total bytes: [%ld] recorded bytes: [%ld]\n", + state_names[new_state], + total_bytes, + recorded_bytes); + + switch (new_state) + { + case STREAM_STATUS_READY: + break; + case STREAM_STATUS_STOPPED: + switch_clear_flag_locked(lv, LVFLAG_READY); + break; + case STREAM_STATUS_END_SPEECH: + switch_set_flag_locked(lv, LVFLAG_HAS_TEXT); + break; + case STREAM_STATUS_BARGE_IN: + switch_set_flag_locked(lv, LVFLAG_BARGE); + break; + } +} + +static switch_status_t lumenvox_asr_pause(switch_asr_handle_t *ah) +{ + lumenvox_t *lv = (lumenvox_t *) ah->private_info; + + if (switch_test_flag(lv, LVFLAG_READY)) { + lv->port.StreamStop(); + switch_clear_flag_locked(lv, LVFLAG_READY); + return SWITCH_STATUS_SUCCESS; + } + + return SWITCH_STATUS_GENERR; +} + +static switch_status_t lumenvox_asr_resume(switch_asr_handle_t *ah) +{ + lumenvox_t *lv = (lumenvox_t *) ah->private_info; + + switch_clear_flag_locked(lv, LVFLAG_HAS_TEXT); + + if (!switch_test_flag(lv, LVFLAG_READY)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Manually Resuming\n"); + if (lv->port.StreamStart()) { + lv->port.ClosePort(); + return SWITCH_STATUS_GENERR; + } + switch_set_flag_locked(lv, LVFLAG_READY); + return SWITCH_STATUS_SUCCESS; + } + + return SWITCH_STATUS_GENERR; +} + +/*! function to open the asr interface */ +static switch_status_t lumenvox_asr_open(switch_asr_handle_t *ah, char *codec, int rate, char *dest, switch_asr_flag_t *flags) +{ + + lumenvox_t *lv; + int error_code; + + if (!(lv = (lumenvox_t *) switch_core_alloc(ah->memory_pool, sizeof(*lv)))) { + return SWITCH_STATUS_MEMERR; + } + + if (rate != 8000 && rate != 16000) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid Rate: Pick 8000 or 16000\n"); + return SWITCH_STATUS_FALSE; + } + + if (!strcasecmp(codec, "PCMU")) { + switch (rate) { + case 8000: + lv->sound_format = ULAW_8KHZ; + break; + } + } else if (!strcasecmp(codec, "PCMA")) { + switch (rate) { + case 8000: + lv->sound_format = ULAW_8KHZ; + break; + } + } else if (!strcasecmp(codec, "speex")) { + switch (rate) { + case 8000: + lv->sound_format = SPX_8KHZ; + break; + case 16000: + lv->sound_format = SPX_16KHZ; + break; + } + } + + if (!lv->sound_format) { + codec = "L16"; + switch (rate) { + case 8000: + lv->sound_format = PCM_8KHZ; + break; + case 16000: + lv->sound_format = PCM_16KHZ; + break; + } + } + + if (!lv->sound_format) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot negotiate sound format.\n"); + return SWITCH_STATUS_FALSE; + } + + ah->rate = rate; + ah->codec = switch_core_strdup(ah->memory_pool, codec); + + lv->port.OpenPort(log_callback, NULL, 5); + error_code = lv->port.GetOpenPortStatus(); + + switch(error_code) + { + case LV_FAILURE: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Licenses Exceeded!\n"); + return SWITCH_STATUS_GENERR; + case LV_OPEN_PORT_FAILED__PRIMARY_SERVER_NOT_RESPONDING: + case LV_NO_SERVER_RESPONDING: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SRE Server Unavailable!\n"); + return SWITCH_STATUS_GENERR; + case LV_SUCCESS: + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Port Opened %d %dkhz.\n", lv->sound_format, rate); + break; + } + + + // turn on sound and response file logging + /* + int save_sound_files = 1; + lv->port.SetPropertyEx(PROP_EX_SAVE_SOUND_FILES, + PROP_EX_VALUE_TYPE_INT_PTR, + &save_sound_files); + */ + lv->channel = 1; + lv->flags = *flags; + lv->port.StreamSetParameter(STREAM_PARM_DETECT_BARGE_IN, 1); + lv->port.StreamSetParameter(STREAM_PARM_DETECT_END_OF_SPEECH, 1); + lv->port.StreamSetParameter(STREAM_PARM_AUTO_DECODE, 1); + lv->port.StreamSetParameter(STREAM_PARM_DECODE_FLAGS, LV_DECODE_SEMANTIC_INTERPRETATION); + lv->port.StreamSetParameter(STREAM_PARM_VOICE_CHANNEL, lv->channel); + lv->port.StreamSetParameter(STREAM_PARM_GRAMMAR_SET, (long unsigned int) LV_ACTIVE_GRAMMAR_SET); + + + lv->port.StreamSetStateChangeCallBack(state_change_callback, lv); + lv->port.StreamSetParameter(STREAM_PARM_SOUND_FORMAT, lv->sound_format); + + if (dest) { + lv->port.SetClientPropertyEx(PROP_EX_SRE_SERVERS, PROP_EX_VALUE_TYPE_STRING, (void *) dest); + } + + switch_mutex_init(&lv->flag_mutex, SWITCH_MUTEX_NESTED, ah->memory_pool); + + switch_set_flag_locked(lv, LVFLAG_READY); + if (lv->port.StreamStart()) { + lv->port.ClosePort(); + return SWITCH_STATUS_GENERR; + } + + ah->private_info = lv; + + return SWITCH_STATUS_SUCCESS; +} + +/*! function to load a grammar to the asr interface */ +static switch_status_t lumenvox_asr_load_grammar(switch_asr_handle_t *ah, char *grammar, char *path) +{ + lumenvox_t *lv = (lumenvox_t *) ah->private_info; + + if (path) { + if (!lv->port.LoadGrammar(grammar, path)) { + if (!lv->port.ActivateGrammar(grammar)) { + return SWITCH_STATUS_SUCCESS; + } + } + } else { + if (lv->port.ActivateGrammar(grammar)) { + return SWITCH_STATUS_GENERR; + } + } + + return SWITCH_STATUS_GENERR; +} + + +/*! function to unload a grammar to the asr interface */ +static switch_status_t lumenvox_asr_unload_grammar(switch_asr_handle_t *ah, char *grammar) +{ + lumenvox_t *lv = (lumenvox_t *) ah->private_info; + + if (lv->port.DeactivateGrammar(grammar)) { + return SWITCH_STATUS_GENERR; + } + + return SWITCH_STATUS_SUCCESS; +} + +/*! function to close the asr interface */ +static switch_status_t lumenvox_asr_close(switch_asr_handle_t *ah, switch_asr_flag_t *flags) +{ + lumenvox_t *lv = (lumenvox_t *) ah->private_info; + + switch_set_flag(ah, SWITCH_ASR_FLAG_CLOSED); + lv->port.ClosePort(); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Port Closed.\n"); + return SWITCH_STATUS_SUCCESS; +} + +/*! function to feed audio to the ASR*/ +static switch_status_t lumenvox_asr_feed(switch_asr_handle_t *ah, void *data, unsigned int len, switch_asr_flag_t *flags) +{ + + lumenvox_t *lv = (lumenvox_t *) ah->private_info; + + if (!switch_test_flag(lv, LVFLAG_HAS_TEXT) && switch_test_flag(lv, LVFLAG_READY)) { + if (lv->port.StreamSendData(data, len)) { + return SWITCH_STATUS_FALSE; + } + } + + return SWITCH_STATUS_SUCCESS; +} + +/*! function to read results from the ASR*/ +static switch_status_t lumenvox_asr_check_results(switch_asr_handle_t *ah, switch_asr_flag_t *flags) +{ + lumenvox_t *lv = (lumenvox_t *) ah->private_info; + + return (switch_test_flag(lv, LVFLAG_HAS_TEXT) || switch_test_flag(lv, LVFLAG_BARGE)) ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; +} + +/*! function to read results from the ASR*/ +static switch_status_t lumenvox_asr_get_results(switch_asr_handle_t *ah, char **xmlstr, switch_asr_flag_t *flags) +{ + lumenvox_t *lv = (lumenvox_t *) ah->private_info; + switch_status_t ret = SWITCH_STATUS_SUCCESS; + + if (switch_test_flag(lv, LVFLAG_BARGE)) { + switch_clear_flag_locked(lv, LVFLAG_BARGE); + ret = SWITCH_STATUS_BREAK; + } + + if (switch_test_flag(lv, LVFLAG_HAS_TEXT)) { + std::stringstream ss; + int numInterp; + int code; + + + lv->port.StreamStop(); + code = lv->port.WaitForEngineToIdle(3000, lv->channel); + + if (code == LV_TIME_OUT) { + return SWITCH_STATUS_FALSE; + } + + numInterp = lv->port.GetNumberOfInterpretations(lv->channel); + + for (int t = 0; t < numInterp; ++t) { + ss << lv->port.GetInterpretation(lv->channel, t); + } + + *xmlstr = strdup((char *)ss.str().c_str()); + switch_clear_flag_locked(lv, LVFLAG_HAS_TEXT); + + + if (switch_test_flag(lv, SWITCH_ASR_FLAG_AUTO_RESUME)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Auto Resuming\n"); + switch_set_flag_locked(lv, LVFLAG_READY); + if (lv->port.StreamStart()) { + lv->port.ClosePort(); + return SWITCH_STATUS_GENERR; + } + } + + ret = SWITCH_STATUS_SUCCESS; + } + + return ret; +} + +static const switch_asr_interface_t lumenvox_asr_interface = { + /*.interface_name*/ "lumenvox", + /*.asr_open*/ lumenvox_asr_open, + /*.asr_load_grammar*/ lumenvox_asr_load_grammar, + /*.asr_unload_grammar*/ lumenvox_asr_unload_grammar, + /*.asr_close*/ lumenvox_asr_close, + /*.asr_feed*/ lumenvox_asr_feed, + /*.asr_resume*/ lumenvox_asr_resume, + /*.asr_pause*/ lumenvox_asr_pause, + /*.asr_check_results*/ lumenvox_asr_check_results, + /*.asr_get_results*/ lumenvox_asr_get_results +}; + +static const switch_loadable_module_interface_t lumenvox_module_interface = { + /*.module_name */ modname, + /*.endpoint_interface */ NULL, + /*.timer_interface */ NULL, + /*.dialplan_interface */ NULL, + /*.codec_interface */ NULL, + /*.application_interface */ NULL, + /*.api_interface */ NULL, + /*.file_interface */ NULL, + /*.speech_interface */ NULL, + /*.directory_interface */ NULL, + /*.chat_interface */ NULL, + /*.say_interface */ NULL, + /*.asr_interface */ &lumenvox_asr_interface +}; + +switch_status_t switch_module_load(const switch_loadable_module_interface_t **module_interface, char *filename) +{ + LVSpeechPort::RegisterAppLogMsg(log_callback, NULL, 5); + //LVSpeechPort::SetClientPropertyEx(PROP_EX_SRE_SERVERS, PROP_EX_VALUE_TYPE_STRING, (void *)"127.0.0.1"); + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = &lumenvox_module_interface; + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_MOD_DECLARE(switch_status_t) switch_module_shutdown(void) +{ + return SWITCH_STATUS_UNLOAD; +}