Index: /pjproject/trunk/pjmedia/build/Makefile
===================================================================
--- /pjproject/trunk/pjmedia/build/Makefile	(revision 692)
+++ /pjproject/trunk/pjmedia/build/Makefile	(revision 693)
@@ -75,5 +75,6 @@
 			resample_port.o rtcp.o rtp.o sdp.o sdp_cmp.o sdp_neg.o \
 			session.o silencedet.o sound_port.o stream.o \
-			transport_udp.o wav_player.o wav_writer.o wave.o \
+			tonegen.o transport_udp.o \
+			wav_player.o wav_writer.o wave.o \
 			$(SOUND_OBJS) $(NULLSOUND_OBJS)
 
Index: /pjproject/trunk/pjmedia/build/pjmedia.dsp
===================================================================
--- /pjproject/trunk/pjmedia/build/pjmedia.dsp	(revision 692)
+++ /pjproject/trunk/pjmedia/build/pjmedia.dsp	(revision 693)
@@ -228,4 +228,8 @@
 # Begin Source File
 
+SOURCE=..\src\pjmedia\tonegen.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\src\pjmedia\transport_udp.c
 # End Source File
@@ -361,4 +365,8 @@
 
 SOURCE=..\include\pjmedia\stream.h
+# End Source File
+# Begin Source File
+
+SOURCE=..\include\pjmedia\tonegen.h
 # End Source File
 # Begin Source File
Index: /pjproject/trunk/pjmedia/include/pjmedia.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia.h	(revision 692)
+++ /pjproject/trunk/pjmedia/include/pjmedia.h	(revision 693)
@@ -51,4 +51,5 @@
 #include <pjmedia/sound_port.h>
 #include <pjmedia/splitcomb.h>
+#include <pjmedia/tonegen.h>
 #include <pjmedia/transport.h>
 #include <pjmedia/transport_udp.h>
