/*
 * Copyright (C) 2002-2003 Fhg Fokus (inband detector code)
 * Copyright (C) 2005 Andriy I Pylypenko
 * Copyright (C) 2007 iptego GmbH
 *
 * This file is part of SEMS, a free SIP media server.
 *
 * SEMS 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 released under
 * the GPL with the additional exemption that compiling, linking,
 * and/or using OpenSSL is allowed.
 *
 * For a license to use the SEMS software under conditions
 * other than those described here, or to purchase support for this
 * software, please contact iptel.org by e-mail at the following addresses:
 *    info@iptel.org
 *
 * SEMS 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#include "AmDtmfDetector.h"
#include "AmSession.h"
#include "log.h"

#include <arpa/inet.h>
#include <netinet/in.h>
#include <math.h>
#include <sys/time.h>

// per RFC this is 5000ms, but in reality then 
// one needs to wait 5 sec on the first keypress
// (e.g. due to a bug on recent snoms)
#define MAX_INFO_DTMF_LENGTH 1000 

//
// AmDtmfEventQueue methods
//
AmDtmfEventQueue::AmDtmfEventQueue(AmDtmfDetector *handler)
  : AmEventQueue(handler), m_detector(handler)
{
}

void AmDtmfEventQueue::processEvents()
{
  AmDtmfDetector *local_handler = reinterpret_cast<AmDtmfDetector *>(handler);
  local_handler->checkTimeout();
  AmEventQueue::processEvents();
}

void AmDtmfEventQueue::putDtmfAudio(const unsigned char *buf, int size, int user_ts)
{
  m_detector->putDtmfAudio(buf, size, user_ts);
}

//
// AmSipDtmfEvent methods
//
AmSipDtmfEvent::AmSipDtmfEvent(const string& request_body)
  : AmDtmfEvent(Dtmf::SOURCE_SIP)
{
  parseRequestBody(request_body);
}

void AmSipDtmfEvent::parseRequestBody(const string& request_body)
{
  string::size_type start = 0;
  string::size_type stop = 0;
  while ((stop = request_body.find('\n', start)) != string::npos)
    {
      parseLine(request_body.substr(start, stop - start));
      start = stop + 1;
    }
  if (start < request_body.length())
    {
      // last chunk was not ended with '\n'
      parseLine(request_body.substr(start, string::npos));
    }
}

void AmSipDtmfEvent::parseLine(const string& line)
{
  static const string KeySignal("Signal=");
  static const string KeyDuration("Duration=");

  if (line.length() > KeySignal.length() &&
      line.substr(0, KeySignal.length()) == KeySignal)
    {
      string event(line.substr(KeySignal.length(), string::npos));
      switch (event.c_str()[0])
        {
        case '*':
	  m_event = 10;
	  break;
        case '#':
	  m_event = 11;
	  break;
        case 'A':
        case 'a':
	  m_event = 12;
	  break;
        case 'B':
        case 'b':
	  m_event = 13;
	  break;
        case 'C':
        case 'c':
	  m_event = 14;
	  break;
        case 'D':
        case 'd':
	  m_event = 15;
	  break;
        default:
	  m_event = atol(event.c_str());
        }
    }
  else if (line.length() > KeyDuration.length() &&
	   line.substr(0, KeyDuration.length()) == KeyDuration)
    {
      m_duration_msec = atol(line.substr(KeyDuration.length(), string::npos).c_str());
      if (m_duration_msec > MAX_INFO_DTMF_LENGTH)
	m_duration_msec  = MAX_INFO_DTMF_LENGTH;

    }
}

//
// AmRtpDtmfEvent methods
//
AmRtpDtmfEvent::AmRtpDtmfEvent(const dtmf_payload_t *payload, int sample_rate, unsigned int ts)
  : AmDtmfEvent(Dtmf::SOURCE_RTP)
{
  m_duration_msec = ntohs(payload->duration) * 1000 / sample_rate;
  m_e = payload->e;
  m_volume = payload->volume;
  m_event = payload->event;
  m_ts = ts;
  // RFC 2833:
  // R: This field is reserved for future use. The sender MUST set it
  // to zero, the receiver MUST ignore it.
  // m_r = payload->r;
}

//
// AmSipDtmfDetector methods
//
AmSipDtmfDetector::AmSipDtmfDetector(AmKeyPressSink *keysink)
  : m_keysink(keysink)
{
}

void AmSipDtmfDetector::process(AmSipDtmfEvent *evt)
{
  struct timeval start;
  struct timeval stop;
  gettimeofday(&start, NULL);
  // stop = start + duration
  memcpy(&stop, &start, sizeof(struct timeval));
  stop.tv_usec += evt->duration() * 1000;
  if (stop.tv_usec > 1000000)
    {
      ++stop.tv_sec;
      stop.tv_usec -= 1000000;
    }
  m_keysink->registerKeyReleased(evt->event(), Dtmf::SOURCE_SIP, start, stop);
}

//
// AmDtmfDetector methods
//
AmDtmfDetector::AmDtmfDetector(AmSession *session)
  : m_session(session), m_rtpDetector(this),
    m_sipDetector(this),
    m_eventPending(false), m_sipEventReceived(false),
    m_inbandEventReceived(false), m_rtpEventReceived(false),
    m_inband_type(Dtmf::SEMSInternal),
    m_currentEvent(-1),
    m_current_eventid_i(false)
{
#ifndef USE_SPANDSP
  setInbandDetector(Dtmf::SEMSInternal);
#else
  setInbandDetector(AmConfig::DefaultDTMFDetector);
#endif
}

void AmDtmfDetector::setInbandDetector(Dtmf::InbandDetectorType t) {
#ifndef USE_SPANDSP
  if (t == Dtmf::SpanDSP) {
    ERROR("trying to use spandsp DTMF detector without support for it"
	  "recompile with -D USE_SPANDSP\n");
  }
  if (!m_inbandDetector.get())
    m_inbandDetector.reset(new AmSemsInbandDtmfDetector(this));

  return;
#else

  if ((t != m_inband_type) || (!m_inbandDetector.get())) {
    if (t == Dtmf::SEMSInternal) {
      DBG("Setting internal DTMF detector\n");
      m_inbandDetector.reset(new AmSemsInbandDtmfDetector(this));
    } else { // if t == SpanDSP
      DBG("Setting spandsp DTMF detector\n");
      m_inbandDetector.reset(new AmSpanDSPInbandDtmfDetector(this));
    }
    m_inband_type = t;
  }
#endif
}


void AmDtmfDetector::process(AmEvent *evt)
{
  AmDtmfEvent *event = dynamic_cast<AmDtmfEvent *>(evt);
  if (NULL == event)
    return;
  switch (event->event_id)
    {
    case Dtmf::SOURCE_RTP:
      m_rtpDetector.process(dynamic_cast<AmRtpDtmfEvent *>(event));
      break;
      //        case AmDtmfEvent::INBAND:
      //            m_audioDetector.process(dynamic_cast<AmAudioDtmfEvent *>(event));
      //            break;
    case Dtmf::SOURCE_SIP:
      m_sipDetector.process(dynamic_cast<AmSipDtmfEvent *>(event));
      break;
    }
  evt->processed = true;
}

void AmDtmfDetector::flushKey(unsigned int event_id) {
  // flush the current key if it corresponds to the one with event_id
#ifdef EXCESSIVE_DTMF_DEBUGINFO
  DBG("flushKey\n");
#endif
  if (m_eventPending && m_current_eventid_i && event_id == m_current_eventid) {
#ifdef EXCESSIVE_DTMF_DEBUGINFO
    DBG("flushKey - reportEvent()\n");
#endif
    reportEvent();
  }
}

void AmDtmfDetector::registerKeyReleased(int event, Dtmf::EventSource source,
                                         const struct timeval& start,
                                         const struct timeval& stop,
					 bool has_eventid, unsigned int event_id)
{
  // Old event has not been sent yet
  // push out it now
  if ((m_eventPending && m_currentEvent != event) ||
      (m_eventPending && has_eventid && m_current_eventid_i && (event_id != m_current_eventid))) {
#ifdef EXCESSIVE_DTMF_DEBUGINFO
    DBG("event differs - reportEvent()\n");
#endif
    reportEvent();
  }

  m_eventPending = true;
  m_currentEvent = event;
  if (has_eventid) {
    m_current_eventid_i = true;
    m_current_eventid = event_id;
  }

  memcpy(&m_startTime, &start, sizeof(struct timeval));
  memcpy(&m_lastReportTime, &stop, sizeof(struct timeval));
  switch (source)
    {
    case Dtmf::SOURCE_SIP:
      m_sipEventReceived = true;
      break;
    case Dtmf::SOURCE_RTP:
      m_rtpEventReceived = true;
      break;
    case Dtmf::SOURCE_INBAND:
      m_inbandEventReceived = true;
      break;
    default:
      break;
    }
}

void AmDtmfDetector::registerKeyPressed(int event, Dtmf::EventSource type, bool has_eventid, unsigned int event_id)
{
#ifdef EXCESSIVE_DTMF_DEBUGINFO
  DBG("registerKeyPressed(%d, .., %s, %u); m_eventPending=%s, m_currentEvent=%d, "
      "m_current_eventid=%u,m_current_eventid_i=%s\n",
      event, has_eventid?"true":"false", event_id, m_eventPending?"true":"false", 
      m_currentEvent, m_current_eventid, m_current_eventid_i?"true":"false");
#endif
  struct timeval tm;
  gettimeofday(&tm, NULL);

  if (!m_eventPending)
    {
      m_eventPending = true;
      m_currentEvent = event;
      memcpy(&m_startTime, &tm, sizeof(struct timeval));
      memcpy(&m_lastReportTime, &tm, sizeof(struct timeval));
    }
  else
    {
      // Old event has not been sent yet
      // push out it now
      if ((m_currentEvent != event) ||
	  (has_eventid && m_current_eventid_i && (event_id != m_current_eventid))) {
#ifdef EXCESSIVE_DTMF_DEBUGINFO
	DBG("event differs - reportEvent() from key pressed\n");
#endif
	reportEvent();
      }

      long delta_msec = (tm.tv_sec - m_lastReportTime.tv_sec) * 1000 +
	(tm.tv_usec - m_lastReportTime.tv_usec) / 1000;
      // SIP INFO can report stop time is in future so avoid changing 
      // m_lastReportTime during that period
      if (delta_msec > 0)
	memcpy(&m_lastReportTime, &tm, sizeof(struct timeval));


    }

  if (has_eventid) {
    m_current_eventid_i = true;
    m_current_eventid = event_id;
  }
}

void AmDtmfDetector::checkTimeout()
{
  m_rtpDetector.checkTimeout();
  if (m_eventPending)
    {
      if (m_sipEventReceived && m_rtpEventReceived && m_inbandEventReceived)
        {
	  // all three methods triggered - do not wait until timeout
	  reportEvent();
        }
      else
        {
	  // ... else wait until timeout
	  struct timeval tm;
	  gettimeofday(&tm, NULL);
	  long delta_msec = (tm.tv_sec - m_lastReportTime.tv_sec) * 1000 +
	    (tm.tv_usec - m_lastReportTime.tv_usec) / 1000;
	  if (delta_msec > WAIT_TIMEOUT)
	    reportEvent();
        }
    }
}

void AmDtmfDetector::reportEvent()
{
  m_reportLock.lock();

  long duration = (m_lastReportTime.tv_sec - m_startTime.tv_sec) * 1000 +
    (m_lastReportTime.tv_usec - m_startTime.tv_usec) / 1000;
  m_session->postDtmfEvent(new AmDtmfEvent(m_currentEvent, duration));
  m_eventPending = false;
  m_sipEventReceived = false;
  m_rtpEventReceived = false;
  m_inbandEventReceived = false;
  m_current_eventid_i = false;

  m_reportLock.unlock();
}

void AmDtmfDetector::putDtmfAudio(const unsigned char *buf, int size, int user_ts)
{
  m_inbandDetector->streamPut(buf, size, user_ts);
}

// AmRtpDtmfDetector methods
AmRtpDtmfDetector::AmRtpDtmfDetector(AmKeyPressSink *keysink)
  : m_keysink(keysink), m_eventPending(false), m_packetCount(0), 
    m_currentTS(0), m_currentTS_i(false), m_lastTS_i(false)
{
}

void AmRtpDtmfDetector::process(AmRtpDtmfEvent *evt)
{
  if (evt && evt->volume() < 55) // From RFC 2833:
    // The range of valid DTMF is from 0 to -36 dBm0 (must
    // accept); lower than -55 dBm0 must be rejected (TR-TSY-000181,
    // ITU-T Q.24A)
    {
      m_packetCount = 0; // reset idle packet counter

      if (m_lastTS_i && m_lastTS == evt->ts()) {
	// ignore events from past key press which was already reported
#ifdef EXCESSIVE_DTMF_DEBUGINFO
	DBG("ignore RTP event ts ==%u\n", evt->ts());
#endif
	return;
      }

      if (!m_eventPending)
        {
#ifdef EXCESSIVE_DTMF_DEBUGINFO
	  DBG("new m_eventPending, event()==%d, ts=%u\n", evt->event(), evt->ts());
#endif
	  gettimeofday(&m_startTime, NULL);
	  m_eventPending = true;
	  m_currentEvent = evt->event();
	  m_currentTS = evt->ts();
	  m_currentTS_i = true;
        }
      else
        {
#ifdef EXCESSIVE_DTMF_DEBUGINFO
	  DBG("RTP event, event()==%d, m_currentEvent == %d, m_currentTS_i=%s, "
	      "evt->ts=%u, m_currentTS=%u\n",
	      evt->event(), m_currentEvent, m_currentTS_i?"true":"false", 
	      evt->ts(), m_currentTS);
#endif

	  if ((evt->event() != m_currentEvent) || 
	      (m_currentTS_i && (evt->ts() != m_currentTS)))
            {
	      // Previous event does not end correctly so send out it now...
#ifdef EXCESSIVE_DTMF_DEBUGINFO
	      DBG("flushKey %u\n", m_currentTS);
#endif
	      m_keysink->flushKey(m_currentTS);
	      // ... and reinitialize to process current event
	      gettimeofday(&m_startTime, NULL);
	      m_eventPending = true;
	      m_currentEvent = evt->event();
	      m_currentTS = evt->ts();
	      m_currentTS_i = true;
            }
        }
#ifdef EXCESSIVE_DTMF_DEBUGINFO
      DBG("registerKeyPressed %d, %u\n", m_currentEvent, m_currentTS);
#endif
      m_keysink->registerKeyPressed(m_currentEvent, Dtmf::SOURCE_RTP, true, m_currentTS);
    }
}

void AmRtpDtmfDetector::sendPending()
{
  if (m_eventPending)
    {
      struct timeval end_time;
      gettimeofday(&end_time, NULL);
#ifdef EXCESSIVE_DTMF_DEBUGINFO
      DBG("registerKeyReleased(%d, ... %u)\n", m_currentEvent, m_currentTS);
#endif
      m_keysink->registerKeyReleased(m_currentEvent, Dtmf::SOURCE_RTP, m_startTime, end_time, true, m_currentTS);
      m_eventPending = false;
      m_currentTS_i = false;
      m_lastTS = m_currentTS;
      m_lastTS_i = true;     
    }
}

void AmRtpDtmfDetector::checkTimeout()
{
  if (m_eventPending && m_packetCount++ > MAX_PACKET_WAIT)
    {
#ifdef EXCESSIVE_DTMF_DEBUGINFO
      DBG("idle timeout ... sendPending()\n");
#endif
      sendPending();
    }
}

//
// AmInbandDtmfDetector methods

AmInbandDtmfDetector::AmInbandDtmfDetector(AmKeyPressSink *keysink) 
 : m_keysink(keysink)
{
}

//
// -------------------------------------------------------------------------------------------
#define IVR_DTMF_ASTERISK 10
#define IVR_DTMF_HASH     11
#define IVR_DTMF_A        12
#define IVR_DTMF_B        13 
#define IVR_DTMF_C        14 
#define IVR_DTMF_D        15

/* the detector returns these values */

