轉載請注明出處:http://blog.csdn.net/zhaokaiqiang1992
在APP市場上,經常有一些充滿新意的應用讓我們眼前一亮,比如微信的面對面加好友,支付寶的聲波支付等等,都是通過聲波的方式進行握手通信,今天這篇文章将介紹聲波通信和聲波驗證的實作原理和代碼實作。
首先介紹一下聲波驗證的原理。如果我們想發出聲音,就必須震動,說話是聲帶在震動,手機能播放音樂是喇叭在震動。既然發出聲音必須震動,那麼就有震動快慢之分,我們把震動的快慢叫做聲音的頻率。頻率低的聲音低沉有力,能傳播很遠的距離,比如說大象之間通信就是利用次聲波,也就是頻率很低的聲波進行的。而蝙蝠,我們都知道是通過超音波進行探路的,超音波就是震動頻率比較高的聲音。頻率太高或者太低,人的耳朵都聽不到,人耳的識别範圍是20HZ-20000HZ。這裡引出了一個機關,叫做赫茲(HZ),它是指一秒鐘的震動次數。
知道什麼是聲音的頻率之後,我們就可以開始介紹聲波通信的原理了。既然不同的聲音有不同的頻率,那麼我們就可以假設1000HZ的聲音代表1,2000HZ的聲音代表2,以此類推,我們就可以用不同的頻率代表不同的數字組合。在接收到聲波之後,再根據不同的頻率解析成我們需要的資料就好。
如果我們想發出單頻率的聲音,我們就需要自己構造特定頻率的正弦函數。手機喇叭在震動的時候,實際上是根據不同的電流帶動鼓紙,進行不同頻率的震動才發出聲音的。而如果我們想要發出1000HZ的聲音,我們就需要設計對應的正弦函數,來提供一定規律的電流。
既然說到我們要自己設計正弦函數,還有幾個名詞我要解釋一下:
1.采樣率
是指每一秒要采集的聲音的次數。因為平常我們說話的時候,産生的是模拟信号,就是時間連續的信号,如果我們想把語音錄制下來怎麼辦呢?我們是做不到完完全全的都錄制下來的,我們隻能每隔一段時間采集一次資料,将模拟信号轉化成數字信号,是以,采樣點的多少就影響到語音的品質了。如果采樣點多,那麼品質就高,聽起來就和原聲的差别小;相對的,采樣點少,品質就次,聽起來就和原聲不一樣。這就是采樣率的作用。
2.采樣定理
上面說道,如果采樣率高,錄音的品質就高,那麼,是不是采樣率越高越好呢?當然不是。随着采樣率的提高,雖然品質提高了,但是采樣的難度也對應的增加了,而且,采樣出來的資料需要存儲,采樣率越高,産生的資料檔案就越大,是以品質高的音樂比一般的音樂體積大。是以,我們通常要選用一個合适的采樣率。在信号處理領域有一個定理叫做“采樣定理”,也稱“奈奎斯特定理”,内容是:如果采樣的頻率高于信号最高頻率的兩倍,采樣之後的數字信号就可以完整的保留下原始信号中的資訊。因為人的聽力範圍在20HZ-20000HZ,是以一般采樣頻率在44.1kHZ,也就是一分鐘44100次。
在明白了這些預備知識之後,下面開始介紹開源項目SinVoice。
上面是整個項目的結構,圈中的主要的類,下面把幾個重要的類的功能和注意點介紹一下。為了便于了解,我自己添加了一些注釋,并不是故意侵占原作者的版權哈。
首先,我們先看一下到底怎麼用,下面是MainActivity的代碼:
package com.example.sinvoicedemo;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import com.libra.sinvoice.LogHelper;
import com.libra.sinvoice.SinVoicePlayer;
import com.libra.sinvoice.SinVoiceRecognition;
/**
*
* @ClassName: com.example.sinvoicedemo.MainActivity
* @Description: 聲波通信
* @author zhaokaiqiang
* @date 2014-11-15 下午12:36:32
*
*/
public class MainActivity extends Activity implements
SinVoiceRecognition.Listener, SinVoicePlayer.Listener {
private final static String TAG = "MainActivity";
// 最大數字
private final static int MAX_NUMBER = 5;
// 識别成功
private final static int MSG_SET_RECG_TEXT = 1;
// 開始識别
private final static int MSG_RECG_START = 2;
// 識别結束
private final static int MSG_RECG_END = 3;
private final static String CODEBOOK = "12345";
private Handler mHanlder;
// 播放
private SinVoicePlayer mSinVoicePlayer;
// 錄音
private SinVoiceRecognition mRecognition;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSinVoicePlayer = new SinVoicePlayer(CODEBOOK);
mSinVoicePlayer.setListener(this);
mRecognition = new SinVoiceRecognition(CODEBOOK);
mRecognition.setListener(this);
final TextView playTextView = (TextView) findViewById(R.id.play_text);
mHanlder = new RegHandler((TextView) findViewById(R.id.regtext));
// 開始播放聲音
findViewById(R.id.start_play).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
String text = genText(15);
playTextView.setText(text);
mSinVoicePlayer.play(text);
}
});
// 停止播放聲音
findViewById(R.id.stop_play).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
mSinVoicePlayer.stop();
}
});
// 開始聲音識别
findViewById(R.id.start_reg).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
mRecognition.start();
}
});
// 停止聲音識别
findViewById(R.id.stop_reg).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
mRecognition.stop();
}
});
}
// 擷取長度為count且最大值為MAX_NUMBER的随機數
private String genText(int count) {
StringBuilder sb = new StringBuilder();
int pre = 0;
while (count > 0) {
int x = (int) (Math.random() * MAX_NUMBER + 1);
if (Math.abs(x - pre) > 0) {
sb.append(x);
--count;
pre = x;
}
}
return sb.toString();
}
private static class RegHandler extends Handler {
private StringBuilder mTextBuilder = new StringBuilder();
private TextView mRecognisedTextView;
public RegHandler(TextView textView) {
mRecognisedTextView = textView;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_RECG_TEXT:
char ch = (char) msg.arg1;
mTextBuilder.append(ch);
if (null != mRecognisedTextView) {
mRecognisedTextView.setText(mTextBuilder.toString());
}
break;
case MSG_RECG_START:
mTextBuilder.delete(0, mTextBuilder.length());
break;
case MSG_RECG_END:
LogHelper.d(TAG, "recognition end");
break;
}
}
}
@Override
public void onRecognitionStart() {
mHanlder.sendEmptyMessage(MSG_RECG_START);
}
@Override
public void onRecognition(char ch) {
mHanlder.sendMessage(mHanlder.obtainMessage(MSG_SET_RECG_TEXT, ch, 0));
}
@Override
public void onRecognitionEnd() {
mHanlder.sendEmptyMessage(MSG_RECG_END);
}
@Override
public void onPlayStart() {
LogHelper.d(TAG, "start play");
}
@Override
public void onPlayEnd() {
LogHelper.d(TAG, "stop play");
}
}
我們可以看出,聲波播放和識别的代碼封裝的非常簡單易用,我主要強調以下幾點
1.常量CODEBOOK是一個編碼本,因為是這個功能可以商用,是以開源的代碼中隻給出了使用12345這5個數字進行編碼的執行個體,是以這個常量不要修改。
2.SinVoicePlayer和SinVoiceRecognition是兩個非常重要的類,前者可以實作将數字轉化成單頻率的音頻進行輸出,後者則可以根據音頻進行識别。我們可以設定監聽器,來監聽識别成功的事件回調。
3.genText(int count) 方法是為了擷取一個長度是count的随機數,而且這個随機數是有要求的,因為示例代碼隻實作了1到5的編碼和解碼,是以,生成的随機數必須在1到5之間才能進行正确的編解碼,是以使用MAX_NUMBER進行随機數的大小控制
如果隻是想簡單的使用這個功能,了解上面的知識之後,就完全可以用了,下一篇文章中,我将介紹實作過程中的一些細節問題,下一篇再見。
項目的Github位址:https://github.com/JesseGu/SinVoice