float a1=2*cos(w*Ts);
float a2=-1;
假如我们需要产生取样频率为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]; //// 递归推导公式,f(n)
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];)。
上面的singen函数每调用一次只产生一个值,如果要产生实时的正弦波的话,函数的调用频率和取样频率相同,DSP的负担相对比较大。一般DSP计算都采取块计算方式,一次计算n个(例如64)个取样值,这样不但减少了函数的调用负担,也可以减少中间的内存移动的次数(y[2]=y[1];y[1]= y[0];)。
文件: ./media/libaudioclient/ToneGenerator.cpp
const ToneGenerator::ToneDescriptor ToneGenerator::sToneDescriptors[] = {
{ .segments = {
{ .duration = ToneGenerator::TONEGEN_INF,
.waveFreq = { 1336, 941, 0 }, 0, 0},
{ .duration = 0 ,
.waveFreq = { 0 }, 0, 0}}, /////暂停时间,间隔
.repeatCnt = ToneGenerator::TONEGEN_INF,
.repeatSegment = 0 }, // TONE_DTMF_0
........................ // 看到这个就明白了
while (mpToneDesc->segments[segmentIdx].duration) { //// 13 个 Segment
// Get total number of sine waves: needed to adapt sine wave gain.
unsigned int lNumWaves = numWaves(segmentIdx);
unsigned int freqIdx = 0;
unsigned int frequency = mpToneDesc->segments[segmentIdx].waveFreq[freqIdx];
while (frequency) {
// Instantiate a wave generator if ot already done for this frequency
if (mWaveGens.indexOfKey(frequency) == NAME_NOT_FOUND) {
ToneGenerator::WaveGenerator *lpWaveGen =
new ToneGenerator::WaveGenerator(mSamplingRate,
frequency,
TONEGEN_GAIN/lNumWaves);
mWaveGens.add(frequency, lpWaveGen);
//// 将频率和对象存入向量中,以供后续使用
}
frequency = mpNewToneDesc->segments[segmentIdx].waveFreq[++freqIdx];
}
segmentIdx++;
////// 由于tone不是声音文件,需要按规则生成
unsigned int lFreqIdx = 0;
unsigned short lFrequency = lpToneDesc->segments[lpToneGen->mCurSegment].waveFreq[lFreqIdx];
while (lFrequency != 0) {
WaveGenerator *lpWaveGen = lpToneGen->mWaveGens.valueFor(lFrequency);
lpWaveGen->getSamples(lpOut, lGenSmp, lWaveCmd);
/// 请注意:lpOut,用的巧,高低频率都在这里加上了
lFrequency = lpToneDesc->segments[lpToneGen->mCurSegment].waveFreq[++lFreqIdx];
}
F_div_Fs = frequency / (double)samplingRate;
d0 = - (float)GEN_AMP * sin(2 * M_PI * F_div_Fs);
mS2_0 = (short)d0;
mAmplitude_Q15 = (short)(32767. * 32767. * volume / GEN_AMP); // 这个不太理解
d0 = 32768.0 * cos(2 * M_PI * F_div_Fs); // Q15*2*cos()
// loop generation
while (count) {
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,使用加号很有意义
}
从某些连续段落的波形资料中找到是某两个已定义的高频与低频的组合, 即可对应出 tone digit.
我们使用的是 goertzel 算法,
这个算法的效率比 DFT 或是 FFT 都高. 一组波形资料经过 Goertzel 算法后都会得到该频率的匹配度.
def goertzel_mag(numSamples, target_freq, sample_rate, data):
'''
int k, i
float floatnumSamples
float omega, sine, cosine, coeff, q0, q1, r2, magnitude, real, imag
'''
#floatnumSamples = (float)numSamples
scalingFactor = numSamples / 2.0
k = (int) (0.5 + ((numSamples * target_freq)/sample_rate))
omega = (2.0 * math.pi * k)/numSamples
sine = math.sin(omega)
cosine = math.cos(omega)
coeff = 2.0 * cosine
q0 = 0
q1 = 0
q2 = 0
for i in range(0, (int)(numSamples)):
#print("Hello")
q0 = (coeff * q1) - q2 + data[i]
q2 = q1
q1 = q0
#real = (q1 - (q2 * cosine)) / scalingFactor
#imag = (q2 * sine) / scalingFactor
#magnitude = math.sqrt((real * real) + (imag * imag))
magnitude = (q2*q2) + (q1*q1) - (coeff * q1 * q2)
return magnitude
max_mag_hi_freq = 0
maxvalue_mag_hi_freq = 0
for freq in hi_freqs:
mag = goertzel_mag(chunk_sample_count, freq, sample_rate, chunk_data)
#if c == 277:
# print("(%d)CHUNK %d magnitude with higher frequency (%d) = %f" % \
# (max_mag_hi_freq, c, freq, mag))
if(mag > maxvalue_mag_hi_freq):
maxvalue_mag_hi_freq = mag
max_mag_hi_freq = freq
max_mag_lo_freq = 0
maxvalue_mag_lo_freq = 0
for freq in lo_freqs:
mag = goertzel_mag(chunk_sample_count, freq, sample_rate, chunk_data)
#print("CHUNK %d magnitude with lower frequency (%d) = %f" % \
# (c, freq, mag))
if(mag > maxvalue_mag_lo_freq):
maxvalue_mag_lo_freq = mag
max_mag_lo_freq = freq
参考原文:https://blog.csdn.net/hankhanti/article/details/49902441
我们从1209, 1336, 1477和1633四个频率对应的能量P中取最大值,记作Px,从679,770,852和941四个频率对应的能量P中取出最大值Py。那么Px和Py对应的频率组合极有可能代表识别出一个DTMF符号。但是,我们还需要做一系列的判断,来进一步评估:
如果上述三个检验关卡都通过了,那么我们可以将这N个采样评估为包含一个DTMF符号,即Px和Py对应的频率组合对应的某个符号。
参考资料:
[1] https://en.wikipedia.org/wiki/Goertzel_algorithm
[2] https://en.wikipedia.org/w/index.php?title=Goertzel_algorithm&oldid=17802166
解码算法:
/* Twist check
* CEPT => twist < 6dB
* AT&T => forward twist < 4dB and reverse twist < 8dB
* -ndB < 10 log10( v1 / v2 ), where v1 < v2
* -4dB < 10 log10( v1 / v2 )
* -0.4 < log10( v1 / v2 )
* 0.398 < v1 / v2
* 0.398 * v2 < v1
*/
if ( r[col] > r[row] )
{
/* Normal twist */
max_index = col;
if ( r[row] < (r[col] * 0.398) ) /* twist > 4dB, error */
see_digit = FALSE;
}
else /* if ( r[row] > r[col] ) */
{
/* Reverse twist */
max_index = row;
if ( r[col] < (r[row] * 0.158) ) /* twist > 8db, error */
see_digit = FALSE;
}