| /* $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; |
| } |
| |
| |