android dtmf

转载自:http://blog.csdn.net/xl19862005/article/details/41863293


一、何为DTMF

在百度百科中对DTMF是这样解释的:

双音多频(DTMF)是由贝尔实验室开发的信令方式,通过承载语音的模拟电话线传送电话拨号信息。每个数字利用两个不同频率突发模式的正弦波编码,选择双音方式是由于它能够可靠地将拨号信息从语音中区分出来。一般情况下,声音信号很难造成对DTMF接收器的错误触发。DTMF是“TouchTone” (早期AT&T的商标)的基础, 替代机械式拨号转盘的按键

说得简单明了一点,就是:用两种不同频率的信号叠加组合成一个信号,这个组合信号就代码一个字符,DTMF信号有16个编码(而半个字节4bit刚好也是16种状态,这就为我所设计的DTMF通信奠定了基础——基于DTMF的16进制编码),如下图所示,高频和低频相交处就是相应的一个编码:


下图是用手机音频分析仪检测到拨号一直按着“1”键时的频谱图,与上图中字符1相交的两个频率:1209、697吻合!


二、DTMF信号的产生

DTMF音调的产生可以分为硬件和软件的方式。电话已经是一个很古老的产品了,在按键式电话替代拨盘式电话的时候,DTMF就已经产生了,随着数字电路技术和DSP技术的发展,市面上已经有很多DTMF专用的编解码芯片了,如:MITEL公司生产的MT8880。

随着DSP技术和大规模集成电路的发展,用纯软件的方式调制和解调DTMF信号已经不再是难事了……在本文中主要也是介绍DTMF软件调制解调。

先来看看如下一张图:


U1是频率为770Hz的正弦信号,U2是频率为1366Hz的正弦信号,两个信号相叠加,就得到了相应的DTMF信号U1+U2,可以用如下公式表示:


其中f1为正弦信号U1的频率,f2为正弦信号U2的频率。

在数字系统中,信号都是离散的,要用软件的方式产生DTMF音调就必需使用离散信号处理,要将模拟的连续信号转换成离散的数字信号,就必需将模拟信号进行采样处理,模拟信号的采样要提到:奈奎斯特定理,也既采样定理

奈奎斯特定理是这样定义的:在进行模拟/数字信号的转换过程中,当采样频率fs.max大于信号中最高频率fmax的2倍时(fs.max>2fmax),采样之后的数字信号就可以完整地保留原始信号中的信息,一般实际应用中保证采样频率为信号最高频率的5~10倍;采样定理又称奈奎斯特定理

DTMF音调中,最高的正弦信号的频率是1633Hz,采样频率为8000Hz就足够满足要求了!当采用8KHz的采样频率,采样精度为8bit时,DTMF信号的离散序列可用如下公式表示:


对正弦信号的采样,如下图所示:


   这些离散的序列经过DA系统转换又可以还原成相应的连续模拟信号了!

在android系统中,有现成的DTMF音调产生方法:ToneGenerator,在frameworks/base/media/java/android/media/ToneGenerator.java都是一些native方法的封装,真正起作用的代码在:frameworks/av/media/libmedia/ToneGenerator.cpp中!

