天天看點

【Android開發經驗】移動裝置的“聲波通信/驗證”的實作——SinVoice開源項目介紹(一)

    轉載請注明出處:http://blog.csdn.net/zhaokaiqiang1992

    在APP市場上,經常有一些充滿新意的應用讓我們眼前一亮,比如微信的面對面加好友,支付寶的聲波支付等等,都是通過聲波的方式進行握手通信,今天這篇文章将介紹聲波通信和聲波驗證的實作原理和代碼實作。

    首先介紹一下聲波驗證的原理。如果我們想發出聲音,就必須震動,說話是聲帶在震動,手機能播放音樂是喇叭在震動。既然發出聲音必須震動,那麼就有震動快慢之分,我們把震動的快慢叫做聲音的頻率。頻率低的聲音低沉有力,能傳播很遠的距離,比如說大象之間通信就是利用次聲波,也就是頻率很低的聲波進行的。而蝙蝠,我們都知道是通過超音波進行探路的,超音波就是震動頻率比較高的聲音。頻率太高或者太低,人的耳朵都聽不到,人耳的識别範圍是20HZ-20000HZ。這裡引出了一個機關,叫做赫茲(HZ),它是指一秒鐘的震動次數。

    知道什麼是聲音的頻率之後,我們就可以開始介紹聲波通信的原理了。既然不同的聲音有不同的頻率,那麼我們就可以假設1000HZ的聲音代表1,2000HZ的聲音代表2,以此類推,我們就可以用不同的頻率代表不同的數字組合。在接收到聲波之後,再根據不同的頻率解析成我們需要的資料就好。

    如果我們想發出單頻率的聲音,我們就需要自己構造特定頻率的正弦函數。手機喇叭在震動的時候,實際上是根據不同的電流帶動鼓紙,進行不同頻率的震動才發出聲音的。而如果我們想要發出1000HZ的聲音,我們就需要設計對應的正弦函數,來提供一定規律的電流。

    既然說到我們要自己設計正弦函數,還有幾個名詞我要解釋一下:

    1.采樣率

    是指每一秒要采集的聲音的次數。因為平常我們說話的時候,産生的是模拟信号,就是時間連續的信号,如果我們想把語音錄制下來怎麼辦呢?我們是做不到完完全全的都錄制下來的,我們隻能每隔一段時間采集一次資料,将模拟信号轉化成數字信号,是以,采樣點的多少就影響到語音的品質了。如果采樣點多,那麼品質就高,聽起來就和原聲的差别小;相對的,采樣點少,品質就次,聽起來就和原聲不一樣。這就是采樣率的作用。

    2.采樣定理

    上面說道,如果采樣率高,錄音的品質就高,那麼,是不是采樣率越高越好呢?當然不是。随着采樣率的提高,雖然品質提高了,但是采樣的難度也對應的增加了,而且,采樣出來的資料需要存儲,采樣率越高,産生的資料檔案就越大,是以品質高的音樂比一般的音樂體積大。是以,我們通常要選用一個合适的采樣率。在信号處理領域有一個定理叫做“采樣定理”,也稱“奈奎斯特定理”,内容是:如果采樣的頻率高于信号最高頻率的兩倍,采樣之後的數字信号就可以完整的保留下原始信号中的資訊。因為人的聽力範圍在20HZ-20000HZ,是以一般采樣頻率在44.1kHZ,也就是一分鐘44100次。

    在明白了這些預備知識之後,下面開始介紹開源項目SinVoice。

【Android開發經驗】移動裝置的“聲波通信/驗證”的實作——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

繼續閱讀