Teensy 听音辨数

有一千个读者便有一千个哈姆雷特。又好比鲁迅先生评价《红楼梦》 “经学家看见《易》,道学家看见淫,才子看见缠绵,革命家看见排满,流言家看见宫闱秘事。” 再比如经常被人批判的《古惑仔》系列电影,觉得它教坏小朋友,我觉得并不是。在我看起来应该属于青春励志题材。展现了“以陈浩南为首的香港下层青年人不甘平庸,通过打拼努力向上”的故事。当然,还可以说他是批判现实主义题材的作品,也包含了做人的道理。比如,影片中靓坤转眼间就被几个小时前羞辱的小警察射杀。

再比如3Q大战之时,记者采访了红衣教主,没成想有牛人直接从采访的视频声音中分析出来了他的电话【参考1】。谁能想到流氓软件之父竟然栽倒这样的事情中。

这次我们使用 Teensy 和数字麦克风来实现这个功能。

先介绍一下原理。电话实现拨号有两种方式:脉冲拨号和音频拨号。

脉冲拨号是一种时域处理方法,它用脉冲的个数来表示号码数字。脉冲拨号方式对脉冲的宽度、大小、间距、形状都有着严格的要求,如果由于线路的干扰或其他原因而使得这些参数发生了变化,则可能引起号码接收的错误。另一方面,由于每个脉冲都占有一定的时间(一般每个脉冲占用的时间为100ms),而使得这种拨号方式比较慢。当拨号时,用户通常会听到一串拨号音,老式的转盘电话就使用脉冲拨号。比如,拨号“0”时,电路“断”、“续”10次,代表数字“0”。可以看到,如果号码较长拨号耗时也会很长。因此,这种拨号方式逐渐为音频拨号所取代。

我们常用的音频拨号是双音多频 DTMF(Dual Tone Multi Frequency),双音多频,由高频群和低频群组成,低频群包含3个频率,高频群包含4个频率。一个高频信号和一个低频信号叠加组成一个组合信号,代表一个数字。

 120913361447
697123
770456
852789
941*0#

比如,用频率为770Hz 的正弦波加到1366Hz 的正弦波合成一个声音表示数字“5”

这个合成过程用Matlab 模拟如下:

首先是 770Hz 的正弦波:

这是 1366Hz 的正弦波

二者相加的结果是

对我们来说,目标是将上面这个合成后的结果分解得到具体是哪两个信号合成的。自然而然想到使用傅立叶变换来处理:

放大,可以看到是 770 和 1366 合成的。

这次选择 Teensy+SPH0645LM4H实现声音的采集和分析,最终结果显示在一个 1602 LCD上。

接线方式:

Teensy 3.2     SPH0645LM4H

        GND             SEL

        D23              LRCL

         D13             DOUT

         D9               BCLK

         GND           GND

         3.3V            3V

Teensy 3.2     LCD1602

         GND           GND

         Vin              VCC     

         Pin19          SCL

         Pin18          SDA

代码上分析获得频率并没有通过傅立叶变换,而是直接使用 Teensy 的音频库中的AudioAnalyzeToneDetect 函数。这个函数使用的是Goertzel算法,该算法的主要思想是检查音频数据中是否包含某一个给定的频率【参考2】。因此,对于我们来说就是不断测试当前的信号中是否有697到1477这些频率,如果存在的话就转化为对应的数字。

完整代码如下:

// Dial Tone (DTMF) decoding

 

#include <Audio.h>

#include <Wire.h>

#include <SPI.h>

#include <LiquidCrystal_I2C.h>

 

LiquidCrystal_I2C lcd(0x3f,20,4);

 

// Create the Audio components.  These should be created in the

// order data flows, inputs/sources -> processing -> outputs

//

AudioInputI2S            audioIn;

AudioAnalyzeToneDetect   row1;     // 7 tone detectors are needed

AudioAnalyzeToneDetect   row2;     // to receive DTMF dial tones

AudioAnalyzeToneDetect   row3;

AudioAnalyzeToneDetect   row4;

AudioAnalyzeToneDetect   column1;

AudioAnalyzeToneDetect   column2;

AudioAnalyzeToneDetect   column3;

 

// Create Audio connections between the components

//

AudioConnection patchCord01(audioIn, 0, row1, 0);