static int IVR_dtmf_matrix[4][4] =
  {
    {                1, 2, 3,             IVR_DTMF_A},
    {                4, 5, 6,             IVR_DTMF_B},
    {                7, 8, 9,             IVR_DTMF_C},
    {IVR_DTMF_ASTERISK, 0, IVR_DTMF_HASH, IVR_DTMF_D}
  };


#define LOGRP             0
#define HIGRP             1

#define REL_DTMF_TRESH     4000     /* above this is dtmf                         */
#define REL_SILENCE_TRESH   200     /* below this is silence                      */
#define REL_AMP_BITS          9     /* bits per sample, reduced to avoid overflow */
#define PI              3.1415926
#define NELEMSOF(x) (sizeof(x)/sizeof(*x))

/** \brief DTMF tone filter type */
typedef struct {
  int freq;			/* frequency */
  int grp;			/* low/high group */
} dtmf_t;

static dtmf_t dtmf_tones[8] = 
  {
    { 697, LOGRP},
    { 770, LOGRP},
    { 852, LOGRP},
    { 941, LOGRP},
    {1209, HIGRP},
    {1336, HIGRP},
    {1477, HIGRP},
    {1633, HIGRP}
  };

static char dtmf_matrix[4][4] =
  {
    {'1', '2', '3', 'A'},
    {'4', '5', '6', 'B'},
    {'7', '8', '9', 'C'},
    {'*', '0', '#', 'D'}
  };

