有一千个读者便有一千个哈姆雷特。又好比鲁迅先生评价《红楼梦》 “经学家看见《易》,道学家看见淫,才子看见缠绵,革命家看见排满,流言家看见宫闱秘事。” 再比如经常被人批判的《古惑仔》系列电影,觉得它教坏小朋友,我觉得并不是。在我看起来应该属于青春励志题材。展现了“以陈浩南为首的香港下层青年人不甘平庸,通过打拼努力向上”的故事。当然,还可以说他是批判现实主义题材的作品,也包含了做人的道理。比如,影片中靓坤转眼间就被几个小时前羞辱的小警察射杀。
再比如3Q大战之时,记者采访了红衣教主,没成想有牛人直接从采访的视频声音中分析出来了他的电话【参考1】。谁能想到流氓软件之父竟然栽倒这样的事情中。
这次我们使用 Teensy 和数字麦克风来实现这个功能。
先介绍一下原理。电话实现拨号有两种方式:脉冲拨号和音频拨号。
脉冲拨号是一种时域处理方法,它用脉冲的个数来表示号码数字。脉冲拨号方式对脉冲的宽度、大小、间距、形状都有着严格的要求,如果由于线路的干扰或其他原因而使得这些参数发生了变化,则可能引起号码接收的错误。另一方面,由于每个脉冲都占有一定的时间(一般每个脉冲占用的时间为100ms),而使得这种拨号方式比较慢。当拨号时,用户通常会听到一串拨号音,老式的转盘电话就使用脉冲拨号。比如,拨号“0”时,电路“断”、“续”10次,代表数字“0”。可以看到,如果号码较长拨号耗时也会很长。因此,这种拨号方式逐渐为音频拨号所取代。
我们常用的音频拨号是双音多频 DTMF(Dual Tone Multi Frequency),双音多频,由高频群和低频群组成,低频群包含3个频率,高频群包含4个频率。一个高频信号和一个低频信号叠加组成一个组合信号,代表一个数字。
1209 | 1336 | 1447 | |
697 | 1 | 2 | 3 |
770 | 4 | 5 | 6 |
852 | 7 | 8 | 9 |
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