嵌入式项目之Android导航语音识别——语音听写


文章目录


前言

最近在学习嵌入式系统的一个功能——导航语音识别,该功能是基于Android和科大讯飞语音识别语音合成,利用RFID射频识别技术实现的功能,整个导航语音识别功能包括语音识别、语音合成、导航听写、语音提示语音识别的语音合成三大模块。这里我整理了第一模块——语音识别。


一、原理流程图

本系统的导航语音识别功能通过使用科大讯飞完备的语音识别和语音合成技术结合RFID射频识别技术得以实现

科大讯飞官网:讯飞开放平台-以语音交互为核心的人工智能开放平台

二、语音听写

将短音频( <=60秒 ) 精准识别成文字,除中文普通话和英文外,支持65个语种、24种方言和1个名族语言,实时返回结果,达到边说边返回的效果。

       首先在科大讯飞的服务器上申请语音识别的应用,并获取它的APPID;然后下载其SDK,导入到我们开发的项目开发包中,每个应用根据不同的SDK和对应的APPID,不匹配则无法成功进行语音识别,将SDK中源码关于APPID替换成我们自己申请的ID。SDK中有已经训练好的语音转换成字符串的模型,本系统在开发中仅需要在activity文件中设置自己的指令集语音模型即可。由于采用的是科大讯飞语音在线转写功能,因此,APP需要在联网的情况下进行使用,通过调用语音听写方法,对声音进行监听,通过对用户语音指令的捕捉,将语音信息上传到科大讯飞的云服务器中,按照规定的语法标准获取语音中的关键字,并形成Json字符串返回至应用中,同时我们利用gson-2.8.0.jar包对Json数据进行解析,使其成为一条完整的指令。同时,为防止客户语音指令中同音字对解析结果产生混淆,我们在科大讯飞服务器中上传了我们的指令集进行模型训练,使语音指令的解析更加精确。

语音识别合成

1.初始化无ui识别听写

代码如下(示例):

 /*首页语音识别*/
    protected void Speech(){
        dictationResultStr = "[";
        // 语音配置对象初始化
        SpeechUtility.createUtility(MainActivity.this, SpeechConstant.APPID
                + "=" + APPID);
        // 1.创建SpeechRecognizer对象,第2个参数:本地听写时传InitListener
        SpeechRecognizer mIat = SpeechRecognizer.createRecognizer(
                MainActivity.this, null);
        //设置语法ID和 SUBJECT 为空,以免因之前有语法调用而设置了此参数;或直接清空所有参数,具体可参考 DEMO 的示例。
        mIat.setParameter( SpeechConstant.CLOUD_GRAMMAR, null );
        mIat.setParameter( SpeechConstant.SUBJECT, null );
//设置返回结果格式,目前支持json,xml以及plain 三种格式,其中plain为纯听写文本内容
        mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");
//此处engineType为“cloud”
        mIat.setParameter( SpeechConstant.ENGINE_TYPE, "cloud" );
//设置语音输入语言,zh_cn为简体中文
        mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
//设置结果返回语言
        mIat.setParameter(SpeechConstant.ACCENT, "mandarin");
// 设置语音前端点:静音超时时间,单位ms,即用户多长时间不说话则当做超时处理
//取值范围{1000~10000}
        mIat.setParameter(SpeechConstant.VAD_BOS, "2000");
//设置语音后端点:后端点静音检测时间,单位ms,即用户停止说话多长时间内即认为不再输入, 
//自动停止录音,范围{0~10000}
        mIat.setParameter(SpeechConstant.VAD_EOS, "1000");
//设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
        mIat.setParameter(SpeechConstant.ASR_PTT,"0");
        
        
        // 开始听写
        mIat.startListening(mRecognizerListener);
    }

 其中SpeechRecognizer为语音识别类,包括听写、语法识别功能

本类使用单例,调用者使用本类的对象,只需要通过createRecognizer(android.content.Context, com.iflytek.cloud.InitListener)创建 一次对象后,便可一直使用该对象,直到通过调用destroy()进行单例对象销毁。调 用者可通过getRecognizer()获取当前已经创建的单例。在销毁本类的单例对象后, 需要先通过createRecognizer(android.content.Context, com.iflytek.cloud.InitListener)再次创建单例对象,方可再使用。

在当前应用生命周期第一次使用本类的任何函数前,须先调用 [SpeechUtility.createUtility(android.content.Context, java.lang.String)]进行SDK初始化。