AmSemsInbandDtmfDetector::AmSemsInbandDtmfDetector(AmKeyPressSink *keysink)
  : AmInbandDtmfDetector(keysink),
    m_last(' '),
    m_idx(0),
    m_count(0),
    SAMPLERATE(SYSTEM_SAMPLERATE)
{
  /* precalculate 2 * cos (2 PI k / N) */
  for(unsigned i = 0; i < NELEMSOF(rel_cos2pik); i++) {
    // FIXME: fixed samplerate. won't work for wideband
    int k = (int)((double)dtmf_tones[i].freq * REL_DTMF_NPOINTS / SAMPLERATE + 0.5);
    rel_cos2pik[i] = (int)(2 * 32768 * cos(2 * PI * k / REL_DTMF_NPOINTS));
  }
}
AmSemsInbandDtmfDetector::~AmSemsInbandDtmfDetector() {
}

/*
 * Goertzel algorithm.
 * See http://ptolemy.eecs.berkeley.edu/~pino/Ptolemy/papers/96/dtmf_ict/
 * for more info.
 */

void AmSemsInbandDtmfDetector::isdn_audio_goertzel_relative()
{
  int sk, sk1, sk2;

  for (int k = 0; k < REL_NCOEFF; k++) {
    // like m_buf, sk..sk2 are in (32-REL_AMP_BITS).REL_AMP_BITS fixed-point format
    sk = sk1 = sk2 = 0;
    for (int n = 0; n < REL_DTMF_NPOINTS; n++) {
      sk = m_buf[n] + ((rel_cos2pik[k] * sk1) >> 15) - sk2;
      sk2 = sk1;
      sk1 = sk;
    }
    /* Avoid overflows */
    sk >>= 1;
    sk2 >>= 1;
    /* compute |X(k)|**2 */
    /* report overflows. This should not happen. */
    /* Comment this out if desired */
    /*if (sk < -32768 || sk > 32767)
      DBG("isdn_audio: dtmf goertzel overflow, sk=%d\n", sk);
      if (sk2 < -32768 || sk2 > 32767)
      DBG("isdn_audio: dtmf goertzel overflow, sk2=%d\n", sk2);
    */

    // note that the result still is in (32-REL_AMP_BITS).REL_AMP_BITS format
    m_result[k] =
      ((sk * sk) >> REL_AMP_BITS) -
      ((((rel_cos2pik[k] * sk) >> 15) * sk2) >> REL_AMP_BITS) +
      ((sk2 * sk2) >> REL_AMP_BITS);
  }
}


