android的tone音正弦波快速产生算法源代码DTMF双音频和如何快速计算正弦波

假如我们需要产生取样频率为8KHz的440Hz的正弦波,那么a1=2*cos(2*pi*440/8000)=1.8817615,而y[1]=sin(2*pi*440/8000)=0.33873792。

来拆开解读一下:[luther.gliethttp-20110419]

{{{

1. 其中2*pi为一个正弦,2*pi*440表示1秒内会有440个正弦,

2. 2*pi*440/8000那就表示1秒内要作8000次采样,结果为每次采样的步进弧度值

}}}

也可以这样理解:

{{{

1. 2*pi为一个正弦

2. 440/8000为有效数据占用百分比

3. 2*pi*440/8000就表示一个正弦每个有效数据之间的弧度间隔值

4. 如果按照2*pi*440/8000为有效数据的步进大小,因为1秒会做8000次步进,

所以在1秒整时我们会刚刚好完成440个2*pi正弦数据输出.

}}}

// 下面是android的tone正弦波快速产生算法的源代码[luther.gliethttp]

//                WaveGenerator::WaveGenerator class    Implementation

//---------------------------------- public methods ----------------------------

//

//    Method:        WaveGenerator::WaveGenerator()

//

//    Description:    Constructor.

//

//    Input:

//        samplingRate:    Output sampling rate in Hz

//        frequency:       Frequency of the sine wave to generate in Hz

//        volume:          volume (0.0 to 1.0)

//

//    Output:

//        none

//

ToneGenerator::WaveGenerator::WaveGenerator(unsigned short samplingRate,

unsigned short frequency, float volume) {

double d0;

double F_div_Fs;  // frequency / samplingRate

F_div_Fs = frequency / (double)samplingRate;

d0 = - (float)GEN_AMP * sin(2 * M_PI * F_div_Fs);

mS2_0 = (short)d0;

mS1 = 0;

mS2 = mS2_0;

mAmplitude_Q15 = (short)(32767. * 32767. * volume / GEN_AMP);

// take some margin for amplitude fluctuation

if (mAmplitude_Q15 > 32500)

mAmplitude_Q15 = 32500;

d0 = 32768.0 * cos(2 * M_PI * F_div_Fs);  // Q14*2*cos()

if (d0 > 32767)

d0 = 32767;

mA1_Q14 = (short) d0;

LOGV("WaveGenerator init, mA1_Q14: %d, mS2_0: %d, mAmplitude_Q15: %d\n",

mA1_Q14, mS2_0, mAmplitude_Q15);

}

//

//    Method:        WaveGenerator::getSamples()

//

//    Description:    Generates count samples of a sine wave and accumulates

//        result in outBuffer.

//

//    Input:

//        outBuffer:      Output buffer where to accumulate samples.

//        count:          number of samples to produce.

//        command:        special action requested (see enum gen_command).

//

//    Output:

//        none

//

void ToneGenerator::WaveGenerator::getSamples(short *outBuffer,

unsigned int count, unsigned int command) {

long lS1, lS2;

long lA1, lAmplitude;

long Sample;  // current sample

// init local

if (command == WAVEGEN_START) {

lS1 = (long)0;

lS2 = (long)mS2_0;

} else {

lS1 = (long)mS1;

lS2 = (long)mS2;

}

lA1 = (long)mA1_Q14;

lAmplitude = (long)mAmplitude_Q15;

if (command == WAVEGEN_STOP) {

lAmplitude <<= 16;

if (count == 0) {

return;

}

long dec = lAmplitude/count;

// loop generation

while (count--) {

Sample = ((lA1 * lS1) >> S_Q14) - lS2;

// shift delay

lS2 = lS1;

lS1 = Sample;

Sample = ((lAmplitude>>16) * Sample) >> S_Q15;

*(outBuffer++) += (short)Sample;  // put result in buffer

lAmplitude -= dec;

}

} else {

// loop generation

while (count--) {

Sample = ((lA1 * lS1) >> S_Q14) - lS2;

// shift delay

lS2 = lS1;

lS1 = Sample;

Sample = (lAmplitude * Sample) >> S_Q15;

*(outBuffer++) += (short)Sample;  // put result in buffer

}

}

// save status

mS1 = (short)lS1;

mS2 = (short)lS2;

}

==========================================================================

转:http://hi.baidu.com/cui1206/blog/item/e3d1cd18d0fc11b54bedbc35.html

快速计算正弦波

在DSP运用中,经常需要产生正弦波。如果直接用c的数学函数sin,当然可以产生正弦波,但是由于sin函数本身的效率很低,产生正弦波所需要的 MIPS就会占去DSP处理能力的相当大的一部分。本章介用递推数列算正弦波的方法,先介绍原理,推导出递推公式,然后用浮点小数实现计算,再用定点小数进一步优化算法,最后进行误差分析,并提出更精确的定点小数算法。

先来看看如何推导出递推数列的公式。

