blob: ea7c7bf2b25c8afef6f180455b75592c18f20663 [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
* Copyright (C) 2003-2008 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 <pjmedia/silencedet.h>
#include <pj/assert.h>
#include <pj/ctype.h>
#include <pj/lock.h>
#include <pj/log.h>
#include <pj/pool.h>
/* amplitude */
#define AMP PJMEDIA_TONEGEN_VOLUME
#ifndef M_PI
# define M_PI ((DATA)3.141592653589793238462643383279)
#endif
#if PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_SINE
#include <math.h>
#define DATA double
/*
* This is the good old tone generator using sin().
* Speed = 1347 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
* approx. 10.91 MIPS
*
* 506,535 usec/100.29 MIPS on ARM926EJ-S.
*/
struct gen
{
DATA add;
DATA c;
DATA vol;
};
#define GEN_INIT(var,R,F,A) var.add = ((DATA)F)/R, var.c=0, var.vol=A
#define GEN_SAMP(val,var) val = (short)(sin(var.c * 2 * M_PI) * \
var.vol); \
var.c += var.add
#elif PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_FLOATING_POINT
#include <math.h>
#define DATA float
/*
* 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 = 350 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
* approx. 2.84 MIPS
*
* 18,037 usec/3.57 MIPS on ARM926EJ-S.
*/
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 = 0; \
var.s1 = (DATA)(0 - (int)A)
#define GEN_SAMP(val,var) var.s0 = var.s0 - var.a * var.s1; \
var.s1 = var.s1 + var.a * var.s0; \
val = (short) var.s0
#elif PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_FIXED_POINT_CORDIC
/* Cordic algorithm with 28 bit size, from:
* http://www.dcs.gla.ac.uk/~jhw/cordic/
* Speed = 742 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
* (PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP=7)
* approx. 6.01 MIPS
*
* ARM926EJ-S results:
* loop=7: 8,943 usec/1.77 MIPS
* loop=8: 9,872 usec/1.95 MIPS
* loop=10: 11,662 usec/2.31 MIPS
* loop=12: 13,561 usec/2.69 MIPS
*/
#define CORDIC_1K 0x026DD3B6
#define CORDIC_HALF_PI 0x06487ED5
#define CORDIC_PI (CORDIC_HALF_PI * 2)
#define CORDIC_MUL_BITS 26
#define CORDIC_MUL (1 << CORDIC_MUL_BITS)
#define CORDIC_NTAB 28
#define CORDIC_LOOP PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP
static int cordic_ctab [] =
{
0x03243F6A, 0x01DAC670, 0x00FADBAF, 0x007F56EA, 0x003FEAB7,
0x001FFD55, 0x000FFFAA, 0x0007FFF5, 0x0003FFFE, 0x0001FFFF,
0x0000FFFF, 0x00007FFF, 0x00003FFF, 0x00001FFF, 0x00000FFF,
0x000007FF, 0x000003FF, 0x000001FF, 0x000000FF, 0x0000007F,
0x0000003F, 0x0000001F, 0x0000000F, 0x00000007, 0x00000003,
0x00000001, 0x00000000, 0x00000000
};
static pj_int32_t cordic(pj_int32_t theta, unsigned n)
{
unsigned k;
int d;
pj_int32_t tx;
pj_int32_t x = CORDIC_1K, y = 0, z = theta;
for (k=0; k<n; ++k) {
#if 0
d = (z>=0) ? 0 : -1;
#else
/* Only slightly (~2.5%) faster, but not portable? */
d = z>>27;
#endif
tx = x - (((y>>k) ^ d) - d);
y = y + (((x>>k) ^ d) - d);
z = z - ((cordic_ctab[k] ^ d) - d);
x = tx;
}
return y;
}
/* Note: theta must be uint32 here */
static pj_int32_t cordic_sin(pj_uint32_t theta, unsigned n)
{
if (theta < CORDIC_HALF_PI)
return cordic(theta, n);
else if (theta < CORDIC_PI)
return cordic(CORDIC_HALF_PI-(theta-CORDIC_HALF_PI), n);
else if (theta < CORDIC_PI + CORDIC_HALF_PI)
return -cordic(theta - CORDIC_PI, n);
else if (theta < 2 * CORDIC_PI)
return -cordic(CORDIC_HALF_PI-(theta-3*CORDIC_HALF_PI), n);
else {
pj_assert(!"Invalid cordic_sin() value");
return 0;
}
}
struct gen
{
unsigned add;
pj_uint32_t c;
unsigned vol;
};
#define VOL(var,v) (((v) * var.vol) >> 15)
#define GEN_INIT(var,R,F,A) gen_init(&var, R, F, A)
#define GEN_SAMP(val,var) val = gen_samp(&var)
static void gen_init(struct gen *var, unsigned R, unsigned F, unsigned A)
{
var->add = 2*CORDIC_PI/R * F;
var->c = 0;
var->vol = A;
}
PJ_INLINE(short) gen_samp(struct gen *var)
{
pj_int32_t val;
val = cordic_sin(var->c, CORDIC_LOOP);
/*val = (val * 32767) / CORDIC_MUL;
*val = VOL((*var), val);
*/
val = ((val >> 10) * var->vol) >> 16;
var->c += var->add;
if (var->c > 2*CORDIC_PI)
var->c -= (2 * CORDIC_PI);
return (short) val;
}
#elif PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_FAST_FIXED_POINT
/*
* 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 = 117 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4).
* approx. 0.95 MIPS
*
* 1,449 usec/0.29 MIPS on ARM926EJ-S.
*/
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;
unsigned vol;
};
#define MAXI ((unsigned)0xFFFFFFFF)
#define SIN approximate_sin3
#define VOL(var,v) (((v) * var.vol) >> 15)
#define GEN_INIT(var,R,F,A) var.add = MAXI/R * F, var.c=0, var.vol=A
#define GEN_SAMP(val,var) val = (short) VOL(var,SIN(var.c)>>16); \
var.c += var.add
#else
#error "PJMEDIA_TONEGEN_ALG is not set correctly"
#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,
unsigned vol)
{
GEN_INIT(state->tone1,clock_rate,freq,vol);
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);
}
} else if (channel_count == 2) {
while (buf < end) {
GEN_SAMP(*buf, state->tone1);
*(buf+1) = *buf;
buf += 2;
}
}
}
static void init_generate_dual_tone(struct gen_state *state,
unsigned clock_rate,
unsigned freq1,
unsigned freq2,
unsigned vol)
{
GEN_INIT(state->tone1,clock_rate,freq1,vol);
GEN_INIT(state->tone2,clock_rate,freq2,vol);
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);
GEN_SAMP(val2, state->tone2);
*buf++ = (short)((val+val2) >> 1);
}
} else if (channel_count == 2) {
int val, val2;
while (buf < end) {
GEN_SAMP(val, state->tone1);
GEN_SAMP(val2, state->tone2);
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,
unsigned vol)
{
if (freq2)
init_generate_dual_tone(state, clock_rate, freq1, freq2 ,vol);
else
init_generate_single_tone(state, clock_rate, freq1,vol);
}
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_SIG_PORT_TONEGEN
#define THIS_FILE "tonegen.c"
#if 0
# define TRACE_(expr) PJ_LOG(4,expr)
#else
# define TRACE_(expr)
#endif
enum flags
{
PJMEDIA_TONE_INITIALIZED = 1,
PJMEDIA_TONE_ENABLE_FADE = 2
};
struct tonegen
{
pjmedia_port base;
/* options */
unsigned options;
unsigned playback_options;
unsigned fade_in_len; /* fade in for this # of samples */
unsigned fade_out_len; /* fade out for this # of samples*/
/* lock */
pj_lock_t *lock;
/* 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, 1477 },
{ '4', 770, 1209 },
{ '5', 770, 1336 },
{ '6', 770, 1477 },
{ '7', 852, 1209 },
{ '8', 852, 1336 },
{ '9', 852, 1477 },
{ '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);
static pj_status_t tonegen_destroy(pjmedia_port *this_port);
/*
* 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_create2(pj_pool_t *pool,
const pj_str_t *name,
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("tonegen");
struct tonegen *tonegen;
pj_status_t status;
PJ_ASSERT_RETURN(pool && clock_rate && channel_count &&
samples_per_frame && bits_per_sample == 16 &&
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_T(pool, struct tonegen);
if (name == NULL || name->slen == 0) name = &STR_TONE_GEN;
status = pjmedia_port_info_init(&tonegen->base.info, name,
SIGNATURE, clock_rate, channel_count,
bits_per_sample, samples_per_frame);
if (status != PJ_SUCCESS)
return status;
tonegen->options = options;
tonegen->base.get_frame = &tonegen_get_frame;
tonegen->base.on_destroy = &tonegen_destroy;
tonegen->digit_map = &digit_map;
tonegen->fade_in_len = PJMEDIA_TONEGEN_FADE_IN_TIME * clock_rate / 1000;
tonegen->fade_out_len = PJMEDIA_TONEGEN_FADE_OUT_TIME * clock_rate / 1000;
/* Lock */
if (options & PJMEDIA_TONEGEN_NO_LOCK) {
status = pj_lock_create_null_mutex(pool, "tonegen", &tonegen->lock);
} else {
status = pj_lock_create_simple_mutex(pool, "tonegen", &tonegen->lock);
}
if (status != PJ_SUCCESS) {
return status;
}
TRACE_((THIS_FILE, "Tonegen created: %u/%u/%u/%u", clock_rate,
channel_count, samples_per_frame, bits_per_sample));
/* Done */
*p_port = &tonegen->base;
return PJ_SUCCESS;
}
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)
{
return pjmedia_tonegen_create2(pool, NULL, clock_rate, channel_count,
samples_per_frame, bits_per_sample,
options, p_port);
}
/*
* 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);
TRACE_((THIS_FILE, "tonegen_stop()"));
pj_lock_acquire(tonegen->lock);
tonegen->count = 0;
tonegen->cur_digit = 0;
tonegen->dig_samples = 0;
pj_lock_release(tonegen->lock);
return PJ_SUCCESS;
}
/*
* Instruct the tone generator to stop current processing.
*/
PJ_DEF(pj_status_t) pjmedia_tonegen_rewind(pjmedia_port *port)
{
struct tonegen *tonegen = (struct tonegen*) port;
PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
TRACE_((THIS_FILE, "tonegen_rewind()"));
/* Reset back to the first tone */
pj_lock_acquire(tonegen->lock);
tonegen->cur_digit = 0;
tonegen->dig_samples = 0;
pj_lock_release(tonegen->lock);
return PJ_SUCCESS;
}
/*
* Callback to destroy tonegen
*/
static pj_status_t tonegen_destroy(pjmedia_port *port)
{
struct tonegen *tonegen = (struct tonegen*) port;
PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
TRACE_((THIS_FILE, "tonegen_destroy()"));
pj_lock_acquire(tonegen->lock);
pj_lock_release(tonegen->lock);
pj_lock_destroy(tonegen->lock);
return PJ_SUCCESS;
}
/*
* 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 = PJMEDIA_PIA_SRATE(&tonegen->base.info);
PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL);
pj_lock_acquire(tonegen->lock);
if (tonegen->count == 0) {
/* We don't have digits to play */
frame->type = PJMEDIA_FRAME_TYPE_NONE;
goto on_return;
}
if (tonegen->cur_digit > tonegen->count) {
/* We have played all the digits */
if ((tonegen->options|tonegen->playback_options)&PJMEDIA_TONEGEN_LOOP)
{
/* Reset back to the first tone */
tonegen->cur_digit = 0;
tonegen->dig_samples = 0;
TRACE_((THIS_FILE, "tonegen_get_frame(): rewind"));
} else {
tonegen->count = 0;
tonegen->cur_digit = 0;
frame->type = PJMEDIA_FRAME_TYPE_NONE;
TRACE_((THIS_FILE, "tonegen_get_frame(): no more digit"));
goto on_return;
}
}
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;
TRACE_((THIS_FILE, "tonegen_get_frame(): next digit"));
}
if (tonegen->cur_digit >= tonegen->count) {
/* After we're finished with the last digit, we have played all
* the digits
*/
if ((tonegen->options|tonegen->playback_options)&PJMEDIA_TONEGEN_LOOP)
{
/* Reset back to the first tone */
tonegen->cur_digit = 0;
tonegen->dig_samples = 0;
TRACE_((THIS_FILE, "tonegen_get_frame(): rewind"));
} else {
tonegen->count = 0;
tonegen->cur_digit = 0;
frame->type = PJMEDIA_FRAME_TYPE_NONE;
TRACE_((THIS_FILE, "tonegen_get_frame(): no more digit"));
goto on_return;
}
}
dst = (short*) frame->buf;
end = dst + PJMEDIA_PIA_SPF(&port->info);
while (dst < end) {
pjmedia_tone_desc *dig = &tonegen->digits[tonegen->cur_digit];
unsigned required, cnt, on_samp, off_samp;
required = (unsigned)(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 &&
(tonegen->count!=1 || !(dig->flags & PJMEDIA_TONE_INITIALIZED)))
{
init_generate_tone(&tonegen->state,
PJMEDIA_PIA_SRATE(&port->info),
dig->freq1, dig->freq2, dig->volume);
dig->flags |= PJMEDIA_TONE_INITIALIZED;
if (tonegen->cur_digit > 0) {
/* Clear initialized flag of previous digit */
tonegen->digits[tonegen->cur_digit-1].flags &=
(~PJMEDIA_TONE_INITIALIZED);
}
}
/* Add tone signal */
if (tonegen->dig_samples < on_samp) {
cnt = on_samp - tonegen->dig_samples;
if (cnt > required)
cnt = required;
generate_tone(&tonegen->state,
PJMEDIA_PIA_CCNT(&port->info),
cnt, dst);
dst += cnt;
tonegen->dig_samples += cnt;
required -= cnt;
if ((dig->flags & PJMEDIA_TONE_ENABLE_FADE) &&
tonegen->dig_samples == cnt)
{
/* Fade in */
short *samp = (dst - cnt);
short *end;
if (cnt > tonegen->fade_in_len)
cnt = tonegen->fade_in_len;
end = samp + cnt;
if (cnt) {
const unsigned step = 0xFFFF / cnt;
unsigned scale = 0;
for (; samp < end; ++samp) {
(*samp) = (short)(((*samp) * scale) >> 16);
scale += step;
}
}
} else if ((dig->flags & PJMEDIA_TONE_ENABLE_FADE) &&
tonegen->dig_samples==on_samp)
{
/* Fade out */
if (cnt > tonegen->fade_out_len)
cnt = tonegen->fade_out_len;
if (cnt) {
short *samp = (dst - cnt);
const unsigned step = 0xFFFF / cnt;
unsigned scale = 0xFFFF - step;
for (; samp < dst; ++samp) {
(*samp) = (short)(((*samp) * scale) >> 16);
scale -= step;
}
}
}
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 (tonegen->cur_digit >= tonegen->count) {
/* All digits have been played */
if ((tonegen->options & PJMEDIA_TONEGEN_LOOP) ||
(tonegen->playback_options & PJMEDIA_TONEGEN_LOOP))
{
tonegen->cur_digit = 0;
} else {
break;
}
}
}
}
if (dst < end)
pjmedia_zero_samples(dst, (unsigned)(end-dst));
frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
frame->size = PJMEDIA_PIA_AVG_FSZ(&port->info);
TRACE_((THIS_FILE, "tonegen_get_frame(): frame created, level=%u",
pjmedia_calc_avg_signal((pj_int16_t*)frame->buf, frame->size/2)));
if (tonegen->cur_digit >= tonegen->count) {
if ((tonegen->options|tonegen->playback_options)&PJMEDIA_TONEGEN_LOOP)
{
/* Reset back to the first tone */
tonegen->cur_digit = 0;
tonegen->dig_samples = 0;
TRACE_((THIS_FILE, "tonegen_get_frame(): rewind"));
} else {
tonegen->count = 0;
tonegen->cur_digit = 0;
TRACE_((THIS_FILE, "tonegen_get_frame(): no more digit"));
}
}
on_return:
pj_lock_release(tonegen->lock);
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;
unsigned i;
PJ_ASSERT_RETURN(port && port->info.signature == SIGNATURE &&
count && tones, PJ_EINVAL);
/* Don't put more than available buffer */
PJ_ASSERT_RETURN(count+tonegen->count <= PJMEDIA_TONEGEN_MAX_DIGITS,
PJ_ETOOMANY);
pj_lock_acquire(tonegen->lock);
/* Set playback options */
tonegen->playback_options = options;
/* Copy digits */
pj_memcpy(tonegen->digits + tonegen->count,
tones, count * sizeof(pjmedia_tone_desc));
/* Normalize volume, and check if we need to disable fading.
* Disable fading if tone off time is zero. Application probably
* wants to play this tone continuously (e.g. dial tone).
*/
for (i=0; i<count; ++i) {
pjmedia_tone_desc *t = &tonegen->digits[i+tonegen->count];
if (t->volume == 0)
t->volume = AMP;
else if (t->volume < 0)
t->volume = (short) -t->volume;
/* Reset flags */
t->flags = 0;
if (t->off_msec != 0)
t->flags |= PJMEDIA_TONE_ENABLE_FADE;
}
tonegen->count += count;
pj_lock_release(tonegen->lock);
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;
unsigned i;
PJ_ASSERT_RETURN(port && port->info.signature == SIGNATURE &&
count && digits, PJ_EINVAL);
PJ_ASSERT_RETURN(count < PJMEDIA_TONEGEN_MAX_DIGITS, PJ_ETOOMANY);
pj_lock_acquire(tonegen->lock);
map = tonegen->digit_map;
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) {
pj_lock_release(tonegen->lock);
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;
tones[i].volume = digits[i].volume;
}
pj_lock_release(tonegen->lock);
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);
pj_lock_acquire(tonegen->lock);
tonegen->digit_map = m;
pj_lock_release(tonegen->lock);
return PJ_SUCCESS;
}