void AmSemsInbandDtmfDetector::isdn_audio_eval_dtmf_relative()
{
  int silence;
  int grp[2];
  char what;
  int thresh;

  grp[LOGRP] = grp[HIGRP] = -1;
  silence = 0;
  thresh = 0;

  for (int i = 0; i < REL_NCOEFF; i++) 
    {
      if (m_result[i] > REL_DTMF_TRESH) {
	if (m_result[i] > thresh)
	  thresh = m_result[i];
      }
      else if (m_result[i] < REL_SILENCE_TRESH)
	silence++;
    }
  if (silence == REL_NCOEFF)
    what = ' ';
  else {
    if (thresh > 0)	{
      thresh = thresh >> 4;  /* touchtones must match within 12 dB */

      for (int i = 0; i < REL_NCOEFF; i++) {
	if (m_result[i] < thresh)
	  continue;  /* ignore */

	/* good level found. This is allowed only one time per group */
	if (i < REL_NCOEFF / 2) {
	  /* lowgroup*/
	  if (grp[LOGRP] >= 0) {
	    // Bad. Another tone found. */
	    grp[LOGRP] = -1;
	    break;
	  }
	  else
	    grp[LOGRP] = i;
	}
	else { /* higroup */
	  if (grp[HIGRP] >= 0) { // Bad. Another tone found. */
	    grp[HIGRP] = -1;
	    break;
	  }
	  else
	    grp[HIGRP] = i - REL_NCOEFF/2;
	}
      }

      if ((grp[LOGRP] >= 0) && (grp[HIGRP] >= 0)) {
	what = dtmf_matrix[grp[LOGRP]][grp[HIGRP]];
	m_lastCode = IVR_dtmf_matrix[grp[LOGRP]][grp[HIGRP]];
		
	if (what != m_last)
	  {
	    m_startTime.tv_sec = m_last_ts / SAMPLERATE;
	    m_startTime.tv_usec = (m_last_ts * 1000 / SAMPLERATE) % 1000;
	  }
      } else
	what = '.';
    }
    else
      what = '.';
  }
  if (what != ' ' && what != '.')
    {
      if (++m_count >= DTMF_INTERVAL)
        {
	  m_keysink->registerKeyPressed(m_lastCode, Dtmf::SOURCE_INBAND);
        }
    }
  else
    {
      if (m_last != ' ' && m_last != '.' && m_count >= DTMF_INTERVAL)
        {
	  struct timeval stop;
	  stop.tv_sec = m_last_ts / SAMPLERATE;
	  stop.tv_usec = (m_last_ts * 1000 / SAMPLERATE) % 1000;
	  m_keysink->registerKeyReleased(m_lastCode, Dtmf::SOURCE_INBAND, m_startTime, stop);
        }
      m_count = 0;
    }
  m_last = what;
}

