diff --git a/configure.in b/configure.in index 7a3fd748..64c92749 100644 --- a/configure.in +++ b/configure.in @@ -412,6 +412,51 @@ fi AC_SUBST(HAVE_ILBC) AC_SUBST(ILBC_INC) +HAVE_SPANDSP=no +SPANDSP_INC="" +AC_ARG_WITH(spandsp,AC_HELP_STRING([--with-spandsp],[use spandsp library if available (default)]),[ac_cv_use_spandsp=$withval],[ac_cv_use_spandsp=yes]) +if [[ "x$ac_cv_use_spandsp" = "xyes" ]]; then +for i in /usr/include /usr/local/include; do + ac_cv_use_spandsp="$i" + test -f "$ac_cv_use_spandsp/spandsp.h" && break +done +fi +if [[ "x$ac_cv_use_spandsp" != "xno" ]]; then +AC_MSG_CHECKING([for usable spandsp in $ac_cv_use_spandsp]) +if [[ -f "$ac_cv_use_spandsp/spandsp.h" ]]; then +AC_LANG_SAVE +AC_LANG([C++]) +SAVE_CFLAGS="$CFLAGS" +CFLAGS="$CFLAGS -Wall -Werror" +AC_TRY_COMPILE([ +#define __STDC_LIMIT_MACROS +#include +#include +#include +#include +#include +],[ +fax_state_t fax_state; +t38_terminal_state_t t38_t_state; +t38_gateway_state_t t38_g_state; +fax_init(&fax_state,TRUE); +t30_set_tx_file(&fax_state.t30_state,"abc",-1,-1); +t38_terminal_init(&t38_t_state,TRUE,NULL,NULL); +t38_gateway_init(&t38_g_state,NULL,NULL); +], +HAVE_SPANDSP="yes" +) +CFLAGS="$SAVE_CFLAGS" +AC_LANG_RESTORE +if [[ "x$HAVE_SPANDSP" = "xyes" ]]; then +SPANDSP_INC="-I$ac_cv_use_spandsp" +fi +fi +AC_MSG_RESULT([$HAVE_SPANDSP]) +fi +AC_SUBST(HAVE_SPANDSP) +AC_SUBST(SPANDSP_INC) + HAVE_PWLIB=no PWLIB_RTTI=none PWLIB_INC="" diff --git a/modules/Makefile.in b/modules/Makefile.in index 477c68f8..b756e89e 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -7,11 +7,13 @@ DESTDIR := # override DEBUG at compile time to enable full debug or remove it all DEBUG := +CC := @CC@ -Wall CXX := @CXX@ -Wall SED := sed DEFS := INCLUDES := -I.. -I@top_srcdir@ -CFLAGS := -O2 @MODULE_CPPFLAGS@ @INLINE_FLAGS@ +CFLAGS := -O2 @MODULE_CFLAGS@ @INLINE_FLAGS@ +CPPFLAGS := -O2 @MODULE_CPPFLAGS@ @INLINE_FLAGS@ LDFLAGS:= -L.. -lyate MODFLAGS:= @MODULE_LDFLAGS@ MODRELAX:= @MODULE_LDRELAX@ @@ -22,7 +24,8 @@ SUBDIRS := skin help gtk2 MKDEPS := ../config.status PROGS := cdrbuild.yate cdrfile.yate \ regexroute.yate regfile.yate accfile.yate register.yate \ - tonegen.yate wavefile.yate conference.yate moh.yate \ + tonegen.yate tonedetect.yate wavefile.yate \ + conference.yate moh.yate \ callgen.yate analyzer.yate rmanager.yate msgsniff.yate \ pbx.yate dbpbx.yate pbxassist.yate dumbchan.yate callfork.yate \ extmodule.yate yradius.yate \ @@ -57,6 +60,10 @@ ifeq (@HAVE_PRI_CB@_@HAVE_WANPIPE@,yes_yes) PROGS := $(PROGS) wpchan.yate endif +ifneq (@HAVE_SPANDSP@,no) +PROGS := $(PROGS) faxchan.yate +endif + ifneq (@HAVE_H323@,no) PROGS := $(PROGS) h323chan.yate endif @@ -80,7 +87,8 @@ endif LOCALFLAGS = LOCALLIBS = -COMPILE = $(CXX) $(DEFS) $(DEBUG) $(INCLUDES) $(CFLAGS) +CCOMPILE = $(CC) $(DEFS) $(DEBUG) $(INCLUDES) $(CFLAGS) +COMPILE = $(CXX) $(DEFS) $(DEBUG) $(INCLUDES) $(CPPFLAGS) LINK = $(CXX) $(LDFLAGS) MODLINK = $(CXX) $(MODFLAGS) $(MODSTRIP) $(LDFLAGS) MODCOMP = $(COMPILE) $(MODFLAGS) $(MODSTRIP) $(LDFLAGS) @@ -200,6 +208,9 @@ ilbccodec.yate: LOCALFLAGS = @ILBC_INC@ gsmcodec.yate: LOCALLIBS = -lgsm gsmcodec.yate: LOCALFLAGS = @GSM_INC@ +faxchan.yate: LOCALLIBS = -lspandsp +faxchan.yate: LOCALFLAGS = @SPANDSP_INC@ + ysipchan.yate: ../contrib/ysip/libyatesip.a ysipchan.yate: LOCALFLAGS = -I@top_srcdir@/contrib/ysip ysipchan.yate: LOCALLIBS = ../contrib/ysip/libyatesip.a diff --git a/modules/faxchan.cpp b/modules/faxchan.cpp index c69be308..f06eb9a3 100644 --- a/modules/faxchan.cpp +++ b/modules/faxchan.cpp @@ -5,8 +5,7 @@ * This module is based on SpanDSP (a series of DSP components for telephony), * written by Steve Underwood . * - * This great software can be found at - * ftp://opencall.org/pub/spandsp/ + * This great software can be found at http://soft-switch.org/ * * Fax driver (transmission+receiving) * @@ -29,18 +28,14 @@ */ // For SpanDSP we have to ask for various C99 stuff -#define __USE_ISOC99 #define __STDC_LIMIT_MACROS -extern "C" { #include #include -#include #include -#include +#include -#include "spandsp.h" -}; +#include #include @@ -51,48 +46,122 @@ extern "C" { #include using namespace TelEngine; + namespace { // anonymous -class FaxChan : public CallEndpoint -{ -public: - FaxChan(const char *file, bool receive, bool iscaller, const char *ident = 0); - ~FaxChan(); - virtual void disconnected(bool final, const char *reason); - void rxData(const DataBlock &data); - void rxBlock(void *buff, int len); - int txBlock(); - void phaseB(int result); - void phaseD(int result); - void phaseE(int result); -private: - t30_state_t m_fax; - Mutex m_mutex; - DataBlock m_buf; - int m_lastr; - bool m_eof; -}; +#define DATA_CHUNK 320 -class FaxSource : public ThreadedSource +class FaxWrapper; + +// A thread to run the fax data +class FaxThread : public Thread { public: - FaxSource(FaxChan *chan); - ~FaxSource(); + inline FaxThread(FaxWrapper* wrapper) + : Thread("Fax"), m_wrap(wrapper) + { } virtual void run(); private: - unsigned m_total; - FaxChan *m_chan; + RefPointer m_wrap; +}; + +class FaxSource : public DataSource +{ +public: + FaxSource(FaxWrapper* wrapper, const char* format = "slin"); + ~FaxSource(); +private: + RefPointer m_wrap; }; class FaxConsumer : public DataConsumer { public: - FaxConsumer(FaxChan *chan); + FaxConsumer(FaxWrapper* wrapper, const char* format = "slin"); ~FaxConsumer(); - virtual void Consume(const DataBlock &data,unsigned long); + virtual void Consume(const DataBlock& data, unsigned long tStamp); private: - unsigned m_total; - FaxChan *m_chan; + RefPointer m_wrap; +}; + +// This class encapsulates an abstract T.30 fax interface +class FaxWrapper : public RefObject, public Mutex, public DebugEnabler +{ + friend class FaxSource; + friend class FaxConsumer; +public: + void debugName(const char* name); + void setECM(bool enable); + bool startup(CallEndpoint* chan = 0); + virtual void cleanup(); + virtual void run() = 0; + virtual void rxData(const DataBlock& data, unsigned long tStamp) = 0; + void phaseB(int result); + void phaseD(int result); + void phaseE(int result); + inline t30_state_t* t30() const + { return m_t30; } + inline bool eof() const + { return m_eof; } +protected: + FaxWrapper(); + void init(t30_state_t* t30, const char* ident, const char* file, bool sender); + String m_name; + t30_state_t* m_t30; + FaxSource* m_source; + FaxConsumer* m_consumer; + CallEndpoint* m_chan; + bool m_eof; +}; + +// An audio fax terminal, sends or receives a local file +class FaxTerminal : public FaxWrapper +{ +public: + FaxTerminal(const char *file, const char *ident, bool sender, bool iscaller); + virtual ~FaxTerminal(); + virtual void run(); + virtual void rxData(const DataBlock& data, unsigned long tStamp); +private: + void rxBlock(void *buff, int len); + int txBlock(); + fax_state_t m_fax; + int m_lastr; +}; + +// A digital fax terminal +class T38Terminal : public FaxWrapper +{ +public: + T38Terminal(const char *file, const char *ident, bool sender, bool iscaller); + virtual ~T38Terminal(); + virtual void run(); + virtual void rxData(const DataBlock& data, unsigned long tStamp); +private: + t38_terminal_state_t m_t38; +}; + +// A channel (terminal) that sends or receives a local TIFF file +class FaxChan : public Channel +{ +public: + FaxChan(bool outgoing, const char *file, bool sender, Message& msg); + virtual ~FaxChan(); + virtual bool msgAnswered(Message& msg); + void answer(const char* targetid); + inline const String& ident() const + { return m_ident; } + inline bool isSender() const + { return m_sender; } + inline bool isCaller() const + { return m_caller; } +private: + bool startup(FaxWrapper* wrap, const char* type = "audio"); + bool startup(bool digital = false); + String m_ident; + bool m_sender; + bool m_caller; + bool m_ecm; }; class FaxHandler : public MessageHandler @@ -102,293 +171,469 @@ public: virtual bool received(Message &msg); }; -class FaxPlugin : public Plugin +// Driver and plugin +class FaxDriver : public Driver { public: - FaxPlugin(); + FaxDriver(); virtual void initialize(); + virtual bool msgExecute(Message& msg, String& dest); private: - FaxHandler *m_handler; + bool m_first; }; -FaxSource::FaxSource(FaxChan *chan) - : m_total(0), m_chan(chan) +static FaxDriver plugin; + +FaxSource::FaxSource(FaxWrapper* wrapper, const char* format) + : DataSource(format), m_wrap(wrapper) { - Debug(DebugAll,"FaxSource::FaxSource(%p) [%p]",chan,this); - start("FaxSource"); + DDebug(m_wrap,DebugAll,"FaxSource::FaxSource(%p,'%s') [%p]",wrapper,format,this); + if (m_wrap) + m_wrap->m_source = this; } FaxSource::~FaxSource() { - Debug(DebugAll,"FaxSource::~FaxSource() [%p] total=%u",this,m_total); -} - -void FaxSource::run() -{ - u_int64_t tpos = Time::now(); - for (;;) { - int r = m_chan->txBlock(); - if (r < 0) - break; - if (!r) { - r = 80; - DDebug(DebugAll,"FaxSource inserting %d bytes silence [%p]",r,this); - DataBlock data(0,r); - Forward(data); - } - - m_total += r; - tpos += (r*1000000ULL/16000); - - int64_t dly = tpos - Time::now(); - if (dly > 10000) - dly = 10000; - if (dly > 0) - Thread::usleep((unsigned long)dly); + DDebug(m_wrap,DebugAll,"FaxSource::~FaxSource() [%p]",this); + if (m_wrap && (m_wrap->m_source == this)) { + m_wrap->m_source = 0; + m_wrap->check(); } - Debug(DebugAll,"FaxSource [%p] end of data total=%u",this,m_total); + m_wrap = 0; } -FaxConsumer::FaxConsumer(FaxChan *chan) - : m_total(0), m_chan(chan) + +FaxConsumer::FaxConsumer(FaxWrapper* wrapper, const char* format) + : DataConsumer(format), m_wrap(wrapper) { - Debug(DebugAll,"FaxConsumer::FaxConsumer(%p) [%p]",chan,this); + DDebug(m_wrap,DebugAll,"FaxConsumer::FaxConsumer(%p,'%s') [%p]",wrapper,format,this); + if (m_wrap) + m_wrap->m_consumer = this; } FaxConsumer::~FaxConsumer() { - Debug(DebugAll,"FaxConsumer::~FaxConsumer() [%p] total=%u",this,m_total); + DDebug(m_wrap,DebugAll,"FaxConsumer::~FaxConsumer() [%p]",this); + if (m_wrap && (m_wrap->m_consumer == this)) { + m_wrap->m_consumer = 0; + m_wrap->check(); + } + m_wrap = 0; } -void FaxConsumer::Consume(const DataBlock &data,unsigned long) +void FaxConsumer::Consume(const DataBlock& data, unsigned long tStamp) { - if (data.null()) + if (data.null() || !m_wrap) return; - m_total += data.length(); - m_chan->rxData(data); + m_wrap->rxData(data,tStamp); } -static void phase_b_handler(t30_state_t *s, void *user_data, int result) + +static void phase_b_handler(t30_state_t* s, void* user_data, int result) { if (user_data) - static_cast(user_data)->phaseB(result); + static_cast(user_data)->phaseB(result); } -static void phase_d_handler(t30_state_t *s, void *user_data, int result) +static void phase_d_handler(t30_state_t* s, void* user_data, int result) { if (user_data) - static_cast(user_data)->phaseD(result); + static_cast(user_data)->phaseD(result); } -static void phase_e_handler(t30_state_t *s, void *user_data, int result) +static void phase_e_handler(t30_state_t* s, void* user_data, int result) { if (user_data) - static_cast(user_data)->phaseE(result); + static_cast(user_data)->phaseE(result); } -FaxChan::FaxChan(const char *file, bool receive, bool iscaller, const char *ident) - : CallEndpoint("faxfile"), m_lastr(0), m_eof(false) +FaxWrapper::FaxWrapper() + : Mutex(true), + m_t30(0), m_source(0), m_consumer(0), m_chan(0), m_eof(false) +{ + debugChain(&plugin); + debugName(plugin.debugName()); +} + +// Set the debugging name, forward it to spandsp +void FaxWrapper::debugName(const char* name) +{ + if (name) { + m_name = name; + DebugEnabler::debugName(m_name); + } + if (m_t30) { + int level = SPAN_LOG_SHOW_SEVERITY|SPAN_LOG_SHOW_PROTOCOL|SPAN_LOG_SHOW_TAG; + if (debugAt(DebugAll)) + level |= SPAN_LOG_DEBUG; + else if (debugAt(DebugInfo)) + level |= SPAN_LOG_FLOW; + else if (debugAt(DebugNote)) + level |= SPAN_LOG_PROTOCOL_WARNING; + else if (debugAt(DebugMild)) + level |= SPAN_LOG_PROTOCOL_ERROR; + else if (debugAt(DebugWarn)) + level |= SPAN_LOG_WARNING; + else if (debugAt(DebugGoOn)) + level |= SPAN_LOG_ERROR; + span_log_set_tag(&m_t30->logging,m_name); + span_log_set_level(&m_t30->logging,level); + } +} + +// Initialize terminal T.30 state +void FaxWrapper::init(t30_state_t* t30, const char* ident, const char* file, bool sender) { - Debug(DebugAll,"FaxChan::FaxChan(%s \"%s\") [%p]", - (receive ? "receive" : "transmit"),file,this); if (!ident) - ident = "unknown"; - fax_init(&m_fax, iscaller, NULL); - fax_set_local_ident(&m_fax, ident); - if (receive) - fax_set_rx_file(&m_fax, file); + ident = "anonymous"; + t30_set_local_ident(t30,ident); + t30_set_phase_e_handler(t30,phase_e_handler,this); + t30_set_phase_d_handler(t30,phase_d_handler,this); + t30_set_phase_b_handler(t30,phase_b_handler,this); + m_t30 = t30; + if (!file) + return; + if (sender) + t30_set_tx_file(t30,file,-1,-1); else - fax_set_tx_file(&m_fax, file); - - //TODO add in the futher a callback to find number of pages and stuff like that. - fax_set_phase_e_handler(&m_fax, phase_e_handler, this); - fax_set_phase_d_handler(&m_fax, phase_d_handler, this); - fax_set_phase_b_handler(&m_fax, phase_b_handler, this); - m_fax.verbose = 1; - - setConsumer(new FaxConsumer(this)); - getConsumer()->deref(); - setSource(new FaxSource(this)); - getSource()->deref(); + t30_set_rx_file(t30,file,-1); } -FaxChan::~FaxChan() +// Set the ECM capability in T.30 state +void FaxWrapper::setECM(bool enable) { - Debug(DebugAll,"FaxChan::~FaxChan() [%p]",this); - setConsumer(); - setSource(); + if (!m_t30) + return; + t30_set_ecm_capability(m_t30,enable); + if (enable) + t30_set_supported_compressions(m_t30,T30_SUPPORT_T4_1D_COMPRESSION | + T30_SUPPORT_T4_2D_COMPRESSION | T30_SUPPORT_T6_COMPRESSION); } -int FaxChan::txBlock() +// Start the terminal's running thread +bool FaxWrapper::startup(CallEndpoint* chan) { - Lock lock(m_mutex); + FaxThread* t = new FaxThread(this); + if (t->startup()) { + m_chan = chan; + return true; + } + delete t; + return false; +} + +// Disconnect the channel if we can assume it's still there +void FaxWrapper::cleanup() +{ + if (m_chan && (m_source || m_consumer)) + m_chan->disconnect(); +} + +// Called on intermediate states +void FaxWrapper::phaseB(int result) +{ + Debug(this,DebugInfo,"Phase B code 0x%X [%p]",result,this); +} + +// Called after transferring a page +void FaxWrapper::phaseD(int result) +{ + Debug(this,DebugInfo,"Phase D code 0x%X [%p]",result,this); + + t30_stats_t t; + char ident[21]; + + t30_get_transfer_statistics(t30(), &t); + Debug(this,DebugAll,"bit rate %d", t.bit_rate); + Debug(this,DebugAll,"pages transferred %d", t.pages_transferred); + Debug(this,DebugAll,"image size %d x %d", t.width, t.length); + Debug(this,DebugAll,"image resolution %d x %d", t.x_resolution, t.y_resolution); + Debug(this,DebugAll,"bad rows %d", t.bad_rows); + Debug(this,DebugAll,"longest bad row run %d", t.longest_bad_row_run); + Debug(this,DebugAll,"compression type %d", t.encoding); + Debug(this,DebugAll,"image size %d", t.image_size); + + t30_get_local_ident(t30(), ident); + Debug(this,DebugAll,"local ident '%s'", ident); + + t30_get_far_ident(t30(), ident); + Debug(this,DebugAll,"remote ident '%s'", ident); +} + +// Called to report end of transfer +void FaxWrapper::phaseE(int result) +{ + Debug(this,DebugInfo,"Phase E code 0x%X [%p]",result,this); + m_eof = true; +} + + +// Constructor for the analog fax terminal +FaxTerminal::FaxTerminal(const char *file, const char *ident, bool sender, bool iscaller) + : m_lastr(0) +{ + Debug(this,DebugAll,"FaxTerminal::FaxTerminal(%s %s \"%s\") [%p]", + (iscaller ? "caller" : "called"), + (sender ? "transmit" : "receive"), + file,this); + fax_init(&m_fax,iscaller); + init(&m_fax.t30_state,ident,file,sender); + fax_set_transmit_on_idle(&m_fax,1); +} + +FaxTerminal::~FaxTerminal() +{ + Debug(this,DebugAll,"FaxTerminal::~FaxTerminal() [%p]",this); + fax_release(&m_fax); +} + +// Run the terminal - send data blocks and sleep accordingly +void FaxTerminal::run() +{ + u_int64_t tpos = Time::now(); + while ((m_source || m_consumer) && !m_eof) { + int r = txBlock(); + if (r < 0) + break; + + tpos += ((u_int64_t)1000000*r/16000); + int64_t dly = tpos - Time::now(); + if (dly > 10000) + dly = 10000; + if (dly > 0) + Thread::usleep(dly,true); + } +} + +// Build and send encoded audio data blocks +int FaxTerminal::txBlock() +{ + Lock lock(this); if (m_lastr < 0) return m_lastr; - int r = m_buf.length(); - if (r) { - getSource()->Forward(m_buf); - m_buf.clear(); - } - else if (m_eof) { - lock.drop(); - disconnect("eof"); - r = -1; - } - return r; -} -void FaxChan::rxBlock(void *buff, int len) -{ - Lock lock(m_mutex); - fax_rx_process(&m_fax, (int16_t *)buff,len/2); - - DataBlock data(0,len); - int r = 2*fax_tx_process(&m_fax, (int16_t *) data.data(),len/2); - if (r != len && r != m_lastr) - Debug("FaxChan",DebugWarn,"Generated %d bytes! [%p]",r,this); + DataBlock data(0,DATA_CHUNK); + int r = 2*fax_tx(&m_fax, (int16_t *) data.data(),data.length()/2); + if (r != DATA_CHUNK && r != m_lastr) + Debug(this,DebugNote,"Generated %d bytes! [%p]",r,this); m_lastr = r; - if (r <= 0) { - return; - } - data.truncate(r); - m_buf.append(data); + lock.drop(); + if (m_source) + m_source->Forward(data); + return data.length(); } -void FaxChan::rxData(const DataBlock &data) +// Deliver small chunks of audio data to the decoder +void FaxTerminal::rxBlock(void *buff, int len) +{ + Lock lock(this); + fax_rx(&m_fax, (int16_t *)buff,len/2); +} + +// Break received audio data into manageable chunks, forward them to decoder +void FaxTerminal::rxData(const DataBlock& data, unsigned long tStamp) { unsigned int pos = 0; while (pos < data.length()) { + // feed the decoder with small chunks of data (16 bytes/ms) int len = data.length() - pos; - if (len > 80) - len = 80; + if (len > DATA_CHUNK) + len = DATA_CHUNK; rxBlock(((char *)data.data())+pos, len); pos += len; } } -void FaxChan::phaseB(int result) + +// Constructor for the digital fax terminal +T38Terminal::T38Terminal(const char *file, const char *ident, bool sender, bool iscaller) { - Debug(DebugAll,"FaxChan::phaseB code 0x%X [%p]",result,this); + Debug(this,DebugAll,"T38Terminal::T38Terminal(%s %s \"%s\") [%p]", + (iscaller ? "caller" : "called"), + (sender ? "transmit" : "receive"), + file,this); + t38_terminal_init(&m_t38,iscaller,NULL,this); + init(&m_t38.t30_state,ident,file,sender); } -void FaxChan::phaseD(int result) +T38Terminal::~T38Terminal() { - Debug(DebugAll,"FaxChan::phaseD code 0x%X [%p]",result,this); - - t30_stats_t t; - char ident[21]; - - fax_get_transfer_statistics(&m_fax, &t); - Debug("Fax",DebugAll,"bit rate %d", t.bit_rate); - Debug("Fax",DebugAll,"pages transferred %d", t.pages_transferred); - Debug("Fax",DebugAll,"image size %d x %d", t.columns, t.rows); - Debug("Fax",DebugAll,"image resolution %d x %d", t.column_resolution, t.row_resolution); - Debug("Fax",DebugAll,"bad rows %d", t.bad_rows); - Debug("Fax",DebugAll,"longest bad row run %d", t.longest_bad_row_run); - Debug("Fax",DebugAll,"compression type %d", t.encoding); - Debug("Fax",DebugAll,"image size %d", t.image_size); - - fax_get_local_ident(&m_fax, ident); - Debug("Fax",DebugAll,"local ident '%s'", ident); - - fax_get_far_ident(&m_fax, ident); - Debug("Fax",DebugAll,"remote ident '%s'", ident); + Debug(this,DebugAll,"T38Terminal::~T38Terminal() [%p]",this); } -void FaxChan::phaseE(int result) +// Run the terminal +void T38Terminal::run() { - Debug(DebugAll,"FaxChan::phaseE code 0x%X [%p]",result,this); - m_eof = true; + Debug(this,DebugStub,"Please implement T38Terminal::run()"); } -void FaxChan::disconnected(bool final, const char *reason) +// Handle received digital data +void T38Terminal::rxData(const DataBlock& data, unsigned long tStamp) { - Debug(DebugInfo,"FaxChan::disconnected() '%s' [%p]",reason,this); + Debug(this,DebugStub,"Please implement T38Terminal::rxData()"); } -bool FaxHandler::received(Message &msg) + +// Helper thread +void FaxThread::run() { - String dest(msg.getValue("callto")); - if (dest.null()) - return false; - Regexp r("^fax/\\([^/]*\\)/\\([^/]*\\)/\\(.*\\)$"); + m_wrap->run(); + m_wrap->cleanup(); +} + + +// Constructor for a generic fax terminal channel +FaxChan::FaxChan(bool outgoing, const char *file, bool sender, Message& msg) + : Channel(plugin,0,outgoing), m_sender(sender) +{ + Debug(this,DebugAll,"FaxChan::FaxChan(%s \"%s\") [%p]", + (sender ? "transmit" : "receive"), + file,this); + const char* ident = msg.getValue("faxident",msg.getValue("caller")); + if (!ident) + ident = "anonymous"; + m_ident = ident; + // outgoing means from Yate to file so the fax should answer by default + m_caller = msg.getBoolValue("faxcaller",!outgoing); + m_ecm = msg.getBoolValue("faxecm"); + m_address = file; + Engine::enqueue(message("chan.startup")); +} + +// Destructor - clears all (audio, image) endpoints early +FaxChan::~FaxChan() +{ + Debug(DebugAll,"FaxChan::~FaxChan() [%p]",this); + clearEndpoint(); + Engine::enqueue(message("chan.hangup")); +} + +// Build data channels, attaches a wrapper and starts it up +bool FaxChan::startup(FaxWrapper* wrap, const char* type) +{ + wrap->debugName(debugName()); + FaxSource* fs = new FaxSource(wrap); + setSource(fs,type); + fs->deref(); + FaxConsumer* fc = new FaxConsumer(wrap); + setConsumer(fc,type); + fc->deref(); + wrap->setECM(m_ecm); + bool ok = wrap->startup(this); + wrap->deref(); + return ok; +} + +// Attach and start an analog or digital wrapper +bool FaxChan::startup(bool digital) +{ + if (digital) + return startup(new T38Terminal(address(),m_ident,m_sender,m_caller),"image"); + else + return startup(new FaxTerminal(address(),m_ident,m_sender,m_caller)); +} + +// Handler for an originator fax start request +bool FaxChan::msgAnswered(Message& msg) +{ + if (Channel::msgAnswered(msg)) { + startup(); + return true; + } + return false; +} + +// Handler for an answerer fax start request +void FaxChan::answer(const char* targetid) +{ + if (targetid) + m_targetid = targetid; + status("answered"); + startup(); + Engine::enqueue(message("call.answered")); +} + +bool FaxDriver::msgExecute(Message& msg, String& dest) +{ + Regexp r("^\\([^/]*\\)/\\(.*\\)$"); if (!dest.matches(r)) return false; bool transmit = false; - if (dest.matchString(1) == "transmit") + if ((dest.matchString(1) == "send") || (dest.matchString(1) == "transmit")) transmit = true; else if (dest.matchString(1) != "receive") { - Debug(DebugGoOn,"Invalid fax method '%s', use 'receive' or 'transmit'", + Debug(this,DebugWarn,"Invalid fax method '%s', use 'receive' or 'transmit'", dest.matchString(1).c_str()); return false; } - bool iscaller = (dest.matchString(2) == "caller"); + dest = dest.matchString(2); - FaxChan *fc = 0; - if (transmit) { - Debug(DebugInfo,"Transmit fax from file '%s'",dest.matchString(3).c_str()); - fc = new FaxChan(dest.matchString(3).c_str(),false,iscaller); - } - else { - Debug(DebugInfo,"Receive fax into file '%s'",dest.matchString(3).c_str()); - fc = new FaxChan(dest.matchString(3).c_str(),true,iscaller); - } + RefPointer fc; CallEndpoint* ce = static_cast(msg.userData()); if (ce) { - if (ce->connect(fc)) { - fc->deref(); + fc = new FaxChan(true,dest,transmit,msg); + fc->deref(); + if (fc->connect(ce)) { + msg.setParam("peerid",fc->id()); + msg.setParam("targetid",fc->id()); + fc->answer(msg.getValue("id",ce->id())); return true; } } else { - const char *targ = msg.getValue("target"); - if (!targ) { - Debug(DebugWarn,"Fax outgoing call with no target!"); - fc->destruct(); - return false; - } + fc = new FaxChan(false,dest,transmit,msg); + fc->deref(); Message m("call.route"); - m.addParam("id",dest); - m.addParam("caller",dest); - m.addParam("called",targ); + fc->complete(m); m.userData(fc); - if (Engine::dispatch(m)) { - m = "call.execute"; - m.addParam("callto",m.retValue()); - m.retValue().clear(); - if (Engine::dispatch(m)) { - fc->deref(); - return true; + String callto = msg.getValue("caller"); + if (callto) + m.addParam("caller",callto); + callto = msg.getValue("direct"); + if (callto.null()) { + const char* targ = msg.getValue("target"); + if (!targ) { + Debug(DebugWarn,"Outgoing fax call with no target!"); + return false; } - Debug(DebugWarn,"Fax outgoing call not accepted!"); + m.addParam("called",targ); + if (!Engine::dispatch(m) || m.retValue().null()) { + Debug(this,DebugWarn,"Outgoing fax call but no route!"); + return false; + } + callto = m.retValue(); } - else - Debug(DebugWarn,"Fax outgoing call but no route!"); + m = "call.execute"; + m.addParam("callto",callto); + m.retValue().clear(); + if (Engine::dispatch(m)) { + fc->callAccept(m); + return true; + } + Debug(this,DebugWarn,"Outgoing fax call not accepted!"); } - fc->destruct(); return false; } -FaxPlugin::FaxPlugin() - : m_handler(0) +FaxDriver::FaxDriver() + : Driver("fax"), m_first(true) { Output("Loaded module Fax"); } -void FaxPlugin::initialize() +void FaxDriver::initialize() { Output("Initializing module Fax"); - if (!m_handler) { - m_handler = new FaxHandler("call.execute"); - Engine::install(m_handler); + setup(); + if (m_first) { + m_first = false; + // TODO: add other handlers } } -INIT_PLUGIN(FaxPlugin); - }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */ diff --git a/modules/tonedetect.cpp b/modules/tonedetect.cpp new file mode 100644 index 00000000..5b54b6c1 --- /dev/null +++ b/modules/tonedetect.cpp @@ -0,0 +1,208 @@ +/** + * tonedetect.cpp + * This file is part of the YATE Project http://YATE.null.ro + * + * Detectors for various tones + * + * Yet Another Telephony Engine - a fully featured software PBX and IVR + * Copyright (C) 2004-2006 Null Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include + +using namespace TelEngine; + +namespace { // anonymous + +#define NZEROS 2 +#define NPOLES 2 +#define GAIN 1.167519293e+02 + +#define FAX_THRESHOLD 2000.0 + +class ToneConsumer : public DataConsumer +{ +public: + ToneConsumer(const String &id); + virtual ~ToneConsumer(); + virtual void Consume(const DataBlock& data, unsigned long tStamp); + inline const String& id() const + { return m_id; } +private: + void init(); + String m_id; + bool m_found; + float m_xv[NZEROS+1], m_yv[NPOLES+1], m_avg; +}; + +class DetectHandler : public MessageHandler +{ +public: + DetectHandler() : MessageHandler("chan.detectdtmf") { } + virtual bool received(Message &msg); +}; + +class RecordHandler : public MessageHandler +{ +public: + RecordHandler() : MessageHandler("chan.record") { } + virtual bool received(Message &msg); +}; + +class ToneDetectorModule : public Module +{ +public: + ToneDetectorModule(); + virtual ~ToneDetectorModule(); + virtual void initialize(); +private: + bool m_first; +}; + +static ToneDetectorModule plugin; + + +ToneConsumer::ToneConsumer(const String &id) + : m_id(id), m_found(false) +{ + Debug(&plugin,DebugAll,"ToneConsumer::ToneConsumer(%s) [%p]",id.c_str(),this); + init(); +} + +ToneConsumer::~ToneConsumer() +{ + Debug(&plugin,DebugAll,"ToneConsumer::~ToneConsumer [%p]",this); +} + +void ToneConsumer::init() +{ + m_xv[0] = m_xv[1] = 0.0; + m_yv[0] = m_yv[1] = 0.0; + m_avg = 0.0; +} + +void ToneConsumer::Consume(const DataBlock& data, unsigned long timeDelta) +{ + if (m_found || data.null()) + return; + const int16_t* s= (const int16_t*)data.data(); + for (unsigned int i=0; i FAX_THRESHOLD) { + DDebug(DebugInfo,"Fax detected on %s, average=%f",m_id.c_str(),m_avg); + // prepare for new detection + init(); + m_found = true; + Message* m = new Message("chan.masquerade"); + m->addParam("message","call.fax"); + m->addParam("id",m_id); + Engine::enqueue(m); + break; + } + } +} + + +// Attach a tone detector on "chan.detectdtmf" - needs a DataSource +bool DetectHandler::received(Message &msg) +{ + String src(msg.getValue("consumer")); + if (src.null()) + return false; + Regexp r("^tone/$"); + if (!src.matches(r)) + 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); + c->deref(); + return true; + } + } + else + Debug(DebugWarn,"ToneDetector attach request with no data source!"); + return false; +} + + +// Attach a tone detector on "chan.record" - needs just a CallEndpoint +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)) + return false; + DataEndpoint* de = static_cast(msg.userObject("DataEndpoint")); + CallEndpoint* ch = static_cast(msg.userObject("CallEndpoint")); + if (ch) { + id = ch->id(); + if (!de) + de = ch->setEndpoint(); + } + if (de) { + ToneConsumer* c = new ToneConsumer(id); + de->setCallRecord(c); + c->deref(); + return true; + } + else + Debug(DebugWarn,"ToneDetector record request with no call endpoint!"); + return false; +} + + +ToneDetectorModule::ToneDetectorModule() + : Module("tonedetect","misc"), m_first(true) +{ + Output("Loaded module ToneDetector"); +} + +ToneDetectorModule::~ToneDetectorModule() +{ + Output("Unloading module ToneDetector"); +} + +void ToneDetectorModule::initialize() +{ + Output("Initializing module ToneDetector"); + setup(); + if (m_first) { + m_first = false; + Engine::install(new DetectHandler); + Engine::install(new RecordHandler); + } +} + +}; // anonymous namespace + +/* vi: set ts=8 sw=4 sts=4 noet: */