AudioConnection patchCord02(audioIn, 0, row2, 0);

AudioConnection patchCord03(audioIn, 0, row3, 0);

AudioConnection patchCord04(audioIn, 0, row4, 0);

AudioConnection patchCord05(audioIn, 0, column1, 0);

AudioConnection patchCord06(audioIn, 0, column2, 0);

AudioConnection patchCord07(audioIn, 0, column3, 0);

 

int charpos=0;

 

void setup() {

  // Audio connections require memory to work.  For more

  // detailed information, see the MemoryAndCpuUsage example

  AudioMemory(12);

 

  lcd.init();

  lcd.backlight();

 

 

  while (!Serial);

  delay(100);

  Serial.println("Start decoding");

 

  // Configure the tone detectors with the frequency and number

  // of cycles to match.  These numbers were picked for match

  // times of approx 30 ms.  Longer times are more precise.

  row1.frequency(697, 21);

  row2.frequency(770, 23);

  row3.frequency(852, 25);

  row4.frequency(941, 28);

  column1.frequency(1209, 36);

  column2.frequency(1336, 40);

  column3.frequency(1477, 44);

}

 

const float row_threshold = 0.009;

const float column_threshold = 0.009;

char lastdigit=0;

 

void loop() {

  float r1, r2, r3, r4, c1, c2, c3;

  char digit=0;

 

  // read all seven tone detectors

  r1 = row1.read();

  r2 = row2.read();

  r3 = row3.read();

  r4 = row4.read();

  c1 = column1.read();

  c2 = column2.read();

  c3 = column3.read();

 

/*

  // print the raw data, for troubleshooting

  Serial.print("tones: ");

  Serial.print(r1);

  Serial.print(", ");

  Serial.print(r2);

  Serial.print(", ");

  Serial.print(r3);

  Serial.print(", ");

  Serial.print(r4);

  Serial.print(",   ");

  Serial.print(c1);

  Serial.print(", ");

  Serial.print(c2);

  Serial.print(", ");

  Serial.println(c3);

*/

  // check all 12 combinations for key press

  if (r1 >= row_threshold) {

    if (c1 > column_threshold) {

      digit = '1';

    } else if (c2 > column_threshold) {

      digit = '2';

    } else if (c3 > column_threshold) {

      digit = '3';

    }

  } else if (r2 >= row_threshold) {

    if (c1 > column_threshold) {

      digit = '4';

    } else if (c2 > column_threshold) {

      digit = '5';

    } else if (c3 > column_threshold) {

      digit = '6';

    }

  } else if (r3 >= row_threshold) {

    if (c1 > column_threshold) {

      digit = '7';

    } else if (c2 > column_threshold) {

      digit = '8';

    } else if (c3 > column_threshold) {

      digit = '9';

    }

  } else if (r4 >= row_threshold) {

    if (c1 > column_threshold) {

      digit = '*';

    } else if (c2 > column_threshold) {

      digit = '0';

    } else if (c3 > column_threshold) {

      digit = '#';

    }

  }

 

  // print the key, if any found

  if ((digit > 0)&&(lastdigit!=digit)) {

  //if (digit > 0) {

    Serial.print("  --> Key: ");

    Serial.println(digit);

   

    lcd.setCursor(charpos % 16, (charpos / 16)%2);

    lcd.print(digit);

    charpos++;  

  }

  lastdigit=digit;

 

 

 

  // uncomment these lines to see how much CPU time

  // the tone detectors and audio library are using

  //Serial.print("CPU=");

  //Serial.print(AudioProcessorUsage());

  //Serial.print("%, max=");

  //Serial.print(AudioProcessorUsageMax());

  //Serial.print("%   ");

 

}

工作的照片:

工作的视频:

https://zhuanlan.zhihu.com/p/70966370

很明显,你的按键信息包含在了发出的声音中,这是一个安全隐患。现实生活中,自动柜员机(ATM)也有一个键盘。仔细观察能发现每个按键会发出相同的声音。但是在十几年前,那个键盘的数字对应着的是不同的声音………..

参考:

1. https://www.cnblogs.com/emouse/archive/2012/09/01/2666308.html?utm_source=debugrun&utm_medium=referral 转:技术宅逆天了!如何从按键音中听出周鸿祎的手机号码

2. https://blog.csdn.net/silent123go/article/details/54022037

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注