void AmSemsInbandDtmfDetector::isdn_audio_calc_dtmf(const signed short* buf, int len, unsigned int ts)
{
  int c;

  if(m_idx == 0) m_last_ts = ts;

  while (len) {
    c = (len < ((int)NELEMSOF(m_buf) - m_idx)) ? len : (NELEMSOF(m_buf) - m_idx);

    if (c <= 0) break;

    for (int i = 0; i < c; i++) {
      // m_buf is in (32-REL_AMP_BITS).REL_AMP_BITS fixed-point format, the samples
      // itself are in the last REL_AMP_BITS bits, i.e. they go from -1.0 to +1.0
      // (or more exactly from -1.0 to ~+0.996)
      m_buf[m_idx++] = (*buf++) >> (15 - REL_AMP_BITS);
    }
    if (m_idx == NELEMSOF(m_buf)) {
      isdn_audio_goertzel_relative();
      isdn_audio_eval_dtmf_relative();
      m_idx = 0;
      m_last_ts = ts + c;
    }
    len -= c;
    ts += c;
  }
}

int AmSemsInbandDtmfDetector::streamPut(const unsigned char* samples, unsigned int size, unsigned int user_ts)
{
  isdn_audio_calc_dtmf((const signed short *)samples, size / 2, user_ts);
  return size;
}

