以下代码主要参考博客:
Android 科大讯飞语音识别

Android蓝牙串口开发部分请参照博客:
Android蓝牙串口开发

讯飞语音官方开发文档:
语音听写 Android SDK 文档


前言

语音识别目前使用的比较频繁,项目需要使用语音识别用户指令,然后通过蓝牙通信传递指定,命令下位机做出反应。手机端主要负责命令的发送,选用讯飞语音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
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        // 此处为android 6.0以上动态授权的回调,用户自行实现。
    }

c.语音监听

/**
     * 初始化监听器。
     */
    private InitListener mInitListener = new InitListener() {

        @Override
        public 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();
    }

    @Override
    protected 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
     */
    @Override
    public void onClick(View v) {
        if( null == mIat ){
            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
            showMsg( "创建对象失败,请确认 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蓝牙串口开发

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