1. 程式人生 > >聲波配網:通過特定的聲波序列將wifi密碼發到無螢幕的裝置上

聲波配網:通過特定的聲波序列將wifi密碼發到無螢幕的裝置上

聲波配網,即通過手機發出聲波,將ssid、password等資訊傳給裝置的一種配網方式。適用於沒有觸屏或觸屏較小不易於資訊輸入,但是擁有麥克風的智慧裝置,如智慧音箱、智慧家庭助手等。其優點是配網速度快、可人耳感知,缺點是受環境干擾較大。

實現聲波配網,首先需要一套特定的演算法庫(我司有專門的演算法部門在做,由於保密的原因,演算法庫不能公開),演算法庫分手機端和裝置端兩部分。手機端演算法庫將ssid資訊由字串轉化為聲音訊號(PCM),然後將聲音訊號通過音訊模組播放出來。同時,裝置端錄下這一段聲音,然後用同一套演算法庫將聲音資訊解析出來,還原成原來的ssid資訊(字串),最後用解析到的ssid資訊用於連線wifi。

聲波配網主要流程如下:

(1)首先,在手機(或平板等其它一代裝置)輸入ssid資訊(或獲取當前或系統儲存的ssid資訊),使用我司提供的演算法庫,將資訊由buffer編碼為pcm資料;

(2)將使用演算法庫編碼出來的pcm資料通過喇叭播放出來,同時,裝置端開啟錄音,捕獲pcm資料;

(3)裝置端將pcm資料通過演算法庫解碼回原來的buffer資料;

(4)從資料中解析出ssid、password等資訊,並將其用於連線路由器。

編解碼可選擇範圍分為低頻、中頻、高頻三種,其中低頻的頻率範圍為2K~5K,中頻的範圍為8K~12K,高頻的範圍為16K~20K。頻率越高,聲音越尖銳,抗噪效能越強。

手機端實現

此處已Android端實現為例。

在Android系統上實現聲波配網時,需要通過jni呼叫我司提供的演算法庫。

接收端的程式碼由於用的是我司自己做的系統,程式碼不能公開,而寫出來也沒有意義,總的一個實現思路就是,接收聲波,並將聲波用演算法庫轉換會原來的ssid等資訊。

package com.aw.soundauthenticationtest;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

