智能家居 物联网 声纹开锁
啥话不说,先上效果图:
前几天给客户做一个物联网的项目,完事后,自己觉得挺好玩,也花100多块钱买了一个8路的继电器模块,买了一个小锁头,我的初衷是要通过手机蓝牙信号控制锁头开关,继而打开房门,而手机蓝牙信号我想通过科大讯飞提供的语音api声纹识别来进行发送,综上所述,我就是要通过语音识别进自己的家门,而别人再怎么说都不会进得去。
说干就干,问某宝的继电器模块商家要了蓝牙广播所需要的指令代码和发送蓝牙广播的源码,研究后,点动和自锁的指令抛弃,只采用了开和关的指令,首先要跑通发送广播联通硬件的代码,上代码:
/** * 蓝牙链接线程类 */ class bluetoothMsgThread extends Thread { private DataInputStream mmInStream; //in数据流 private Handler msgHandler; //Handler public bluetoothMsgThread(DataInputStream mmInStream,Handler msgHandler) { //构造函数,获得mmInStream和msgHandler对象 this.mmInStream = mmInStream; this.msgHandler = msgHandler; } public void run() { byte[] InBuffer = new byte[15]; //创建 缓冲区 while (!Thread.interrupted()) { try { mmInStream.readFully(InBuffer, 0, 15); //读取蓝牙数据流 Message msg = new Message(); //定义一个消息,并填充数据 msg.what = 0x1234; msg.obj = InBuffer; msg.arg1 = InBuffer.length; msgHandler.sendMessage(msg); //通过handler发送消息 }catch(IOException e) { e.printStackTrace(); } } } }
//蓝牙连接用 UUID 标识 uuid = UUID.fromString("xxxxxxx-xxxx-xxxxx-xxxx-xxxxxxxxx");
/** * 初始化蓝牙设备 */ private void intBuleTooth() { mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); //获取 蓝牙 适配器 if (mBluetoothAdapter == null) { //手机无蓝牙功能,提示并退出 Toast.makeText(getApplicationContext(), "手机无蓝牙功能",Toast.LENGTH_LONG).show(); return; } mBluetoothAdapter.enable(); //打开手机 蓝牙 功能 if (!mBluetoothAdapter.isEnabled()) { //手机未打开蓝牙功能,提示并退出 Toast.makeText(getApplicationContext(), "手机未打开蓝牙功能",Toast.LENGTH_LONG).show(); return; } Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); //获取 已经配对的蓝牙设备列表 if (pairedDevices.size() < 1) { //无配对蓝牙设备,则退出 Toast.makeText(getApplicationContext(), "没有找到已经配对的蓝牙设备,请配对后再操作",Toast.LENGTH_LONG).show(); return; } List<String> list = new ArrayList<String>(); //创建列表,用于保存蓝牙设备地址 for (BluetoothDevice device:pairedDevices) { list.add(device.getAddress()); //将蓝牙地址进入到列表 } //创建数组适配器 ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(),android.R.layout.select_dialog_item,list); adapter.setDropDownViewResource(android.R.layout.select_dialog_item); //设置 下来显示方式 spinner.setAdapter(adapter); //将适配器中数据给下拉框对象 address = spinner.getSelectedItem().toString(); //从下拉框中选择项目,并获得它的地址 try { device = mBluetoothAdapter.getRemoteDevice(address); //根据蓝牙设备的地址 连接 单片机蓝牙 设备 clientSocket = device.createRfcommSocketToServiceRecord(uuid); //根据uuid创建 socket clientSocket.connect(); //手机socket连接远端蓝牙设备 mmOutStream = clientSocket.getOutputStream(); //从socket获得 数据流对象,实现读写操作 mmInStream = new DataInputStream(new BufferedInputStream(clientSocket.getInputStream())); Toast.makeText(getApplicationContext(), "蓝牙设备连接成功,可以操作了", Toast.LENGTH_SHORT).show(); blue_tooth_msg_thread = new bluetoothMsgThread(mmInStream,bluetoothMessageHandle); blue_tooth_msg_thread.start(); }catch (Exception e) { Toast.makeText(getApplicationContext(), "蓝牙设备连接失败!"+e, Toast.LENGTH_SHORT).show(); e.printStackTrace(); } }
/** * 发送蓝牙广播 */ public void sendBrocast(String key){ byte[] InBuffer = new byte[64]; //输入缓存 byte buffer[] = key.getBytes(); //创建字符数组 a,只有一个字符,当然也可以自己定义协议 try { mmOutStream.write(buffer); //数据流发送数组,发送给单片机蓝牙设备 mmInStream.readFully(InBuffer, 0, 8); //读取 外部蓝牙设备发送回来的数据 show_result(InBuffer,8); //显示到界面上 }catch (Exception e) { e.printStackTrace(); } }
/** * 显示从蓝牙设备接收到的数据 * @param buffer * @param count */ public void show_result(byte[] buffer,int count) { StringBuffer msg = new StringBuffer(); //创建缓冲区 StringBuffer msg2 = new StringBuffer(); for (int i = 0; i < count; i++) { //循环 加入 数据,16进制 格式 msg.append(String.format("0x%x ", buffer[i])); msg2.append(String.format("%c", buffer[i])); } msg.append("\r\n"); msg.append(msg2); Log.e("","接受到的数据为"+msg.toString()); }
代码跑起来,顺利跑通和硬件的链接,可以正常打开关闭临时接到继电器上的小灯泡,接着就耍了不久,就接通了讯飞的语音文字转语音api,这个简单,其实就一个主要方法搞定:(初始化,什么这key那key的在这里就不多赘述了)
/** * 聊天语音 * * @param string */ public void speak_chat(String string,int i) { //2.合成参数设置,详见《 MSC Reference Manual》 SpeechSynthesizer 类 //设置发音人(更多在线发音人,用户可参见 附录13.2 if(i==0){ mTts.setParameter(SpeechConstant.VOICE_NAME, "aisbabyxu"); //设置发音人 }else if(i==1){ mTts.setParameter(SpeechConstant.VOICE_NAME, "aisduck"); //设置发音人 } //mTts.setParameter(SpeechConstant.VOICE_NAME, "xiaoqi"); //设置发音人 mTts.setParameter(SpeechConstant.SPEED, "70");//设置语速 mTts.setParameter(SpeechConstant.VOLUME, "80");//设置音量,范围 0~100 mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD); //设置云端 //设置合成音频保存位置(可自定义保存位置),保存在“./sdcard/iflytek.pcm” //保存在 SD 卡需要在 AndroidManifest.xml 添加写 SD 卡权限 //仅支持保存为 pcm 和 wav 格式, 如果不需要保存合成音频,注释该行代码 mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, "./sdcard/iflytek.pcm"); //3.开始合成 mTts.startSpeaking(string, null); }
能够说话了,但是我听到它说,它还听不到我说,那么不停歇,接声纹识别,代码走起:(有点小麻烦)
private void initOrderSdk() { // 初始化识别无UI识别对象 // 使用SpeechRecognizer对象,可根据回调消息自定义界面; mIat = SpeechRecognizer.createRecognizer(MainActivity.this, mInitListener); // 初始化听写Dialog,如果只使用有UI听写功能,无需创建SpeechRecognizer // 使用UI听写功能,请根据sdk文件目录下的notice.txt,放置布局文件和图片资源 mIatDialog = new RecognizerDialog(MainActivity.this, mInitListener); mSharedPreferences = getSharedPreferences(IatSettings.PREFER_NAME, Activity.MODE_PRIVATE); mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT); mResultText = ((TextView) findViewById(R.id.iat_text)); mInstaller = new ApkInstaller(MainActivity.this); }
private void startOrder() { // 移动数据分析,收集开始听写事件 FlowerCollector.onEvent(MainActivity.this, "iat_recognize"); mResultText.setText(null);// 清空显示内容 mIatResults.clear(); // 设置参数 setParam(); boolean isShowDialog = mSharedPreferences.getBoolean( getString(R.string.pref_key_iat_show), true); if (isShowDialog) { // 显示听写对话框 mIatDialog.setListener(mRecognizerDialogListener); mIatDialog.show(); showTip(getString(R.string.text_begin)); } else { // 不显示听写对话框 ret = mIat.startListening(mRecognizerListener); if (ret != ErrorCode.SUCCESS) { showTip("听写失败,错误码:" + ret); } else { showTip(getString(R.string.text_begin)); } } }
/** * 听写监听器。 */ private RecognizerListener mRecognizerListener = new RecognizerListener() { @Override public void onBeginOfSpeech() { // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入 showTip("开始说话"); } @Override public void onError(SpeechError error) { // Tips: // 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。 // 如果使用本地功能(语记)需要提示用户开启语记的录音权限。 showTip(error.getPlainDescription(true)); } @Override public void onEndOfSpeech() { // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入 showTip("结束说话"); } @Override public void onResult(RecognizerResult results, boolean isLast) { Log.d(TAG, results.getResultString()); printResult(results); if (isLast) { // TODO 最后的结果 } } @Override public void onVolumeChanged(int volume, byte[] data) { showTip("当前正在说话,音量大小:" + volume); Log.d(TAG, "返回音频数据:"+data.length); } @Override public void onEvent(int eventType, int arg1, int arg2, Bundle obj) { // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因 // 若使用本地能力,会话id为null // if (SpeechEvent.EVENT_SESSION_ID == eventType) { // String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID); // Log.d(TAG, "session id =" + sid); // } } };
/** * 参数设置 * * @param * @return */ public void setParam() { // 清空参数 mIat.setParameter(SpeechConstant.PARAMS, null); // 设置听写引擎 mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType); // 设置返回结果格式 mIat.setParameter(SpeechConstant.RESULT_TYPE, "json"); String lag = mSharedPreferences.getString("iat_language_preference", "mandarin"); if (lag.equals("en_us")) { // 设置语言 mIat.setParameter(SpeechConstant.LANGUAGE, "en_us"); mIat.setParameter(SpeechConstant.ACCENT, null); } else { // 设置语言 mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn"); // 设置语言区域 mIat.setParameter(SpeechConstant.ACCENT, lag); } // 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理 mIat.setParameter(SpeechConstant.VAD_BOS, mSharedPreferences.getString("iat_vadbos_preference", "4000")); // 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音 mIat.setParameter(SpeechConstant.VAD_EOS, mSharedPreferences.getString("iat_vadeos_preference", "1000")); // 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点 mIat.setParameter(SpeechConstant.ASR_PTT, mSharedPreferences.getString("iat_punc_preference", "1")); // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限 // 注:AUDIO_FORMAT参数语记需要更新版本才能生效 mIat.setParameter(SpeechConstant.AUDIO_FORMAT,"wav"); mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/iat.wav"); }
/** * 开始声纹识别 */ private void voice_varfar() { // 清空参数 mVerifier.setParameter(SpeechConstant.PARAMS, null); mVerifier.setParameter(SpeechConstant.ISV_AUDIO_PATH, Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/verify.pcm"); mVerifier = SpeakerVerifier.getVerifier(); // 设置业务类型为验证 mVerifier.setParameter(SpeechConstant.ISV_SST, "verify"); // 对于某些麦克风非常灵敏的机器,如nexus、samsung i9300等,建议加上以下设置对录音进行消噪处理 // mVerify.setParameter(SpeechConstant.AUDIO_SOURCE, "" + MediaRecorder.AudioSource.VOICE_RECOGNITION); mVerifier.setParameter(SpeechConstant.ISV_PWD, mTextPwd); //mVerifier.setParameter(SpeechConstant.ISV_PWD, verifyPwd); // 设置auth_id,不能设置为空 mVerifier.setParameter(SpeechConstant.AUTH_ID, "aa123456"); mVerifier.setParameter(SpeechConstant.ISV_PWDT, "1"); // 开始验证 mVerifier.startListening(mVerifyListener); }
/** * 声纹识别获取密码结果 */ private String[] items; private SpeechListener mPwdListenter = new SpeechListener() { @Override public void onEvent(int eventType, Bundle params) { } @Override public void onBufferReceived(byte[] buffer) { Log.e("", "黑0"); String result = new String(buffer); switch (mPwdType) { case PWD_TYPE_TEXT: try { JSONObject object = new JSONObject(result); if (!object.has("txt_pwd")) { Log.e("", "黑1"); return; } JSONArray pwdArray = object.optJSONArray("txt_pwd"); items = new String[pwdArray.length()]; for (int i = 0; i < pwdArray.length(); i++) { items[i] = pwdArray.getString(i); Log.e("", "黑2"); } Log.e("", "黑3"); mTextPwd = items[0]; mShowMsgTextView.setVisibility(View.VISIBLE); mShowMsgTextView.setTextColor(Color.WHITE); mShowMsgTextView.setText("请说出口令:" + mTextPwd); voice_varfar(); } catch (JSONException e) { e.printStackTrace(); } break; case PWD_TYPE_NUM: StringBuffer numberString = new StringBuffer(); try { JSONObject object = new JSONObject(result); if (!object.has("num_pwd")) { Log.e("", "黑4"); return; } JSONArray pwdArray = object.optJSONArray("num_pwd"); numberString.append(pwdArray.get(0)); for (int i = 1; i < pwdArray.length(); i++) { numberString.append("-" + pwdArray.get(i)); } } catch (JSONException e) { e.printStackTrace(); } mNumPwd = numberString.toString(); mNumPwdSegs = mNumPwd.split("-"); //mShowMsgTextView.setText("您的密码:\n" + mNumPwd); break; default: Log.e("", "黑6"); break; } } @Override public void onCompleted(SpeechError error) { if (null != error && ErrorCode.SUCCESS != error.getErrorCode()) { } } };
/** * 声纹验证监听 */ private VerifierListener mVerifyListener = new VerifierListener() { @Override public void onVolumeChanged(int volume, byte[] data) { // mShowMsgTextView.setTextColor(Color.WHITE); progressBar.setVisibility(View.VISIBLE); progressBar.setProgress(volume); //mShowMsgTextView.setText("当前正在说话,音量大小:" + volume); Log.d(TAG, "返回音频数据:" + data.length); } @Override public void onResult(VerifierResult result) { //mShowMsgTextView.setText(result.source); if (result.ret == 0) { // 验证通过 speak("恭喜您验证通过!门锁已开启,欢迎主人回家!"); mShowMsgTextView.setVisibility(View.VISIBLE); mShowMsgTextView.setTextColor(Color.GREEN); mShowMsgTextView.setText("恭喜您验证通过!"); //sendBrocast("17"); byte[] bytes = HexStringUtil.hexString2Bytes("03"); write(bytes); startThread(); dismissTv(); } else { // 验证不通过 switch (result.err) { case VerifierResult.MSS_ERROR_IVP_GENERAL: mShowMsgTextView.setText("内核异常"); break; case VerifierResult.MSS_ERROR_IVP_TRUNCATED: mShowMsgTextView.setText("出现截幅"); break; case VerifierResult.MSS_ERROR_IVP_MUCH_NOISE: mShowMsgTextView.setText("太多噪音"); break; case VerifierResult.MSS_ERROR_IVP_UTTER_TOO_SHORT: mShowMsgTextView.setText("录音太短"); break; case VerifierResult.MSS_ERROR_IVP_TEXT_NOT_MATCH: /* mShowMsgTextView.setVisibility(View.VISIBLE); mShowMsgTextView.setTextColor(Color.RED); // mShowMsgTextView.setText("验证不通过,请按照您口令读"); //new SperkerUtils().speak("验证不通过,请按照屏幕显示的口令读",mTts); // mShowMsgTextView.setVisibility(View.INVISIBLE);*/ break; case VerifierResult.MSS_ERROR_IVP_TOO_LOW: mShowMsgTextView.setText("音量太低"); break; case VerifierResult.MSS_ERROR_IVP_NO_ENOUGH_AUDIO: mShowMsgTextView.setText("音频长达不到自由说的要求"); speak("音频长达不到自由说的要求"); break; default: if (count >= 1) { speak("您已连续识别三次且无法验证身份,系统已经将您的模样实时拍摄下来,并已向主人发送信息," + "为主人住宅安全考虑,还请您见谅!10分钟后将自动解除警戒!谢谢!"); count++; mShowMsgTextView.setVisibility(View.VISIBLE); mShowMsgTextView.setTextColor(Color.RED); mShowMsgTextView.setText("警告:您已超过验证次数!"); gif1.setVisibility(View.VISIBLE); StartTime(1); return; } speak("很抱歉验证不通过,身份无法识别,请您重试!"); mShowMsgTextView.setVisibility(View.VISIBLE); mShowMsgTextView.setTextColor(Color.RED); mShowMsgTextView.setText("很抱歉验证不通过!"); dismissTv(); count++; break; } } }
代码量稍微有点大哈,好了,目前是可以识别的,只是有其他声音,它就会懵,就听不懂了,现在我俩能对话了,那就试下三者跑一下流程,经过各种反复调试,终于在识别我的声音的情况下顺利打开锁头,我用假声或录音,都只会有美女报告:您不是主人本人,门锁不能为您开启。哈哈,这下感觉好有成就感,但是总还是觉得缺点啥,啥呢,就是我既然和手机能够沟通了,就不想再那么简单的沟通,想让它变成一个有感情的人。这个就更简单了,上图灵机器人,找到官网,查下api,我的天,简单的不要不要:
String url="http://www.tuling123.com/openapi/api?key=xxxxxxxxxxxxxxxxxxxx&info=";
就这么一个他们官方的接口,key一丢,info一丢,将返回的对话信息再通过讯飞api,走一下我上面写到的speak方法,完事!
一个简单的物联网小程序就完成了,武力值+1,以后我会再在这个基础上继续开发,帖子也会跟上。