【云驻共创】一起来做一个《语音识别》小应用,无门槛学会该实用技能。

呆呆敲代码的小Y 发表于 2022/01/21 17:17:22 2022/01/21
【摘要】 语音识别在我们的日常生活中已经被广泛应用。曾经听起来高大上的功能词汇,放到现在其实很简单就可以自己实现。本文就来带你了解 语音识别技术的原理,顺便做一个 语音识别的实战应用。

前言

  • 语音识别 在我们的日常生活中已经被广泛应用。
  • 曾经听起来高大上的功能词汇,放到现在其实很简单就可以自己实现。
  • 本文就来带你了解 语音识别技术的原理 顺便做一个 语音识别的实战应用 。

一、语音识别原理

1.1 语音识别的相关解释

语音识别,通常称为自动语音识别,英文是Automatic Speech Recognition,缩写为ASR,主要是将人类语音中的词汇内容转换为计算机可读的输入,一般都是可以理解的文本内容,也有可能是二进制编码或者字符序列。但是,我们一般理解的语音识别其实都是狭义的语音转文字的过程,简称语音转文本识别(SpeechToText, STT)更合适,这样就能与语音合成(TextToSpeech,TTS)对应起来。

1.2 语音识别的发展历程

1952年贝尔研究所Davis等人研究成功了世界上第一个能识别10个英文数字发音的实验系统。
进入了70年代以后,出现了大规模的语音识别研究,在小词汇量、孤立词的识别方面取得了实质惟的进展。
进入80年代以后,研究的重点逐渐转向大词汇量、非特定人连续语音识别。
进入90年代以后,在语音识别的系统框架方面并没有什么重大突破。但是,在语音识别技术的应用及产品化方面出现了很大的进展。
进入2000后,语音识别的研究方向多了机器学习中的深度学习领域,特别是2009年以来,借助机器学习领域深度学习研究的发展,以及大数据语料的积累,语音识别技术得到突飞猛进的发展。

1.3 语音识别的简单分类

语音识别根据不同对象分类可以分为下面三种识别种类。

  • 孤立词识别:任务是识别事先已知的孤立的词,如“开机”、“关机”等。
  • 关键词识别(或称关键词检出,keyword spotting):连续语音流中的关键词检测针对的是连续语音,但它并不识别全部文字,而只是检测已知的若干关键词在何处出现,如在一段话中检测“计算机”、“世界”这两个词。
  • 连续语音识别:连续语音识别的任务则是识别任意的连续语音,如一个句子或一段话。

1.4 语音识别原理

语音识别 是一门涉及面很广的交叉学科,它与声学、语音学、语言学、信息理论、模式识别理论以及神经生物学等学科都有非常密切的关系。语音识别技术正逐步成为计算机信息处理技术中的关键技术。

所谓语音识别,就是将一段语音信号转换成相对应的文本信息,系统主要包含特征提取声学模型语言模型以及字典与解码四大部分,其中为了更有效地提取特征往往还需要对所采集到的声音信号进行滤波、分帧等预处理工作,把要分析的信号从原始信号中提取出来。

之后,特征提取工作将声音信号从时域转换到频域,为声学模型提供合适的特征向量;声学模型中再根据声学特性计算每一个特征向量在声学特征上的得分。

而语言模型则根据语言学相关的理论,计算该声音信号对应可能词组序列的概率;最后根据已有的字典,对词组序列进行解码,得到最后可能的文本表示。

在这里插入图片描述
关于语音识别的原理还是很复杂的,经历这么多时间和反复的模型深度学习才达到今天这个程度。

但是现在我们站在前人的肩膀上事情就变得简单多了,下面就来完整演示一个 接入百度语音识别SDK的实战案例 来深入学习一下语音识别。


二、接入讯飞语音识别SDK

这里使用的是 讯飞开放平台 语音识别的SDK然后接入到Unity中,最后打包出来一个Android应用。

在拿到讯飞语音识别SDK之后也可以直接在Android Studio中进行打包使用,这里就不演示了。

2.1 获取讯飞语音SDK

准备工具:
1.语音识别的基本知识
2.讯飞的官网注册信息并创建一个应用用于使用SDK

那接下来就是获取到语音识别SDK后的部分了!


2.2 Android Studio端操作步骤

2.2.1.工程开始

在AS新建一个工程,名字随便。然后新建一个module,起一个名字
File-new-new Module(下图)
在这里插入图片描述

2.2.2.建立相应文件夹

