以下代码主要参考博客:
Android 科大讯飞语音识别
Android蓝牙串口开发部分请参照博客:
Android蓝牙串口开发
讯飞语音官方开发文档:
语音听写 Android SDK 文档
文章目录
- 前言
- 一、SDK的下载和导入
- 1.SDK的下载
- 2.新建工程
- 3.配置项目
- a.导包
- b.权限
- c.修改buid.gradle
- 二、工具类
- 1.Json解析类
- 2.语音识别相关方法
- a.变量声明
- b.权限请求
- c.语音监听
- d.数据解析
- c.参数配置
- 三、调用代码编写
- 1.点击事件的监听
- 2.识别结果的显示
- 3.语音命令识别和发送
- 总结
前言
语音识别目前使用的比较频繁,项目需要使用语音识别用户指令,然后通过蓝牙通信传递指定,命令下位机做出反应。手机端主要负责命令的发送,选用讯飞语音SDK,学习官方文档和参考博客进行开发。写这篇博客记录一下。
一、SDK的下载和导入
这一部分可以可以参照讯飞语音官方文档来进行
官方文档:语音听写 Android SDK 文档
1.SDK的下载
首先在讯飞开放平台登录,没有账号进行注册
在控制台中,按流程创建新应用。
在下方的SDK下载中,下载相应的SDK。
解压完成后,先建立工程。
2.新建工程
打开Android Studio 开发平台,新建工程画好前端设计界面,建立工程这一部分就不在赘述。
3.配置项目
a.导包
将解压后的libs文件夹中的文件放入项目的libs文件夹中。
对Msc包右键,选择Add As Library导入,此时Msc包可展开。
然后再添加assets,将assets文件夹添加到工程main文件夹下面。
b.权限
导包完成后,我们需要给App一些必要权限,来使用手机的硬件功能。
<!--连接网络权限,用于执行云端语音能力 --><uses-permission android:name="android.permission.INTERNET"/><!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 --><uses-permission android:name="android.permission.RECORD_AUDIO"/><!--读取网络信息状态 --><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><!--获取当前wifi状态 --><uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/><!--允许程序改变网络连接状态 --><uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/><!--读取手机信息权限 --><uses-permission android:name="android.permission.READ_PHONE_STATE"/><!--读取联系人权限,上传联系人需要用到此权限 --><uses-permission android:name="android.permission.READ_CONTACTS"/><!--外存储写权限,构建语法需要用到此权限 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><!--外存储读权限,构建语法需要用到此权限 --><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><!--配置权限,用来记录应用配置信息 --><uses-permission android:name="android.permission.WRITE_SETTINGS"tools:ignore="ProtectedPermissions" /><!--手机定位信息,用来为语义等功能提供定位,提供更精准的服务--><!--定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 --><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/><!--如需使用人脸识别,还要添加:摄相头权限,拍照需要用到 --><uses-permission android:name="android.permission.CAMERA" />
c.修改buid.gradle
打开buid.gradle后,在文件中做以下修改。
dependencies {implementation fileTree(include: ['*.jar'], dir: 'libs')implementation 'androidx.appcompat:appcompat:1.0.2'implementation 'androidx.constraintlayout:constraintlayout:1.1.3'testImplementation 'junit:junit:4.12'androidTestImplementation 'androidx.test.ext:junit:1.1.1'androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'implementation files('libs/Msc.jar')
}
然后点击Sync Now。
二、工具类
1.Json解析类
识别语音时,需要用到Json格式,所以需要有一个解析Json格式的类。
在工程中,新建一个JsonParser 的类。
package com.example.speechrecognitionforbluetooth;import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;/*** Json结果解析类*/
public class JsonParser {public static String parseIatResult(String json) {StringBuffer ret = new StringBuffer();try {JSONTokener tokener = new JSONTokener(json);JSONObject joResult = new JSONObject(tokener);JSONArray words = joResult.getJSONArray("ws");for (int i = 0; i < words.length(); i++) {// 转写结果词,默认使用第一个结果JSONArray items = words.getJSONObject(i).getJSONArray("cw");JSONObject obj = items.getJSONObject(0);ret.append(obj.getString("w"));
// 如果需要多候选结果,解析数组其他字段
// for(int j = 0; j < items.length(); j++)
// {
// JSONObject obj = items.getJSONObject(j);
// ret.append(obj.getString("w"));
// }}} catch (Exception e) {e.printStackTrace();} return ret.toString();}public static String parseGrammarResult(String json) {StringBuffer ret = new StringBuffer();try {JSONTokener tokener = new JSONTokener(json);JSONObject joResult = new JSONObject(tokener);JSONArray words = joResult.getJSONArray("ws");for (int i = 0; i < words.length(); i++) {JSONArray items = words.getJSONObject(i).getJSONArray("cw");for(int j = 0; j < items.length(); j++){JSONObject obj = items.getJSONObject(j);if(obj.getString("w").contains("nomatch")){ret.append("没有匹配结果.");return ret.toString();}ret.append("【结果】" + obj.getString("w"));ret.append("【置信度】" + obj.getInt("sc"));ret.append("\n");}}} catch (Exception e) {e.printStackTrace();ret.append("没有匹配结果.");} return ret.toString();}public static String parseLocalGrammarResult(String json) {StringBuffer ret = new StringBuffer();try {JSONTokener tokener = new JSONTokener(json);JSONObject joResult = new JSONObject(tokener);JSONArray words = joResult.getJSONArray("ws");for (int i = 0; i < words.length(); i++) {JSONArray items = words.getJSONObject(i).getJSONArray("cw");for(int j = 0; j < items.length(); j++){JSONObject obj = items.getJSONObject(j);if(obj.getString("w").contains("nomatch")){ret.append("没有匹配结果.");return ret.toString();}ret.append("【结果】" + obj.getString("w"));ret.append("\n");}}ret.append("【置信度】" + joResult.optInt("sc"));} catch (Exception e) {e.printStackTrace();ret.append("没有匹配结果.");} return ret.toString();}public static String parseTransResult(String json, String key) {StringBuffer ret = new StringBuffer();try {JSONTokener tokener = new JSONTokener(json);JSONObject joResult = new JSONObject(tokener);String errorCode = joResult.optString("ret");if(!errorCode.equals("0")) {return joResult.optString("errmsg");}JSONObject transResult = joResult.optJSONObject("trans_result");ret.append(transResult.optString(key));/*JSONArray words = joResult.getJSONArray("results");for (int i = 0; i < words.length(); i++) {JSONObject obj = words.getJSONObject(i);ret.append(obj.getString(key));}*/} catch (Exception e) {e.printStackTrace();}return ret.toString();}
}
用来给语音识别的方法调用。
2.语音识别相关方法
现在需要实现语音识别的核心代码了,具体代码请参考官方文档Demo或访问参考博客学习,这里做一个展示。
a.变量声明
首先对比要的包进行导入。
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.ui.RecognizerDialog;
import com.iflytek.cloud.ui.RecognizerDialogListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import static android.widget.Toast.LENGTH_SHORT;
对于语音识别部分必要的变量进行声明。
//语音识别部分private SpeechRecognizer mIat;// 语音听写对象private RecognizerDialog mIatDialog;// 语音听写UI// 用HashMap存储听写结果private HashMap<String, String> mIatResults = new LinkedHashMap<String, String>();private SharedPreferences mSharedPreferences;//缓存private String mEngineType = SpeechConstant.TYPE_CLOUD;// 引擎类型private String language = "zh_cn";//识别语言private TextView tvResult;//识别结果//语音识别功能按钮private Button mBTN_SPEECH;private String resultType = "json";//结果内容数据格式
b.权限请求
调用过程中,需要动态的获取权限。
/*** android 6.0 以上需要动态申请权限*/private void initPermission() {String permissions[] = {Manifest.permission.RECORD_AUDIO,Manifest.permission.ACCESS_NETWORK_STATE,Manifest.permission.INTERNET,Manifest.permission.WRITE_EXTERNAL_STORAGE};ArrayList<String> toApplyList = new ArrayList<String>();for (String perm : permissions) {if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {toApplyList.add(perm);}}String tmpList[] = new String[toApplyList.size()];if (!toApplyList.isEmpty()) {ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);}}/*** 权限申请回调,可以作进一步处理** @param requestCode* @param permissions* @param grantResults*/@Overridepublic void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {// 此处为android 6.0以上动态授权的回调,用户自行实现。}
c.语音监听
/*** 初始化监听器。*/private InitListener mInitListener = new InitListener() {@Overridepublic void onInit(int code) {Log.d(TAG, "SpeechRecognizer init() code = " + code);if (code != ErrorCode.SUCCESS) {showMsg("初始化失败,错误码:" + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");}}};/*** 听写UI监听器*/private RecognizerDialogListener mRecognizerDialogListener = new RecognizerDialogListener() {public void onResult(RecognizerResult results, boolean isLast) {printResult(results);//结果数据解析}/*** 识别回调错误.*/public void onError(SpeechError error) {showMsg(error.getPlainDescription(true));}};/*** 提示消息* @param msg*/private void showMsg(String msg) {Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();}@Overrideprotected void onDestroy() {super.onDestroy();if (null != mIat) {// 退出时释放连接mIat.cancel();mIat.destroy();}}
d.数据解析
分析得到的数据,利用Text将结果显示出来,然后分析结果,利用蓝牙将指令发送出去。
/*** 数据解析** @param results*/private void printResult(RecognizerResult results) {String text = JsonParser.parseIatResult(results.getResultString());String sn = null;// 读取json结果中的sn字段try {JSONObject resultJson = new JSONObject(results.getResultString());sn = resultJson.optString("sn");} catch (JSONException e) {e.printStackTrace();}mIatResults.put(sn, text);StringBuffer resultBuffer = new StringBuffer();for (String key : mIatResults.keySet()) {resultBuffer.append(mIatResults.get(key));}mET_DATE.setText(resultBuffer.toString());//听写结果显示sendCMDByBluetooth(resultBuffer.toString());//发送命令}
c.参数配置
/*** 参数设置** @return*/public void setParam() {// 清空参数mIat.setParameter(SpeechConstant.PARAMS, null);// 设置听写引擎mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);// 设置返回结果格式mIat.setParameter(SpeechConstant.RESULT_TYPE, resultType);if (language.equals("zh_cn")) {String lag = mSharedPreferences.getString("iat_language_preference","mandarin");Log.e(TAG, "language:" + language);// 设置语言mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");// 设置语言区域mIat.setParameter(SpeechConstant.ACCENT, lag);} else {mIat.setParameter(SpeechConstant.LANGUAGE, language);}Log.e(TAG, "last language:" + mIat.getParameter(SpeechConstant.LANGUAGE));//此处用于设置dialog中不显示错误码信息//mIat.setParameter("view_tips_plain","false");// 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理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权限mIat.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/iat.wav");}
三、调用代码编写
在准备的工作基本完成之后,现在就需要在主程序的按逻辑去调用方法实现功能。
1.点击事件的监听
首先对于屏幕实现一个点击事件的监听。
public class MainActivity extends AppCompatActivity implements View.OnClickListener{//这里省略了其他必要的代码,仅展示改动部分private static final String TAG ="MainActivity" ;/*** 屏幕点击响应事件* @param v*/@Overridepublic void onClick(View v) {if( null == mIat ){// 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688showMsg( "创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化" );return;}mIatResults.clear();//清除数据setParam(); // 设置参数mIatDialog.setListener(mRecognizerDialogListener);//设置监听mIatDialog.show();// 显示对话框}
}
2.识别结果的显示
结果的显示包含在数据分析的部分代码中
//该部分代码位于数据解析代码段中,具体情况自己按情况更改
mET_DATE.setText(resultBuffer.toString());//听写结果显示
sendCMDByBluetooth(resultBuffer.toString());//发送命令
3.语音命令识别和发送
检测到语音输入后,产生一个识别结果,显示出来后,根据自己需要的判断逻辑对识别结果进行分析,发出指令。
/*** 识别语音由蓝牙发送指令* @param Speech* 为语音指令*/public void sendCMDByBluetooth(String Speech){//去掉命令中的句号String CMD = Speech.substring(0,Speech.indexOf("。"));//判断是否打开蓝牙if (CONNECT_STATUS) {switch (CMD){case "打开灯光":{write("A");Toast.makeText(MainActivity.this, "指令:打开灯光 CMD:A", LENGTH_SHORT).show();break;}case "关闭灯光":{write("B");Toast.makeText(MainActivity.this, "指令:关闭灯光 CMD:B", LENGTH_SHORT).show();break;}case "打开风扇":{write("C");Toast.makeText(MainActivity.this, "指令:打开风扇 CMD:C", LENGTH_SHORT).show();break;}case "关闭风扇":{write("D");Toast.makeText(MainActivity.this, "指令:关闭风扇 CMD:D", LENGTH_SHORT).show();break;}}} else {Toast.makeText(getApplicationContext(), "请先连接蓝牙", Toast.LENGTH_SHORT).show();}}
总结
对我来说,该项目主要的难点在于对SDK的使用,以及类的调用。更灵活的使用还需要加深理解。
关于Android经典蓝牙串口开发在我的另一篇博客有记录。
Android蓝牙串口开发