其中在线听写UI设置——sdk提供了两种识别方式,分别带UI识别和无UI识别

主要区别在于如果只是用UI听写功能,无需创建SpeechRecognizer,根据sdk文件目录下的notice.txt,放置布局文件和图片资源

代码示例:

无UI识别

//初始化识别无UI识别对象
//使用SpeechRecognizer对象,可根据回调消息自定义界面;
mIat = SpeechRecognizer.createRecognizer(IatDemo.this, mInitListener);

//设置语法ID和 SUBJECT 为空,以免因之前有语法调用而设置了此参数;或直接清空所有参数,具体可参考 DEMO 的示例。
mIat.setParameter( SpeechConstant.CLOUD_GRAMMAR, null );
...
    
//开始识别,并设置监听器
mIat.startListening(mRecogListener);

UI识别

// 初始化听写Dialog,如果只使用有UI听写功能,无需创建SpeechRecognizer
// 使用UI听写功能,请根据sdk文件目录下的notice.txt,放置布局文件和图片资源
mIatDialog = new RecognizerDialog(IatDemo.this, mInitListener);

//以下为dialog设置听写参数
mIatDialog.setParams("xxx","xxx");
....

//开始识别并设置监听器
mIatDialog.setListener(mRecognizerDialogListener);
//显示听写对话框
mIatDialog.show();

2.设置识别监听器

科大讯飞默认只提供单次的点击式语音监听解析,所以需要我们手动实现。

持续监听需要做到两点:

代码如下(示例):

private RecognizerListener mRecognizerListener = new RecognizerListener() {

    @Override
    public void onBeginOfSpeech() {
        //此回调表示:sdk内部录音机已经准备好,用户可以开始语音输入showTip("开始说话");
    }
    @Override
    public void onError(SpeechError error) {
        //Tips:
        //错误码: 10118(没有说话),可能事录音机权限被禁,需要提示用户打开应用的录音机权限。showTip(error.getPlainDescription(true));
    }

    @Override
    public void onEndOfSpeech() {
        //此回调表示:检测了语音的尾端点,已经进入识别过程,不再接收语音输入showTip("结束说话");      IatStart(); //这里检测识别完毕之后,再次重新启动监听
    }
    @Override
    //
    public void onResult(RecognizerResult results, boolean isLast) {
        //Log是Android的日志工具类,用来打印日志
        //对应的Debug调试
        //第一个参数tag:打印信息的标签(标志)(如果设置该参数为TAG,在查看logcat时,可以通过搜索栏来搜索标签为TAG的打印信息)
        //第二个参数msg:表示需要打印出来的信息
        Log.d(TAG, results.getResultString());
        // TODO 自动生成的方法存根
        // Log.d("Result", results.getResultString());
        // contentTv.setText(results.getResultString());
        if (!isLast) { //isLast用来判断是否最后一次
            dictationResultStr += results.getResultString() + ",";
          
        } else {
            dictationResultStr += results.getResultString() + "]";
        }
        if (isLast) {
            // 解析Json列表字符串
            Gson gson = new Gson();
            List<DictationResult> dictationResultList = gson
                    .fromJson(dictationResultStr,
                            new TypeToken<List<DictationResult>>() {
                            }.getType());
            String finalResult = "";
            for (int i = 0; i < dictationResultList.size() - 1; i++) {
                finalResult += dictationResultList.get(i)
                        .toString();
            }
            //rfid射频识别技术,在下面介绍RFID技术的优点,这里为什么使用的是RFID技术。
            if(rfid.equals(""))speakText("请先尝试识别到盲道上的电子标签点,以便我们确认您的位置。");
            else if(finalResult.equals("导航"))
            {
              speakText4("系统正在为您导航,是否启动路况识别功能");
            }
            else if(finalResult.equals(""))
            {
                Speech();
            }
            else if(finalResult.equals("当前位置"))
            {
                String now_position = dao.curPositionQuery(rfid);
                speakText("您当前的位置是"+ now_position);
            }
            else if(finalResult.equals("使用说明")){
                speakText("您可以通过说,导航,来获取导航服务,在导航服务中,你可以选择启动或者不启动路况识别功能,若启动路况识别功能,则会对行走过程中的障碍物进行识别播报,在非导航过程中,您可以通过说,当前位置,来确定自己所在的位置");
            }
            else
            {
                speakText("未识别到正确指令,请重复您想进行的操作");
            }
        }
    }

    @Override
    public void onVolumeChanged(int volume, byte[] data) {
    //showTip("当前正在说话,音量大小:" + volume);
    //Log.d(TAG, "返回音频数据:" + data.length);
    }
    

    @Override
    public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
//云端会话
    }
};

 通过接口RecognizerListener获取当前识别的状态和结果

 在讯飞自己检测到停止的回调中再次开启:

