From fd64f65109f5cc5ce611cd5ec842c08796566a05 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Thu, 18 Sep 2008 11:14:21 +0000 Subject: Large reorganization of the tonegen for ticket #619: - Deprecate the automatic selection of algorithm - Introduced various constants for tonegen backends - Allow user to specify/override the algorithm by setting - Fix the floating-point approximation backend git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@2292 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/include/pjmedia/config.h | 95 ++++++++++++------ pjmedia/src/pjmedia/tonegen.c | 207 +++++++++++++++++++++++++++------------ 2 files changed, 212 insertions(+), 90 deletions(-) (limited to 'pjmedia') diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h index 70354ee7..86a408e9 100644 --- a/pjmedia/include/pjmedia/config.h +++ b/pjmedia/include/pjmedia/config.h @@ -541,47 +541,82 @@ #endif +/* + * Below specifies the various tone generator backend algorithm. + */ + +/** + * The math's sine(), floating point. This has very good precision + * but it's the slowest and requires floating point support and + * linking with the math library. + */ +#define PJMEDIA_TONEGEN_SINE 1 + /** - * Enable high quality of tone generation, the better quality will cost - * more CPU load. This is only applied to floating point enabled machines. + * Floating point approximation of sine(). This has relatively good + * precision and much faster than plain sine(), but it requires floating- + * point support and linking with the math library. + */ +#define PJMEDIA_TONEGEN_FLOATING_POINT 2 + +/** + * Fixed point using sine signal generated by Cordic algorithm. This + * algorithm can be tuned to provide balance between precision and + * performance by tuning the PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP + * setting, and may be suitable for platforms that lack floating-point + * support. + */ +#define PJMEDIA_TONEGEN_FIXED_POINT_CORDIC 3 + +/** + * Fast fixed point using some approximation to generate sine waves. + * The tone generated by this algorithm is not very precise, however + * the algorithm is very fast. + */ +#define PJMEDIA_TONEGEN_FAST_FIXED_POINT 4 + + +/** + * Specify the tone generator algorithm to be used. * - * By default it is enabled when PJ_HAS_FLOATING_POINT is set. + * Default value: + * - PJMEDIA_TONEGEN_FLOATING_POINT when PJ_HAS_FLOATING_POINT is set + * - PJMEDIA_TONEGEN_FIXED_POINT_CORDIC when PJ_HAS_FLOATING_POINT is not set + */ +#ifndef PJMEDIA_TONEGEN_ALG +# if defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT +# define PJMEDIA_TONEGEN_ALG PJMEDIA_TONEGEN_FLOATING_POINT +# else +# define PJMEDIA_TONEGEN_ALG PJMEDIA_TONEGEN_FIXED_POINT_CORDIC +# endif +#endif + + +/** + * Specify the number of calculation loops to generate the tone, when + * PJMEDIA_TONEGEN_FIXED_POINT_CORDIC algorithm is used. With more calculation + * loops, the tone signal gets more precise, but this will add more + * processing. + * + * Valid values are 1 to 28. * - * @see PJMEDIA_TONEGEN_FORCE_FLOAT + * Default value: 7 */ -#ifndef PJMEDIA_USE_HIGH_QUALITY_TONEGEN -# define PJMEDIA_USE_HIGH_QUALITY_TONEGEN PJ_HAS_FLOATING_POINT +#ifndef PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP +# define PJMEDIA_TONEGEN_FIXED_POINT_CORDIC_LOOP 7 #endif /** - * Force the tone generation to use floating point computation, even when - * PJ_HAS_FLOATING_POINT is disabled. This may be necessary if the tone - * generator is used to produce DTMF to be sent inband, since the fixed - * point algorithm may not have the correct frequency accuracy. + * Enable high quality of tone generation, the better quality will cost + * more CPU load. This is only applied to floating point enabled machines. * - * This option, combined with PJ_HAS_FLOATING_POINT will produce the - * following selection of tone generator algorithm: - * - if both PJ_HAS_FLOATING_POINT and PJMEDIA_USE_HIGH_QUALITY_TONEGEN - * are set, the standard sin() function will be used. This will produce - * the highest quality tones, at the expense of more processing power. - * - if PJ_HAS_FLOATING_POINT is not set: - * - if both PJMEDIA_USE_HIGH_QUALITY_TONEGEN and - * PJMEDIA_TONEGEN_FORCE_FLOAT are set, sin() based algorithm will - * be used (similar as above). - * - if PJMEDIA_USE_HIGH_QUALITY_TONEGEN is not set but the - * PJMEDIA_TONEGEN_FORCE_FLOAT is set, a floating point approximation - * algorithm will be used. This should produce good enough tone - * for most uses, and the performance is faster than using pure - * sin() based algorithm. Note that linking to math library may - * still be needed. - * - if both are not set, the fixed point approximation algorithm - * will be used. + * By default it is enabled when PJ_HAS_FLOATING_POINT is set. * - * Default: 1 + * This macro has been deprecated in version 1.0-rc3. */ -#ifndef PJMEDIA_TONEGEN_FORCE_FLOAT -# define PJMEDIA_TONEGEN_FORCE_FLOAT 1 +#ifdef PJMEDIA_USE_HIGH_QUALITY_TONEGEN +# error "The PJMEDIA_USE_HIGH_QUALITY_TONEGEN macro is obsolete" #endif diff --git a/pjmedia/src/pjmedia/tonegen.c b/pjmedia/src/pjmedia/tonegen.c index 60732500..3f2cbe37 100644 --- a/pjmedia/src/pjmedia/tonegen.c +++ b/pjmedia/src/pjmedia/tonegen.c @@ -25,66 +25,156 @@ #include #include - -/* float can be twice slower on i686! */ -#define DATA double - /* 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 -#if (defined(PJ_HAS_FLOATING_POINT) && PJ_HAS_FLOATING_POINT!=0) || \ - (defined(PJMEDIA_TONEGEN_FORCE_FLOAT) && PJMEDIA_TONEGEN_FORCE_FLOAT != 0) -# include + /* + * 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 + */ + struct gen + { + DATA add; + DATA c; + DATA vol; + }; -# if defined(PJMEDIA_USE_HIGH_QUALITY_TONEGEN) && \ - PJMEDIA_USE_HIGH_QUALITY_TONEGEN!=0 + #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 + */ + struct gen + { + DATA a, s0, s1; + }; - /* - * This is the good old tone generator using sin(). - * Speed = 222.5 cycles per sample. - */ - 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 - -# else - - /* - * 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 = 40.6 cycles per sample. - */ - 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 + */ + #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; + } + } -# 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) var.s0 = var.s0 - var.a * var.s1; \ - var.s1 = var.s1 + var.a * var.s0; \ - val = (short) var.s0 -# endif + struct gen + { + unsigned add; + pj_uint32_t c; + unsigned vol; + }; -#else + #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. @@ -92,9 +182,8 @@ * approximation from * http://www.audiomulch.com/~rossb/code/sinusoids/ * Quality wise not so good, but it's blazing fast! - * Speed: - * - with volume adjustment: 14 cycles per sample - * - without volume adjustment: 12.22 cycles per sample + * Speed = 117 usec to generate 1 second, 8KHz dual-tones (2.66GHz P4). + * approx. 0.95 MIPS */ PJ_INLINE(int) approximate_sin3(unsigned x) { @@ -112,17 +201,15 @@ unsigned vol; }; -# define MAXI ((unsigned)0xFFFFFFFF) -# define SIN approximate_sin3 -# if 1 /* set this to 0 to disable volume adjustment */ -# define VOL(var,v) (((v) * var.vol) >> 15) -# else -# define VOL(var,v) (v) -# endif -# 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);\ + #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 -- cgit v1.2.3