关于Goertzel

双音频是电话系统中电话机与交换机之间的一种用户信令,通俗的讲, 就是两种不同的频率混音在一起的音频信号, 并代表某个数值.
双音频信号是贝尔实验室发明的,其目的是为了自动完成长途呼叫。
双音频的拨号键盘是4×4的矩阵,每一行代表一个低频,每一列代表一个高频。每按一个键就发送一个高频和低频的正弦信号组合,比如'1'相当于697和1209赫兹(Hz)。交换机可以解码这些频率组合並确定所对应的按键。

                           双音多频键盘

        1209 Hz  1336 Hz  1477 Hz  1633 Hz
697 Hz    1        2        3        A
770 Hz    4        5        6        B
852 Hz    7        8        9        C
941 Hz    *        0        #        D


            特別頻率

忙音       480 Hz     620 Hz
拔号提示音  350 Hz     440 Hz

国内忙音是450hz的音频信号, 
350 毫秒的音频和350毫秒的静音不停的切换.
国内的拔号提示音也是450Hz的音频信号, 只不过是连续的不间断的音频信号.

像这种固定的频率信号的检测, 可以使用FFT将音频从时域转到频域, 然后分析频点即可确定信号内容. 但是, FFT是针对整体信号的整体频域的计算, FFT算法复杂, 执行效率低, 因而并不推荐. 
Goertzel算法刚好能够高效的检测频率点, 并且算法简单, 执行效率高, 下面是
Goertzel算法的描述:

s_prev = 0
s_prev2 = 0
normalized_frequency = target_frequency / sample_rate;
coeff = 2*cos(2*PI*normalized_frequency);
for each sample, x[n],
  s = x[n] + coeff*s_prev - s_prev2;
  s_prev2 = s_prev;
  s_prev = s;
end
power = s_prev2*s_prev2 + s_prev*s_prev - coeff*s_prev*s_prev2;

有了上面的算法描述, 不难写出代码的. 下面给出一段开源的关于Goertzel算法的c代码实现:

#define SAMPLING_RATE       8000
#define MAX_BINS            8
#define GOERTZEL_N          92

int         sample_count;
double      q1[ MAX_BINS ];
double      q2[ MAX_BINS ];
double      r[ MAX_BINS ];

/*
 * coef = 2.0 * cos( (2.0 * PI * k) / (float)GOERTZEL_N)) ;
 * Where k = (int) (0.5 + ((float)GOERTZEL_N * target_freq) / SAMPLING_RATE));
 *
 * More simply: coef = 2.0 * cos( (2.0 * PI * target_freq) / SAMPLING_RATE );
 */
double      freqs[ MAX_BINS] = 
{
  697,
  770,
  852,
  941,
  1209,
  1336,
  1477,
  1633
};

double      coefs[ MAX_BINS ] ;


/*----------------------------------------------------------------------------
 *  calc_coeffs
 *----------------------------------------------------------------------------
 * This is where we calculate the correct co-efficients.
 */
void calc_coeffs()
{
  int n;

  for(n = 0; n < MAX_BINS; n++)
  {
    coefs[n] = 2.0 * cos(2.0 * 3.141592654 * freqs[n] / SAMPLING_RATE);
  }
}


/*----------------------------------------------------------------------------
 *  post_testing
 *----------------------------------------------------------------------------
 * This is where we look at the bins and decide if we have a valid signal.
 */
void post_testing()
{
  int         row, col, see_digit;
  int         peak_count, max_index;
  double      maxval, t;
  int         i;
  char *  row_col_ascii_codes[4][4] = {
    {"1", "2", "3", "A"},
    {"4", "5", "6", "B"},
    {"7", "8", "9", "C"},
    {"*", "0", "#", "D"}};


  /* Find the largest in the row group. */
  row = 0;
  maxval = 0.0;
  for ( i=0; i<4; i++ )
  {
    if ( r[i] > maxval )
    {
      maxval = r[i];
      row = i;
    }
  }

  /* Find the largest in the column group. */
  col = 4;
  maxval = 0.0;
  for ( i=4; i<8; i++ )
  {
    if ( r[i] > maxval )
    {
      maxval = r[i];
      col = i;
    }
  }


  /* Check for minimum energy */

  if ( r[row] < 4.0e5 )   /* 2.0e5 ... 1.0e8 no change */
  {
    /* energy not high enough */
  }
  else if ( r[col] < 4.0e5 )
  {
    /* energy not high enough */
  }
  else
  {
    see_digit = TRUE;

    /* 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;
    }

    /* Signal to noise test
     * AT&T states that the noise must be 16dB down from the signal.
     * Here we count the number of signals above the threshold and
     * there ought to be only two.
     */
    if ( r[max_index] > 1.0e9 )
      t = r[max_index] * 0.158;
    else
      t = r[max_index] * 0.010;

    peak_count = 0;
    for ( i=0; i<8; i++ )
    {
      if ( r[i] > t )
        peak_count++;
    }
    if ( peak_count > 2 )
      see_digit = FALSE;

    if ( see_digit )
    {
      printf( "%s", row_col_ascii_codes[row][col-4] );
      fflush(stdout);
    }
  }
}


/*----------------------------------------------------------------------------
 *  goertzel
 *----------------------------------------------------------------------------
 */
void goertzel( int sample )
{
  double      q0;
  ui32        i;

  sample_count++;
  for ( i=0; i<MAX_BINS; i++ )
  {
    q0 = coefs[i] * q1[i] - q2[i] + sample;
    q2[i] = q1[i];
    q1[i] = q0;
  }

  if (sample_count == GOERTZEL_N)
  {
    for ( i=0; i<MAX_BINS; i++ )
    {
      r[i] = (q1[i] * q1[i]) + (q2[i] * q2[i]) - (coefs[i] * q1[i] * q2[i]);
      q1[i] = 0.0;
      q2[i] = 0.0;
    }
    post_testing();
    sample_count = 0;
  }
}

到这为止, 你只需要用麦克风把拔电话号码发出的声音录下来, 然后用这段代码一跑, 按了什么号码便立即可以得到了. Goertzel算法同样也可以检测忙音和拔号音.

Goertzel算法还可以用于解码FSK信号.

FSK也称为频移键控, 通俗的讲, 就是两种AB不同的频率组成的信号, 其中令A频率为1令B频率为0, 从而实现传输2进制信息. FSK在传统电话机中也是使用非常广泛.
像FSK这种已知固定的两种频率, 显然比检测DTMF还要简单, FSK只需要检测2个频点, 即可检测出信号内容, 而DTMF需要检测8个频点.

Goertzel算法关于N的取值
N的值需要一定的经验和根据实际情况需要来确定.
影响N取值一般有2个: 1. 采样率, 2. 信号长度.
总之N取值太小可能导致出现重码, 太大会导致检测结果不正确, 我通常是取信号时长的5/4来做N, 但这不一定, 需要多做试验来确定N值.

固定频率检测的另类算法
我曾经还使用过神经网络来检测双音频和FSK, 效果也不错, 运算量略大于 Goertzel, 准确率和 Goertzel 相当, 难点在于训练网络, 所以只是作学习而尝试.



你可能感兴趣的:(信号,音频,fft,Goertzel,信号系统)