private RecognizerListener mRecognizerListener = new RecognizerListener() {
...
    //识别结果
     @Override
        public void onResult(RecognizerResult results, boolean isLast) {
                  Log.d(TAG, results.getResultString());
    ...
}
}

 其中RecognizerResult识别结果为文本内容

 getResultString获取识别结果,返回:String类型识别结果文本。返回的结果文本有可能为空字符串。

Gson gson = new Gson();

Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。

使用时:

使用Maven导入依赖:

    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.5</version>
    </dependency>

Gradle导入依赖:

compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'

创建Gson示例

1.1、new Gson()

示例如下:

   Gson gson = new Gson();

1.2、GsonBuilder.build()

示例如下:

   GsonBuilder builder = new GsonBuilder();
   Gson gson = builder.create();
List<DictationResult> dictationResultList = gson
        .fromJson(dictationResultStr,
                new TypeToken<List<DictationResult>>() {
                }.getType());

gson的反序列化,Gson提供了fromJson()方法来实现从Json相关对象到java实体的方法,一般情况下有两种情况,一种是转成单一实体对象,另一种就是上面的转换成对象列表或其他结构。

上面的代码可以看到使用了TypeToken,它是gson提供的数据类型转换器,可以支持各种数据集合类型转换。

另外一种:

比如json字符串为:[{"name":"name0","age":0}] 代码:

Person person = gson.fromJson(str, Person.class);  

提供两个参数,分别是json字符串以及需要转换对象的类型。  

-------------------------------------补充说明---------------------------------------------

RFID射频识别技术

一、 概述:

无线射频识别技术,是自动识别技术的一种 ,通过无线电波不接触快速信息交换和存储技术,通过无线通信结合数据访问技术,然后连接数据库系统,加以实现非接触式的双向通信,从而达到识别的目的,在识别系统中,通过电磁波实现电子标签的读写与通信。根据通信距离,可分为近场和远场,为此读/写设备和电子标签之间的数据交换方式也对应地被分为负载调制和反向散射调制。

二、组成:

从结构上来看,无线射频识别系统是一种简单的无线系统。最基本的无线射频识别系统由电子标签、读写器、应用系统软件构成[9]。RFID电子标签:它由耦合组件及芯片构成,每个电子标签都有独特的电子编码,放在被测目标上以达到标记目标物体的目的;RFID读写器:不仅能够读取电子标签上的信息,而且还能够写入电子标签上的信息。应用软件系统:把接收的数据进一步的处理成人们所需要的数据是它的主要任务。

 三、 工作原理:

电子标签进入电磁场后,接收读写器发出的射频信号,无源电子标签或者被动电子标签利用空间中产生的电磁场得到的能量,将被测物体的信息传送出去,读写器读取信息并且进行解码后,将信息传送到中央信息系统进行相应的数据处理。有源电子标签或者主动电子标签则是主动发射射频信号,然后读写器读取信息并进行解码后,将信息传送到中央信息系统进行相应的数据处理。

 四、 优点:

  1. 抗干扰性超强,具有最重要的优点就是非接触式识别,在极具恶劣的环境下都能工作,并且穿透力极强,可以快速识别并阅读标签;因为我们做的项目的主要使用对象是盲人,目的是实现盲人独立出行方便,自然需要考虑到系统在极端情况下是否还能正常运行,同时快速识别。

  2. RFID标签的数据容量庞大,可以根据用户的需求扩充到10k,远远高于二维码条形2725个数字的容量。这个优点可以支持系统存储更多的线路数据。

  3. 可以动态操作,它的标签数据可以利用编程进行动态修改,并且只要RFID标签所附着的物体出现在解读器的有效识别范围内,就可以动态追踪和监控。这个优点可以有效的追踪用户当前的位置,以及其所在周围的环境,保障了用户的安全性问题。

  4. 使用寿命长,其抗干扰性强,所以不易被破坏,使用寿命很长。可使系统的使用期限长。

  5. 防冲突性强,在解读器有效的识别范围内,可以同时读取多个RFID标签。

  6. 安全性高,可以以任何形式附着在产品上,可以为标签数据进行密码加密,提高安全性。

  7. 识别速度快,只要进入RFID标签已进入解读器的有效识别范围内,就能马上开始读取数据。

  8. 无需可视、批量读取,大量的RFID标签可被读写器同时快速、批量读取。

  9. 读取距离远,根据读写器的功率和天线的增益率,读取距离可从几十厘米到几米不等;现在远距离可以达到几十米以上。因为可以精准定位到厘米级,所以RFID也是极好的室内定位的有效手段。

  10. 全球唯一性,不可复制。每个RFID标签都是唯一的,在生产标签过程中,便已将标签与商品信息绑定,所以在后续商品流通、使用过程中,这个标签都是唯一代表所对应的那一件商品。

