diff --git a/engine/Channel.cpp b/engine/Channel.cpp index 5d89931e..38ff6124 100644 --- a/engine/Channel.cpp +++ b/engine/Channel.cpp @@ -585,6 +585,12 @@ void Channel::callAccept(Message& msg) if (m_billid.null()) m_billid = msg.getValue("billid"); m_targetid = msg.getValue("targetid"); + String detect = msg.getValue("tonedetect_in"); + if (detect && detect.toBoolean(true)) { + if (detect.toBoolean(false)) + detect = "tone/*"; + toneDetect(detect); + } if (msg.getBoolValue("autoanswer")) msgAnswered(msg); else if (msg.getBoolValue("autoring")) @@ -599,6 +605,16 @@ void Channel::callAccept(Message& msg) } } +void Channel::callConnect(Message& msg) +{ + String detect = msg.getValue("tonedetect_out"); + if (detect && detect.toBoolean(true)) { + if (detect.toBoolean(false)) + detect = "tone/*"; + toneDetect(detect); + } +} + void Channel::callRejected(const char* error, const char* reason, const Message* msg) { Debug(this,DebugMild,"Call rejected error='%s' reason='%s' [%p]",error,reason,this); @@ -619,6 +635,18 @@ bool Channel::dtmfInband(const char* tone) return Engine::dispatch(m); } +bool Channel::toneDetect(const char* sniffer) +{ + if (null(sniffer)) + return false; + Message m("chan.attach"); + complete(m,true); + m.userData(this); + m.setParam("sniffer",sniffer); + m.setParam("single","yes"); + return Engine::dispatch(m); +} + bool Channel::setDebug(Message& msg) { String str = msg.getValue("line"); diff --git a/engine/DataFormat.cpp b/engine/DataFormat.cpp index 3f41db21..7d8fe0e0 100644 --- a/engine/DataFormat.cpp +++ b/engine/DataFormat.cpp @@ -616,6 +616,7 @@ DataEndpoint::~DataEndpoint() disconnect(); setPeerRecord(); setCallRecord(); + clearSniffers(); setSource(); setConsumer(); } @@ -748,6 +749,9 @@ void DataEndpoint::setSource(DataSource* source) if (m_callRecord->getConnSource()) Debug(DebugWarn,"consumer source not cleared in %p",m_callRecord); } + ObjList* l = m_sniffers.skipNull(); + for (; l; l = l->skipNext()) + DataTranslator::detachChain(temp,static_cast(l->get())); temp->deref(); } if (source) { @@ -758,6 +762,9 @@ void DataEndpoint::setSource(DataSource* source) DataTranslator::attachChain(source,c2); if (m_callRecord) DataTranslator::attachChain(source,m_callRecord); + ObjList* l = m_sniffers.skipNull(); + for (; l; l = l->skipNext()) + DataTranslator::attachChain(source,static_cast(l->get())); } m_source = source; if (c1) @@ -842,6 +849,53 @@ void DataEndpoint::setCallRecord(DataConsumer* consumer) } } +bool DataEndpoint::addSniffer(DataConsumer* sniffer) +{ + if (!sniffer) + return false; + Lock lock(s_dataMutex); + if (m_sniffers.find(sniffer)) + return false; + if (!sniffer->ref()) + return false; + XDebug(DebugInfo,"DataEndpoint::addSniffer(%p) s=%p [%p]", + sniffer,m_source,this); + m_sniffers.append(sniffer); + if (m_source) + DataTranslator::attachChain(m_source,sniffer); + return true; +} + +bool DataEndpoint::delSniffer(DataConsumer* sniffer) +{ + if (!sniffer) + return false; + Lock lock(s_dataMutex); + XDebug(DebugInfo,"DataEndpoint::delSniffer(%p) s=%p [%p]", + sniffer,m_source,this); + if (!m_sniffers.remove(sniffer,false)) + return false; + if (m_source) + DataTranslator::detachChain(m_source,sniffer); + sniffer->deref(); + return true; +} + +void DataEndpoint::clearSniffers() +{ + Lock lock(s_dataMutex); + for (;;) { + DataConsumer* sniffer = static_cast(m_sniffers.remove(false)); + if (!sniffer) + return; + XDebug(DebugInfo,"DataEndpoint::clearSniffers() sn=%p s=%p [%p]", + sniffer,m_source,this); + if (m_source) + DataTranslator::detachChain(m_source,sniffer); + sniffer->deref(); + } +} + ThreadedSource::~ThreadedSource() { diff --git a/modules/h323chan.cpp b/modules/h323chan.cpp index fab55ad2..530ef900 100644 --- a/modules/h323chan.cpp +++ b/modules/h323chan.cpp @@ -956,6 +956,7 @@ YateH323Connection::YateH323Connection(YateH323EndPoint& endpoint, CallEndpoint* ch = YOBJECT(CallEndpoint,msg->userData()); if (ch && ch->connect(m_chan,msg->getValue("reason"))) { + m_chan->callConnect(*msg); m_chan->setTarget(msg->getValue("id")); msg->setParam("peerid",m_chan->id()); msg->setParam("targetid",m_chan->id()); diff --git a/modules/libypri.cpp b/modules/libypri.cpp index 62f8ed3a..7fb2f22f 100644 --- a/modules/libypri.cpp +++ b/modules/libypri.cpp @@ -898,8 +898,10 @@ bool PriChan::call(Message &msg, const char *called) CallEndpoint *ch = static_cast(msg.userData()); if (ch) { openData(lookup(layer1,dict_str2law),msg.getIntValue("cancelecho",dict_numtaps)); - if (connect(ch,msg.getValue("reason"))) + if (connect(ch,msg.getValue("reason"))) { msg.setParam("peerid",id()); + callConnect(msg); + } m_targetid = msg.getValue("id"); msg.setParam("targetid",id()); } diff --git a/modules/tonedetect.cpp b/modules/tonedetect.cpp index 5b54b6c1..4d2837c9 100644 --- a/modules/tonedetect.cpp +++ b/modules/tonedetect.cpp @@ -34,35 +34,41 @@ namespace { // anonymous #define NPOLES 2 #define GAIN 1.167519293e+02 -#define FAX_THRESHOLD 2000.0 +#define FAX_THRESHOLD_ABS 2000.0 +#define FAX_THRESHOLD_REL 0.8 class ToneConsumer : public DataConsumer { public: - ToneConsumer(const String &id); + ToneConsumer(const String& id, const String& name); virtual ~ToneConsumer(); virtual void Consume(const DataBlock& data, unsigned long tStamp); + virtual const String& toString() const + { return m_name; } inline const String& id() const { return m_id; } -private: void init(); +private: + static void update(double& avg, double val); String m_id; + String m_name; bool m_found; - float m_xv[NZEROS+1], m_yv[NPOLES+1], m_avg; + double m_xv[NZEROS+1], m_yv[NPOLES+1]; + double m_pwr, m_sig; }; -class DetectHandler : public MessageHandler +class AttachHandler : public MessageHandler { public: - DetectHandler() : MessageHandler("chan.detectdtmf") { } - virtual bool received(Message &msg); + AttachHandler() : MessageHandler("chan.attach") { } + virtual bool received(Message& msg); }; class RecordHandler : public MessageHandler { public: RecordHandler() : MessageHandler("chan.record") { } - virtual bool received(Message &msg); + virtual bool received(Message& msg); }; class ToneDetectorModule : public Module @@ -78,10 +84,11 @@ private: static ToneDetectorModule plugin; -ToneConsumer::ToneConsumer(const String &id) - : m_id(id), m_found(false) +ToneConsumer::ToneConsumer(const String& id, const String& name) + : m_id(id), m_name(name), m_found(false) { - Debug(&plugin,DebugAll,"ToneConsumer::ToneConsumer(%s) [%p]",id.c_str(),this); + Debug(&plugin,DebugAll,"ToneConsumer::ToneConsumer(%s,'%s') [%p]", + id.c_str(),name.c_str(),this); init(); } @@ -94,7 +101,12 @@ void ToneConsumer::init() { m_xv[0] = m_xv[1] = 0.0; m_yv[0] = m_yv[1] = 0.0; - m_avg = 0.0; + m_pwr = m_sig = 0.0; +} + +void ToneConsumer::update(double& avg, double val) +{ + avg = 0.9*avg + 0.1*val*val; } void ToneConsumer::Consume(const DataBlock& data, unsigned long timeDelta) @@ -105,15 +117,17 @@ void ToneConsumer::Consume(const DataBlock& data, unsigned long timeDelta) for (unsigned int i=0; i FAX_THRESHOLD) { - DDebug(DebugInfo,"Fax detected on %s, average=%f",m_id.c_str(),m_avg); + if ((m_sig > FAX_THRESHOLD_ABS) && (m_sig > m_pwr*FAX_THRESHOLD_REL)) { + DDebug(&plugin,DebugInfo,"Fax detected on %s, signal=%f, total=%f", + m_id.c_str(),m_sig,m_pwr); // prepare for new detection init(); m_found = true; @@ -121,46 +135,58 @@ void ToneConsumer::Consume(const DataBlock& data, unsigned long timeDelta) m->addParam("message","call.fax"); m->addParam("id",m_id); Engine::enqueue(m); - break; + return; } } + XDebug(&plugin,DebugInfo,"Fax detector on %s: signal=%f, total=%f", + m_id.c_str(),m_sig,m_pwr); } -// Attach a tone detector on "chan.detectdtmf" - needs a DataSource -bool DetectHandler::received(Message &msg) +// Attach a tone detector on "chan.attach" as consumer or sniffer +bool AttachHandler::received(Message& msg) { - String src(msg.getValue("consumer")); - if (src.null()) - return false; - Regexp r("^tone/$"); - if (!src.matches(r)) + String cons(msg.getValue("consumer")); + if (!cons.startsWith("tone/")) + cons.clear(); + String snif(msg.getValue("sniffer")); + if (!snif.startsWith("tone/")) + snif.clear(); + if (cons.null() && snif.null()) return false; CallEndpoint* ch = static_cast(msg.userObject("CallEndpoint")); if (ch) { - DataSource* s = ch->getSource(); - if (s) { - ToneConsumer* c = new ToneConsumer(ch->id()); - DataTranslator::attachChain(s,c); + if (cons) { + ToneConsumer* c = new ToneConsumer(ch->id(),cons); + ch->setConsumer(c); c->deref(); - return true; } + if (snif) { + DataEndpoint* de = ch->setEndpoint(); + // try to reinit sniffer if one already exists + ToneConsumer* c = static_cast(de->getSniffer(snif)); + if (c) + c->init(); + else { + c = new ToneConsumer(ch->id(),snif); + de->addSniffer(c); + c->deref(); + } + } + return msg.getBoolValue("single"); } else - Debug(DebugWarn,"ToneDetector attach request with no data source!"); + Debug(&plugin,DebugWarn,"ToneDetector attach request with no call endpoint!"); return false; } // Attach a tone detector on "chan.record" - needs just a CallEndpoint -bool RecordHandler::received(Message &msg) +bool RecordHandler::received(Message& msg) { String src(msg.getValue("call")); String id(msg.getValue("id")); - if (src.null()) - return false; - Regexp r("^tone/$"); - if (!src.matches(r)) + if (!src.startsWith("tone/")) return false; DataEndpoint* de = static_cast(msg.userObject("DataEndpoint")); CallEndpoint* ch = static_cast(msg.userObject("CallEndpoint")); @@ -170,13 +196,13 @@ bool RecordHandler::received(Message &msg) de = ch->setEndpoint(); } if (de) { - ToneConsumer* c = new ToneConsumer(id); + ToneConsumer* c = new ToneConsumer(id,src); de->setCallRecord(c); c->deref(); return true; } else - Debug(DebugWarn,"ToneDetector record request with no call endpoint!"); + Debug(&plugin,DebugWarn,"ToneDetector record request with no call endpoint!"); return false; } @@ -198,7 +224,7 @@ void ToneDetectorModule::initialize() setup(); if (m_first) { m_first = false; - Engine::install(new DetectHandler); + Engine::install(new AttachHandler); Engine::install(new RecordHandler); } } diff --git a/modules/yiaxchan.cpp b/modules/yiaxchan.cpp index c2f6e16d..8ddb6eca 100644 --- a/modules/yiaxchan.cpp +++ b/modules/yiaxchan.cpp @@ -1118,6 +1118,7 @@ bool YIAXDriver::msgExecute(Message& msg, String& dest) tr->setUserData(conn); Channel* ch = static_cast(msg.userData()); if (ch && conn->connect(ch,msg.getValue("reason"))) { + conn->callConnect(msg); msg.setParam("peerid",conn->id()); msg.setParam("targetid",conn->id()); // Enable trunking if trunkout parameter is enabled diff --git a/modules/ysipchan.cpp b/modules/ysipchan.cpp index edfeb37d..42770cba 100644 --- a/modules/ysipchan.cpp +++ b/modules/ysipchan.cpp @@ -3505,6 +3505,7 @@ bool SIPDriver::msgExecute(Message& msg, String& dest) if (conn->getTransaction()) { CallEndpoint* ch = static_cast(msg.userData()); if (ch && conn->connect(ch,msg.getValue("reason"))) { + conn->callConnect(msg); msg.setParam("peerid",conn->id()); msg.setParam("targetid",conn->id()); conn->deref(); diff --git a/yatephone.h b/yatephone.h index cf13844c..e5b1c8f7 100644 --- a/yatephone.h +++ b/yatephone.h @@ -884,6 +884,33 @@ public: inline DataConsumer* getCallRecord() const { return m_callRecord; } + /** + * Adds a data consumer to the list of sniffers of the local call data + * @param sniffer Pointer to the DataConsumer to add to sniffer list + * @return True if the sniffer was added to list, false if NULL or already added + */ + bool addSniffer(DataConsumer* sniffer); + + /** + * Remove a data consumer from the list of sniffers of the local call data + * @param sniffer Pointer to the DataConsumer to remove from sniffer list + * @return True if the sniffer was removed from list + */ + bool delSniffer(DataConsumer* sniffer); + + /** + * Find a sniffer by name + * @param name Name of the sniffer to find + * @return Pointer to DataConsumer or NULL if not found + */ + inline DataConsumer* getSniffer(const String& name) + { return static_cast(m_sniffers[name]); } + + /** + * Removes all sniffers from the list and dereferences them + */ + void clearSniffers(); + /** * Get a pointer to the peer endpoint * @return A pointer to the peer endpoint or NULL @@ -922,6 +949,7 @@ private: CallEndpoint* m_call; DataConsumer* m_peerRecord; DataConsumer* m_callRecord; + ObjList m_sniffers; }; /** @@ -1454,6 +1482,13 @@ public: */ virtual void callRejected(const char* error, const char* reason = 0, const Message* msg = 0); + /** + * Common processing after connecting the outgoing call, should be called + * from Driver's msgExecute() + * @param msg Notification call.execute message while being dispatched + */ + virtual void callConnect(Message& msg); + /** * Set the local debugging level * @param msg Debug setting message @@ -1660,6 +1695,14 @@ protected: */ bool dtmfInband(const char* tone); + /** + * Attempt to install a data sniffer to detect inband tones + * Needs a tone detector module capable of attaching sniffer consumers. + * @param sniffer Name of the sniffer to install + * @return True on success + */ + bool toneDetect(const char* sniffer = "tone/*"); + private: void init(); Channel(); // no default constructor please