/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * 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 #include #include #include #include #include #include #include /* amplitude */ #define AMP PJMEDIA_TONEGEN_VOLUME #ifndef M_PI # define M_PI ((DATA)3.141592653589793238462643383279) #endif #if PJMEDIA_TONEGEN_ALG==PJMEDIA_TONEGEN_SINE #include #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 #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=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 *samp_end; if (cnt > tonegen->fade_in_len) cnt = tonegen->fade_in_len; samp_end = samp + cnt; if (cnt) { const unsigned step = 0xFFFF / cnt; unsigned scale = 0; for (; samp < 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; idigits[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; icount; ++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; }