#ifdef USE_SPANDSP

AmSpanDSPInbandDtmfDetector::AmSpanDSPInbandDtmfDetector(AmKeyPressSink *keysink)
  : AmInbandDtmfDetector(keysink) 
{
#ifdef HAVE_OLD_SPANDSP_CALLBACK
  rx_state = (dtmf_rx_state_t*)malloc(sizeof(dtmf_rx_state_t));
  if (NULL == rx_state) {
    throw string("error allocating memory for DTMF detector\n");
  }
#else
  rx_state = NULL;
#endif
  rx_state = dtmf_rx_init(rx_state, NULL /* dtmf_rx_callback */, (void*)this); 

  if (rx_state==NULL) {
    throw string("error allocating memory for DTMF detector\n");
  }
  dtmf_rx_set_realtime_callback(rx_state, tone_report_func, (void*)this); 
}

AmSpanDSPInbandDtmfDetector::~AmSpanDSPInbandDtmfDetector() {
  // not in 0.0.4:
  //  dtmf_rx_release(rx_state);
#ifdef HAVE_OLD_SPANDSP_CALLBACK
  free(rx_state);
#else
  dtmf_rx_free(rx_state);
#endif
}

int AmSpanDSPInbandDtmfDetector::streamPut(const unsigned char* samples, unsigned int size, unsigned int user_ts) {
  dtmf_rx(rx_state, (const int16_t*) samples, size/2);
  return size;
}