然后在java文件夹下新建两个文件夹便于管理语音识别跟语音唤醒,再新建一个MainActivity.class(下图)
在这里插入图片描述

2.2.3.接入Unity的ckasses.jar包

把Unity的class接入,路径在安装Unity客户端的路径下
Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes
在这里插入图片描述

2.2.4.接入讯飞语音的classess.jar包

将下载的讯飞SDK文件夹libs下的所有文件也复制到AS工程的libs下,与Unity的classes.jar一样,与下图一样即可
在这里插入图片描述

2.2.5.关联两个classes.jar包

右键libs文件夹下两个.jar文件,Add As Libray…
在这里插入图片描述
也可以右键iflytevoice,Open Module Settings
将.jar文件手动添加,添加完了记得点apply应用一下
在这里插入图片描述
在这里插入图片描述

2.2.6.添加libmsc.so

在main文件夹下新建一个Jnilibs文件夹,然后将libmsc.so添加进去。libmsc.so在讯飞SDK文件夹里的libs\armeabi-v7a下,最好连armeabi-v7a文件夹一起复制进去。效果如下
在这里插入图片描述

2.2.7.修改AndroidManifest文件

将app下的AndroidManifest的以下蓝色内容复制到我们module的AndroidManifest中,如下
在这里插入图片描述
改成这样就行了,这里注意一下,将这个<activity android:name=".asr.asrPort">的name改成自己之前建立的类名,我的是asr.asrPort
在这里插入图片描述

然后将以下代码添加到AndroidManifest中,这些代码是获取相应的权限,比如存取、录音机等权限
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <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.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" />

AndroidManifest算是配置完了

2.2.8.写SDK的接口

到了这一步,讯飞的SDK算是配置差不多了,接下来就是写方法使用了
先写语音识别的代码,在之前建立的asrPort类中写

package com.example.iflytekvoice.asr;

import android.os.Bundle;

import com.example.iflytekvoice.JsonParser;
import com.iflytek.cloud.RecognizerListener;
import com.iflytek.cloud.RecognizerResult;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.SpeechUtility;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.LinkedHashMap;

public class asrPort extends  UnityPlayerActivity{

    private SpeechRecognizer mIat;
    private HashMap<String, String> mIatResults = new LinkedHashMap<String, String>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //初始化
        SpeechUtility.createUtility(this, SpeechConstant.APPID + "=60307482");
        mIat = SpeechRecognizer.createRecognizer(this, null);
        //设置mIat的参数
        //表示是什么服务
        mIat.setParameter(SpeechConstant.DOMAIN, "iat");
        //设置语言
        mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
        //接受语言的类型
        mIat.setParameter(SpeechConstant.ACCENT, "mandarin");
        //使用什么样引擎
        mIat.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);

    }
    RecognizerListener mRecognizerLis=new RecognizerListener() {
        @Override
        public void onVolumeChanged(int i, byte[] bytes) {

        }

        @Override
        public void onBeginOfSpeech() {

        }

        @Override
        public void onEndOfSpeech() {

        }

        @Override
        public void onResult(RecognizerResult recognizerResult, boolean b) {
            printResult(recognizerResult);
        }

        @Override
        public void onError(SpeechError speechError) {

        }

        @Override
        public void onEvent(int i, int i1, int i2, Bundle bundle) {

        }
    };
    //解析Json的方法
    //方法来自speechDemo->java->voicedemo->IatDemo中的printResult方法
    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));
        }
        //把消息发送给Unity场景中iFlytekASRController物体上的OnResult方法
        UnityPlayer.UnitySendMessage("iFlytekASRController", "OnResult", resultBuffer.toString());
    }

    public void beginListen(){
        //开始识别
        mIat.startListening(mRecognizerLis);
    }

    public void connected(){
        UnityPlayer.UnitySendMessage("iFlytekASRController","tryConnected","连通成功了");

    }
    public int beginTest(int a, int b){
        //交互测试
        return a+b;
    }


}


其中还加了一个解析Json的类,直接新建在java文件夹下就行。JsonParser如下

package com.example.iflytekvoice;

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

在asrPort后面加入的几个方法是用于后边与unity交互使用的,暂时写上即可,后边会用到。
到此为止,在asrPort中写完了语音识别的方法类,打包aar包给Unity导入用即可。

2.2.9.打包aar

上面在AS写的工程给Unity使用,选中module,右键选择"Make Module ‘iflytekvoice’"等待片刻

在这里插入图片描述

