聲波配網:通過特定的聲波序列將wifi密碼發到無螢幕的裝置上
阿新 • • 發佈:2019-02-15
聲波配網,即通過手機發出聲波,將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(); } } } }