20212408 2023-2024-2 《移动平台开发与实践》第6次作业

20212408 2023-2024-2 《移动平台开发与实践》第6次作业

一、实验内容

掌握基于Android平台的程序设计技术,并能够编写完成语音识别系统。
设计并开发一个语音识别应用系统。
通过使用RecognizerIntent实现语音识别功能,开发一个Android语音识别系统。

二、实验过程

(一)实验准备

  • 创建应用并下载对应的SDK
    根据科大讯飞语音识别官网教程进行sdk获取和appid获取,首先需要进行科大讯飞平台的注册与认证,这里相较于百度地图申请速度就很快了。

进入讯飞开放平台:个人注册完成后,进入控制台。
点击创建新应用。

请添加图片描述
点击这个应用名称查看详细信息,选择左侧的语音听写(流式版)
请添加图片描述
右边的是对接过程需要用到的值,APPID用SDK中,APIKey或APISecret适用于WebAPI调用方式。
然后我们需要下载SDK,

请添加图片描述
下载到本地,然后解压,至此实验准备完成,下面打开Android studio进行实验。

(二)实验步骤

1.配置资源文件

等待项目创建好之后,复制文件中libs里面的三个文件,到项目的libs中
在这里插入图片描述
不过需要做一些调整,在main目录下新建文件夹“jniLibs”,并把下载的“arm64-v8a”“armeabi-v7a”添加至其中。
右键Mac.jar添加Add As Library,添加好之后jar包就会有一个三角箭头,可以展开,这个时候就可以使用里面的方法了。
将assets下的文件复制到项目main目录下。

具体项目目录如下:
请添加图片描述
在buile.gradle.kts添加下面代码

sourceSets {
        getByName("main") {
            jniLibs.srcDirs("libs")
        }
    }

在这里插入图片描述
到此项目的基本配置完成,下面是代码的撰写

2.编写代码

首先是对应权限的申请
将下面代码添加至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"/>

activity_main.xml布局文件代码如下:

<?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>

新建JsonPares文件负责json数据解析

package com.example.kedaxunfei

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()
    }
}

新建SpeechApplication文件,负责将初始化,需要填写应用的appid
请添加图片描述

package com.example.kedaxunfei


import android.app.Application
import com.iflytek.cloud.SpeechUtility

class SpeechApplication: Application() {
    override fun onCreate() {
        SpeechUtility.createUtility(this@SpeechApplication, "appid=6526455f")
        super.onCreate()
    }
}

MainActivity中代码

package com.example.kedaxun


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.kedaxun.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 val REQUEST_RECORD_AUDIO = 1
//    private val REQUEST_WRITE_EXTERNAL_STORAGE = 2
//
//    // 检查并请求权限
//    private fun checkAndRequestPermissions() {
//        if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
//            != PackageManager.PERMISSION_GRANTED
//        ) {
//            // REQUEST_RECORD_AUDIO 是我们定义的请求码
//            ActivityCompat.requestPermissions(
//                this, arrayOf<String>(Manifest.permission.RECORD_AUDIO),
//                REQUEST_RECORD_AUDIO
//            )
//        }
//        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
//            != PackageManager.PERMISSION_GRANTED
//        ) {
//            // REQUEST_WRITE_EXTERNAL_STORAGE 是我们定义的请求码
//            // 注意:从Android 10开始,Google建议使用MediaStore API或Scoped Storage
//            ActivityCompat.requestPermissions(
//                this, arrayOf<String>(Manifest.permission.WRITE_EXTERNAL_STORAGE),
//                REQUEST_WRITE_EXTERNAL_STORAGE
//            )
//        }
//    }
//
//    // 处理权限请求结果
//    override fun onRequestPermissionsResult(
//        requestCode: Int,
//        permissions: Array<String?>,
//        grantResults: IntArray
//    ) {
//        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
//        when (requestCode) {
//            REQUEST_RECORD_AUDIO -> {
//                if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//                    // RECORD_AUDIO 权限被授予
//                } else {
//                    // RECORD_AUDIO 权限被拒绝
//                }
//                return
//            }
//
//            REQUEST_WRITE_EXTERNAL_STORAGE -> {
//                if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//                    // WRITE_EXTERNAL_STORAGE 权限被授予
//                } else {
//                    // WRITE_EXTERNAL_STORAGE 权限被拒绝
//                }
//                return
//            }
//        }
//    }
    /**
     * android 6.0 以上需要动态申请权限
     */
    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"
    }
}
3.实验结果演示视频

语音识别

三、学习中遇到的问题及解决

问题1:
一直显示“创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化”?
请添加图片描述
这里我是直接在真机上进行测试,因为提前开了同学的博客,发现在电脑进行运行很多情况下不能正常运行,会出现如图报错,于是我直接就是使用真机测试,但是结果仍然是报错,网上也找不到合适的解决方案,于是我重新创建了项目工程,重新做一遍便可以了,可能是我不小心改了什么东西,没有正确检测到。

四、学习感悟、思考等

本次实验和上次的实验内容相差不大,都是通过调用SDK进行一些已经开发好的应用功能的调用,这些实验比较容易出错误的都是对资源的一些配置,和我们直接编代码是存在区别的,容易弄错,需要我们仔细细心。并且有些实验环境可能和我们电脑虚拟机有所不同,也需要我们掌握使用真机进行实验结果的实现,需要我们灵活变通。本次实验也是这学期移动平台开发与实践的最后一个实验了,我学到很多,对安卓开发也更加熟悉,非常感谢王老师一学期的指导,也感谢同学们的帮助!!

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值