| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- #include "EslGateway.h"
- #include <thread>
- #include <regex>
- #include "pubdef.h"
- #include "Config.h"
- #include "Log.h"
- #include "Util.h"
- #define ESL_CMD_SCAN_INTERNAL "api sofia status profile internal reg\n\n"
- #define ESL_HEADER_CALLID "Channel-Call-UUID"
- #define ESL_HEADER_CCID "variable_cc_member_session_uuid" // freeswitchs自带的callcenter模块转接坐席后,callid产生变化,可通过该通道变量进行关联
- #define ESL_HEADER_CHANID "Unique-ID"
- #define ESL_HEADER_CALLER "Caller-Caller-ID-Number"
- #define ESL_HEADER_CALLEE "Caller-Callee-ID-Number"
- #define ESL_HEADER_DEST_NUM "Caller-Destination-Number"
- #define ESL_HEADER_DIRECTION "Call-Direction"
- #define ESL_HEADER_SUBCLASS "Event-Subclass"
- #define ESL_HEADER_EXTEN_NO "username"
- // ESL事件头域常量值
- #define ESL_HDR_DIRECTION_INBOUND "inbound"
- #define ESL_HDR_DIRECTION_OUTBOUND "outbound"
- #define ESL_HDR_SUBCLASS_SIP_REG "sofia::register"
- #define ESL_HDR_SUBCLASS_SIP_UNREG "sofia::unregister"
- EslGateway::EslGateway() :m_Stop(true), m_pEventThread(nullptr), m_pEsSearch(nullptr)
- {
- }
- EslGateway::~EslGateway()
- {
- }
- bool EslGateway::initDB()
- {
- auto cfg = CConfig::GetInstance();
- return true;
- }
- bool EslGateway::start()
- {
- memset(&m_EslHdl4Listen, '\0', sizeof(esl_handle_t));
- memset(&m_EslHdl4Send, '\0', sizeof(esl_handle_t));
- auto cfg = CConfig::GetInstance();
- LOG_INFO("{EslGateway}: 处理客户端连接, ip = %s,port=%d,pwd=%s", cfg->fsIp().c_str(), cfg->fsPort(), cfg->fsPwd().c_str());
- esl_connect(&m_EslHdl4Listen, cfg->fsIp().c_str(), cfg->fsPort(), NULL, cfg->fsPwd().c_str());
- if (!m_EslHdl4Listen.connected)
- return false;
- LOG_INFO("{EslGateway}: 处理客户端连接m_EslHdl4Listen OK");
- esl_events(&m_EslHdl4Listen, ESL_EVENT_TYPE_PLAIN,
- ("CHANNEL_CREATE CHANNEL_ANSWER CHANNEL_HANGUP_COMPLETE CHANNEL_PARK CHANNEL_EXECUTE_COMPLETE DETECTED_SPEECH CUSTOM sofia::register sofia::unregister callcenter::info"));
- esl_connect(&m_EslHdl4Send, cfg->fsIp().c_str(), cfg->fsPort(), NULL, cfg->fsPwd().c_str());
- if (!m_EslHdl4Send.connected) {
- esl_disconnect(&m_EslHdl4Listen);
- return false;
- }
- scanExten();
- m_Stop = false;
- if (m_pEventThread == nullptr) {
- m_pEventThread = std::make_unique<boost::thread>(std::bind(&this_type::__eventThread, this));
- }
- if (m_pEsSearch == nullptr) {
- m_pEsSearch = std::make_unique<EsSearch>();
- }
- return true;
- }
- void EslGateway::stop()
- {
- m_Stop.store(true);
- if (m_pEventThread) {
- m_pEventThread->interrupt();
- m_pEventThread->join();
- m_pEventThread.reset();
- }
- memset(&m_EslHdl4Listen, '\0', sizeof(esl_handle_t));
- memset(&m_EslHdl4Send, '\0', sizeof(esl_handle_t));
- }
- void EslGateway::scanExten()
- {
- if (esl_send_recv(&m_EslHdl4Send, ESL_CMD_SCAN_INTERNAL) != ESL_SUCCESS)
- return;
- if (m_EslHdl4Send.last_sr_event == nullptr || m_EslHdl4Send.last_sr_event->body == nullptr)
- return;
- std::string body = m_EslHdl4Send.last_sr_event->body;
- std::list<std::string> rows;
- boost::split(rows, body, boost::is_any_of("\n"));
- while (!rows.empty()) {
- std::string var = rows.front();
- rows.pop_front();
- std::regex space("\t| ");
- var = std::regex_replace(var, space, "");
- std::regex reg("IP:(.*)");
- if (std::regex_match(var, reg)) {
- std::string extenNo, extenIP;
- extenIP = std::regex_replace(var, reg, "$1");
- while (!rows.empty()) {
- var = rows.front();
- rows.pop_front();
- std::regex space("\t| ");
- var = std::regex_replace(var, space, "");
- reg = std::regex("Auth-User:(.*)");
- if (std::regex_match(var, reg)) {
- extenNo = std::regex_replace(var, reg, "$1");
- if (!extenNo.empty() && !extenIP.empty()) {
- LOG_DEBUG("分机注册:%s %s", extenNo.c_str(), extenIP.c_str());
- __addExtenChan(extenNo);
- }
- break;
- }
- }
- }
- }
- }
- bool EslGateway::execPlay(const std::string & strChanId, const std::string & strFilePath)
- {
- //return false;
- execte("set", "playback_terminators=none", strChanId);
- //execte("break", "continue", strChanId);
- return execte("playback", strFilePath.c_str(), strChanId);
- }
- bool EslGateway::excDetectSpeech(const std::string & strChanId)
- {
- //return true;
- //execte("set", "fire_asr_events=true", strChanId);
- return execte("detect_speech", "unimrcp:mrcpv2 hello hello", strChanId);
- }
- bool EslGateway::excDetectSpeechPause(const std::string & strChanId)
- {
- return execte("detect_speech", "pause", strChanId);
- }
- bool EslGateway::excDetectSpeechResume(const std::string & strChanId)
- {
- //return true;
- return execte("detect_speech", "resume", strChanId);
- }
- bool EslGateway::excPlayAndDetectSpeech(const std::string & strChanId, const std::string & strFilePath)
- {
- /*Format fmt("%s detect:mrcpv2 {start-input-timers=false,no-input-timeout=80000,recognition-timeout=80000}builtin:grammar/boolean?language=en-US;y=1;n=2");
- fmt %strFilePath;*/
- return execte("play_and_detect_speech", "ivr/say_yes_or_no.wavdetect:unimrcp {start-input-timers=false,no-input-timeout=5000,recognition-timeout=5000}builtin:grammar/boolean?language=en-US;y=1;n=2", strChanId);
- }
- bool EslGateway::execte(const std::string & strApp, const std::string & strParam, const std::string & strChanId)
- {
- esl_status_t Status = esl_execute(&m_EslHdl4Send, strApp.c_str(), strParam.c_str(), strChanId.c_str());
- return Status == ESL_SUCCESS;
- }
- void EslGateway::__eventThread(void)
- {
- esl_event_t* pEvent = nullptr;
- esl_handle_t* pListenHandle = &m_EslHdl4Listen;
- // 下面的设置保证执行同时多个APP时,后面的不会抢占前面的
- pListenHandle->event_lock = 1;
- while (!m_Stop)
- {
- // esl_recv_event会阻塞线程,其第二个参数为1,表示优先检查内部缓存列表,防止遗漏事件。
- auto status = esl_recv_event(pListenHandle, 1, nullptr);
- if ((status == ESL_SUCCESS || status == ESL_BREAK) && !m_Stop)
- {
- pEvent = pListenHandle->last_ievent;
- if (pEvent != nullptr)
- {
- __onEslEvent(pEvent);
- }
- }
- else // 连接中断
- {
- // 连接中断
- LOG_WARN_S("FS链接断开...");
- do
- {
- LOG_WARN_S("FS 3秒后自动重连...");
- std::this_thread::sleep_for(std::chrono::seconds(3));
- LOG_WARN_S("FS开始重连...");
- } while (!start() && !m_Stop);
- }
- }
- }
- void EslGateway::__onEslEvent(esl_event_t * pEvent)
- {
- switch (pEvent->event_id)
- {
- case ESL_EVENT_CHANNEL_CREATE:
- case ESL_EVENT_CHANNEL_ANSWER:
- case ESL_EVENT_CHANNEL_HANGUP_COMPLETE:
- __onEslEvtChanEvent(pEvent);
- break;
- case ESL_EVENT_DETECTED_SPEECH:
- __detectSpeech(pEvent);
- break;
- case ESL_EVENT_CHANNEL_EXECUTE_COMPLETE:
- __onEslEvtChanExecComplete(pEvent);
- break;
- case ESL_EVENT_CHANNEL_PARK:
- __onEslEvtPark(pEvent);
- break;
- case ESL_EVENT_CUSTOM:
- __onEslEvtCustom(pEvent);
- break;
- default:
- break;
- }
- }
- void EslGateway::__onEslEvtChanEvent(esl_event_t * pEvent)
- {
- // 获取会话ID
- std::string strCallID = "";
- if ((strCallID = std::to_string(esl_event_get_header(pEvent, ESL_HEADER_CCID))) == "") {
- strCallID = std::to_string(esl_event_get_header(pEvent, ESL_HEADER_CALLID));
- }
- // 获取通道ID
- const auto pChanID = esl_event_get_header(pEvent, ESL_HEADER_CHANID);
- const std::string strChanID = std::to_string(pChanID);
- // 获取主被叫
- std::string strCaller = std::to_string(esl_event_get_header(pEvent, ESL_HEADER_CALLER));
- std::string strCallee = std::to_string(esl_event_get_header(pEvent, ESL_HEADER_CALLEE));
- if (strCallee == "") {
- strCallee = std::to_string(esl_event_get_header(pEvent, ESL_HEADER_DEST_NUM));
- }
- // 获取通道方向
- /*CALL_DIRECTION emDirection;
- if (strcmp(esl_event_get_header(pEvent, ESL_HEADER_DIRECTION), ESL_HDR_DIRECTION_INBOUND) == 0)
- emDirection = CALL_DIRECTION_INBOUND;
- else
- emDirection = CALL_DIRECTION_OUTBOUND;*/
- switch (pEvent->event_id)
- {
- case ESL_EVENT_CHANNEL_CREATE:
- {
- LOG_INFO_S(boost::str(Format("ESL通道创建事件[%s][%s]") % strCallID %strChanID));
-
- }
- break;
- case ESL_EVENT_CHANNEL_ANSWER:
- {
- LOG_INFO_S(boost::str(Format("ESL通道接听事件[%s][%s]") % strCallID %strChanID));
-
- }
- break;
- case ESL_EVENT_CHANNEL_HANGUP_COMPLETE:
- {
- LOG_INFO_S(boost::str(Format("ESL通道挂机事件[%s][%s]") % strCallID %strChanID));
-
- }
- break;
- default:
- break;
- }
- }
- void EslGateway::__hangupEvent(esl_event_t * pEvent)
- {
- }
- void EslGateway::__detectSpeech(esl_event_t * pEvent)
- {
- /*LOG_DEBUG_S("语音识别消息");
- char *pContent = new char[2048];
- esl_event_serialize(pEvent, &pContent, ESL_TRUE);
- LOG_DEBUG_S(pContent);
- if (pContent) {
- delete pContent;
- pContent = nullptr;
- }*/
- std::string strCallID = "";
- auto pCallID = esl_event_get_header(pEvent, ESL_HEADER_CALLID);
- strCallID = pCallID == nullptr ? "" : pCallID;
- auto pCallCCID = esl_event_get_header(pEvent, ESL_HEADER_CCID);
- if (pCallCCID != nullptr) {
- strCallID = pCallCCID;
- }
- // 获取通道ID
- std::string strChanID = "";
- auto pChanID = esl_event_get_header(pEvent, ESL_HEADER_CHANID);
- strChanID = pChanID == nullptr ? "" : pChanID;
- char* speechType = nullptr;
- speechType = esl_event_get_header(pEvent, "Speech-Type");
- if (speechType == nullptr) {
- LOG_WARN_S(boost::str(Format("ESL语音识别事件[%s][%s],Speech-Type 空") % strCallID %strChanID));
- return;
- }
- LOG_INFO_S(boost::str(Format("ESL语音识别事件[%s][%s][%s]") % speechType % strCallID %strChanID));
- if (strcmp(speechType, "begin-speaking") == 0) {
-
- }
- else if (strcmp(speechType, "detected-speech") == 0)
- {
- excDetectSpeechPause(strChanID); // 暂停识别
-
- char* result = esl_event_get_body(pEvent);
- if (result) {
- LOG_DEBUG("%s",result);
- std::string strRes, err;
- parseASRXml(result, strRes, err);
- LOG_INFO_S(boost::str(Format("识别结果:\n[%s]") % strRes));
- std::wstring wsRes = string2wstring(strRes);
- if (strRes == "") { // 没有识别到说的内容
- execPlay(pChanID, "E:\\zhinengwav\\empty.wav");
- }
- /*else if (wsRes.compare(L"人工") == 0) {*/
- else if (strRes.find("人工")!=std::string::npos) {
- execte("set", "trunkCallIn=2", strChanID);
- execPlay(pChanID, "E:\\zhinengwav\\turnagent.wav");
- }
- else {
- auto answer = m_pEsSearch->Search(strRes);
- if (answer == "") { // 未匹配到答案
- execPlay(pChanID, "E:\\zhinengwav\\unknown.wav");
- }
- else {
- execPlay(pChanID, "E:\\zhinengwav\\wav\\" + answer);
- }
- }
-
- }
- //excDetectSpeechResume(strChanID); // 识别结束,继续识别
- }
- }
- void EslGateway::__onEslEvtChanExecComplete(esl_event_t * pEvent)
- {
- /*LOG_DEBUG_S("APP执行完成消息");
- char *pContent = new char[2048];
- esl_event_serialize(pEvent, &pContent, ESL_TRUE);
- LOG_DEBUG_S(pContent);
- if (pContent) {
- delete pContent;
- pContent = nullptr;
- }*/
- auto isTrunkCallIn = esl_event_get_header(pEvent, "variable_trunkCallIn");
- if (isTrunkCallIn == NULL || strcmp(isTrunkCallIn, "1") == 0 || strcmp(isTrunkCallIn, "2") == 0) return;
- auto ChanId = esl_event_get_header(pEvent, ESL_HEADER_CHANID);
- const char* pApp = esl_event_get_header(pEvent, "Application");
- LOG_INFO("通道[%s]执行APP[%s]完成",ChanId,pApp);
- if (strcmp(pApp, "playback") == 0) {
- LOG_INFO("通道[%s]放音完成,继续识别", ChanId);
- excDetectSpeechResume(ChanId); // 放音完成,继续识别
- }
-
- }
- void EslGateway::__onEslEvtCustom(esl_event_t * pEvent)
- {
- const char* pSubClass = esl_event_get_header(pEvent, ESL_HEADER_SUBCLASS);
- if (pSubClass == nullptr)return;
- auto HeaderValue = esl_event_get_header(pEvent, ESL_HEADER_EXTEN_NO);
- if (strcmp(pSubClass, ESL_HDR_SUBCLASS_SIP_REG) == 0) { // 分机注册
- if (HeaderValue == nullptr) return;
- __addExtenChan(std::to_string(HeaderValue));
- }
- else if (strcmp(pSubClass, ESL_HDR_SUBCLASS_SIP_UNREG) == 0) { // 分机取消注册
- if (HeaderValue == nullptr) return;
- __delExtenChan(std::to_string(HeaderValue));
- }
- }
- void EslGateway::__onEslEvtPark(esl_event_t * pEvent)
- {
- // 获取通道ID
- auto ChanId = esl_event_get_header(pEvent, ESL_HEADER_CHANID);
- if (ChanId == NULL)return;
- // 获取通道方向
- auto Direct = esl_event_get_header(pEvent, ESL_HEADER_DIRECTION);
- if (!Direct || strcmp(Direct, ESL_HDR_DIRECTION_INBOUND) != 0) return;
- auto isTrunkCallIn = esl_event_get_header(pEvent, "variable_trunkCallIn");
- if (isTrunkCallIn == NULL || strcmp(isTrunkCallIn, "1") != 0) return;
- auto strCaller = esl_event_get_header(pEvent, ESL_HEADER_CALLER);
- execte("set", "trunkCallIn=0", ChanId);
- excDetectSpeech(ChanId);
- //excPlayAndDetectSpeech("E:\\zhinengwav\\welcome.wav",ChanId);
- LOG_INFO("来电[%s]进入智能语音系统", strCaller);
- }
- void EslGateway::__addExtenChan(const std::string & strNo)
- {
- LOG_INFO_S(boost::str(Format("分机[%s]注册") % strNo));
- }
- void EslGateway::__delExtenChan(const std::string & strNo)
- {
- LOG_INFO_S(boost::str(Format("分机[%s]注册取消成功") % strNo));
- }
- std::string EslGateway::__createJson(const std::string & strType, const bool & bOk, const std::string&strDesc)
- {
- Json::Value root;
- root["Type"] = strType;
- root["Result"] = bOk;
- root["Desc"] = strDesc;
- Json::StreamWriterBuilder builder;
- return Json::writeString(builder, root);
- }
|