import com.aw.SoundAuthentication;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.app.Activity;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {
	
	private final String TAG = "llh>>>MainActivity";
	private AudioTrack mAudioTrack;
	private SoundAuthentication mSoundAuthentication;
	private EditText mInputSsidEditText;
	private EditText mInputPasswordEditText;
	private Button mStartBt;
	private File mSaveTransferedPcmFile;
	private DataOutputStream mDataOutputStream;
	private final int ENABLE_START_BUTTON_MSG = 0;
	private final int DISABLE_START_BUTTON_MSG = 1;
	
	private int mMaxStrLen = 128;
	private int mSampleRate = 44100;
	private int mFreqType = 1;//0 is low freq,1 is middle freq,2 is high freq
	private int mErrorCorrect = 1;
	private int mErrorCorrectNum = 4;
	private int mGroupSymbolNum = 10;
	
	private Handler mHandler = new Handler(){
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case ENABLE_START_BUTTON_MSG:
				mStartBt.setEnabled(true);
				break;
			case DISABLE_START_BUTTON_MSG:
				mStartBt.setEnabled(false);
				break;
			default:
				break;
			}
		};
	};
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Log.d(TAG, "onCreate");
		mSoundAuthentication = new SoundAuthentication();
		mInputSsidEditText = (EditText)findViewById(R.id.ssid_editText_id);
		mInputPasswordEditText = (EditText)findViewById(R.id.password_editText_id);
		mStartBt = (Button)findViewById(R.id.start_bt_id);
		mStartBt.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				Log.d(TAG, "mStartBt is clicked");
				if(mInputPasswordEditText.getText().toString().length() < 8){
					Toast toast = Toast.makeText(MainActivity.this, "the password length is less than 8,please input more data", Toast.LENGTH_LONG);
					toast.setGravity(Gravity.TOP, 50, 220);
					toast.show();
				}else{
					mStartBt.setEnabled(false);
//					creatTransferFile();
					new Thread(){
						public void run() {
							String tmpInputSsidStr = mInputSsidEditText.getText().toString();
							String tmpInputPasswordStr = mInputPasswordEditText.getText().toString();
							String inputSsidStr = null;
							String inputPasswordStr = null;
							try {
								byte[] tmpSsidByte = tmpInputSsidStr.getBytes();
								byte[] tmpPasswordByte = tmpInputPasswordStr.getBytes();

								inputSsidStr = new String(tmpSsidByte, "utf-8");
								inputPasswordStr = new String(tmpPasswordByte, "utf-8");

							} catch (UnsupportedEncodingException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							String allWifiStr = null;
							if(inputSsidStr.equals("")){//沒有手動輸入wifi名稱,所以直接使用已連線的那個wifi名稱
								Log.d(TAG, "do not input ssid");
								//把wifi的ssid和password組成一個字串,用"::div::"這個字串把他們分隔開,接收端也要根據這個分隔字串取出相應的ssid和password
								allWifiStr = getSSIDname()+"::div::"+inputPasswordStr;
							}else{//手動輸入了wifi名稱,所以用手動輸入的那個wifi名稱
								Log.d(TAG, "manual input ssid:"+inputSsidStr);
								//把wifi的ssid和password組成一個字串,用"::div::"這個字串把他們分隔開,接收端也要根據這個分隔字串取出相應的ssid和password
								allWifiStr = inputSsidStr+"::div::"+inputPasswordStr;
							}
							mSoundAuthentication.setDebugFlag(true);
							mSoundAuthentication.setEnDecoderParameters(mMaxStrLen,mSampleRate,mFreqType,mErrorCorrect,mErrorCorrectNum,mGroupSymbolNum);
							short[] pcm_data = mSoundAuthentication.nativeEncodeStrToPcm(allWifiStr);//把需要編碼的字串(allWifiStr)傳給編碼器
							if(pcm_data == null){//編碼失敗
								Log.e(TAG, "encode error");
							}else{//編碼成功,返回值為該字串編碼後的pcm資料								
								Log.d(TAG, "pcm_data len = "+pcm_data.length);
//								savePcmDataInFile(pcm_data);
								playPcm(pcm_data);//把編碼後的pcm資料拿去播放
								stopPcm();//停止播放
//								closeFile();
							}
							mHandler.sendEmptyMessage(ENABLE_START_BUTTON_MSG);
						};
					}.start();
				}
			}
		});
		
	}
	
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	/*注:以下兩個引數不能改變:取樣精度為 16bit , 取樣通道為:單聲道(mono)。取樣率需要和接收端的取樣率一致,一般設為44100*/
	private void playPcm(short[] pcmData){
		int index = 0;
		int minBufSizeInByte = AudioTrack.getMinBufferSize(mSampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
		Log.d("llh>>>", "minBufSizeInByte = "+minBufSizeInByte);
		if(mAudioTrack == null){
			mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, mSampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufSizeInByte, AudioTrack.MODE_STREAM);
		}
		int minBufSizeInShrot = minBufSizeInByte/2;
		mAudioTrack.play();
		while((index*minBufSizeInShrot) < pcmData.length){
			if((pcmData.length - index*minBufSizeInShrot)>=minBufSizeInShrot){
				mAudioTrack.write(pcmData, index*minBufSizeInShrot, minBufSizeInShrot);
			}else{
				mAudioTrack.write(pcmData, index*minBufSizeInShrot, pcmData.length - index*minBufSizeInShrot);
			}
			index++;
		}
		
	}
	
	private void stopPcm(){
		if(mAudioTrack != null){
			mAudioTrack.flush();
			mAudioTrack.stop();
			mAudioTrack = null;
		}
	}
	
	private String getSSIDname(){
		String ssidName = null;
		WifiManager wifiManager = (WifiManager)getSystemService(WIFI_SERVICE);
		WifiInfo wifiInfo = wifiManager.getConnectionInfo();
		String tempSsidName = wifiInfo.getSSID().substring(1, wifiInfo.getSSID().length()-1);
		try {
			ssidName = new String(tempSsidName.getBytes(), "utf-8");
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
		Log.d(TAG, "ssid name = "+ssidName);
		Log.d(TAG, "ssid name len = "+ssidName.length());
		Log.d(TAG, "bssid name = "+wifiInfo.getBSSID());
		Log.d(TAG, "wifi info = "+wifiInfo.toString());
		return ssidName;
	}
	
	/*注:以下3個函式是除錯的時候為了儲存資料用的,實際使用不需要用到這3個函式。
	 * creatTransferFile(),savePcmDataInFile(short[] inputPcmData),closeFile()*/
	private void creatTransferFile(){
		//在這裡我們建立一個檔案,用於儲存字串轉換成pcm的內容  
        File fpath = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/llhtest/");  
        fpath.mkdirs();//建立資料夾  
        try {  
            //建立臨時檔案,注意這裡的格式為.pcm  
        	mSaveTransferedPcmFile = File.createTempFile("transfer", ".pcm", fpath);  
        	Log.d(TAG, "record file path name = "+mSaveTransferedPcmFile.getAbsolutePath());
        	mDataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(mSaveTransferedPcmFile)));
        } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        } 
        
	}
	
	private void savePcmDataInFile(short[] inputPcmData){
		if(mDataOutputStream != null){
			for(int i=0; i < inputPcmData.length; i++){
				try {
					mDataOutputStream.writeShort(inputPcmData[i]);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	private void closeFile(){
		if(mDataOutputStream != null){
			try {
				mDataOutputStream.close();
				mDataOutputStream = null;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	
}