近日公司提出了一个需求,要求做一个类似当面付中通过音频传输用户ID的方法。
拿到任务后马上祭起GOOGLE大法去查找。(此处再次吐槽一下GWF——我去年买了个表,超耐磨)
首先找到的是一个开源的软件 https://github.com/JesseGu/SinVoice 是Android的 。
首先试用,但是发现这个软件在传输过程中会有很多尖锐的声音产生,而支付宝就没有。
不管了,先看代码。。
里面有2个重要的类,SinGenerator(正弦生成)、VoiceRecognition(声音识别)。
经过研究 其原理就是通过SinGenerator将数据转换为不同频率的音频, 然后播放出去,接受方收到数据后查找从整数到负数变化的点,标记好后先后继续查找,当再找找到从整数到负数的变化点后,用新的变化点减去标记的变化点,就得出了一个时间,然后就可以算出这个正弦音频的频率。
生成音频的主要过程(
- public void gen(int genRate, int duration) {
- if (STATE_START == mState) {
- mGenRate = genRate;
- mDuration = duration;
-
- if (null != mListener) {
- mListener.onStartGen();
- }
-
- int n = mBits/2;
- int totalCount = (mDuration * mSampleRate) / 1000;
- double per = ( 2 * Math.PI * (double)mGenRate / (double) mSampleRate) ;
- double d =0;
- LogHelper.d(TAG, "genRate:" + genRate);
- if (null != mCallback) {
- mFilledSize = 0;
- BufferData buffer = mCallback.getGenBuffer();
- if (null != buffer) {
- for (int i = 0; i < totalCount; ++i) {
- if (STATE_START == mState) {
- int out = (int) (Math.sin(dh)*n) ;
- if (mFilledSize >= mBufferSize - 1) {
-
- buffer.setFilledSize(mFilledSize);
- mCallback.freeGenBuffer(buffer);
-
- mFilledSize = 0;
- buffer = mCallback.getGenBuffer();
- if (null == buffer) {
- LogHelper.d(TAG, "get null buffer");
- break;
- }
- }
-
- buffer.mData[mFilledSize++] = (byte) (out & 0xff);
- if (BITS_16 == mBits) {
- buffer.mData[mFilledSize++] = (byte) ((out >> 8) & 0xff);
- }
-
- d +=per;
- } else {
- LogHelper.d(TAG, "sin gen force stop");
- break;
- }
- }
- } else {
- LogHelper.d(TAG, "get null buffer");
- }
-
- if (null != buffer) {
- buffer.setFilledSize(mFilledSize);
- mCallback.freeGenBuffer(buffer);
- }
- mFilledSize = 0;
-
- if (null != mListener) {
- mListener.onStopGen();
- }
- }
- }
- }
识别的主要函数
- private void process(BufferData data) {
- int size = data.getFilledSize() - 1;
- short sh = 0;
- for (int i = 0; i < size; i++) {
- short sh1 = data.mData[i];
- sh1 &= 0xff;
- short sh2 = data.mData[++i];
- sh2 <<= 8;
- sh = (short) ((sh1) | (sh2));
-
- if (!mIsStartCounting) {
- if (STATE_STEP1 == mStep) {
- if (sh < 0) {
- mStep = STATE_STEP2;
- }
- } else if (STATE_STEP2 == mStep) {
- if (sh > 0) {
- mIsStartCounting = true;
- mCirclePointCount = 0;
- mStep = STATE_STEP1;
- }
- }
- } else {
- ++mCirclePointCount;
- if (STATE_STEP1 == mStep) {
- if (sh < 0) {
- mStep = STATE_STEP2;
- }
- } else if (STATE_STEP2 == mStep) {
- if (sh > 0) {
-
- int circleCount = preReg(mCirclePointCount);
-
-
- reg(circleCount);
-
- mCirclePointCount = 0;
- mStep = STATE_STEP1;
- }
- }
- }
- }
- }
后面的事情就是移植的IOS上,很简单,基本上就是把代码复制过去就可以了。
然后给领导看~~~ 领导一句话就将所有的努力变成了狗屎——声音太刺耳。。。。。。。。。
没有办法了,只好去研究当面付
1)录音。 用CoolEdit录音
这就是支付宝产生的音频 其中"咻"的一声是红色的部分,但是我们看到字两个咻之间还是有音频波形,但是听不见,所有由此判断 ,这部分数据肯定是在人的听力范围之外。也就是说, 不是次声波就是超声波。
放大这部分数据
放大的波形还是没有什么特点,而且频率都一样,接近50Hz(次声波),所以由此判断数据还是不对,只有继续放大。
好了到此我们可以肯定 支付宝应该是使用2个正弦的乘积产生的波形 sin(2*PI*超声频率/采样率)*sin(2*PI*次声频率/采样率)——很像调频的公式。
好了 我们可以修改SinGenerator这个类进行来产生类似支付宝的音频。结果很理想,播放出啦后听不见, 用CoolEdit可以采集到类似支付宝的数据。
为题出来了 怎么判断高频啊~~ 因为手机的采样最高为44100,也就是说 最多能表达22050Hz的波形, 而我们的差生的数据有20000Hz,基本上每个采样周期都其数据都回产生呢跨过基线的数据, 素以 已经不能SinVoice的方法判断频率了,太快了。
开始重点了
使用CoolEdit查看支付宝音频的频谱。。。。。。
看到了什么,上面的粗线。。。。原来支付使用超声波频率并不一样,明显是一段一段的,由此判断 数据肯定就是这些。。而且每段都大概是20ms,吼吼。。数据出来了
仔细观察,支付宝的数据都是意48段重复播放的。
既然用CoolEdit的频谱能看到数据 我们用手机算出来这个频谱吧。。。(继续Google大法——再次吐槽GWF)(频谱的计算方法)
搜索后 悲剧了....真心看不懂啊用FFT计算连续信号的频谱.......好吧 怎么说有个关键词了 “FFT” 。什么是FFT呢 ——快速傅里叶变换-,继续不懂。。。。
哥是学计算机的 貌似没有学过这个啊,好吧就算是学过,毕业小20年了,早还给老师了。。。。。
没有办法 学习吧。。。 苦逼。。。。。
下面几篇文章,很不错
http://blog.csdn.net/lhfslhfs/article/details/7684494
http://blog.csdn.net/xuexiang0704/article/details/8295425
http://blog.csdn.net/v_JULY_v/article/details/6196862
好了有目标了 后边慢慢进行吧。。。。