diff --git a/conf.d/h323chan.conf.sample b/conf.d/h323chan.conf.sample index 07ca7ab8..409b0c6c 100644 --- a/conf.d/h323chan.conf.sample +++ b/conf.d/h323chan.conf.sample @@ -49,6 +49,15 @@ ; This parameter is applied on reload for new calls only ;dtmfmethods=rfc2833,h323,inband +; honor_dtmf_detect: bool: Honor DTMF detected method when sending DTMFs +; If enabled the channel will try to send a DTMF using the same method as received +; If the detected method is not enabled it won't be used +; This parameter can be overridden from routing by 'ohonor_dtmf_detect' for outgoing call leg +; and 'ihonor_dtmf_detect' for incoming call leg +; This parameter is applied on reload for new calls only +; Defaults to enable +;honor_dtmf_detect=enable + ; Use an external RTP module instead of the native OpenH323 RTP stack, which is ; very cpu intensive. If no external RTP can be found it will fallback to the ; native stack. The only external RTP now is yrtp (see the yrtpchan module). diff --git a/conf.d/ysipchan.conf.sample b/conf.d/ysipchan.conf.sample index 762075ad..6b7b824e 100644 --- a/conf.d/ysipchan.conf.sample +++ b/conf.d/ysipchan.conf.sample @@ -130,6 +130,15 @@ ; This parameter is applied on reload for new calls only ;dtmfmethods=rfc2833,info,inband +; honor_dtmf_detect: bool: Honor DTMF detected method when sending DTMFs +; If enabled the channel will try to send a DTMF using the same method as received +; If the detected method is not enabled it won't be used +; This parameter can be overridden from routing by 'ohonor_dtmf_detect' for outgoing call leg +; and 'ihonor_dtmf_detect' for incoming call leg +; This parameter is applied on reload for new calls only +; Defaults to enable +;honor_dtmf_detect=enable + ; rfc2833: bool: Offer RFC2833 telephone-event by default ; A numeric payload >= 96 can be provided ;rfc2833=yes diff --git a/modules/h323chan.cpp b/modules/h323chan.cpp index b3b767a0..383f02f6 100644 --- a/modules/h323chan.cpp +++ b/modules/h323chan.cpp @@ -525,7 +525,6 @@ public: void stoppedExternal(H323Channel::Directions dir); void setRemoteAddress(const char* remoteIP, WORD remotePort); void cleanups(bool closeChans = true, bool dropChan = true); - bool sendTone(Message& msg, const char* tone); void setCallerID(const char* number, const char* name); void rtpExecuted(Message& msg); void rtpForward(Message& msg, bool init = false); @@ -622,10 +621,14 @@ public: protected: virtual void endDisconnect(const Message& msg, bool handled); private: + // Send tone(s) using method + bool sendTone(Message& msg, const char* tone, int meth, bool& retVal); + YateH323Connection* m_conn; H323Connection::CallEndReason m_reason; bool m_hungup; DtmfMethods m_dtmfMethods; + bool m_honorDtmfDetect; }; class YateGkRegThread : public PThread @@ -759,6 +762,7 @@ protected: unsigned int YateGkRegThread::s_count = 0; Mutex YateGkRegThread::s_mutexCount(false,"H323GkThreads"); static DtmfMethods s_dtmfMethods; +static bool s_honorDtmfDetect = true; // Deprecated dtmf params warn static bool s_warnDtmfInbandCfg = true; static bool s_warnDtmfInbandCallExecute = true; @@ -2382,17 +2386,6 @@ void YateH323Connection::stoppedExternal(H323Channel::Directions dir) } } -bool YateH323Connection::sendTone(Message& msg, const char* tone) -{ - if (m_rtpid) { - msg.setParam("targetid",m_rtpid); - return false; - } - while (*tone) - SendUserInputTone(*tone++); - return true; -} - void YateH323Connection::setEpConn(bool created) { YateH323EndPoint* ep = static_cast(&endpoint); @@ -2784,7 +2777,8 @@ void YateH323Connection::setCallerID(const char* number, const char* name) YateH323Chan::YateH323Chan(YateH323Connection* conn,Message* msg,const char* addr) : Channel(hplugin,0,(msg != 0)), m_conn(conn), m_reason(H323Connection::EndedByLocalUser), - m_hungup(false) + m_hungup(false), + m_honorDtmfDetect(s_honorDtmfDetect) { s_mutex.lock(); s_chanCount++; @@ -2806,6 +2800,7 @@ YateH323Chan::YateH323Chan(YateH323Connection* conn,Message* msg,const char* add else m_dtmfMethods.getDeprecatedDtmfMethod(*msg,"dtmfinband",DtmfMethods::Inband,&s_warnDtmfInbandCallExecute); s->copyParams(*msg,"caller,callername,called,billid,callto,username"); + m_honorDtmfDetect = msg->getBoolValue(YSTRING("ohonor_dtmf_detect"),m_honorDtmfDetect); } Engine::enqueue(s); } @@ -2915,6 +2910,33 @@ void YateH323Chan::endDisconnect(const Message& msg, bool handled) #endif } +// Send tone(s) using method +bool YateH323Chan::sendTone(Message& msg, const char* tone, int meth, bool& retVal) +{ + if (!(m_conn && tone)) + return false; + bool ok = false; + if (meth == DtmfMethods::H323) { + const char* t = tone; + while (*t) + m_conn->SendUserInputTone(*t++); + retVal = true; + ok = true; + } + else if (meth == DtmfMethods::Rfc2833) { + ok = m_conn->rtpStarted() && m_conn->rtpId() && m_conn->dtmfPayload() > 0; + if (ok) + msg.setParam("targetid",m_conn->rtpId()); + } + else if (meth == DtmfMethods::Inband) { + ok = dtmfInband(tone); + retVal = ok; + } + XDebug(this,ok ? DebugAll : DebugNote,"sendTone(%s) meth=%s (%d) ok=%u [%p]", + tone,lookup(meth,DtmfMethods::s_methodName),meth,ok,this); + return ok; +} + // Set the signalling address void YateH323Chan::setAddress(const char* addr) { @@ -2994,6 +3016,7 @@ void YateH323Chan::callAccept(Message& msg) DtmfMethods old = m_dtmfMethods; m_dtmfMethods.set(*meths,&old); } + m_honorDtmfDetect = msg.getBoolValue(YSTRING("ihonor_dtmf_detect"),m_honorDtmfDetect); Channel::callAccept(msg); if (m_conn) { m_conn->rtpExecuted(msg); @@ -3056,23 +3079,18 @@ bool YateH323Chan::msgTone(Message& msg, const char* tone) } bool retVal = false; bool ok = false; + if (msg.getBoolValue(YSTRING("honor_dtmf_detect"),m_honorDtmfDetect)) { + const String& detected = msg[YSTRING("detected")]; + int meth = lookup(detected,DtmfMethods::s_methodName,DtmfMethods::MethodCount); + if (meth != DtmfMethods::MethodCount && methods.hasMethod(meth)) { + ok = sendTone(msg,tone,meth,retVal); + methods.reset(meth); + } + } for (int i = 0; !ok && i < DtmfMethods::MethodCount; i++) { int meth = methods[i]; - if (meth == DtmfMethods::H323) { - while (*tone) - m_conn->SendUserInputTone(*tone++); - ok = true; - retVal = true; - } - else if (meth == DtmfMethods::Rfc2833) { - ok = m_conn->rtpStarted() && m_conn->rtpId() && m_conn->dtmfPayload() > 0; - if (ok) - msg.setParam("targetid",m_conn->rtpId()); - } - else if (meth == DtmfMethods::Inband) { - ok = dtmfInband(tone); - retVal = ok; - } + if (meth != DtmfMethods::MethodCount) + ok = sendTone(msg,tone,meth,retVal); } if (!ok && debugAt(DebugNote)) { String tmp; @@ -3280,6 +3298,7 @@ void H323Driver::initialize() else s_dtmfMethods.setDefault(); s_cfgMutex.unlock(); + s_honorDtmfDetect = s_cfg.getBoolValue("general","honor_dtmf_detect",true); s_externalRtp = s_cfg.getBoolValue("general","external_rtp",true); s_passtrough = s_cfg.getBoolValue("general","forward_rtp",false); s_fallbackRtp = s_cfg.getBoolValue("general","fallback_rtp",true); diff --git a/modules/ysipchan.cpp b/modules/ysipchan.cpp index 91fffc74..b75ff0fb 100644 --- a/modules/ysipchan.cpp +++ b/modules/ysipchan.cpp @@ -940,6 +940,8 @@ private: void updateRtpNatAddress(NamedList* params = 0); // Process allow list. Get INFO support bool infoAllowed(const SIPMessage* msg); + // Send tone(s) using method + bool sendTone(Message& msg, const char* tone, int meth, bool& retVal); SIPTransaction* m_tr; SIPTransaction* m_tr2; @@ -967,6 +969,7 @@ private: bool m_checkAllowInfo; // Check Allow in INVITE and OK for INFO support bool m_missingAllowInfoDefVal; // Default INFO support if Allow header is missing DtmfMethods m_dtmfMethods; + bool m_honorDtmfDetect; // REFER already running bool m_referring; // reINVITE requested or in progress @@ -1132,6 +1135,7 @@ int YateSIPEndPoint::s_evCount = 0; static bool s_checkAllowInfo = true; // Check Allow in INVITE and OK for INFO support static bool s_missingAllowInfoDefVal = true; // Default INFO support if Allow header is missing static DtmfMethods s_dtmfMethods; +static bool s_honorDtmfDetect = true; // Deprecated dtmf params warn static bool s_warnDtmfInfoCfg = true; static bool s_warnDtmfInbandCfg = true; @@ -1143,6 +1147,7 @@ const TokenDict DtmfMethods::s_methodName[] = { { "info", Info}, { "rfc2833", Rfc2833}, { "inband", Inband}, + { "sip-info", Info}, { 0, 0 }, }; @@ -5075,6 +5080,7 @@ YateSIPConnection::YateSIPConnection(SIPEvent* ev, SIPTransaction* tr) m_state(Incoming), m_port(0), m_route(0), m_routes(0), m_authBye(true), m_checkAllowInfo(s_checkAllowInfo), m_missingAllowInfoDefVal(s_missingAllowInfoDefVal), + m_honorDtmfDetect(s_honorDtmfDetect), m_referring(false), m_reInviting(ReinviteNone), m_lastRseq(0) { Debug(this,DebugAll,"YateSIPConnection::YateSIPConnection(%p,%p) [%p]",ev,tr,this); @@ -5247,6 +5253,7 @@ YateSIPConnection::YateSIPConnection(Message& msg, const String& uri, const char m_state(Outgoing), m_port(0), m_route(0), m_routes(0), m_authBye(false), m_checkAllowInfo(s_checkAllowInfo), m_missingAllowInfoDefVal(s_missingAllowInfoDefVal), + m_honorDtmfDetect(s_honorDtmfDetect), m_referring(false), m_reInviting(ReinviteNone), m_lastRseq(0) { Debug(this,DebugAll,"YateSIPConnection::YateSIPConnection(%p,'%s') [%p]", @@ -5265,6 +5272,7 @@ YateSIPConnection::YateSIPConnection(Message& msg, const String& uri, const char m_dtmfMethods.getDeprecatedDtmfMethod(msg,"dtmfinband",DtmfMethods::Inband,&s_warnDtmfInbandCallExecute); } s_globalMutex.unlock(); + m_honorDtmfDetect = msg.getBoolValue(YSTRING("ohonor_dtmf_detect"),m_honorDtmfDetect); m_secure = msg.getBoolValue(YSTRING("secure"),plugin.parser().secure()); setRfc2833(msg.getParam(YSTRING("rfc2833"))); m_rtpForward = msg.getBoolValue(YSTRING("rtp_forward")); @@ -6581,60 +6589,25 @@ bool YateSIPConnection::msgTone(Message& msg, const char* tone) s_warnDtmfMethodChanDtmf = false; Debug(this,DebugConf,"Deprecated 'method' parameter in '%s'. Use 'methods' instead!",msg.c_str()); } - if ((*method == YSTRING("info")) || (*method == YSTRING("sip-info"))) - methods.set(DtmfMethods::Info); - else if (*method == YSTRING("rfc2833")) - methods.set(DtmfMethods::Rfc2833); - else if (*method == YSTRING("inband")) - methods.set(DtmfMethods::Inband); + int meth = lookup(*method,DtmfMethods::s_methodName,DtmfMethods::MethodCount); + if (meth != DtmfMethods::MethodCount) + methods.set(meth); } } bool retVal = false; bool ok = false; + if (msg.getBoolValue(YSTRING("honor_dtmf_detect"),m_honorDtmfDetect)) { + const String& detected = msg[YSTRING("detected")]; + int meth = lookup(detected,DtmfMethods::s_methodName,DtmfMethods::MethodCount); + if (meth != DtmfMethods::MethodCount && methods.hasMethod(meth)) { + ok = sendTone(msg,tone,meth,retVal); + methods.reset(meth); + } + } for (int i = 0; !ok && i < DtmfMethods::MethodCount; i++) { int meth = methods[i]; - if (meth == DtmfMethods::Info) { - // Send INFO only if initial transaction finished - if (m_tr) - continue; - for (; tone && *tone; tone++) { - char c = *tone; - for (int j = 0; j <= 16; j++) { - if (s_dtmfs[j] != c) - continue; - SIPMessage* m = createDlgMsg("INFO"); - if (!m) - break; - copySipHeaders(*m,msg); - String tmp; - tmp << "Signal=" << j << "\r\n"; - m->setBody(new MimeStringBody("application/dtmf-relay",tmp)); - plugin.ep()->engine()->addMessage(m); - m->deref(); - break; - } - } - ok = true; - retVal = true; - } - else if (meth == DtmfMethods::Rfc2833 || meth == DtmfMethods::Inband) { - // RFC2833 and inband require media to be started - if (!(m_rtpMedia && (m_mediaStatus == MediaStarted))) - continue; - ObjList* l = m_rtpMedia->find("audio"); - const SDPMedia* m = static_cast(l ? l->get() : 0); - if (!m) - continue; - if (meth == DtmfMethods::Rfc2833) { - ok = m->rfc2833().toBoolean(true); - if (ok) - msg.setParam("targetid",m->id()); - } - else { - ok = dtmfInband(tone); - retVal = ok; - } - } + if (meth != DtmfMethods::MethodCount) + ok = sendTone(msg,tone,meth,retVal); } if (!ok && debugAt(DebugNote)) { String tmp; @@ -6877,6 +6850,7 @@ void YateSIPConnection::callAccept(Message& msg) // Update dtmf methods from message m_checkAllowInfo = msg.getBoolValue(YSTRING("icheck_allow_info"),m_checkAllowInfo); m_missingAllowInfoDefVal = msg.getBoolValue(YSTRING("imissing_allow_info"),m_missingAllowInfoDefVal); + m_honorDtmfDetect = msg.getBoolValue(YSTRING("ihonor_dtmf_detect"),m_honorDtmfDetect); String* meths = msg.getParam(YSTRING("idtmfmethods")); if (meths) { DtmfMethods old = m_dtmfMethods; @@ -7144,6 +7118,56 @@ bool YateSIPConnection::infoAllowed(const SIPMessage* msg) return ok; } +// Send tone(s) using method +bool YateSIPConnection::sendTone(Message& msg, const char* tone, int meth, bool& retVal) +{ + bool ok = false; + if (meth == DtmfMethods::Info) { + // Send INFO only if initial transaction finished + if (!m_tr) { + const char* t = tone; + for (; t && *t; t++) { + char c = *t; + for (int j = 0; j <= 16; j++) { + if (s_dtmfs[j] != c) + continue; + SIPMessage* m = createDlgMsg("INFO"); + if (!m) + break; + copySipHeaders(*m,msg); + String tmp; + tmp << "Signal=" << j << "\r\n"; + m->setBody(new MimeStringBody("application/dtmf-relay",tmp)); + plugin.ep()->engine()->addMessage(m); + m->deref(); + break; + } + } + retVal = true; + ok = true; + } + } + else if (meth == DtmfMethods::Rfc2833 || meth == DtmfMethods::Inband) { + // RFC2833 and inband require media to be started + if (m_rtpMedia && (m_mediaStatus == MediaStarted)) { + ObjList* l = m_rtpMedia->find("audio"); + const SDPMedia* m = static_cast(l ? l->get() : 0); + if (meth == DtmfMethods::Rfc2833) { + ok = m && m->rfc2833().toBoolean(true); + if (ok) + msg.setParam("targetid",m->id()); + } + else if (m) { + ok = dtmfInband(tone); + retVal = ok; + } + } + } + XDebug(this,ok ? DebugAll : DebugNote,"sendTone(%s) meth=%s (%d) ok=%u [%p]", + tone,lookup(meth,DtmfMethods::s_methodName),meth,ok,this); + return ok; +} + YateSIPLine::YateSIPLine(const String& name) : String(name), Mutex(true,"YateSIPLine"), @@ -7983,6 +8007,7 @@ void SIPDriver::initialize() s_globalMutex.unlock(); s_checkAllowInfo = s_cfg.getBoolValue("general","check_allow_info",true); s_missingAllowInfoDefVal = s_cfg.getBoolValue("general","missing_allow_info",true); + s_honorDtmfDetect = s_cfg.getBoolValue("general","honor_dtmf_detect",true); s_maxForwards = s_cfg.getIntValue("general","maxforwards",20); s_floodEvents = s_cfg.getIntValue("general","floodevents",100); s_floodProtection = s_cfg.getBoolValue("general","floodprotection",true);