20212313 2023-2024-2 《移动平台开发与实践》第6次作业
1.实验内容
设计并开发一个语音识别应用系统。
通过使用RecognizerIntent实现语音识别功能,开发一个Android语音识别系统。
2.实验过程
2.1下载语音识别的SDK
这里我们选择的是科大讯飞的语音识别(流失版)
(1)先到讯飞开发平台进行账号注册和实名认证。下载网站
(2)点击我的应用,然后创建应用,其中的分类什么的都可以随便选,记住其中的APPID就行,(13398b0d)
(3)点击应用名称,进入页面,往下滑,会看到Android MSC 的下载
(4)点击下载后,再次选择语音识别(流式版)即可
2.2 配置实验环境
(1)打开下载后的压缩包,进入到libs目录,将以下三个文件复制到实验项目下的app/libs目录下即可,然后右键将Msc.jar文件导入库(Add As Library)
但后续实验过程中会报错,据了解,是在libs目录下只要有Msc.jar文件即可!!!
(2)在src/main目录下新建JniLibs文件夹,再将两个so文件复制其目录下
(3)在buile.gradle.kts添加下面代码
sourceSets {
getByName("main") {
jniLibs.srcDirs("libs")
}
}
(4)在AndroidManifest.xml中添加权限
<!--连接网络权限,用于执行云端语音能力 -->
<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"/>
2.3编写核心代码
(1)activity_main.xml布局文件代码:
添加最简单的按钮(开始识别)和显示语音识别结果的内容的TextView即可
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="识别到的内容"
android:textColor="@color/white"
android:layout_marginStart="32dp"
android:layout_marginTop="128dp"
android:textSize="50sp"
android:textStyle="bold"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btn_start"
android:layout_width="match_parent"
android:layout_height="50dp"
style="@android:style/Widget.Button"
android:layout_marginTop="64dp"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:text="开始识别"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="@color/white"/>
</LinearLayout>
(2)新建JsonPares文件
将语音识别结果的 JSON 字符串解析成可读的文本,其中使用了 JSONObject 和 JSONTokener 这两个类来处理 JSON 数据。方便在Mainactivity文件中调用该类,最终显示出语音识别
package com.example.wjb
import org.json.JSONObject
import org.json.JSONTokener
/**
* Json结果解析类
*/
object JsonParser {
fun parseIatResult(json: String?): String {
val ret = StringBuffer()
try {
val tokener = JSONTokener(json)
val joResult = JSONObject(tokener)
val words = joResult.getJSONArray("ws")
for (i in 0 until words.length()) {
val items = words.getJSONObject(i).getJSONArray("cw")
val obj = items.getJSONObject(0)
ret.append(obj.getString("w"))
}
} catch (e: Exception) {
e.printStackTrace()
}
return ret.toString()
}
}
(3)新建SpeechApplication文件,负责将初始化,填写之前应用的appid,也就是(13398b0d)也是最关键的一步!!!
package com.example.wjb
import android.app.Application
import com.iflytek.cloud.SpeechUtility
class SpeechApplication: Application() {
override fun onCreate() {
SpeechUtility.createUtility(this@SpeechApplication, "appid=13398b0d")
super.onCreate()
}
}
(4)Mainactivity.kt文件
package com.example.wjb
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.wjb.JsonParser.parseIatResult
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 org.json.JSONException
import org.json.JSONObject
import android.Manifest
class MainActivity : AppCompatActivity(), View.OnClickListener {
private var mIat: SpeechRecognizer? = null // 语音听写对象
private var mIatDialog: RecognizerDialog? = null // 语音听写UI
// 用HashMap存储听写结果
private val mIatResults: HashMap<String?, String> = LinkedHashMap()
private var mSharedPreferences: SharedPreferences? = null //缓存
private val mEngineType = SpeechConstant.TYPE_CLOUD // 引擎类型
private val language = "zh_cn" //识别语言
private var tvResult: TextView? = null //识别结果
private var btnStart: Button? = null //开始识别
private val resultType = "json" //结果内容数据格式
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvResult = findViewById<TextView>(R.id.tv_result)
btnStart = findViewById<Button>(R.id.btn_start)
btnStart?.setOnClickListener(this)
initPermission() //权限请求
// checkAndRequestPermissions()
// 使用SpeechRecognizer对象,可根据回调消息自定义界面;
mIat = SpeechRecognizer.createRecognizer(this@MainActivity, mInitListener)
// 使用UI听写功能,请根据sdk文件目录下的notice.txt,放置布局文件和图片资源
mIatDialog = RecognizerDialog(this@MainActivity, mInitListener)
mSharedPreferences = getSharedPreferences(
"ASR",
MODE_PRIVATE
)
}
override fun onClick(v: View) {
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() // 显示对话框
}
/**
* 初始化监听器。
*/
private val mInitListener = InitListener { code ->
Log.d(TAG, "SpeechRecognizer init() code = $code")
if (code != ErrorCode.SUCCESS) {
showMsg("初始化失败,错误码:$code,请点击网址https://www.xfyun.cn/document/error-code查询解决方案")
}
}
/**
* 听写UI监听器
*/
private val mRecognizerDialogListener: RecognizerDialogListener =
object : RecognizerDialogListener {
override fun onResult(results: RecognizerResult, isLast: Boolean) {
printResult(results) //结果数据解析
}
/**
* 识别回调错误.
*/
override fun onError(error: SpeechError) {
showMsg(error.getPlainDescription(true))
}
}
/**
* 数据解析
*
* @param results
*/
private fun printResult(results: RecognizerResult) {
val text = parseIatResult(results.resultString)
var sn: String? = null
// 读取json结果中的sn字段
try {
val resultJson = JSONObject(results.resultString)
sn = resultJson.optString("sn")
} catch (e: JSONException) {
e.printStackTrace()
}
mIatResults[sn] = text
val resultBuffer = StringBuffer()
for (key in mIatResults.keys) {
resultBuffer.append(mIatResults[key])
}
tvResult!!.text = resultBuffer.toString() //听写结果显示
}
/**
* 参数设置
*
* @return
*/
fun setParam() {
// 清空参数
mIat!!.setParameter(SpeechConstant.PARAMS, null)
// 设置听写引擎
mIat!!.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType)
// 设置返回结果格式
mIat!!.setParameter(SpeechConstant.RESULT_TYPE, resultType)
if (language == "zh_cn") {
val 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().toString() + "/msc/iat.wav"
)
}
/**
* 提示消息
* @param msg
*/
private fun showMsg(msg: String) {
Toast.makeText(this@MainActivity, msg, Toast.LENGTH_SHORT).show()
}
override fun onDestroy() {
super.onDestroy()
if (null != mIat) {
// 退出时释放连接
mIat!!.cancel()
mIat!!.destroy()
}
}
private fun initPermission() {
val permissions = arrayOf<String>(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.INTERNET,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
val toApplyList = ArrayList<String>()
for (perm in permissions) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(
this,
perm
)
) {
toApplyList.add(perm)
}
}
val tmpList = arrayOfNulls<String>(toApplyList.size)
if (!toApplyList.isEmpty()) {
ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123)
}
}
/**
* 权限申请回调,可以作进一步处理
*
* @param requestCode
* @param permissions
* @param grantResults
*/
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
// 此处为android 6.0以上动态授权的回调,用户自行实现。
}
companion object {
private const val TAG = "MainActivity"
}
}
2.4实验结果
手机打开开发者模式,连上数据线,下载app。然后就可以进行语音识别。以下视频也可以看出语音识别还是有一些弊端的,就是不能准确的获取我的名字(希望不是我普通话的问题)
3.学习中遇到的问题及解决
-
问题1:在AndroidManifest.xml添加权限是爆红,也没说明原因
-
问题1解决方案:重新找一个同学的权限代码,再复制进去就可以了
-
问题2:运行项目时,显示so文件的一些问题,报错特别长,很难看懂,于是将报错内容复制发给gpt,通过gpt得知是so文件重复了
-
问题2解决方案:根据gpt的回答,可以知道在 jniLibs 和 libs 目录中都有相同的库文件。所以我们把libs目录下的so文件删除,只留Msc.jar文件即可。再次运行发现不报错
-
问题3:运行项目后点击开始识别后,出现“创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化”的错误
-
问题3解决方案:根据同学们的提示,说实验真机就可以不报错了。于是我将手机设置成开发者模式,连上数据线,将软件下载到手机上就可以进行语音识别了。
4.学习感悟、思考等)
本次实验是移动平台的最后一次实验,也相对简单,到开放平台上下载sdk(与第五次实验类似),然后导入包进行环境配置,编写核心代码即可完成实验。经过这么多次实验,对于Android studio的熟练度也越来越高。面对报错时也相对从容,之前看到那么多红,都不知道该怎么做。现在我至少能大致判断出是so文件出错了。然后摘取一些内容复制给gpt,让gpt告诉我们出了什么错,根据gpt的回答再进行调试。
这学期的课也到此结束了,本学期的课程,让我学习到了特别多,之前总觉得开发一款app是非常难的。但是现在看来,我也可以做出一个百度定位、语音识别。让自己特别有成就感。对于app开发有了深入的了解,至少知道了四大组件,如何开放等等。
最后感谢王志强老师的辛勤付出!
参考资料
Android Studio语音识别实现