Index: /pjproject/trunk/pjmedia/include/pjmedia/config.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/config.h	(revision 692)
+++ /pjproject/trunk/pjmedia/include/pjmedia/config.h	(revision 693)
@@ -242,4 +242,12 @@
 
 
+/**
+ * Maximum tones/digits that can be enqueued in the tone generator.
+ */
+#ifndef PJMEDIA_TONEGEN_MAX_DIGITS
+#   define PJMEDIA_TONEGEN_MAX_DIGITS		    32
+#endif
+
+
 
 /**
Index: /pjproject/trunk/pjmedia/include/pjmedia/tonegen.h
===================================================================
--- /pjproject/trunk/pjmedia/include/pjmedia/tonegen.h	(revision 693)
+++ /pjproject/trunk/pjmedia/include/pjmedia/tonegen.h	(revision 693)
@@ -0,0 +1,225 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#ifndef __PJMEDIA_TONEGEN_PORT_H__
+#define __PJMEDIA_TONEGEN_PORT_H__
+
+/**
+ * @file tonegen.h
+ * @brief Tone (sine, MF, DTMF) generator media port.
+ */
+#include <pjmedia/port.h>
+
+
+/**
+ * @defgroup PJMEDIA_MF_DTMF_TONE_GENERATOR Tone (sine, MF, DTMF) Generator
+ * @ingroup PJMEDIA_PORT
+ * @brief Tone (sine, MF, DTMF) Generator
+ * @{
+ * This page describes tone generator media port. A tone generator can be
+ * used to generate a single frequency sine wave or dual frequency tones
+ * such as DTMF.
+ *
+ * The tone generator media port provides two functions to generate tones.
+ * The function #pjmedia_tonegen_play() can be used to generate arbitrary
+ * single or dual frequency tone, and #pjmedia_tonegen_play_digits() is
+ * used to play digits such as DTMF. Each tone specified in the playback
+ * function has individual on and off signal duration that must be
+ * specified by application.
+ *
+ * In order to play digits such as DTMF, the tone generator is equipped
+ * with digit map, which contain information about the frequencies of
+ * the digits. The default digit map is DTMF (0-9,a-d,*,#), but application
+ * may specifiy different digit map to the tone generator by calling
+ * #pjmedia_tonegen_set_digit_map() function.
+ */
+
+PJ_BEGIN_DECL
+
+
+/**
+ * This structure describes individual MF digits to be played
+ * with #pjmedia_tonegen_play().
+ */
+typedef struct pjmedia_tone_desc
+{
+    short   freq1;	    /**< First frequency.			    */
+    short   freq2;	    /**< Optional second frequency.		    */
+    short   on_msec;	    /**< Playback ON duration, in miliseconds.	    */
+    short   off_msec;	    /**< Playback OFF duration, ini miliseconds.    */
+} pjmedia_tone_desc;
+
+
+
+/**
+ * This structure describes individual MF digits to be played
+ * with #pjmedia_tonegen_play_digits().
+ */
+typedef struct pjmedia_tone_digit
+{
+    char    digit;	    /**< The ASCI identification for the digit.	    */
+    short   on_msec;	    /**< Playback ON duration, in miliseconds.	    */
+    short   off_msec;	    /**< Playback OFF duration, ini miliseconds.    */
+} pjmedia_tone_digit;
+
+
+/**
+ * This structure describes the digit map which is used by the tone generator
+ * to produce tones from an ASCII digits.
+ * Digit map used by a particular tone generator can be retrieved/set with
+ * #pjmedia_tonegen_get_digit_map() and #pjmedia_tonegen_load_digit_map().
+ */
+typedef struct pjmedia_tone_digit_map
+{
+    unsigned count;	    /**< Number of digits in the map.		*/
+
+    struct {
+	char    digit;	    /**< The ASCI identification for the digit.	*/
+	short   freq1;	    /**< First frequency.			*/
+	short   freq2;	    /**< Optional second frequency.		*/
+    } digits[16];
+} pjmedia_tone_digit_map;
+
+
+
+/**
+ * Create an instance of tone generator with the specified parameters.
+ * When the tone generator is first created, it will be loaded with the
+ * default digit map.
+ *
+ * @param pool		    Pool to allocate memory for the port structure.
+ * @param clock_rate	    Sampling rate.
+ * @param channel_count	    Number of channels. Currently only mono and stereo
+ *			    are supported.
+ * @param samples_per_frame Number of samples per frame.
+ * @param bits_per_sample   Number of bits per sample. This version of PJMEDIA
+ *			    only supports 16bit per sample.
+ * @param options	    Option flags, must be zero for now.
+ * @param p_port	    Pointer to receive the port instance.
+ *
+ * @return		    PJ_SUCCESS on success, or the appropriate
+ *			    error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_tonegen_create(pj_pool_t *pool,
+					    unsigned clock_rate,
+					    unsigned channel_count,
+					    unsigned samples_per_frame,
+					    unsigned bits_per_sample,
+					    unsigned options,
+					    pjmedia_port **p_port);
+
+
+/**
+ * Check if the tone generator is still busy producing some tones.
+ *
+ * @param tonegen	    The tone generator instance.
+ *
+ * @return		    Non-zero if busy.
+ */
+PJ_DECL(pj_bool_t) pjmedia_tonegen_is_busy(pjmedia_port *tonegen);
+
+
+/**
+ * Instruct the tone generator to stop current processing.
+ *
+ * @param tonegen	    The tone generator instance.
+ *
+ * @return		    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_tonegen_stop(pjmedia_port *tonegen);
+
+
+/**
+ * Instruct the tone generator to play single or dual frequency tones 
+ * with the specified duration. The new tones will be appended to currently
+ * playing tones, unless #pjmedia_tonegen_stop() is called before calling
+ * this function. The playback will begin as soon as  the first get_frame()
+ * is called to the generator.
+ *
+ * @param tonegen	    The tone generator instance.
+ * @param count		    The number of tones in the array.
+ * @param tones		    Array of tones to be played.
+ * @param options	    Playback options, must be zero for now.
+ *
+ * @return		    PJ_SUCCESS on success, or PJ_ETOOMANY if
+ *			    there are too many digits in the queue.
+ */
+PJ_DECL(pj_status_t) pjmedia_tonegen_play(pjmedia_port *tonegen,
+					  unsigned count,
+					  const pjmedia_tone_desc tones[],
+					  unsigned options);
+
+/**
+ * Instruct the tone generator to play multiple MF digits with each of
+ * the digits having individual ON/OFF duration. Each of the digit in the
+ * digit array must have the corresponding descriptor in the digit map.
+ * The new tones will be appended to currently playing tones, unless 
+ * #pjmedia_tonegen_stop() is called before calling this function. 
+ * The playback will begin as soon as the first get_frame() is called 
+ * to the generator.
+ *
+ * @param tonegen	    The tone generator instance.
+ * @param count		    Number of digits in the array.
+ * @param digits	    Array of MF digits.
+ * @param options	    Playback options, must be zero for now.
+ *
+ * @return		    PJ_SUCCESS on success, or PJ_ETOOMANY if
+ *			    there are too many digits in the queue, or
+ *			    PJMEDIA_RTP_EINDTMF if invalid digit is
+ *			    specified.
+ */
+PJ_DECL(pj_status_t) pjmedia_tonegen_play_digits(pjmedia_port *tonegen,
+						 unsigned count,
+						 const pjmedia_tone_digit digits[],
+						 unsigned options);
+
+
+/**
+ * Get the digit-map currently used by this tone generator.
+ *
+ * @param tonegen	    The tone generator instance.
+ * @param m		    On output, it will be filled with the pointer to
+ *			    the digitmap currently used by the tone generator.
+ *
+ * @return		    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_tonegen_get_digit_map(pjmedia_port *tonegen,
+						   const pjmedia_tone_digit_map **m);
+
+
+/**
+ * Set digit map to be used by the tone generator.
+ *
+ * @param tonegen	    The tone generator instance.
+ * @param m		    Digitmap to be used by the tone generator.
+ *
+ * @return		    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_tonegen_set_digit_map(pjmedia_port *tonegen,
+						   pjmedia_tone_digit_map *m);
+
+
+PJ_END_DECL
+
+/**
+ * @}
+ */
+
+
+#endif	/* __PJMEDIA_TONEGEN_PORT_H__ */
+
Index: /pjproject/trunk/pjmedia/src/pjmedia/stream.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/stream.c	(revision 692)
+++ /pjproject/trunk/pjmedia/src/pjmedia/stream.c	(revision 693)
@@ -1274,20 +1274,19 @@
 	for (i=0; i<digit_char->slen; ++i) {
 	    unsigned pt;
-
-	    if (digit_char->ptr[i] >= '0' &&
-		digit_char->ptr[i] <= '9')
+	    int dig = pj_tolower(digit_char->ptr[i]);
+
+	    if (dig >= '0' && dig <= '9')
 	    {
-		pt = digit_char->ptr[i] - '0';
+		pt = dig - '0';
 	    } 
-	    else if (pj_tolower(digit_char->ptr[i]) >= 'a' &&
-		     pj_tolower(digit_char->ptr[i]) <= 'd')
+	    else if (dig >= 'a' && dig <= 'd')
 	    {
-		pt = pj_tolower(digit_char->ptr[i]) - 'a' + 12;
+		pt = dig - 'a' + 12;
 	    }
-	    else if (digit_char->ptr[i] == '*')
+	    else if (dig == '*')
 	    {
 		pt = 10;
 	    }
-	    else if (digit_char->ptr[i] == '#')
+	    else if (dig == '#')
 	    {
 		pt = 11;
Index: /pjproject/trunk/pjmedia/src/pjmedia/tonegen.c
===================================================================
--- /pjproject/trunk/pjmedia/src/pjmedia/tonegen.c	(revision 693)
+++ /pjproject/trunk/pjmedia/src/pjmedia/tonegen.c	(revision 693)
@@ -0,0 +1,529 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjmedia/tonegen.h>
+#include <pjmedia/errno.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/pool.h>
+
+
+/* float can be twice slower on i686! */
+#define DATA	double
+
+/* amplitude */
+#define AMP	16383
+
+
+#ifndef M_PI
+#   define M_PI  ((DATA)3.141592653589793238462643383279)
+#endif
+
+
+#if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+    /*
+     * Default floating-point based tone generation using sine wave 
+     * generation from:
+     *   http://www.musicdsp.org/showone.php?id=10.
+     * This produces good quality tone in relatively faster time than
+     * the normal sin() generator.
+     * Speed = 32,500 cycles (to generate 100 msec single-tone).
+     */
+#   include <math.h>
+    struct gen
+    {
+	DATA a, s0, s1;
+    };
+
+#   define GEN_INIT(var,R,F,A)	 var.a = (DATA) (2.0 * sin(M_PI * F / R)); \
+				 var.s0 = A; \
+				 var.s1 = 0
+#   define GEN_SAMP(val,var,A)	 var.s0 = var.s0 - var.a * var.s1; \
+				 var.s1 = var.s1 + var.a * var.s0; \
+				 val = (short) var.s0
+
+
+#elif defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0
+    /* 
+     * Fallback algorithm when floating point is disabled.
+     * This is a very fast fixed point tone generation using sine wave
+     * approximation from
+     *    http://www.audiomulch.com/~rossb/code/sinusoids/ 
+     * Quality wise not so good, but it's blazing fast!
+     * Speed = 9,776 cycles (to generate 100 msec single-tone).
+     */
+    PJ_INLINE(int) approximate_sin3(unsigned x)
+    {	
+	    unsigned s=-(int)(x>>31);
+	    x+=x;
+	    x=x>>16;
+	    x*=x^0xffff;            // x=x*(2-x)
+	    x+=x;                   // optional
+	    return x^s;
+    }
+    struct gen
+    {
+	unsigned add;
+	unsigned c;
+    };
+
+#   define MAXI			((unsigned)0xFFFFFFFF)
+#   define SIN			approximate_sin3
+#   define GEN_INIT(var,R,F,A)	var.add = MAXI/R * F, var.c=0
+#   define GEN_SAMP(val,var,A)	val = (short) (SIN(var.c) >> 16); \
+				var.c += var.add
+
+
+#else
+#   error "Should never get to this part"
+#   include <math.h>
+    /*
+     * Should never really reach here, but anyway it's provided for reference.
+     * This is the good old tone generator using sin().
+     * Speed = 178,000 cycles (to generate 100 msec single-tone).
+     */
+    struct gen
+    {
+	DATA add;
+	DATA c;
+    };
+
+#   define GEN_INIT(var,R,F,A) var.add = ((DATA)F)/R, var.c=0
+#   define GEN_SAMP(val,var,A) val = (short) (sin(var.c * 2 * M_PI) * A); \
+			       var.c += var.add
+
+#endif
+
+struct gen_state
+{
+    struct gen tone1;
+    struct gen tone2;
+    pj_bool_t  has_tone2;
+};
+
+
+static void init_generate_single_tone(struct gen_state *state,
+				      unsigned clock_rate, 
+				      unsigned freq)
+{
+    GEN_INIT(state->tone1,clock_rate,freq,AMP);
+    state->has_tone2 = PJ_FALSE;
+}
+
+static void generate_single_tone(struct gen_state *state,
+				 unsigned channel_count,
+				 unsigned samples,
+				 short buf[]) 
+{
+    short *end = buf + samples;
+
+    if (channel_count==1) {
+
+	while (buf < end) {
+	    GEN_SAMP(*buf++, state->tone1, AMP);
+	}
+
+    } else if (channel_count == 2) {
+
+	while (buf < end) {
+	    GEN_SAMP(*buf, state->tone1, AMP);
+	    *(buf+1) = *buf;
+	    buf += 2;
+	}
+    }
+}
+
+
+static void init_generate_dual_tone(struct gen_state *state,
+				    unsigned clock_rate, 
+				    unsigned freq1,
+				    unsigned freq2)
+{
+    GEN_INIT(state->tone1,clock_rate,freq1,AMP);
+    GEN_INIT(state->tone2,clock_rate,freq2,AMP);
+    state->has_tone2 = PJ_TRUE;
+}
+
+
+static void generate_dual_tone(struct gen_state *state,
+			       unsigned channel_count,
+			       unsigned samples,
+			       short buf[]) 
+{
+    short *end = buf + samples;
+
+    if (channel_count==1) {
+	int val, val2;
+	while (buf < end) {
+	    GEN_SAMP(val, state->tone1, AMP);
+	    GEN_SAMP(val2, state->tone2, AMP);
+	    *buf++ = (short)((val+val2) >> 1);
+	}
+    } else if (channel_count == 2) {
+	int val, val2;
+	while (buf < end) {
+
+	    GEN_SAMP(val, state->tone1, AMP);
+	    GEN_SAMP(val2, state->tone2, AMP);
+	    val = (val + val2) >> 1;
+
+	    *buf++ = (short)val;
+	    *buf++ = (short)val;
+	}
+    }
+}
+
+
+static void init_generate_tone(struct gen_state *state,
+			       unsigned clock_rate, 
+			       unsigned freq1,
+			       unsigned freq2)
+{
+    if (freq2)
+	init_generate_dual_tone(state, clock_rate, freq1, freq2);
+    else
+	init_generate_single_tone(state, clock_rate, freq1);
+}
+
+
+static void generate_tone(struct gen_state *state,
+			  unsigned channel_count,
+			  unsigned samples,
+			  short buf[])
+{
+    if (!state->has_tone2)
+	generate_single_tone(state, channel_count, samples, buf);
+    else
+	generate_dual_tone(state, channel_count, samples, buf);
+}
+
+
+/****************************************************************************/
+
+#define SIGNATURE   PJMEDIA_PORT_SIGNATURE('t', 'n', 'g', 'n')
+
+struct tonegen
+{
+    pjmedia_port	base;
+
+    /* Digit map */
+    pjmedia_tone_digit_map  *digit_map;
+
+    /* Tone generation state */
+    struct gen_state	state;
+
+    /* Currently played digits: */
+    unsigned		count;		    /* # of digits		*/
+    unsigned		cur_digit;	    /* currently played		*/
+    unsigned		dig_samples;	    /* sample pos in cur digit	*/
+    pjmedia_tone_desc	digits[PJMEDIA_TONEGEN_MAX_DIGITS];/* array of digits*/
+};
+
+
+/* Default digit map is DTMF */
+static pjmedia_tone_digit_map digit_map = 
+{
+    16,
+    {
+	{ '0', 941,  1336 },
+	{ '1', 697,  1209 },
+	{ '2', 697,  1336 },
+	{ '3', 697,  1447 },
+	{ '4', 770,  1209 },
+	{ '5', 770,  1336 },
+	{ '6', 770,  1447 },
+	{ '7', 852,  1209 },
+	{ '8', 852,  1336 },
+	{ '9', 852,  1447 },
+	{ 'a', 697,  1633 },
+	{ 'b', 770,  1633 },
+	{ 'c', 852,  1633 },
+	{ 'd', 941,  1633 },
+	{ '*', 941,  1209 },
+	{ '#', 941,  1477 },
+    }
+};
+
+
+static pj_status_t tonegen_get_frame(pjmedia_port *this_port, 
+				     pjmedia_frame *frame);
+
+/*
+ * Create an instance of tone generator with the specified parameters.
+ * When the tone generator is first created, it will be loaded with the
+ * default digit map.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_create( pj_pool_t *pool,
+					    unsigned clock_rate,
+					    unsigned channel_count,
+					    unsigned samples_per_frame,
+					    unsigned bits_per_sample,
+					    unsigned options,
+					    pjmedia_port **p_port)
+{
+    const pj_str_t STR_TONE_GEN = pj_str("tone-gen");
+    struct tonegen  *tonegen;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(pool && clock_rate && channel_count && 
+		     samples_per_frame && bits_per_sample == 16 && 
+		     options == 0 && p_port != NULL, PJ_EINVAL);
+
+    /* Only support mono and stereo */
+    PJ_ASSERT_RETURN(channel_count==1 || channel_count==2, PJ_EINVAL);
+
+    /* Create and initialize port */
+    tonegen = pj_pool_zalloc(pool, sizeof(struct tonegen));
+    status = pjmedia_port_info_init(&tonegen->base.info, &STR_TONE_GEN, 
+				    SIGNATURE, clock_rate, channel_count, 
+				    bits_per_sample, samples_per_frame);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    tonegen->base.get_frame = &tonegen_get_frame;
+    tonegen->digit_map = &digit_map;
+
+    /* Done */
+    *p_port = &tonegen->base;
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Check if the tone generator is still busy producing some tones.
+ */
+PJ_DEF(pj_bool_t) pjmedia_tonegen_is_busy(pjmedia_port *port)
+{
+    struct tonegen *tonegen = (struct tonegen*) port;
+    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_TRUE);
+    return tonegen->count != 0;
+}
+
+
+/*
+ * Instruct the tone generator to stop current processing.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_stop(pjmedia_port *port)
+{
+    struct tonegen *tonegen = (struct tonegen*) port;
+    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+    return tonegen->count = 0;
+}
+
+
+/*
+ * Fill a frame with tones.
+ */
+static pj_status_t tonegen_get_frame(pjmedia_port *port, 
+				     pjmedia_frame *frame)
+{
+    struct tonegen *tonegen = (struct tonegen*) port;
+    short *dst, *end;
+    unsigned clock_rate = tonegen->base.info.clock_rate;
+
+    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+
+    if (tonegen->count == 0) {
+	/* We don't have digits to play */
+	frame->type = PJMEDIA_FRAME_TYPE_NONE;
+	return PJ_SUCCESS;
+    }
+
+    if (tonegen->cur_digit > tonegen->count) {
+	/* We have played all the digits */
+	tonegen->count = 0;
+	frame->type = PJMEDIA_FRAME_TYPE_NONE;
+	return PJ_SUCCESS;
+    }
+
+    if (tonegen->dig_samples>=(tonegen->digits[tonegen->cur_digit].on_msec+
+			       tonegen->digits[tonegen->cur_digit].off_msec)*
+			       clock_rate / 1000)
+    {
+	/* We have finished with current digit */
+	tonegen->cur_digit++;
+	tonegen->dig_samples = 0;
+    }
+
+    if (tonegen->cur_digit > tonegen->count) {
+	/* After we're finished with the last digit, we have played all 
+	 * the digits 
+	 */
+	tonegen->count = 0;
+	frame->type = PJMEDIA_FRAME_TYPE_NONE;
+	return PJ_SUCCESS;
+    }
+    
+    dst = frame->buf;
+    end = dst + port->info.samples_per_frame;
+
+    while (dst < end && tonegen->cur_digit < tonegen->count) {
+	const pjmedia_tone_desc *dig = &tonegen->digits[tonegen->cur_digit];
+	unsigned required, cnt, on_samp, off_samp;
+
+	required = end - dst;
+	on_samp = dig->on_msec * clock_rate / 1000;
+	off_samp = dig->off_msec * clock_rate / 1000;
+
+	/* Init tonegen */
+	if (tonegen->dig_samples == 0) {
+	    init_generate_tone(&tonegen->state, port->info.clock_rate,
+			       dig->freq1, dig->freq2);
+	}
+
+	/* Add tone signal */
+	if (tonegen->dig_samples < on_samp) {
+	    cnt = on_samp - tonegen->dig_samples;
+	    if (cnt > required)
+		cnt = required;
+	    generate_tone(&tonegen->state, port->info.channel_count,
+			  cnt, dst);
+	    dst += cnt;
+	    tonegen->dig_samples += cnt;
+	    required -= cnt;
+
+	    if (dst == end)
+		break;
+	}
+
+	/* Add silence signal */
+	cnt = off_samp + on_samp - tonegen->dig_samples;
+	if (cnt > required)
+	    cnt = required;
+	pjmedia_zero_samples(dst, cnt);
+	dst += cnt;
+	tonegen->dig_samples += cnt;
+
+	/* Move to next digit if we're finished with this tone */
+	if (tonegen->dig_samples == on_samp + off_samp) {
+	    tonegen->cur_digit++;
+	    tonegen->dig_samples = 0;
+	}
+    }
+
+    if (dst < end)
+	pjmedia_zero_samples(dst, end-dst);
+
+    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
+    frame->size = port->info.bytes_per_frame;
+
+    if (tonegen->cur_digit >= tonegen->count)
+	tonegen->count = 0;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Play tones.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_play( pjmedia_port *port,
+					  unsigned count,
+					  const pjmedia_tone_desc tones[],
+					  unsigned options)
+{
+    struct tonegen *tonegen = (struct tonegen*) port;
+
+    PJ_ASSERT_RETURN(port && port->info.signature == SIGNATURE &&
+		     count && tones && options==0, PJ_EINVAL);
+
+    /* Don't put more than available buffer */
+    PJ_ASSERT_RETURN(count+tonegen->count <= PJMEDIA_TONEGEN_MAX_DIGITS,
+		     PJ_ETOOMANY);
+
+    pj_memcpy(tonegen->digits + tonegen->count,
+	      tones, count * sizeof(pjmedia_tone_desc));
+    tonegen->count += count;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Play digits.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_play_digits( pjmedia_port *port,
+						 unsigned count,
+						 const pjmedia_tone_digit digits[],
+						 unsigned options)
+{
+    struct tonegen *tonegen = (struct tonegen*) port;
+    pjmedia_tone_desc tones[PJMEDIA_TONEGEN_MAX_DIGITS];
+    const pjmedia_tone_digit_map *map = tonegen->digit_map;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(port && port->info.signature == SIGNATURE &&
+		     count && digits && options==0, PJ_EINVAL);
+    PJ_ASSERT_RETURN(count < PJMEDIA_TONEGEN_MAX_DIGITS, PJ_ETOOMANY);
+
+    for (i=0; i<count; ++i) {
+	int d = pj_tolower(digits[i].digit);
+	unsigned j;
+
+	/* Translate ASCII digits with digitmap */
+	for (j=0; j<map->count; ++j) {
+	    if (d == map->digits[j].digit)
+		break;
+	}
+	if (j == map->count)
+	    return PJMEDIA_RTP_EINDTMF;
+
+	tones[i].freq1 = map->digits[j].freq1;
+	tones[i].freq2 = map->digits[j].freq2;
+	tones[i].on_msec = digits[i].on_msec;
+	tones[i].off_msec = digits[i].off_msec;
+    }
+
+    return pjmedia_tonegen_play(port, count, tones, options);
+}
+
+
+/*
+ * Get the digit-map currently used by this tone generator.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_get_digit_map(pjmedia_port *port,
+						  const pjmedia_tone_digit_map **m)
+{
+    struct tonegen *tonegen = (struct tonegen*) port;
+    
+    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+    PJ_ASSERT_RETURN(m != NULL, PJ_EINVAL);
+
+    *m = tonegen->digit_map;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Set digit map to be used by the tone generator.
+ */
+PJ_DEF(pj_status_t) pjmedia_tonegen_set_digit_map(pjmedia_port *port,
+						  pjmedia_tone_digit_map *m)
+{
+    struct tonegen *tonegen = (struct tonegen*) port;
+    
+    PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
+    PJ_ASSERT_RETURN(m != NULL, PJ_EINVAL);
+
+    tonegen->digit_map = m;
+
+    return PJ_SUCCESS;
+}
+
+
Index: /pjproject/trunk/pjsip-apps/build/Samples-vc.mak
===================================================================
--- /pjproject/trunk/pjsip-apps/build/Samples-vc.mak	(revision 692)
+++ /pjproject/trunk/pjsip-apps/build/Samples-vc.mak	(revision 693)
@@ -49,5 +49,6 @@
 	  $(BINDIR)\sndinfo.exe \
 	  $(BINDIR)\sndtest.exe \
-	  $(BINDIR)\streamutil.exe
+	  $(BINDIR)\streamutil.exe \
+	  $(BINDIR)\tonegen.exe
 
 
Index: /pjproject/trunk/pjsip-apps/build/Samples.mak
===================================================================
--- /pjproject/trunk/pjsip-apps/build/Samples.mak	(revision 692)
+++ /pjproject/trunk/pjsip-apps/build/Samples.mak	(revision 693)
@@ -52,5 +52,6 @@
 	   sndinfo \
 	   sndtest \
-	   streamutil
+	   streamutil \
+	   tonegen
 
 EXES := $(foreach file, $(SAMPLES), $(BINDIR)/$(file)-$(TARGET_NAME)$(HOST_EXE))
Index: /pjproject/trunk/pjsip-apps/build/samples.dsp
===================================================================
--- /pjproject/trunk/pjsip-apps/build/samples.dsp	(revision 692)
+++ /pjproject/trunk/pjsip-apps/build/samples.dsp	(revision 693)
@@ -153,4 +153,8 @@
 SOURCE=..\src\samples\streamutil.c
 # End Source File
+# Begin Source File
+
+SOURCE=..\src\samples\tonegen.c
+# End Source File
 # End Group
 # Begin Group "Header Files"
Index: /pjproject/trunk/pjsip-apps/src/samples/debug.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 692)
+++ /pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 693)
@@ -28,4 +28,3 @@
  *  #include "playfile.c"
  */
-#include "aectest.c"
-
+#include "tonegen.c"
Index: /pjproject/trunk/pjsip-apps/src/samples/tonegen.c
===================================================================
--- /pjproject/trunk/pjsip-apps/src/samples/tonegen.c	(revision 693)
+++ /pjproject/trunk/pjsip-apps/src/samples/tonegen.c	(revision 693)
@@ -0,0 +1,137 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjmedia.h>
+#include <pjlib.h>
+
+#define SAMPLES_PER_FRAME   64
+#define ON_DURATION	    100
+#define OFF_DURATION	    100
+
+
+/*
+ * main()
+ */
+int main()
+{
+    pj_caching_pool cp;
+    pjmedia_endpt *med_endpt;
+    pj_pool_t *pool;
+    pjmedia_port *port;
+    unsigned i;
+    pj_status_t status;
+
+
+    /* Must init PJLIB first: */
+    status = pj_init();
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    /* Must create a pool factory before we can allocate any memory. */
+    pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0);
+
+    /* 
+     * Initialize media endpoint.
+     * This will implicitly initialize PJMEDIA too.
+     */
+    status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt);
+    PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1);
+
+    /* Create memory pool for our file player */
+    pool = pj_pool_create( &cp.factory,	    /* pool factory	    */
+			   "app",	    /* pool name.	    */
+			   4000,	    /* init size	    */
+			   4000,	    /* increment size	    */
+			   NULL		    /* callback on error    */
+			   );
+
+    status = pjmedia_tonegen_create(pool, 8000, 1, SAMPLES_PER_FRAME, 16, 0, &port);
+    if (status != PJ_SUCCESS)
+	return 1;
+
+    {
+	pjmedia_tone_desc tones[3];
+
+	tones[0].freq1 = 200;
+	tones[0].freq2 = 0;
+	tones[0].on_msec = ON_DURATION;
+	tones[0].off_msec = OFF_DURATION;
+
+	tones[1].freq1 = 400;
+	tones[1].freq2 = 0;
+	tones[1].on_msec = ON_DURATION;
+	tones[1].off_msec = OFF_DURATION;
+
+	tones[2].freq1 = 800;
+	tones[2].freq2 = 0;
+	tones[2].on_msec = ON_DURATION;
+	tones[2].off_msec = OFF_DURATION;
+
+	status = pjmedia_tonegen_play(port, 3, tones, 0);
+	PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
+    }
+
+    {
+	pjmedia_tone_digit digits[2];
+
+	digits[0].digit = '0';
+	digits[0].on_msec = ON_DURATION;
+	digits[0].off_msec = OFF_DURATION;
+
+	digits[1].digit = '0';
+	digits[1].on_msec = ON_DURATION;
+	digits[1].off_msec = OFF_DURATION;
+
+	status = pjmedia_tonegen_play_digits(port, 2, digits, 0);
+	PJ_ASSERT_RETURN(status==PJ_SUCCESS, 1);
+    }
+
+    {
+	pjmedia_frame frm;
+	FILE *f;
+	void *buf;
+
+	buf = pj_pool_alloc(pool, 2*8000);
+	frm.buf = buf;
+
+	f = fopen("tonegen.pcm", "wb");
+
+	for (i=0; i<8000/SAMPLES_PER_FRAME; ++i) {
+	    pjmedia_port_get_frame(port, &frm);
+	    fwrite(buf, SAMPLES_PER_FRAME, 2, f);
+	}
+
+	pj_assert(pjmedia_tonegen_is_busy(port) == 0);
+	fclose(f);
+    }
+
+    /* Delete port */
+    pjmedia_port_destroy(port);
+
+    /* Release application pool */
+    pj_pool_release( pool );
+
+    /* Destroy media endpoint. */
+    pjmedia_endpt_destroy( med_endpt );
+
+    /* Destroy pool factory */
+    pj_caching_pool_destroy( &cp );
+
+
+    /* Done. */
+    return 0;
+}
