From e0fdf7c584e0c68b7ee42f05b7692936eb0946df Mon Sep 17 00:00:00 2001 From: marian Date: Tue, 18 Sep 2012 08:49:39 +0000 Subject: [PATCH] Allow dtmf send method(s) to be configured. Detect RFC2833 support when receiving remote capabilities. git-svn-id: http://yate.null.ro/svn/yate/trunk@5265 acf43c95-373e-0410-b603-e72c3f656dc1 --- conf.d/h323chan.conf.sample | 19 ++- modules/h323chan.cpp | 252 ++++++++++++++++++++++++++++++++++-- 2 files changed, 259 insertions(+), 12 deletions(-) diff --git a/conf.d/h323chan.conf.sample b/conf.d/h323chan.conf.sample index f7e63836..aa9edcb3 100644 --- a/conf.d/h323chan.conf.sample +++ b/conf.d/h323chan.conf.sample @@ -28,8 +28,23 @@ ; needmedia: bool: Drop calls for which no common media could be negotiated ;needmedia=yes -; dtmfinband: bool: Send keypad events as inband DTMF -;dtmfinband=no +; dtmfmethods: string: Comma separated list of methods used to send DTMFs +; Allowed values in list: +; h323: Use library send user input method +; rfc2833: Use RFC 2833 signals if remote party advertised support +; inband: Send tones in audio stream +; The methods will be used in the listed order +; Defaults to 'rfc2833,h323,inband' if missing or empty +; Invalid values are ignored +; E.g. +; 'h323,foo' leads to 'h323' +; 'foo,foo1' leads to 'rfc2833,h323,inband' +; This parameter can be overridden from routing by 'odtmfmethods' for outgoing call leg +; and 'idtmfmethods' for incoming call leg +; Also, this parameter can be overridden in chan.dtmf messages by a 'methods' parameter +; NOTE: When overridden from chan.dtmf an empty or invalid 'methods' parameter will be ignored +; This parameter is applied on reload for new calls only +;dtmfmethods=rfc2833,h323,inband ; 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 diff --git a/modules/h323chan.cpp b/modules/h323chan.cpp index 83f69296..60454b5c 100644 --- a/modules/h323chan.cpp +++ b/modules/h323chan.cpp @@ -90,7 +90,6 @@ typedef PBoolean BOOL; using namespace TelEngine; namespace { // anonymous -static bool s_inband; static bool s_externalRtp; static bool s_fallbackRtp; static bool s_needMedia = true; @@ -269,6 +268,57 @@ class YateGatekeeperServer; class YateH323EndPoint; class YateH323Chan; +class DtmfMethods +{ +public: + enum Method { + H323, + Rfc2833, + Inband, + MethodCount + }; + inline DtmfMethods() + { setDefault(); } + inline void set(int _0 = MethodCount, int _1 = MethodCount, int _2 = MethodCount) { + m_methods[0] = _0; + m_methods[1] = _1; + m_methods[2] = _2; + } + inline void setDefault() + { set(Rfc2833,H323,Inband); } + // Replace all methods from comma separated list + // If no method is set use other or setDefEmpty (reset to default) + // Return false if methods contain unknown methods + bool set(const String& methods, const DtmfMethods* other, bool setDefEmpty = true); + // Retrieve a method from deperecated parameters + // Reset the method if the parameter is false + // Display a message anyway if warn is not false + // Return true if the parameter was found + bool getDeprecatedDtmfMethod(const NamedList& list, const char* param, int method, bool* warn); + // Reset a method + void reset(int method); + // Build a string list from methods + void buildMethods(String& buf, const char* sep = ","); + inline void printMethods(DebugEnabler* enabler, int level, const String& str) { + String tmp; + buildMethods(tmp); + Debug(enabler,level,"Built DTMF methods '%s' from '%s'",tmp.safe(),str.safe()); + } + inline int operator[](unsigned int index) { + if (index < MethodCount) + return m_methods[index]; + return MethodCount; + } + inline DtmfMethods& operator=(const DtmfMethods& other) { + for (int i = 0; i < MethodCount; i++) + m_methods[i] = other.m_methods[i]; + return *this; + } + static const TokenDict s_methodName[]; +protected: + int m_methods[MethodCount]; +}; + class H323Driver : public Driver { public: @@ -451,6 +501,8 @@ public: virtual void OnCleared(); virtual BOOL OnAlerting(const H323SignalPDU& alertingPDU, const PString& user); virtual BOOL OnReceivedProgress(const H323SignalPDU& pdu); + virtual BOOL OnReceivedCapabilitySet(const H323Capabilities& remoteCaps, + const H245_MultiplexCapability* muxCap, H245_TerminalCapabilitySetReject& reject); virtual void OnUserInputTone(char tone, unsigned duration, unsigned logicalChannel, unsigned rtpTimestamp); virtual void OnUserInputString(const PString& value); virtual BOOL OpenAudioChannel(BOOL isEncoding, unsigned bufferSize, @@ -483,8 +535,16 @@ public: { return m_nativeRtp; } inline void rtpLocal() { m_passtrough = false; } + inline bool rtpStarted() const + { return m_rtpStarted; } + inline const String& rtpId() const + { return m_rtpid; } + inline int dtmfPayload() const + { return m_dtmfPayload; } private: void setEpConn(bool created); + // Retrieve RTP DTMF payload from local/remote caps. Return negative if not found + int rtpDtmfPayload(bool local); String m_chanId; YateH323Chan* m_chan; Mutex* m_mutex; @@ -500,6 +560,8 @@ private: String m_remoteAddr; int m_remotePort; bool m_needMedia; + bool m_rtpStarted; + int m_dtmfPayload; }; // this part has been inspired (more or less) from chan_h323 of project asterisk, credits to Jeremy McNamara for chan_h323 and to Mark Spencer for asterisk. @@ -559,7 +621,7 @@ private: YateH323Connection* m_conn; H323Connection::CallEndReason m_reason; bool m_hungup; - bool m_inband; + DtmfMethods m_dtmfMethods; }; class YateGkRegThread : public PThread @@ -692,6 +754,81 @@ protected: unsigned int YateGkRegThread::s_count = 0; Mutex YateGkRegThread::s_mutexCount(false,"H323GkThreads"); +static DtmfMethods s_dtmfMethods; +// Deprecated dtmf params warn +static bool s_warnDtmfInbandCfg = true; +static bool s_warnDtmfInbandCallExecute = true; + +const TokenDict DtmfMethods::s_methodName[] = { + { "h323", H323}, + { "rfc2833", Rfc2833}, + { "inband", Inband}, + { 0, 0 }, +}; + +// Replace all methods from comma separated list +// If no method is set use other or setDefEmpty (reset to default) +bool DtmfMethods::set(const String& methods, const DtmfMethods* other, bool setDefEmpty) +{ + set(); + bool found = false; + bool ok = true; + ObjList* m = methods.split(','); + int i = 0; + for (ObjList* o = m->skipNull(); o && i < MethodCount; o = o->skipNext()) { + String* s = static_cast(o->get()); + int meth = lookup(s->trimBlanks(),s_methodName,MethodCount); + if (meth != MethodCount) { + m_methods[i++] = meth; + found = true; + } + else if (*s) + ok = false; + } + TelEngine::destruct(m); + if (!found) { + if (other) + *this = *other; + else if (setDefEmpty) + setDefault(); + } + return ok; +} + +// Retrieve a method from deperecated parameters +// Reset the method if the parameter is false +// Display a message anyway if warn is not false +bool DtmfMethods::getDeprecatedDtmfMethod(const NamedList& list, const char* param, + int method, bool* warn) +{ + String* p = list.getParam(param); + if (!p) + return false; + if (!p->toBoolean()) + reset(method); + if (warn && *warn) { + *warn = false; + Debug(&hplugin,DebugConf,"Deprecated '%s' in '%s'. Use 'dtmfmethods' instead!",param,list.c_str()); + } + return true; +} + +// Reset a method +void DtmfMethods::reset(int method) +{ + for (int i = 0; i < MethodCount; i++) + if (m_methods[i] == method) { + m_methods[i] = MethodCount; + break; + } +} + +// Build a string list from methods +void DtmfMethods::buildMethods(String& buf, const char* sep) +{ + for (int i = 0; i < MethodCount; i++) + buf.append(lookup(m_methods[i],s_methodName),sep); +} // Get a number of thread idle intervals from a time period @@ -940,7 +1077,7 @@ bool YateH323EndPoint::initInternal(bool reg, const NamedList* params) Lock lck(m_mutex); DDebug(&hplugin,DebugAll,"Endpoint(%s)::initInternal(%u,%p) [%p]", safe(),reg,params,this); - DisableDetectInBandDTMF(!(params && params->getBoolValue("dtmfinband",s_inband))); + DisableDetectInBandDTMF(!(params && params->getBoolValue("dtmfinband"))); DisableFastStart(params && !params->getBoolValue("faststart",true)); DisableH245Tunneling(params && !params->getBoolValue("h245tunneling",true)); DisableH245inSetup(!(params && params->getBoolValue("h245insetup"))); @@ -1532,7 +1669,8 @@ YateH323Connection::YateH323Connection(YateH323EndPoint& endpoint, : H323Connection(endpoint,callReference), m_chan(0), m_mutex(0), m_externalRtp(s_externalRtp), m_nativeRtp(false), m_passtrough(false), m_lockFormats(false), - m_rtpPort(0), m_remotePort(0), m_needMedia(true) + m_rtpPort(0), m_remotePort(0), m_needMedia(true), + m_rtpStarted(false), m_dtmfPayload(-1) { Debug(&hplugin,DebugAll,"YateH323Connection::YateH323Connection(%p,%u,%p) [%p]", &endpoint,callReference,userdata,this); @@ -1923,6 +2061,25 @@ BOOL YateH323Connection::OnReceivedProgress(const H323SignalPDU& pdu) return TRUE; } +BOOL YateH323Connection::OnReceivedCapabilitySet(const H323Capabilities& remoteCaps, + const H245_MultiplexCapability* muxCap, H245_TerminalCapabilitySetReject& reject) +{ + DDebug(this,DebugInfo,"YateH323Connection::OnReceivedCapabilitySet [%p]",this); + bool ok = H323Connection::OnReceivedCapabilitySet(remoteCaps,muxCap,reject); + int payload = rtpDtmfPayload(false); + if (m_dtmfPayload != payload) { + if (m_rtpStarted) { + // TODO: Update external rtp event payload when implemented + if (payload > 0) + Debug(this,DebugInfo,"Unable to change event payload, disabling RFC 2833 [%p]",this); + m_dtmfPayload = -3; + } + else + m_dtmfPayload = payload; + } + return ok; +} + void YateH323Connection::OnUserInputTone(char tone, unsigned duration, unsigned logicalChannel, unsigned rtpTimestamp) { Debug(this,DebugInfo,"YateH323Connection::OnUserInputTone '%c' duration=%u [%p]",tone,duration,this); @@ -2147,6 +2304,8 @@ BOOL YateH323Connection::startExternalRTP(const char* remoteIP, WORD remotePort, } if (!m_externalRtp) return FALSE; + if (m_dtmfPayload < 0) + m_dtmfPayload = rtpDtmfPayload(true); Message m("chan.rtp"); if (m_rtpid) m.setParam("rtpid",m_rtpid); @@ -2158,6 +2317,8 @@ BOOL YateH323Connection::startExternalRTP(const char* remoteIP, WORD remotePort, m.addParam("format",format); if ((payload >= 0) && (payload < 127)) m.addParam("payload",String(payload)); + if (m_dtmfPayload > 0) + m.addParam("evpayload",String(m_dtmfPayload)); TelEngine::Lock lock(m_mutex); if (!(m_chan && m_chan->alive() && m_chan->driver())) @@ -2166,6 +2327,7 @@ BOOL YateH323Connection::startExternalRTP(const char* remoteIP, WORD remotePort, lock.drop(); if (Engine::dispatch(m)) { m_rtpid = m.getValue("rtpid"); + m_rtpStarted = true; return TRUE; } return FALSE; @@ -2214,6 +2376,22 @@ void YateH323Connection::setEpConn(bool created) ep->m_connCount--; } +// Retrieve RTP DTMF payload from local/remote caps +int YateH323Connection::rtpDtmfPayload(bool local) +{ + int payload = -1; + const H323Capabilities& caps = local ? GetLocalCapabilities() : GetRemoteCapabilities(); + // NOTE: RFC2833 capability subtype is not set to H323_UserInputCapability::SignalToneRFC2833 in the library + // It is set to 10000 + H323Capability* cap = caps.FindCapability(H323Capability::e_UserInput,10000); + if (cap) { + payload = cap->GetPayloadType(); + if (payload < 96 || payload > 127) + payload = -2; + } + XDebug(this,DebugNote,"rtpDtmfPayload(%u) %d [%p]",local,payload,this); + return payload; +} YateH323_ExternalRTPChannel::YateH323_ExternalRTPChannel( YateH323Connection& connection, @@ -2579,7 +2757,7 @@ 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_inband(s_inband) + m_hungup(false) { s_mutex.lock(); s_chanCount++; @@ -2589,8 +2767,17 @@ YateH323Chan::YateH323Chan(YateH323Connection* conn,Message* msg,const char* add conn,addr,direction(),this); setMaxcall(msg); Message* s = message("chan.startup",msg); + s_cfgMutex.lock(); + m_dtmfMethods = s_dtmfMethods; + s_cfgMutex.unlock(); if (msg) { - m_inband = msg->getBoolValue("dtmfinband",s_inband); + String* meths = msg->getParam("odtmfmethods"); + if (meths) { + DtmfMethods old = m_dtmfMethods; + m_dtmfMethods.set(*meths,&old); + } + else + m_dtmfMethods.getDeprecatedDtmfMethod(*msg,"dtmfinband",DtmfMethods::Inband,&s_warnDtmfInbandCallExecute); s->copyParams(*msg,"caller,callername,called,billid,callto,username"); } Engine::enqueue(s); @@ -2775,6 +2962,11 @@ bool YateH323Chan::callRouted(Message& msg) void YateH323Chan::callAccept(Message& msg) { + String* meths = msg.getParam(YSTRING("idtmfmethods")); + if (meths) { + DtmfMethods old = m_dtmfMethods; + m_dtmfMethods.set(*meths,&old); + } Channel::callAccept(msg); if (m_conn) { m_conn->rtpExecuted(msg); @@ -2829,9 +3021,36 @@ bool YateH323Chan::msgTone(Message& msg, const char* tone) { if (!(tone && m_conn)) return false; - if (m_inband && dtmfInband(tone)) - return true; - return m_conn->sendTone(msg,tone); + DtmfMethods methods = m_dtmfMethods; + const String* param = msg.getParam(YSTRING("methods")); + if (param) + methods.set(*param,&m_dtmfMethods); + bool retVal = false; + bool ok = false; + 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 (!ok && debugAt(DebugNote)) { + String tmp; + methods.buildMethods(tmp); + Debug(this,DebugNote,"Failed to send tones '%s' methods=%s [%p]",tone,tmp.c_str(),this); + } + return retVal; } bool YateH323Chan::msgText(Message& msg, const char* text) @@ -3017,8 +3236,21 @@ void H323Driver::initialize() s_cfgMutex.lock(); s_cfg = Engine::configFile("h323chan"); s_cfg.load(); + NamedList* general = s_cfg.getSection("general"); + if (general) { + String* dtmfMethods = general->getParam("dtmfmethods"); + if (dtmfMethods) { + if (!s_dtmfMethods.set(*dtmfMethods,0)) + s_dtmfMethods.printMethods(this,DebugConf,*dtmfMethods); + } + else { + s_dtmfMethods.setDefault(); + s_dtmfMethods.getDeprecatedDtmfMethod(*general,"dtmfinband",DtmfMethods::Inband,&s_warnDtmfInbandCfg); + } + } + else + s_dtmfMethods.setDefault(); s_cfgMutex.unlock(); - s_inband = s_cfg.getBoolValue("general","dtmfinband",false); 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);