3.启动功能

 protected void Speech2(){
        dictationResultStr = "[";
        // 语音配置对象初始化
        SpeechUtility.createUtility(MainActivity.this, SpeechConstant.APPID
                + "=" + APPID);
        // 1.创建SpeechRecognizer对象,第2个参数:本地听写时传InitListener
        SpeechRecognizer mIat = SpeechRecognizer.createRecognizer(
                MainActivity.this, null);
        //设置语法ID和 SUBJECT 为空,以免因之前有语法调用而设置了此参数;或直接清空所有参数,具体可参考 DEMO 的示例。
        mIat.setParameter( SpeechConstant.CLOUD_GRAMMAR, null );
        mIat.setParameter( SpeechConstant.SUBJECT, null );
//设置返回结果格式,目前支持json,xml以及plain 三种格式,其中plain为纯听写文本内容
        mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");
//此处engineType为“cloud”
        mIat.setParameter( SpeechConstant.ENGINE_TYPE, "cloud" );
//设置语音输入语言,zh_cn为简体中文
        mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");
//设置结果返回语言
        mIat.setParameter(SpeechConstant.ACCENT, "mandarin");
// 设置语音前端点:静音超时时间,单位ms,即用户多长时间不说话则当做超时处理
//取值范围{1000~10000}
        mIat.setParameter(SpeechConstant.VAD_BOS, "2000");
//设置语音后端点:后端点静音检测时间,单位ms,即用户停止说话多长时间内即认为不再输入,
//自动停止录音,范围{0~10000}
        mIat.setParameter(SpeechConstant.VAD_EOS, "1000");
//设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点
        mIat.setParameter(SpeechConstant.ASR_PTT,"0");


        // 开始听写
        mIat.startListening(mRecognizerListener2);
    }
    private RecognizerListener mRecognizerListener2 = new RecognizerListener() {

        @Override
        public void onBeginOfSpeech() {
        }
        @Override
        public void onError(SpeechError error) {
        }

        @Override
        public void onEndOfSpeech() {
        }
        @Override
        public void onResult(RecognizerResult results, boolean isLast) {
            Log.d(TAG, results.getResultString());
            // TODO 自动生成的方法存根
            // Log.d("Result", results.getResultString());
            // contentTv.setText(results.getResultString());
            if (!isLast) {
                dictationResultStr += results.getResultString() + ",";
            } else {
                dictationResultStr += results.getResultString() + "]";
            }
            if (isLast) {
                // 解析Json列表字符串
                Gson gson = new Gson();
                List<DictationResult> dictationResultList = gson
                        .fromJson(dictationResultStr,
                                new TypeToken<List<DictationResult>>() {
                                }.getType());
                String finalResult = "";
                for (int i = 0; i < dictationResultList.size() - 1; i++) {
                    finalResult += dictationResultList.get(i)
                            .toString();
                }
                if(finalResult.equals("启动"))
                {
                    kaiqi = true;
                    speakText1("路况识别功能已启动,请说出您想到达的目的地");
                }
                else if(finalResult.equals("不启动"))
                {
                    kaiqi = false;
                    speakText1("路况识别功能未启动,请说出您想到达的目的地");
                }
                else
                {
                    speakText4("未识别到正确指令,如需路况识别功能,请说启动,无需请说不启动");
                }
            }
        }

        @Override
        public void onVolumeChanged(int volume, byte[] data) {
        }

        @Override
        public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {

        }
    };


总结

导航语音识别功能之语音识别,其实是利用科大讯飞的语音识别SDK,实现语音转写将用户提供的语音指令转换为字符串,使得系统可以识别到该指令,实现对应的操作。

Logo

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

更多推荐