在这个文件中,首先看到的是如下这样的一个声明:

  1. // Descriptors for all available tones (See ToneGenerator::ToneDescriptor class declaration for details)  
  2. const ToneGenerator::ToneDescriptor ToneGenerator::sToneDescriptors[] = {  
  3.         { segments: {{ duration: ToneGenerator::TONEGEN_INF, waveFreq: { 1336, 941, 0 }, 0, 0},  
  4.                      { duration: 0 , waveFreq: { 0 }, 0, 0}},  
  5.           repeatCnt: ToneGenerator::TONEGEN_INF,  
  6.           repeatSegment: 0 },                              // TONE_DTMF_0  
  7.         { segments: { { duration: ToneGenerator::TONEGEN_INF, waveFreq: { 1209, 697, 0 }, 0, 0 },  
  8.                       { duration: 0 , waveFreq: { 0 }, 0, 0}},  
  9.           repeatCnt: ToneGenerator::TONEGEN_INF,  
  10.           repeatSegment: 0 },                              // TONE_DTMF_1  
  11. ……  
  12. ……  
很容易看出来,这是对DTMF相应编码的高低频定义,如上的1336与941两个频率组合成的音调代表的是TONE_DTMF_0……

在这里还定义了一些因国家而异的一些DTMF音调,如ANSI的忙音是由480与620两组信号合成的,而小鬼子日本的忙音却是一个单纯的400Hz的信号!

在frameworks/av/media/libmedia/ToneGenerator.cpp这个文件中,通过如下代码产生相应的离散正弦序列:

  1.   
  2. //                WaveGenerator::WaveGenerator class    Implementation  
  3.   
  4.   
  5. //---------------------------------- public methods ----------------------------  
  6.   
  7.   
  8. //  
  9. //    Method:        WaveGenerator::WaveGenerator()  
  10. //  
  11. //    Description:    Constructor.  
  12. //  
  13. //    Input:  
  14. //        samplingRate:    Output sampling rate in Hz  
  15. //        frequency:       Frequency of the sine wave to generate in Hz  
  16. //        volume:          volume (0.0 to 1.0)  
  17. //  
  18. //    Output:  
  19. //        none  
  20. //  
  21.   
  22. ToneGenerator::WaveGenerator::WaveGenerator(unsigned short samplingRate,  
  23.         unsigned short frequency, float volume) {  
  24.     double d0;  
  25.     double F_div_Fs;  // frequency / samplingRate  
  26.   
  27.     F_div_Fs = frequency / (double)samplingRate;  
  28.     d0 = - (float)GEN_AMP * sin(2 * M_PI * F_div_Fs);  
  29.     mS2_0 = (short)d0;  
  30.     mS1 = 0;  
  31.     mS2 = mS2_0;  
  32.   
  33.     mAmplitude_Q15 = (short)(32767. * 32767. * volume / GEN_AMP);  
  34.     // take some margin for amplitude fluctuation  
  35.     if (mAmplitude_Q15 > 32500)  
  36.         mAmplitude_Q15 = 32500;  
  37.   
  38.     d0 = 32768.0 * cos(2 * M_PI * F_div_Fs);  // Q14*2*cos()  
  39.     if (d0 > 32767)  
  40.         d0 = 32767;  
  41.     mA1_Q14 = (short) d0;  
  42.   
  43.     ALOGV("WaveGenerator init, mA1_Q14: %d, mS2_0: %d, mAmplitude_Q15: %d",  
  44.             mA1_Q14, mS2_0, mAmplitude_Q15);  
  45. }  
  46.   
  47.   
  48. //  
  49. //    Method:        WaveGenerator::~WaveGenerator()  
  50. //  
  51. //    Description:    Destructor.  
  52. //  
  53. //    Input:  
  54. //        none  
  55. //  
  56. //    Output:  
  57. //        none  
  58. //  
  59.   
  60. ToneGenerator::WaveGenerator::~WaveGenerator() {  
  61. }  
  62.   
  63.   
  64. //  
  65. //    Method:        WaveGenerator::getSamples()  
  66. //  
  67. //    Description:    Generates count samples of a sine wave and accumulates  
  68. //        result in outBuffer.  
  69. //  
  70. //    Input:  
  71. //        outBuffer:      Output buffer where to accumulate samples.  
  72. //        count:          number of samples to produce.  
  73. //        command:        special action requested (see enum gen_command).  
  74. //  
  75. //    Output:  
  76. //        none  
  77. //  
  78.   
  79. void ToneGenerator::WaveGenerator::getSamples(short *outBuffer,  
  80.         unsigned int count, unsigned int command) {  
  81.     long lS1, lS2;  
  82.     long lA1, lAmplitude;  
  83.     long Sample;  // current sample  
  84.   
  85.     // init local  
  86.     if (command == WAVEGEN_START) {  
  87.         lS1 = (long)0;  
  88.         lS2 = (long)mS2_0;  
  89.     } else {  
  90.         lS1 = (long)mS1;  
  91.         lS2 = (long)mS2;  
  92.     }  
  93.     lA1 = (long)mA1_Q14;  
  94.     lAmplitude = (long)mAmplitude_Q15;  
  95.   
  96.     if (command == WAVEGEN_STOP) {  
  97.         lAmplitude <<= 16;  
  98.         if (count == 0) {  
  99.             return;  
  100.         }  
  101.         long dec = lAmplitude/count;  
  102.         // loop generation  
  103.         while (count--) {  
  104.             Sample = ((lA1 * lS1) >> S_Q14) - lS2;  
  105.             // shift delay  
  106.             lS2 = lS1;  
  107.             lS1 = Sample;  
  108.             Sample = ((lAmplitude>>16) * Sample) >> S_Q15;  
  109.             *(outBuffer++) += (short)Sample;  // put result in buffer  
  110.             lAmplitude -= dec;  
  111.         }  
  112.     } else {  
  113.         // loop generation  
  114.         while (count--) {  
  115.             Sample = ((lA1 * lS1) >> S_Q14) - lS2;  
  116.             // shift delay  
  117.             lS2 = lS1;  
  118.             lS1 = Sample;  
  119.             Sample = (lAmplitude * Sample) >> S_Q15;  
  120.             *(outBuffer++) += (short)Sample;  // put result in buffer  
  121.         }  
  122.     }  
  123.   
  124.     // save status  
  125.     mS1 = (short)lS1;  
  126.     mS2 = (short)lS2;  
  127. }  
这里用到了快速计算正弦波的方法,关于快速计算正弦波的方法可参考如下的文章:

http://hi.baidu.com/cui1206/item/0f6081deb877cd51d63aae98

快速计算正弦波
在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];)。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值