Updated fax channel to work with current spandsp. Added tone detector.
git-svn-id: http://voip.null.ro/svn/yate@1012 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
parent
895aae57b7
commit
c2d31ab924
45
configure.in
45
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 <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <spandsp.h>
|
||||
],[
|
||||
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=""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,8 +5,7 @@
|
|||
* This module is based on SpanDSP (a series of DSP components for telephony),
|
||||
* written by Steve Underwood <steveu@coppice.org>.
|
||||
*
|
||||
* 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 <math.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <tiffio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "spandsp.h"
|
||||
};
|
||||
#include <spandsp.h>
|
||||
|
||||
#include <yatephone.h>
|
||||
|
||||
|
@ -51,48 +46,122 @@ extern "C" {
|
|||
#include <errno.h>
|
||||
|
||||
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<FaxWrapper> m_wrap;
|
||||
};
|
||||
|
||||
class FaxSource : public DataSource
|
||||
{
|
||||
public:
|
||||
FaxSource(FaxWrapper* wrapper, const char* format = "slin");
|
||||
~FaxSource();
|
||||
private:
|
||||
RefPointer<FaxWrapper> 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<FaxWrapper> 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<FaxChan*>(user_data)->phaseB(result);
|
||||
static_cast<FaxWrapper*>(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<FaxChan*>(user_data)->phaseD(result);
|
||||
static_cast<FaxWrapper*>(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<FaxChan*>(user_data)->phaseE(result);
|
||||
static_cast<FaxWrapper*>(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<FaxChan> fc;
|
||||
CallEndpoint* ce = static_cast<CallEndpoint *>(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: */
|
||||
|
|
|
@ -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 <yatephone.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
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<data.length(); i+=2) {
|
||||
// mkfilter generated CNG detector (1100Hz)
|
||||
m_xv[0] = m_xv[1]; m_xv[1] = m_xv[2];
|
||||
m_xv[2] = *s++ / GAIN;
|
||||
m_yv[0] = m_yv[1]; m_yv[1] = m_yv[2];
|
||||
m_yv[2] = (m_xv[2] - m_xv[0]) +
|
||||
(-0.9828696621 * m_yv[0]) +
|
||||
( 1.2877708321 * m_yv[1]);
|
||||
m_avg = 0.9*m_avg + 0.1*fabs(m_yv[2]);
|
||||
|
||||
if (m_avg > 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<CallEndpoint *>(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<DataEndpoint *>(msg.userObject("DataEndpoint"));
|
||||
CallEndpoint* ch = static_cast<CallEndpoint *>(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: */
|
Loading…
Reference in New Issue