bf57830c7f
git-svn-id: http://yate.null.ro/svn/yate/trunk@5107 acf43c95-373e-0410-b603-e72c3f656dc1
817 lines
20 KiB
C++
817 lines
20 KiB
C++
/**
|
|
* analyzer.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* Test call generator and audio quality analyzer
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2006 Null Team
|
|
*
|
|
* FFT routine taken from Murphy McCauley's FFT DLL based in turn on the
|
|
* work of Don Cross <dcross@intersrv.com>
|
|
* See http://www.fullspectrum.com/deeth/programming/fft.html
|
|
* and http://www.constantthought.com/
|
|
* Thank both for a sleek routine that doesn't even fill a floppy ;-)
|
|
*
|
|
* 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>
|
|
|
|
// minimum allowed for the maximum
|
|
#define ALLOW_MIN 2500.0
|
|
// threshold from max we consider a peak
|
|
#define PEAKS_THR 0.015
|
|
// expected number of peaks
|
|
#define PEAKS_NUM 2
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
|
|
#ifndef M_2PI
|
|
#define M_2PI (2*M_PI)
|
|
#endif
|
|
|
|
#ifndef M_4PI
|
|
#define M_4PI (4*M_PI)
|
|
#endif
|
|
|
|
using namespace TelEngine;
|
|
namespace { // anonymous
|
|
|
|
// Asynchronous FFT on a power of 2 sample buffer
|
|
class AsyncFFT : public Thread
|
|
{
|
|
public:
|
|
enum WinType {
|
|
None = 0,
|
|
Rectangle = None,
|
|
Triangle,
|
|
Bartlett = Triangle,
|
|
Hanning,
|
|
Hamming,
|
|
Blackman,
|
|
FlatTop
|
|
};
|
|
virtual ~AsyncFFT();
|
|
static AsyncFFT* create(unsigned int length, WinType window = Rectangle, Priority prio = Low);
|
|
inline unsigned int samples() const
|
|
{ return m_length; }
|
|
inline unsigned int length() const
|
|
{ return m_length >> 1; }
|
|
inline bool ready() const
|
|
{ return m_ready; }
|
|
double at(int index) const;
|
|
inline double operator[](int index) const
|
|
{ return at(index); }
|
|
bool prepare(const short* samp);
|
|
inline void stop()
|
|
{ m_notify = 0; m_stop = true; }
|
|
virtual void run();
|
|
inline void setNotify(Runnable* notified = 0)
|
|
{ m_notify = notified; }
|
|
private:
|
|
AsyncFFT(unsigned int length, WinType window, Priority prio);
|
|
void buildWindow(WinType window);
|
|
unsigned int revBits(unsigned int index);
|
|
void compute();
|
|
bool m_ready;
|
|
bool m_start;
|
|
bool m_stop;
|
|
Runnable* m_notify;
|
|
unsigned int m_length;
|
|
double* m_window;
|
|
double* m_real;
|
|
double* m_imag;
|
|
unsigned int m_nBits;
|
|
const char* m_winName;
|
|
};
|
|
|
|
class AnalyzerCons : public DataConsumer, public Runnable
|
|
{
|
|
YCLASS(AnalyzerCons,DataConsumer)
|
|
public:
|
|
AnalyzerCons(const String& type, const char* window = 0);
|
|
virtual ~AnalyzerCons();
|
|
virtual unsigned long Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags);
|
|
virtual void statusParams(String& str, Message* msg = 0);
|
|
virtual void run();
|
|
protected:
|
|
DataBlock m_data;
|
|
u_int64_t m_timeStart;
|
|
unsigned long m_tsStart;
|
|
unsigned int m_tsGapCount;
|
|
unsigned long m_tsGapLength;
|
|
AsyncFFT* m_spectrum;
|
|
unsigned long m_total;
|
|
unsigned long m_valid;
|
|
bool m_analyze;
|
|
};
|
|
|
|
class AnalyzerChan : public Channel
|
|
{
|
|
public:
|
|
AnalyzerChan(const String& type, bool outgoing, const char* window = 0);
|
|
virtual ~AnalyzerChan();
|
|
virtual void destroyed();
|
|
virtual void statusParams(String& str);
|
|
virtual bool callRouted(Message& msg);
|
|
virtual bool msgRinging(Message& msg);
|
|
virtual bool msgAnswered(Message& msg);
|
|
virtual void checkTimers(Message& msg, const Time& tmr);
|
|
void startChannel(NamedList& params);
|
|
void addSource();
|
|
void addConsumer();
|
|
protected:
|
|
void setDuration(NamedList& params);
|
|
void localParams(String& str, Message* msg = 0);
|
|
u_int64_t m_stopTime;
|
|
u_int64_t m_timeStart;
|
|
unsigned long m_timeRoute;
|
|
unsigned long m_timeRing;
|
|
unsigned long m_timeAnswer;
|
|
String m_window;
|
|
};
|
|
|
|
class AttachHandler;
|
|
|
|
class AnalyzerDriver : public Driver
|
|
{
|
|
public:
|
|
AnalyzerDriver();
|
|
virtual ~AnalyzerDriver();
|
|
virtual void initialize();
|
|
virtual bool msgExecute(Message& msg, String& dest);
|
|
bool startCall(NamedList& params, const String& dest);
|
|
private:
|
|
AttachHandler* m_handler;
|
|
};
|
|
|
|
INIT_PLUGIN(AnalyzerDriver);
|
|
|
|
static TokenDict dict_windows[] = {
|
|
{ "rectangle", AsyncFFT::Rectangle },
|
|
{ "no", AsyncFFT::None },
|
|
{ "none", AsyncFFT::None },
|
|
{ "triangle", AsyncFFT::Triangle },
|
|
{ "bartlett", AsyncFFT::Bartlett },
|
|
{ "hanning", AsyncFFT::Hanning },
|
|
{ "hamming", AsyncFFT::Hamming },
|
|
{ "blackman", AsyncFFT::Blackman },
|
|
{ "flattop", AsyncFFT::FlatTop },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static Mutex s_mutex(false,"Analyzer");
|
|
|
|
static int s_res = 1;
|
|
|
|
class AttachHandler : public MessageHandler
|
|
{
|
|
public:
|
|
AttachHandler()
|
|
: MessageHandler("chan.attach",100,__plugin.name())
|
|
{ }
|
|
virtual bool received(Message& msg);
|
|
};
|
|
|
|
static const char* printTime(char* buf,unsigned long usec)
|
|
{
|
|
switch (s_res) {
|
|
case 2:
|
|
// microsecond resolution
|
|
sprintf(buf,"%u.%06u",(unsigned int)(usec / 1000000),(unsigned int)(usec % 1000000));
|
|
break;
|
|
case 1:
|
|
// millisecond resolution
|
|
usec = (usec + 500) / 1000;
|
|
sprintf(buf,"%u.%03u",(unsigned int)(usec / 1000),(unsigned int)(usec % 1000));
|
|
break;
|
|
default:
|
|
// 1-second resolution
|
|
usec = (usec + 500000) / 1000000;
|
|
sprintf(buf,"%u",(unsigned int)usec);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
|
|
AsyncFFT* AsyncFFT::create(unsigned int length, WinType window, Priority prio)
|
|
{
|
|
if (length < 2)
|
|
return 0;
|
|
// thanks to 'byang' for this cute power of two test!
|
|
if (length & (length - 1))
|
|
return 0;
|
|
AsyncFFT* fft = new AsyncFFT(length,window,prio);
|
|
if (fft->startup())
|
|
return fft;
|
|
delete fft;
|
|
return 0;
|
|
}
|
|
|
|
AsyncFFT::AsyncFFT(unsigned int length, WinType window, Priority prio)
|
|
: Thread("Async FFT",prio),
|
|
m_ready(false), m_start(false), m_stop(false), m_notify(0),
|
|
m_length(0), m_window(0), m_real(0), m_imag(0), m_nBits(0), m_winName(0)
|
|
{
|
|
DDebug(&__plugin,DebugAll,"AsyncFFT::AsyncFFT(%u) [%p]",length,this);
|
|
for (unsigned int i = 0; i <= 256 ;i++)
|
|
if (length & (1 << i)) {
|
|
m_nBits = i;
|
|
break;
|
|
}
|
|
if (!m_nBits)
|
|
return;
|
|
m_length = length;
|
|
m_real = new double[m_length];
|
|
m_imag = new double[m_length];
|
|
buildWindow(window);
|
|
}
|
|
|
|
AsyncFFT::~AsyncFFT()
|
|
{
|
|
DDebug(&__plugin,DebugAll,"AsyncFFT::~AsyncFFT() [%p]",this);
|
|
m_notify = 0;
|
|
m_ready = false;
|
|
m_start = false;
|
|
delete[] m_real;
|
|
delete[] m_imag;
|
|
delete[] m_window;
|
|
}
|
|
|
|
void AsyncFFT::buildWindow(WinType window)
|
|
{
|
|
m_winName = lookup(window,dict_windows);
|
|
if (window == Rectangle)
|
|
return;
|
|
m_window = new double[m_length];
|
|
unsigned int n2 = m_length >> 1;
|
|
for (unsigned int i = 0; i < m_length; i++) {
|
|
double omega = i * M_2PI / m_length;
|
|
switch (window) {
|
|
case Triangle:
|
|
{
|
|
int k = i - n2;
|
|
if (k > 0)
|
|
k = -k;
|
|
k += n2;
|
|
m_window[i] = k*1.0/n2;
|
|
}
|
|
break;
|
|
case Hanning:
|
|
m_window[i] = 0.5 - 0.5 * ::cos(omega);
|
|
break;
|
|
case Hamming:
|
|
m_window[i] = 0.54 - 0.46 * ::cos(omega);
|
|
break;
|
|
case Blackman:
|
|
m_window[i] = 0.42 - 0.5 * ::cos(omega) + 0.08 * ::cos(2*omega);
|
|
break;
|
|
case FlatTop:
|
|
m_window[i] = 0.2810639 - 0.5208972 * ::cos(omega) + 0.1980399 * ::cos(2*omega);
|
|
break;
|
|
default:
|
|
m_window[i] = 1.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
double AsyncFFT::at(int index) const
|
|
{
|
|
if ((index < 0) || ((unsigned int)index > (m_length >> 1)))
|
|
return 0.0;
|
|
if (!m_ready)
|
|
return 0.0;
|
|
return m_real[index];
|
|
}
|
|
|
|
void AsyncFFT::run()
|
|
{
|
|
DDebug(&__plugin,DebugAll,"AsyncFFT::run() [%p]",this);
|
|
while (!m_stop) {
|
|
while (!m_start) {
|
|
Thread::idle();
|
|
if (m_stop)
|
|
return;
|
|
}
|
|
m_ready = false;
|
|
compute();
|
|
m_ready = true;
|
|
s_mutex.lock();
|
|
if (m_notify)
|
|
m_notify->run();
|
|
s_mutex.unlock();
|
|
m_start = false;
|
|
}
|
|
}
|
|
|
|
bool AsyncFFT::prepare(const short* samp)
|
|
{
|
|
if (m_start || m_stop || !(samp && m_real))
|
|
return false;
|
|
m_ready = false;
|
|
XDebug(&__plugin,DebugAll,"Preparing FFT buffer from %u samples [%p]",m_length,this);
|
|
unsigned int i, j;
|
|
for (i = 0; i < m_length; i++) {
|
|
j = revBits(i);
|
|
if (m_window)
|
|
m_real[i] = m_window[j] * samp[j];
|
|
else
|
|
m_real[i] = samp[j];
|
|
m_imag[i] = 0.0;
|
|
}
|
|
m_start = true;
|
|
return true;
|
|
}
|
|
|
|
unsigned int AsyncFFT::revBits(unsigned int index)
|
|
{
|
|
unsigned int i, rev;
|
|
for (i = rev = 0; i < m_nBits; i++) {
|
|
rev = (rev << 1) | (index & 1);
|
|
index >>= 1;
|
|
}
|
|
return rev;
|
|
}
|
|
|
|
void AsyncFFT::compute()
|
|
{
|
|
#ifdef XDEBUG
|
|
Debug(&__plugin,DebugInfo,"Computing FFT with length %u [%p]",m_length,this);
|
|
Time t;
|
|
#endif
|
|
unsigned int i, j, n;
|
|
unsigned int blockEnd = 1;
|
|
for (unsigned int blockSize = 2; blockSize <= m_length; blockSize <<= 1) {
|
|
double delta_angle = M_2PI / blockSize;
|
|
double sm1 = ::sin(-delta_angle);
|
|
double sm2 = ::sin(-2 * delta_angle);
|
|
double cm1 = ::cos(-delta_angle);
|
|
double cm2 = ::cos(-2 * delta_angle);
|
|
double w = 2 * cm1;
|
|
double ar[3], ai[3];
|
|
|
|
for (i = 0; i < m_length; i += blockSize) {
|
|
ar[1] = cm1;
|
|
ai[1] = sm1;
|
|
ar[2] = cm2;
|
|
ai[2] = sm2;
|
|
|
|
if (m_stop)
|
|
return;
|
|
for (j = i, n = 0; n < blockEnd; j++, n++) {
|
|
ar[0] = w*ar[1] - ar[2];
|
|
ar[2] = ar[1];
|
|
ar[1] = ar[0];
|
|
ai[0] = w*ai[1] - ai[2];
|
|
ai[2] = ai[1];
|
|
ai[1] = ai[0];
|
|
|
|
unsigned int k = j + blockEnd;
|
|
double tr = ar[0]*m_real[k] - ai[0]*m_imag[k];
|
|
double ti = ar[0]*m_imag[k] + ai[0]*m_real[k];
|
|
m_real[k] = m_real[j] - tr;
|
|
m_imag[k] = m_imag[j] - ti;
|
|
m_real[j] += tr;
|
|
m_imag[j] += ti;
|
|
}
|
|
}
|
|
blockEnd = blockSize;
|
|
}
|
|
n = m_length >> 1;
|
|
for (i = 0; i < n; i++)
|
|
m_real[i] = ::sqrt(m_real[i]*m_real[i] + m_imag[i]*m_imag[i]) / n;
|
|
#ifdef XDEBUG
|
|
Debug(&__plugin,DebugInfo,"Computing FFT with length %u took " FMT64U " usec [%p]",
|
|
m_length,Time::now()-t,this);
|
|
#endif
|
|
|
|
#ifdef XDEBUG
|
|
for (i = 0; i < n; i++)
|
|
Debug(&__plugin,DebugAll,"fft[%u] = %0.2f",i,m_real[i]);
|
|
#endif
|
|
}
|
|
|
|
|
|
AnalyzerCons::AnalyzerCons(const String& type, const char* window)
|
|
: m_timeStart(0), m_tsStart(0), m_tsGapCount(0), m_tsGapLength(0),
|
|
m_spectrum(0), m_total(0), m_valid(0), m_analyze(false)
|
|
{
|
|
DDebug(&__plugin,DebugAll,"AnalyzerCons::AnalyzerCons('%s') [%p]",
|
|
type.c_str(),this);
|
|
unsigned int len = 0;
|
|
if ((type == "probe") || type.startsWith("tone/probe")) {
|
|
len = 256;
|
|
m_analyze = true;
|
|
m_spectrum = AsyncFFT::create(len,(AsyncFFT::WinType)lookup(window,dict_windows,AsyncFFT::Rectangle));
|
|
m_spectrum->setNotify(this);
|
|
return;
|
|
}
|
|
else if (type == "fft1024")
|
|
len = 1024;
|
|
else if (type == "fft512")
|
|
len = 512;
|
|
else if (type == "fft256")
|
|
len = 256;
|
|
else if (type == "fft128")
|
|
len = 128;
|
|
else if (type == "fft64")
|
|
len = 64;
|
|
if (len) {
|
|
m_spectrum = AsyncFFT::create(len,(AsyncFFT::WinType)lookup(window,dict_windows,AsyncFFT::Triangle));
|
|
m_spectrum->setNotify(this);
|
|
}
|
|
}
|
|
|
|
AnalyzerCons::~AnalyzerCons()
|
|
{
|
|
DDebug(&__plugin,DebugAll,"AnalyzerCons::~AnalyzerCons() %p [%p]",m_spectrum,this);
|
|
s_mutex.lock();
|
|
if (m_spectrum) {
|
|
AsyncFFT* tmp = m_spectrum;
|
|
m_spectrum = 0;
|
|
tmp->stop();
|
|
}
|
|
s_mutex.unlock();
|
|
}
|
|
|
|
unsigned long AnalyzerCons::Consume(const DataBlock& data, unsigned long tStamp, unsigned long flags)
|
|
{
|
|
if (!m_timeStart) {
|
|
// the first data block may be garbled or have bad timestamp - ignore
|
|
m_timeStart = Time::now();
|
|
m_tsStart = tStamp;
|
|
return invalidStamp();
|
|
}
|
|
unsigned int samples = data.length() / 2;
|
|
long delta = tStamp - timeStamp() - samples;
|
|
if (delta) {
|
|
XDebug(&__plugin,DebugMild,"Got %u samples with ts=%lu but old ts=%lu (delta=%ld)",
|
|
samples,tStamp,timeStamp(),delta);
|
|
if (delta < 0)
|
|
delta = -delta;
|
|
m_tsGapCount++;
|
|
m_tsGapLength += delta;
|
|
}
|
|
if (!m_spectrum)
|
|
return invalidStamp();
|
|
m_data += data;
|
|
unsigned int len = 2 * m_spectrum->samples();
|
|
if (m_data.length() < len)
|
|
return invalidStamp();
|
|
// limit the length of the buffer
|
|
int toCut = data.length() - (2 * len);
|
|
if (toCut > 0) {
|
|
DDebug(&__plugin,DebugInfo,"Dropping %d samples [%p]",toCut/2,this);
|
|
m_data.cut(-toCut);
|
|
}
|
|
if (m_spectrum->prepare((const short*)m_data.data()))
|
|
m_data.cut(-(int)len);
|
|
return invalidStamp();
|
|
}
|
|
|
|
void AnalyzerCons::run()
|
|
{
|
|
// this method is called with the mutex hold
|
|
if (!m_spectrum)
|
|
return;
|
|
unsigned int n = m_spectrum->length();
|
|
double max = 0.0;
|
|
unsigned int i;
|
|
for (i = 1; i < n; i++) {
|
|
double val = m_spectrum->at(i);
|
|
if (max < val)
|
|
max = val;
|
|
}
|
|
|
|
if (!m_analyze)
|
|
return;
|
|
|
|
double limit = max;
|
|
if (max < ALLOW_MIN) {
|
|
// don't start until we get some data
|
|
if (!m_total)
|
|
return;
|
|
limit = ALLOW_MIN;
|
|
}
|
|
|
|
unsigned int peaks = 0;
|
|
limit *= PEAKS_THR;
|
|
for (i = 1; i < n; i++) {
|
|
if (m_spectrum->at(i) > limit)
|
|
peaks++;
|
|
}
|
|
DDebug(&__plugin,DebugInfo,"Got %u peaks, limit=%f, max=%f [%p]",peaks,limit,max,this);
|
|
|
|
m_total++;
|
|
if (peaks == PEAKS_NUM)
|
|
m_valid++;
|
|
}
|
|
|
|
void AnalyzerCons::statusParams(String& str, Message* msg)
|
|
{
|
|
unsigned long samples = timeStamp() - m_tsStart;
|
|
str.append("gaps=",",") << m_tsGapCount;
|
|
str << ",gaplen=" << (unsigned int)m_tsGapLength;
|
|
str << ",samples=" << (unsigned int)samples;
|
|
if (m_timeStart) {
|
|
u_int64_t utime = Time::now() - m_timeStart;
|
|
utime = utime ? ((1000000 * (u_int64_t)samples) + (utime / 2)) / utime : 0;
|
|
str << ",rate=" << (unsigned int)utime;
|
|
if (msg)
|
|
msg->setParam("rate",String((unsigned int)utime));
|
|
}
|
|
if (m_total > 0) {
|
|
double q = m_valid * 100.0 / m_total;
|
|
char buf[64];
|
|
snprintf(buf,sizeof(buf)-1,"quality=%0.2f",q);
|
|
str.append(buf,",");
|
|
if (msg) {
|
|
char quality[32];
|
|
snprintf(quality,sizeof(quality)-1,"%0.2f",q);
|
|
msg->setParam("quality",quality);
|
|
}
|
|
}
|
|
if (!msg)
|
|
return;
|
|
msg->setParam("gaps",String(m_tsGapCount));
|
|
msg->setParam("gaplen",String((unsigned int)m_tsGapLength));
|
|
msg->setParam("samples",String(((unsigned int)samples)));
|
|
}
|
|
|
|
|
|
AnalyzerChan::AnalyzerChan(const String& type, bool outgoing, const char* window)
|
|
: Channel(__plugin,0,outgoing), m_stopTime(0),
|
|
m_timeStart(Time::now()), m_timeRoute(0), m_timeRing(0), m_timeAnswer(0),
|
|
m_window(window)
|
|
{
|
|
DDebug(this,DebugAll,"AnalyzerChan::AnalyzerChan('%s',%s) [%p]",
|
|
type.c_str(),String::boolText(outgoing),this);
|
|
m_address = type;
|
|
}
|
|
|
|
AnalyzerChan::~AnalyzerChan()
|
|
{
|
|
DDebug(this,DebugAll,"AnalyzerChan::~AnalyzerChan() %s [%p]",id().c_str(),this);
|
|
Engine::enqueue(message("chan.hangup"));
|
|
}
|
|
|
|
void AnalyzerChan::destroyed()
|
|
{
|
|
RefPointer<AnalyzerCons> cons = YOBJECT(AnalyzerCons,getConsumer());
|
|
char buf[32];
|
|
printTime(buf,(unsigned int)(Time::now() - m_timeStart));
|
|
String str(status());
|
|
Message* msg = message("call.analyzer");
|
|
localParams(str,msg);
|
|
str.append("totaltime=",",") << buf;
|
|
msg->setParam("totaltime",buf);
|
|
if (cons)
|
|
cons->statusParams(str,msg);
|
|
Output("Finished '%s' status: %s",id().c_str(),str.c_str());
|
|
Channel::destroyed();
|
|
Engine::enqueue(msg);
|
|
}
|
|
|
|
void AnalyzerChan::statusParams(String& str)
|
|
{
|
|
Channel::statusParams(str);
|
|
localParams(str);
|
|
RefPointer<AnalyzerCons> cons = YOBJECT(AnalyzerCons,getConsumer());
|
|
if (cons)
|
|
cons->statusParams(str);
|
|
}
|
|
|
|
void AnalyzerChan::localParams(String& str, Message* msg)
|
|
{
|
|
char buf[32];
|
|
if (m_timeRoute) {
|
|
printTime(buf,m_timeRoute);
|
|
str.append("routetime=",",") << buf;
|
|
if (msg)
|
|
msg->setParam("routetime",buf);
|
|
}
|
|
if (m_timeRing) {
|
|
printTime(buf,m_timeRing);
|
|
str.append("ringtime=",",") << buf;
|
|
if (msg)
|
|
msg->setParam("ringtime",buf);
|
|
}
|
|
if (m_timeAnswer) {
|
|
printTime(buf,m_timeAnswer);
|
|
str.append("answertime=",",") << buf;
|
|
if (msg)
|
|
msg->setParam("answertime",buf);
|
|
}
|
|
}
|
|
|
|
bool AnalyzerChan::callRouted(Message& msg)
|
|
{
|
|
if (!m_timeRoute)
|
|
m_timeRoute = Time::now() - m_timeStart;
|
|
setDuration(msg);
|
|
return Channel::callRouted(msg);
|
|
}
|
|
|
|
bool AnalyzerChan::msgRinging(Message& msg)
|
|
{
|
|
if (!m_timeRing)
|
|
m_timeRing = Time::now() - m_timeStart;
|
|
return Channel::msgRinging(msg);
|
|
}
|
|
|
|
bool AnalyzerChan::msgAnswered(Message& msg)
|
|
{
|
|
if (!m_timeAnswer)
|
|
m_timeAnswer = Time::now() - m_timeStart;
|
|
addConsumer();
|
|
addSource();
|
|
return Channel::msgAnswered(msg);
|
|
}
|
|
|
|
void AnalyzerChan::checkTimers(Message& msg, const Time& tmr)
|
|
{
|
|
if (m_stopTime && (m_stopTime < tmr))
|
|
msgDrop(msg,"finished");
|
|
else
|
|
Channel::checkTimers(msg,tmr);
|
|
}
|
|
|
|
void AnalyzerChan::startChannel(NamedList& params)
|
|
{
|
|
Message* m = message("chan.startup",params);
|
|
const char* tmp = params.getValue("caller");
|
|
if (tmp)
|
|
m->addParam("caller",tmp);
|
|
tmp = params.getValue("called");
|
|
if (tmp)
|
|
m->addParam("called",tmp);
|
|
if (isOutgoing()) {
|
|
tmp = params.getValue("billid");
|
|
if (tmp)
|
|
m->addParam("billid",tmp);
|
|
}
|
|
Engine::enqueue(m);
|
|
if (isOutgoing()) {
|
|
addConsumer();
|
|
addSource();
|
|
}
|
|
setDuration(params);
|
|
}
|
|
|
|
void AnalyzerChan::setDuration(NamedList& params)
|
|
{
|
|
int t = params.getIntValue("duration",120000);
|
|
if (t > 0)
|
|
m_stopTime = Time::now() + 1000 * (uint64_t)t;
|
|
}
|
|
|
|
void AnalyzerChan::addSource()
|
|
{
|
|
if (getSource())
|
|
return;
|
|
const char* src = "tone/probe";
|
|
if (m_address.startsWith("tone/"))
|
|
src = m_address;
|
|
Message m("chan.attach");
|
|
complete(m,true);
|
|
m.addParam("source",src);
|
|
m.addParam("single","true");
|
|
m.userData(this);
|
|
if (!Engine::dispatch(m))
|
|
Debug(this,DebugWarn,"Could not attach source '%s' [%p]",src,this);
|
|
}
|
|
|
|
void AnalyzerChan::addConsumer()
|
|
{
|
|
if (getConsumer())
|
|
return;
|
|
AnalyzerCons* cons = new AnalyzerCons(m_address,m_window);
|
|
setConsumer(cons);
|
|
cons->deref();
|
|
}
|
|
|
|
bool AnalyzerDriver::startCall(NamedList& params, const String& dest)
|
|
{
|
|
bool direct = true;
|
|
String tmp(params.getValue("direct"));
|
|
if (tmp.null()) {
|
|
direct = false;
|
|
tmp = params.getValue("target");
|
|
if (tmp.null()) {
|
|
Debug(DebugWarn,"Analyzer outgoing call with no target!");
|
|
return false;
|
|
}
|
|
}
|
|
// this is an incoming call!
|
|
AnalyzerChan *ac = new AnalyzerChan(dest,false,params.getValue("window"));
|
|
ac->initChan();
|
|
ac->startChannel(params);
|
|
Message* m = ac->message("call.route",false,true);
|
|
m->addParam("called",tmp);
|
|
if (direct)
|
|
m->addParam("callto",tmp);
|
|
tmp = params.getValue("caller");
|
|
if (tmp.null())
|
|
tmp << prefix() << dest;
|
|
m->addParam("caller",tmp);
|
|
params.setParam("id",ac->id());
|
|
return ac->startRouter(m);
|
|
}
|
|
|
|
bool AnalyzerDriver::msgExecute(Message& msg, String& dest)
|
|
{
|
|
CallEndpoint* ch = static_cast<CallEndpoint*>(msg.userObject("CallEndpoint"));
|
|
if (ch) {
|
|
AnalyzerChan *ac = new AnalyzerChan(dest,true,msg.getValue("window"));
|
|
ac->initChan();
|
|
if (ch->connect(ac,msg.getValue("reason"))) {
|
|
ac->callConnect(msg);
|
|
msg.setParam("peerid",ac->id());
|
|
ac->startChannel(msg);
|
|
ac->deref();
|
|
return true;
|
|
}
|
|
else {
|
|
ac->destruct();
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
return startCall(msg,dest);
|
|
}
|
|
|
|
bool AttachHandler::received(Message& msg)
|
|
{
|
|
String cons(msg.getValue("consumer"));
|
|
if (!cons.startSkip(__plugin.prefix(),false))
|
|
cons.clear();
|
|
if (cons.null())
|
|
return false;
|
|
|
|
CallEndpoint* ch = static_cast<CallEndpoint*>(msg.userObject("CallEndpoint"));
|
|
|
|
if (!ch) {
|
|
Debug(DebugWarn,"Analyzer attach request with no control channel!");
|
|
return false;
|
|
}
|
|
|
|
// if single attach was requested we can return true if everything is ok
|
|
bool ret = msg.getBoolValue("single");
|
|
|
|
AnalyzerCons *ac = new AnalyzerCons(cons,msg.getValue("window"));
|
|
ch->setConsumer(ac);
|
|
ac->deref();
|
|
return ret;
|
|
}
|
|
|
|
AnalyzerDriver::AnalyzerDriver()
|
|
: Driver("analyzer","misc"), m_handler(0)
|
|
{
|
|
Output("Loaded module Analyzer");
|
|
}
|
|
|
|
AnalyzerDriver::~AnalyzerDriver()
|
|
{
|
|
Output("Unloading module Analyzer");
|
|
lock();
|
|
channels().clear();
|
|
unlock();
|
|
}
|
|
|
|
void AnalyzerDriver::initialize()
|
|
{
|
|
Output("Initializing module Analyzer");
|
|
setup(0,true); // no need to install notifications
|
|
Driver::initialize();
|
|
installRelay(Ringing);
|
|
installRelay(Answered);
|
|
installRelay(Halt);
|
|
if (!m_handler) {
|
|
m_handler = new AttachHandler;
|
|
Engine::install(m_handler);
|
|
}
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|