我们所要产生的正弦波,其实是一系列的整数,把这些整数按照一定的取样频率发送给数模转换器,就可以变成真正的正弦波了。假设取样周期是Ts,产生的正弦波的圆频率为w,那么我们需要产生的数列就是:

sin(0), sin(w*Ts), sin(2*w*Ts), ... sin(n*w*Ts)

假设f(n)= sin(n*w*Ts),则问题就变成,从f(n-1), f(n-2), f(n-3),..., 如何计算f(n)了。解决了这个问题,也就找到了递推公式。

下面是这个递推公式的求解过程,假设x=w*Ts:

公式:sin( a + b) = sin(a)*cos(b) + cos(a)*sin(b)

sin( x + (n-1)x ) = sin(x)*cos( (n-1)x ) + cos(x)*sin( (n-1)x )

公式:Sin(a)*cos(b) = 1/2 * [ sin( a+b ) + sin( a-b ) ]

sin(x)*cos( (n-1)x ) = 1/2 * [ sin(nx) - sin( (n-2)x ) ]

sin(nx) = 1/2 * [ sin(nx) - sin( (n-2)x ) ] + cos(x)*sin( (n-1)x )

sin(nx) = 2*cos(x)*sin( (n-1)x ) - sin( (n-2)x )

我们看到这个递推公式是:

f(n)=2*cos(w*Ts)*f(n-1) - f(n-2)

也就是说只要知道最初始的两项f(0)和f(1),就可以计算出整个正弦波了。

根据上面的递推公式,很容易写出下面的正弦波计算程序。只要事先计算一次sin(w*Ts)和cos(w*Ts),以后的值就可以通过递推公式得到,所以计算一个值所需要的工作就是一次乘法,一次加法,两次变量复制而已了。

以下内容为程序代码:

float y[3] = {0, sin(w*Ts),0}; // y(n), y(n-1), y(n-2)

float a1=2*cos(w*Ts);

float a2=-1;

float singen(){

y[0]=a1*y[1]+a2*y[2];

y[2]=y[1];

y[1]=y[0];

return y[0];

}

假如我们需要产生取样频率为8KHz的440Hz的正弦波,那么a1=2*cos(2*pi*440/8000)=1.8817615,而y[1]=sin(2*pi*440/8000)=0.33873792。

现在看如何用定点小数来更快的计算正弦波。我们使用16bit也就是short型的整数来表示定点小数。首先需要决定的是小数的Q值,虽然我们最后计算的正弦波的值都是小于1的,但是在计算过程中需要用2*cos(w*Ts),而这个值最大为2,所以我们选择的Q值必须至少最大能表示2。这里我们选择 Q14,Q14的定点小数能表示-2到2的取值范围,对于本例的正弦波计算正好合适。1.8817615的Q14值是1.8817615*2^14= 5550=0x786F,同样0.33873792的Q14值为0x15AE。

下面就是完整的计算8KHz取样频率的400Hz的定点小数的正弦波的程序。

以下内容为程序代码:

short y[3] = {0, 0x15AE,0}; // y(n), y(n-1), y(n-2)

short a1=0x786F;

short a2=0xC000;

short singen(){

y[0]=( (long)a1*(long)y[1]+(long)a2*(long)y[2] )>>14;

y[2]=y[1];

y[1]=y[0];

return y[0];

}

使用定点小数计算不但速度比浮点更快,而且计算得出来的值是整数,这个数值可以直接传递给DAC(数模转换器)转换为模拟的声音信号,如果使用浮点小数计算的话,还必须把浮点数转换为整数才能传递给DAC。

使用定点小数计算必须仔细分析误差,下面来看看我们产生的正弦波的误差是多少。定点小数计算中的误差就是由定点小数表达精度决定的。在上面的例子中我们用 0x786F表示1.8817615,这存在一定的误差,把Q14的0x786F再转换为浮点数就是0x786F/2^14=1.8817749,我们可以看到相对误差非常小,也就是说最终得到的正弦波在频率上的误差也是非常小的。

但是,定点小数并不是什么时候都这么精确。例如如果用CD 音质的取样频率44100Hz来产生100Hz的正弦波,那么a1=2*cos(2*pi*440/44100)= 1.9960713,这个数转换为16比特的Q14的值是0x7fc0。我们可以看到这时定点小数已经十分接近0x7fff了,最终产生的正弦波的频率也会有很大的误差。为了能够精确地计算这样的正弦波,必须使用32bit的Q30定点小数。关于32bit定点小数的计算方法将在别的章节介绍。

另外上面的singen函数每调用一次只产生一个值,如果要产生实时的正弦波的话,函数的调用频率和取样频率相同,DSP的负担相对比较大。一般DSP计算都采取块计算方式,一次计算n个(例如64)个取样值,这样不但减少了函数的调用负担,也可以减少中间的内存移动的次数(y[2]=y[1]; y[1]= y[0];)。

阅读(4044) | 评论(0) | 转发(1) |

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