将aar包与AndroidManifest复制到自己一个文件夹中(如下)
在这里插入图片描述

2.2.10.修改aar与AndroidManifest

1.aar包中的文件如下,进入libs文件夹,将里面的classes.jar删掉,换成根目录下的这个classes.jar
将下图中的"假"删掉,"真"的剪切进去,只能留"真"的那一个,两个Unity打包时会报错
在这里插入图片描述在这里插入图片描述
然后修改aar包中的AndroidManifest,将 “android:label="这行删掉,这里是设置打包出来的apk名字,这里不删会与aar包外的那个AndroidManifest冲突(跟着做就对了,我都是摸爬滚打一堆bug改过来的…)
在这里插入图片描述
修改aar包外的AndroidManifest,只需要改package即可,这里改成与包内的不一样即可,但是在Unity playerSetting中的PackageName一定要与这里设置的一样,要不然unity掉不到AS中写的方法。如下图
在这里插入图片描述
在这里插入图片描述
到这里,在AS的操作就结束了,aar包也打出来了,在Unity中用即可,下面写在Unity中的操作步骤


三、Unity端的操作

3.1 导入aar包

在Unity的Assets内新建Plugins/Android文件夹,将aar于AndroidManifest文件放入进去即可。如下所示
在这里插入图片描述

3.2 搭建一个简易UI

示例如下。一个Text组件用于显示语音识别出来的内容,一个Button组件用于点击就开始进行语音识别
下面的Try是为了确认aar包在Unity中正常连通了,之前在AS里写了方法就是为了留给点击这个按钮后调用的
在这里插入图片描述

3.3 挂在iFlytekASRController物体上的脚本

代码如下,只有简单两个点击事件,和调用aar包内的方法

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SpeechManager : MonoBehaviour
{
    public Text ASRmsg;
    public Text tryTex;
    public Text aad;

    private Button TryBtn;
    private Button ASR_Btn;


    private AndroidJavaObject jo;

    
    private void Awake()
    {
        ASR_Btn = GameObject.Find("Speech/ASR_Btn").GetComponent<Button>();
        TryBtn = GameObject.Find("Test/TryBtn").GetComponent<Button>();

        AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
    }
    void Start()
    {
        ASR_Btn.onClick.AddListener(()=> {

            jo.Call("beginListen");

        });
        TryBtn.onClick.AddListener(tryConnect);
    }

    public void OnResult(string msg)
    {
        ASRmsg.text = msg;

    }

    /// <summary>
    /// 点击Try按钮
    /// </summary>
    public void tryConnect()
    {
        int aaa;
        aaa=jo.Call<int>("beginTest",2,3);
        aad.text = aaa.ToString();

        jo.Call("connected");
    }

    public void tryConnected(string tryMsg)
    {
        tryTex.text = tryMsg;

        Color ramColor = ColorRandom();
        tryTex.color = ramColor;
    }

    public Color ColorRandom()
    {
        float r = Random.Range(0f,1f);
        float g = Random.Range(0f, 1f);
        float b = Random.Range(0f, 1f);

        Color color = new Color(r, g, b);
        return color;
    }

}

要记得将脚本挂到这个物体上,因为也是在AS中写好的,也可以在AS那边改
在这里插入图片描述

3.4 调用方法

其中下图是固定写法,大家Unity这部分自己随意发挥,只要方法名与在AndroidStudio中写的一样即可,要不然调不到
在这里插入图片描述
①Unity内调用Android Studio内的方法
在这里插入图片描述
在这里插入图片描述


②Android Studio内调用Unity中的方法
在这里插入图片描述
在这里插入图片描述

3.5 修改Unity中 PlayerSetting

这里一定要与aar外的AndroidManifest中的pakeage名字一样才能调用aar包中写的方法
在这里插入图片描述

3.6 打包apk到真机测试即可


四、总结

  • 那到这里就结束了,本文对语音识别的原理做了一个简单的额概述。
  • 还讲了一个接入讯飞语音识别SDK的实战案例,步骤多了一点,但还是很简单的。
  • 在实际操作过程中,会出现一些问题是肯定的,包括我在做这个语音识别的时候也遇到了困难。
  • 反正遇到困难就解决就好了,多尝试几次找一下哪里出现问题。
  • 这个过程就是程序员必备的,也是最脑壳痛的问题,加油!

本文整理自华为云社区【内容共创】活动第12期。
查看活动详情:https://bbs.huaweicloud.com/blogs/325315
相关任务详情:任务41.如何入门语音识别?

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。