#ifndef HAVE_OLD_SPANDSP_CALLBACK
void AmSpanDSPInbandDtmfDetector::tone_report_func(void *user_data, int code, int level, int delay) {
  AmSpanDSPInbandDtmfDetector* o = (AmSpanDSPInbandDtmfDetector*)user_data;
  o->tone_report_f(code, level, delay);
}
#else
void AmSpanDSPInbandDtmfDetector::tone_report_func(void *user_data, int code) {
  AmSpanDSPInbandDtmfDetector* o = (AmSpanDSPInbandDtmfDetector*)user_data;
  o->tone_report_f(code, 0, 0);
}
#endif

void AmSpanDSPInbandDtmfDetector::tone_report_f(int code, int level, int delay) {
  //  DBG("spandsp reports tone %c, %d, %d\n", code, level, delay);
  if (code) { // key pressed
    gettimeofday(&key_start, NULL);
    m_lastCode = code;
    // don't report key press - otherwise reported twice
    //    m_keysink->registerKeyPressed(char2int(code), Dtmf::SOURCE_INBAND);
  } else { // released
    struct timeval now;
    gettimeofday(&now, NULL);
    m_keysink->registerKeyReleased(char2int(m_lastCode), Dtmf::SOURCE_INBAND, key_start, now);    
  }
}

// uh, ugly
int AmSpanDSPInbandDtmfDetector::char2int(char code) {
  if (code>='0' && code<='9') 
    return code-'0';
  if (code == '#') 
    return IVR_DTMF_HASH;
  if (code == '*') 
    return IVR_DTMF_ASTERISK;

  if (code >= 'A' && code <= 'D')
    return code-'A';
  return code;
}

// unused - we use realtime reporting functions instead
// void AmSpanDSPInbandDtmfDetector::dtmf_rx_callback(void* user_data, const char* digits, int len) {
//   AmSpanDSPInbandDtmfDetector* o = (AmSpanDSPInbandDtmfDetector*)user_data;
//   o->dtmf_rx_f(digits, len);
// }

// void AmSpanDSPInbandDtmfDetector::dtmf_rx_f(const char* digits, int len) {
//   DBG("dtmf_rx_callback len=%d\n", len);

//   for (int i=0;i<len;i++)
//     DBG("char %c\n", digits[i]);
// }
#endif // USE_SPANDSP