科大讯飞 语音识别

2018-08-27 22:30:29 hfut_why 阅读数 11416

      最近因为项目的需求,需要在无网络的情况下实现语音识别的功能,因为之前在线识别一直用的科大的,所以经理就和我说,你花半天时间简单熟悉一下,然后出一个Demo,下午有人过来看;因为之前科大在线SR也是别人做的,准确的说我只是了解过一点,也写过相关的blog——百度语音识别结合云知声离线TTSDemo(AS)Android原生TTS的基本使用以及配合中文语音包实现中文TTS等,但是就半天不到的时间写一个Demo还是很赶的,比较不熟悉。下面就来简单的总结一下这半天的经历。   

源码下载地址

 

第一阶段    基础准备

第一步:找到科大讯飞开发平台官网,注册账户

平台地址

第二步:点击右上角“控制台”进入个人控制台

第三步:创建应用,根据选择的服务生成SDK并下载

 

      这里我们添加离线命令词识别服务,获取了对应SDK之后,也就完成的最基本的准备工作了,生成的APPID很重要哟,这个不用说你也应该知道。我们的第一阶段就算完成了

 

第二阶段    Demo导入

第四步:打开AS,创建一个和上图同名的应用

第五步:导入SDK解压文件夹下的sample目录里面的的mscV5PlusDemomodule

 

这里面需要实现在AS项目中导入module操作,如下图所示:

 

选择上面sample下面对应的mscV5PlusDemo即可,如果有需要调整sdk版本的就按照错误提示调整就好了,比较简单;至此,我们就把SDK中的Demo(mscV5PlusDemo)导入到了我们的项目中:

 

第六步:这个时候选择导入的module,在arm机上运行,发现并不能正常运行,那么你需要考虑以下几个问题

(1)Demo中的离线命令词识别的commen.jet文件位置错误

在解压文件夹的res目录下找到asr文件夹,将其copy到Demo里面的assets目录下:

 

(2)一定要在arm机上测试,因为这个Demo里面只有armeabi的so文件

(3)如果可以运行,进入如下界面,发现里面不仅仅只有我们需要的离线命令词识别,还有在线识别等等:

 

我们点击“立刻体验语法识别”,关闭设备网络,选择下图中的“本地”,然后点击“构建语法”,再点击“开始识别”;

这个时候很有可能再报错误,查看错误码发现原来是没有录音权限等权限问题,这个时候你就纳闷了,明明Demo代码中已经添加了权限:

   <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" />

为什么还有问题,这个时候你再进入到Demo代码里面查看,里面并没有做6.0以及以上版本的动态权限申请处理,所以怎么办了,要么我们自己加上,要么换一个低一点的机子测试一下。

// 开始识别,没有权限判断
case R.id.isr_recognize:
   ((EditText)findViewById(R.id.isr_text)).setText(null);// 清空显示内容
   // 设置参数
   if (!setParam()) {
      showTip("请先构建语法。");
      return;
   };
   
   ret = mAsr.startListening(mRecognizerListener);
   if (ret != ErrorCode.SUCCESS) {
      showTip("识别失败,错误码: " + ret);   
   }
   break;

这里我们就不深究了,因为后面还有好多内容了,假设这个时候你能够正常运行了,也能在Demo中完成离线命令词识别了。那么下一阶段就是瘦身处理了。

 

第三阶段    功能瘦身

第七步:提取离线命令词识别功能

      不得不说,这个Demo对于我们只使用离线命令词识别来说有一点冗余,太多了;下面我们就来把离线命令词功能抽取出来,如下图:

 

实现离线命令词识别的功能实现主要是上图中红色框中AsrDemo中的逻辑,其源码如下:

package com.iflytek.mscv5plusdemo;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.Toast;

import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.LexiconListener;
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.util.ContactManager;
import com.iflytek.cloud.util.ContactManager.ContactListener;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;
import com.iflytek.speech.util.FucUtil;
import com.iflytek.speech.util.JsonParser;
import com.iflytek.speech.util.XmlParser;

public class AsrDemo extends Activity implements OnClickListener{
   private static String TAG = AsrDemo.class.getSimpleName();
   // 语音识别对象
   private SpeechRecognizer mAsr;
   private Toast mToast;  
   // 缓存
   private SharedPreferences mSharedPreferences;
   // 本地语法文件
   private String mLocalGrammar = null;
   // 本地词典
   private String mLocalLexicon = null;
   // 云端语法文件
   private String mCloudGrammar = null;
   // 本地语法构建路径    
   private String grmPath = Environment.getExternalStorageDirectory()
                        .getAbsolutePath() + "/msc/test";
   // 返回结果格式,支持:xml,json
   private String mResultType = "json";
   
   private  final String KEY_GRAMMAR_ABNF_ID = "grammar_abnf_id";
   private  final String GRAMMAR_TYPE_ABNF = "abnf";
   private  final String GRAMMAR_TYPE_BNF = "bnf";

   private String mEngineType = "cloud";
   @SuppressLint("ShowToast")
   public void onCreate(Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);
      this.requestWindowFeature(Window.FEATURE_NO_TITLE);
      setContentView(R.layout.isrdemo);
      initLayout();
      
      // 初始化识别对象
      mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);    

      // 初始化语法、命令词
      mLocalLexicon = "张海羊\n刘婧\n王锋\n";
      mLocalGrammar = FucUtil.readFile(this,"call.bnf", "utf-8");
      mCloudGrammar = FucUtil.readFile(this,"grammar_sample.abnf","utf-8");
      
      // 获取联系人,本地更新词典时使用
      ContactManager mgr = ContactManager.createManager(AsrDemo.this, mContactListener); 
      mgr.asyncQueryAllContactsName();
      mSharedPreferences = getSharedPreferences(getPackageName(),    MODE_PRIVATE);
      mToast = Toast.makeText(this,"",Toast.LENGTH_SHORT);   
      
   }
   
   /**
    * 初始化Layout。
    */
   private void initLayout(){
      findViewById(R.id.isr_recognize).setOnClickListener(this);
      
      findViewById(R.id.isr_grammar).setOnClickListener(this);
      findViewById(R.id.isr_lexcion).setOnClickListener(this);
      
      findViewById(R.id.isr_stop).setOnClickListener(this);
      findViewById(R.id.isr_cancel).setOnClickListener(this);

      //选择云端or本地
      RadioGroup group = (RadioGroup)this.findViewById(R.id.radioGroup);
      group.setOnCheckedChangeListener(new OnCheckedChangeListener() {
         @Override
         public void onCheckedChanged(RadioGroup group, int checkedId) {
            if(checkedId == R.id.radioCloud)
            {
               ((EditText)findViewById(R.id.isr_text)).setText(mCloudGrammar);
               findViewById(R.id.isr_lexcion).setEnabled(false);
               mEngineType = SpeechConstant.TYPE_CLOUD;
            }else if(checkedId == R.id.radioLocal)
            {
               ((EditText)findViewById(R.id.isr_text)).setText(mLocalGrammar);
               findViewById(R.id.isr_lexcion).setEnabled(true);
               mEngineType =  SpeechConstant.TYPE_LOCAL;
            }
         }
      });
   }
    
   
   String mContent;// 语法、词典临时变量
    int ret = 0;// 函数调用返回值
   @Override
   public void onClick(View view) {      
      if( null == mAsr ){
         // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
         this.showTip( "创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化" );
         return;
      }
      
      if(null == mEngineType) {
         showTip("请先选择识别引擎类型");
         return;
      }  
      switch(view.getId())
      {
         case R.id.isr_grammar:
            showTip("上传预设关键词/语法文件");
            // 本地-构建语法文件,生成语法id
            if (mEngineType.equals(SpeechConstant.TYPE_LOCAL)) {
               ((EditText)findViewById(R.id.isr_text)).setText(mLocalGrammar);
               mContent = new String(mLocalGrammar);
               mAsr.setParameter(SpeechConstant.PARAMS, null);
               // 设置文本编码格式
               mAsr.setParameter(SpeechConstant.TEXT_ENCODING,"utf-8");
               // 设置引擎类型
               mAsr.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
               // 设置语法构建路径
               mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
               //使用8k音频的时候请解开注释
//             mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
               // 设置资源路径
               mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
               ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
               if(ret != ErrorCode.SUCCESS){
                  showTip("语法构建失败,错误码:" + ret);
               }
            }
            // 在线-构建语法文件,生成语法id
            else { 
               ((EditText)findViewById(R.id.isr_text)).setText(mCloudGrammar);
               mContent = new String(mCloudGrammar);
               // 指定引擎类型
               mAsr.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
               // 设置文本编码格式
               mAsr.setParameter(SpeechConstant.TEXT_ENCODING,"utf-8");
                ret = mAsr.buildGrammar(GRAMMAR_TYPE_ABNF, mContent, grammarListener);
               if(ret != ErrorCode.SUCCESS)
                  showTip("语法构建失败,错误码:" + ret);
            }
            break;
         // 本地-更新词典
         case R.id.isr_lexcion: 
            ((EditText)findViewById(R.id.isr_text)).setText(mLocalLexicon);
            mContent = new String(mLocalLexicon);
            mAsr.setParameter(SpeechConstant.PARAMS, null);
            // 设置引擎类型
            mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
            // 设置资源路径
            mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
            //使用8k音频的时候请解开注释
//          mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
            // 设置语法构建路径
            mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
            // 设置语法名称
            mAsr.setParameter(SpeechConstant.GRAMMAR_LIST, "call");
            // 设置文本编码格式
            mAsr.setParameter(SpeechConstant.TEXT_ENCODING,"utf-8");
            ret = mAsr.updateLexicon("contact", mContent, lexiconListener);
            if(ret != ErrorCode.SUCCESS){
               showTip("更新词典失败,错误码:" + ret);
            }
            break;
         // 开始识别
         case R.id.isr_recognize:
            ((EditText)findViewById(R.id.isr_text)).setText(null);// 清空显示内容
            // 设置参数
            if (!setParam()) {
               showTip("请先构建语法。");
               return;
            };
            
            ret = mAsr.startListening(mRecognizerListener);
            if (ret != ErrorCode.SUCCESS) {
               showTip("识别失败,错误码: " + ret);   
            }
            break;
         // 停止识别
         case R.id.isr_stop:
            mAsr.stopListening();
            showTip("停止识别");
            break;
         // 取消识别
         case R.id.isr_cancel:
            mAsr.cancel();
            showTip("取消识别");
            break;
      }
   }
   
   /**
     * 初始化监听器。
     */
    private InitListener mInitListener = new InitListener() {

      @Override
      public void onInit(int code) {
         Log.d(TAG, "SpeechRecognizer init() code = " + code);
         if (code != ErrorCode.SUCCESS) {
              showTip("初始化失败,错误码:"+code);
           }
      }
    };
       
   /**
     * 更新词典监听器。
     */
   private LexiconListener lexiconListener = new LexiconListener() {
      @Override
      public void onLexiconUpdated(String lexiconId, SpeechError error) {
         if(error == null){
            showTip("词典更新成功");
         }else{
            showTip("词典更新失败,错误码:"+error.getErrorCode());
         }
      }
   };
   
   /**
     * 构建语法监听器。
     */
   private GrammarListener grammarListener = new GrammarListener() {
      @Override
      public void onBuildFinish(String grammarId, SpeechError error) {
         if(error == null){
            if (mEngineType.equals(SpeechConstant.TYPE_CLOUD)) {
               Editor editor = mSharedPreferences.edit();
               if(!TextUtils.isEmpty(grammarId))
                  editor.putString(KEY_GRAMMAR_ABNF_ID, grammarId);
               editor.commit();
            }
            showTip("语法构建成功:" + grammarId);
         }else{
            showTip("语法构建失败,错误码:" + error.getErrorCode());
         }        
      }
   };
   /**
    * 获取联系人监听器。
    */
   private ContactListener mContactListener = new ContactListener() {
      @Override
      public void onContactQueryFinish(String contactInfos, boolean changeFlag) {
         //获取联系人
         mLocalLexicon = contactInfos;
      }     
   };
   /**
     * 识别监听器。
     */
    private RecognizerListener mRecognizerListener = new RecognizerListener() {
        
        @Override
        public void onVolumeChanged(int volume, byte[] data) {
           showTip("当前正在说话,音量大小:" + volume);
           Log.d(TAG, "返回音频数据:"+data.length);
        }
        
      @Override
      public void onResult(final RecognizerResult result, boolean isLast) {
         if (null != result && !TextUtils.isEmpty(result.getResultString())) {
            Log.d(TAG, "recognizer result:" + result.getResultString());
            String text = "";
            if (mResultType.equals("json")) {
               text = JsonParser.parseGrammarResult(result.getResultString(), mEngineType);
            } else if (mResultType.equals("xml")) {
               text = XmlParser.parseNluResult(result.getResultString());
            }
            // 显示
            ((EditText) findViewById(R.id.isr_text)).setText(text);
         } else {
            Log.d(TAG, "recognizer result : null");
         }
      }
        
        @Override
        public void onEndOfSpeech() {
           // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入          
         showTip("结束说话");
        }
        
        @Override
        public void onBeginOfSpeech() {
           // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
           showTip("开始说话");
        }

      @Override
      public void onError(SpeechError error) {
         showTip("onError Code:"    + error.getErrorCode());
      }

      @Override
      public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
         // 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
         // 若使用本地能力,会话id为null
         // if (SpeechEvent.EVENT_SESSION_ID == eventType) {
         //    String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
         //    Log.d(TAG, "session id =" + sid);
         // }
      }

    };
    
   

   private void showTip(final String str) {
      runOnUiThread(new Runnable() {
         @Override
         public void run() {
            mToast.setText(str);
            mToast.show();
         }
      });
   }

   /**
    * 参数设置
    * @param
    * @return 
    */
   public boolean setParam(){
      boolean result = false;
      // 清空参数
      mAsr.setParameter(SpeechConstant.PARAMS, null);
      // 设置识别引擎
      mAsr.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
      if("cloud".equalsIgnoreCase(mEngineType))
      {
         String grammarId = mSharedPreferences.getString(KEY_GRAMMAR_ABNF_ID, null);
         if(TextUtils.isEmpty(grammarId))
         {
            result =  false;
         }else {
            // 设置返回结果格式
            mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
            // 设置云端识别使用的语法id
            mAsr.setParameter(SpeechConstant.CLOUD_GRAMMAR, grammarId);
            result =  true;
         }
      }
      else
      {
         // 设置本地识别资源
         mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
         // 设置语法构建路径
         mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
         // 设置返回结果格式
         mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
         // 设置本地识别使用语法id
         mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
         // 设置识别的门限值
         mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");
         // 使用8k音频的时候请解开注释
//       mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
         result = true;
      }
      
      // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
      // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
      mAsr.setParameter(SpeechConstant.AUDIO_FORMAT,"wav");
      mAsr.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/asr.wav");
      return result;
   }
   
   //获取识别资源路径
   private String getResourcePath(){
      StringBuffer tempBuffer = new StringBuffer();
      //识别通用资源
      tempBuffer.append(ResourceUtil.generateResourcePath(this, RESOURCE_TYPE.assets, "asr/common.jet"));
      //识别8k资源-使用8k的时候请解开注释
//    tempBuffer.append(";");
//    tempBuffer.append(ResourceUtil.generateResourcePath(this, RESOURCE_TYPE.assets, "asr/common_8k.jet"));
      return tempBuffer.toString();
   }
   
   @Override
   protected void onDestroy() {
      super.onDestroy();
      if( null != mAsr ){
         // 退出时释放连接
         mAsr.cancel();
         mAsr.destroy();
      }
   }
   
}

看着还是比较多的,我之所以说多而没有说难就是因为它并不难;下面的介绍中我们会对其进行再次瘦身。

 

第八步:为自己的Demo做准备工作

(1)把assets目录copy到我们的module中

(2)把jniLibs目录copy到我们的module中

这里是在Project视图下完成的,这里在Android视图下展示效果更好一下

(3)打开Project视图,把libs目录中的内容复制到我们的module中

(4)在build.gradle(Module:app)中的depandencies下添加依赖:

compile files('libs/Msc.jar')

 

(5)把Demo中的工具类copy到我们的module中

 

截止到现在,我们还在准备阶段,下面就进入正题,来对我们的需要的功能的实现做一个简要的梳理

 

第九步:提取离线命令词识别功能到我们的项目

定义一个activity,CallStepActivity,把AsrDemo中的逻辑代码copy到CallStepActivty中,把对应的布局文件也对应copy进来

 

第十步:梳理逻辑,继续瘦身

上面也说了,AsrDemo中的Demo还是有点冗余,因为好多我们用不上或者暂时用不上,比如在线的命令词识别等肯定用不上,比如词典更新我们暂时用不上,下面就来分析一下单纯使用离线命令词识别的实现(下面是重点

(1)根据应用ID初始化SpeechUtility,通常在程序入口Application中完成

package com.hfut.offlinerecongnizer.activity.util;

import android.app.Application;

import com.hfut.offlinerecongnizer.R;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechUtility;

/**
 * author:why
 * created on: 2018/8/27 11:10
 * description:
 */
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        // 应用程序入口处调用,避免手机内存过小,杀死后台进程后通过历史intent进入Activity造成SpeechUtility对象为null
        // 注意:此接口在非主进程调用会返回null对象,如需在非主进程使用语音功能,请增加参数:SpeechConstant.FORCE_LOGIN+"=true"
        // 参数间使用“,”分隔。
        // 设置你申请的应用appid
        // 注意: appid 必须和下载的SDK保持一致,否则会出现10407错误
        StringBuffer param = new StringBuffer();
        param.append("appid=" + getString(R.string.app_id));
        param.append(",");
        // 设置使用v5+
        param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC);
        SpeechUtility.createUtility(MyApplication.this, param.toString());
        super.onCreate();
    }
}

(2)在Activity中初始化初始化监听器,用于初始化语音识别引擎

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

    @Override
    public void onInit(int code) {
        Log.d(TAG, "SpeechRecognizer init() code = " + code);
        if (code != ErrorCode.SUCCESS) {
            showTip("初始化失败,错误码:" + code);
        }
    }
};

(3)初始化语音识别监听器

/**
 * 识别监听器。
 */
private RecognizerListener mRecognizerListener = new RecognizerListener() {

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

    @Override
    public void onResult(final RecognizerResult result, boolean isLast) {
        if (null != result && !TextUtils.isEmpty(result.getResultString())) {
            Log.d(TAG, "recognizer result:" + result.getResultString());
            String text = "";
            if (mResultType.equals("json")) {
                text = JsonParser.parseGrammarResult(result.getResultString(), SpeechConstant.TYPE_LOCAL);
            } else if (mResultType.equals("xml")) {
                text = XmlParser.parseNluResult(result.getResultString());
            }
            // 显示
            ((EditText) findViewById(R.id.isr_text)).setText(text);
        } else {
            Log.d(TAG, "recognizer result : null");
        }
    }

    @Override
    public void onEndOfSpeech() {
        // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
        showTip("结束说话");
    }

    @Override
    public void onBeginOfSpeech() {
        // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
        showTip("开始说话");
    }

    @Override
    public void onError(SpeechError error) {
        showTip("onError Code:" + error.getErrorCode());
    }

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

    }
};

(4)初始化语法文件构建监听器

/**
 * 构建语法监听器。
 */
private GrammarListener grammarListener = new GrammarListener() {
    @Override
    public void onBuildFinish(String grammarId, SpeechError error) {
        if (error == null) {
            showTip("语法构建成功:" + grammarId);
        } else {
            showTip("语法构建失败,错误码:" + error.getErrorCode());
        }
    }
};

(5)初始化语音识别引擎并完成参数设置

// 初始化识别引擎
mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);
//设置识别引擎参数
setParam();

其中setPatam():

public void setParam() {
    boolean result = true;
    // 清空参数
    mAsr.setParameter(SpeechConstant.PARAMS, null);
    // 设置识别引擎
    mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
    // 设置本地识别资源
    mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
    // 设置语法构建路径
    mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
    // 设置返回结果格式
    mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
    // 设置本地识别使用语法id
    mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
    // 设置识别的门限值
    mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");

}

(6)完成语法构建

private void  buildGrammer() {
        mLocalGrammar = FucUtil.readFile(this, "call.bnf", "utf-8");
        // 本地-构建语法文件,生成语法id
        ((EditText) findViewById(R.id.isr_text)).setText(mLocalGrammar);
        mContent = new String(mLocalGrammar);
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 设置文本编码格式
        mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
        // 设置引擎类型
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
        // 设置语法构建路径
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
        // 设置资源路径
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
        if (ret != ErrorCode.SUCCESS) {
            showTip("语法构建失败,错误码:" + ret);
        } else {
            showTip("语法构建成功");
        }

    }

(7)开启识别,停止识别,取消识别分别是:

 mAsr.startListening(mRecognizerListener);
 mAsr.stopListening();
 mAsr.cancel();

第十一步:最简单的功能实现代码

所以最后组合起来,我们实现剥离了所有其他功能的只是实现离线命令词识别的代码,CallStepActivity代码如下:

package com.hfut.offlinerecongnizer.activity.activity;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.SharedPreferences;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.hfut.offlinerecongnizer.R;
import com.hfut.offlinerecongnizer.activity.util.FucUtil;
import com.hfut.offlinerecongnizer.activity.util.JsonParser;
import com.hfut.offlinerecongnizer.activity.util.XmlParser;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.LexiconListener;
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.util.ContactManager;
import com.iflytek.cloud.util.ResourceUtil;

/**
 * @author why
 * @date 2018-8-27 15:09:38
 */
public class CallStepActivity extends AppCompatActivity implements View.OnClickListener {

    private static String TAG = OffLineTestActivity.class.getSimpleName();
    // 语音识别对象
    private SpeechRecognizer mAsr;
    private Toast mToast;
    // 本地语法文件
    private String mLocalGrammar = null;
    // 本地语法构建路径
    private String grmPath = Environment.getExternalStorageDirectory()
            .getAbsolutePath() + "/msc/call";
    // 返回结果格式,支持:xml,json
    private String mResultType = "json";
    private final String GRAMMAR_TYPE_BNF = "bnf";

    @SuppressLint("ShowToast")
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_call_step);
        initLayout();
        mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);
        // 初始化识别引擎
        mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);

        //构建本地语法
        buildGrammer();
    }

    /**
     * 初始化Layout。
     */
    private void initLayout() {
        findViewById(R.id.isr_recognize).setOnClickListener(this);
        findViewById(R.id.isr_stop).setOnClickListener(this);
        findViewById(R.id.isr_cancel).setOnClickListener(this);
    }

    String mContent;// 语法、词典临时变量
    int ret = 0;// 函数调用返回值

    @Override
    public void onClick(View view) {
        if (null == mAsr) {
            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
            this.showTip("创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化");
            return;
        }
        switch (view.getId()) {

            // 开始识别
            case R.id.isr_recognize:
                ((EditText) findViewById(R.id.isr_text)).setText(null);// 清空显示内容
                //设置识别引擎参数
                setParam();
                ret = mAsr.startListening(mRecognizerListener);
                if (ret != ErrorCode.SUCCESS) {
                    showTip("识别失败,错误码: " + ret);
                }
                break;

            // 停止识别
            case R.id.isr_stop:
                mAsr.stopListening();
                showTip("停止识别");
                break;

            // 取消识别
            case R.id.isr_cancel:
                mAsr.cancel();
                showTip("取消识别");
                break;
        }
    }

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

        @Override
        public void onInit(int code) {
            Log.d(TAG, "SpeechRecognizer init() code = " + code);
            if (code != ErrorCode.SUCCESS) {
                showTip("初始化失败,错误码:" + code);
            }
        }
    };


    /**
     * 构建语法监听器。
     */
    private GrammarListener grammarListener = new GrammarListener() {
        @Override
        public void onBuildFinish(String grammarId, SpeechError error) {
            if (error == null) {
                showTip("语法构建成功:" + grammarId);
            } else {
                showTip("语法构建失败,错误码:" + error.getErrorCode());
            }
        }
    };

    /**
     * 识别监听器。
     */
    private RecognizerListener mRecognizerListener = new RecognizerListener() {

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

        @Override
        public void onResult(final RecognizerResult result, boolean isLast) {
            if (null != result && !TextUtils.isEmpty(result.getResultString())) {
                Log.d(TAG, "recognizer result:" + result.getResultString());
                String text = "";
                if (mResultType.equals("json")) {
                    text = JsonParser.parseGrammarResult(result.getResultString(), SpeechConstant.TYPE_LOCAL);
                } else if (mResultType.equals("xml")) {
                    text = XmlParser.parseNluResult(result.getResultString());
                }
                // 显示
                ((EditText) findViewById(R.id.isr_text)).setText(text);
            } else {
                Log.d(TAG, "recognizer result : null");
            }
        }

        @Override
        public void onEndOfSpeech() {
            // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
            showTip("结束说话");
        }

        @Override
        public void onBeginOfSpeech() {
            // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
            showTip("开始说话");
        }

        @Override
        public void onError(SpeechError error) {
            showTip("onError Code:" + error.getErrorCode());
        }

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

        }
    };

    private void showTip(final String str) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mToast.setText(str);
                mToast.show();
            }
        });
    }

    /**
     * 参数设置
     *
     * @param
     * @return
     */
    public void setParam() {
        boolean result = true;
        // 清空参数
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 设置识别引擎
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
        // 设置本地识别资源
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        // 设置语法构建路径
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
        // 设置返回结果格式
        mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
        // 设置本地识别使用语法id
        mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
        // 设置识别的门限值
        mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");
        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
        // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
        mAsr.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        mAsr.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/asr.wav");
    }

    //获取识别资源路径
    private String getResourcePath() {
        StringBuffer tempBuffer = new StringBuffer();
        //识别通用资源
        tempBuffer.append(ResourceUtil.generateResourcePath(this, ResourceUtil.RESOURCE_TYPE.assets, "asr/common.jet"));
        return tempBuffer.toString();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != mAsr) {
            // 退出时释放连接
            mAsr.cancel();
            mAsr.destroy();
        }
    }


    private void buildGrammer() {

        mLocalGrammar = FucUtil.readFile(this, "call.bnf", "utf-8");
        // 本地-构建语法文件,生成语法id
        ((EditText) findViewById(R.id.isr_text)).setText(mLocalGrammar);
        mContent = new String(mLocalGrammar);
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 设置文本编码格式
        mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
        // 设置引擎类型
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
        // 设置语法构建路径
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
        //使用8k音频的时候请解开注释
//             mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
        // 设置资源路径
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
        if (ret != ErrorCode.SUCCESS) {
            showTip("语法构建失败,错误码:" + ret);
        } else {
            showTip("语法构建成功");
        }

    }
}

activity_call_step.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="10dip" >
    <include layout="@layout/title" />

    <EditText
        android:id="@+id/isr_text"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:gravity="top|left"
        android:textSize="20sp" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dip"
        android:layout_marginBottom="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dip"
        android:layout_marginBottom="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >
        <Button
            android:id="@+id/isr_recognize"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="开始识别"
            android:textSize="20sp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="2dip"
        android:layout_marginLeft="10dip"
        android:layout_marginRight="10dip"
        android:layout_marginTop="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/isr_stop"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="停止录音"
            android:textSize="20sp" />

        <Button
            android:id="@+id/isr_cancel"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消"
            android:textSize="20sp" />
    </LinearLayout>

</LinearLayout>

如果不出意外的话,运行应该没有任何问题的。至此,最难的最复杂的第三阶段已经结束了,下面就来看看第四阶段的工作任务:

 

第四阶段    提高

第十二步:丰富我们的功能

因为API里面提供了更新词典的功能(从这里我们也可以推出来后面介绍的bnf文件中词槽的定义也可以通过代码来实现):

mAsr.updateLexicon(groupName, mLocalLexicon, lexiconListener);

所以我们就该利用起来,毕竟如果我想修改某一个词槽的定义时,不能每次都是通过编辑bnf文件,然后在运行程序来实现,太麻烦了。这里我通过一个自定义的AlertDialog来实现对词槽的重新赋值,并列的同义词用“,”隔开即可,类似于bnf文件中的  |  符号;下面直接给出OffLineTestActivity代码:

package com.hfut.offlinerecongnizer.activity.activity;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.Toast;

import com.hfut.offlinerecongnizer.R;
import com.hfut.offlinerecongnizer.activity.util.FucUtil;
import com.hfut.offlinerecongnizer.activity.util.JsonParser;
import com.hfut.offlinerecongnizer.activity.util.XmlParser;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.LexiconListener;
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.util.ContactManager;
import com.iflytek.cloud.util.ContactManager.ContactListener;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;

/**
 * @author why
 * @date 2018-8-27 13:20:58
 */
public class OffLineTestActivity extends AppCompatActivity implements View.OnClickListener {

    private static String TAG = OffLineTestActivity.class.getSimpleName();
    // 语音识别对象
    private SpeechRecognizer mAsr;
    private Toast mToast;
    // 缓存
    //private SharedPreferences mSharedPreferences;
    // 本地语法文件
    private String mLocalGrammar = null;
    // 本地词典
    private String mLocalLexicon = null;
    // 本地语法构建路径
    private String grmPath = Environment.getExternalStorageDirectory()
            .getAbsolutePath() + "/msc/call";
    // 返回结果格式,支持:xml,json
    private String mResultType = "json";
    private final String GRAMMAR_TYPE_BNF = "bnf";

    private String groupName;
    private String groupInfo;

    @SuppressLint("ShowToast")
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_off_line_test);
        initLayout();

        // 初始化识别引擎对象
        mAsr = SpeechRecognizer.createRecognizer(this, mInitListener);
        mToast = Toast.makeText(this, "", Toast.LENGTH_SHORT);

        //构建本地语法
        buildGrammer();
    }

    /**
     * 初始化Layout
     */
    private void initLayout() {
        findViewById(R.id.isr_recognize).setOnClickListener(this);
        findViewById(R.id.isr_lexcion).setOnClickListener(this);
        findViewById(R.id.isr_stop).setOnClickListener(this);
        findViewById(R.id.isr_cancel).setOnClickListener(this);
    }

    String mContent;// 语法、词典临时变量
    int ret = 0;// 函数调用返回值

    @Override
    public void onClick(View view) {
        if (null == mAsr) {
            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
            this.showTip("创建对象失败,请确认 libmsc.so 放置正确,\n 且有调用 createUtility 进行初始化");
            return;
        }
        switch (view.getId()) {
            // 本地-更新词典
            case R.id.isr_lexcion:

                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                LayoutInflater inflater = LayoutInflater.from(this);
                final View v = inflater.inflate(R.layout.user_info_editor, null);
                final EditText wordGroupName = v.findViewById(R.id.enter_word_group_name);
                final EditText wordGroupInfo = v.findViewById(R.id.enter_word_group_info);
                Button cancleButton = v.findViewById(R.id.register_cancle);
                Button confirmButton = v.findViewById(R.id.register_confirm);
                final Dialog dialog = builder.create();
                //点击EditText弹出软键盘
                cancleButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(OffLineTestActivity.this, "取消", Toast.LENGTH_SHORT).show();
                        dialog.cancel();
                    }
                });


                confirmButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {

                        if (!wordGroupName.getText().toString().equals("")) {
                            groupName= wordGroupName.getText().toString();
                        }
                        if (!wordGroupInfo.getText().toString().equals("")) {
                            groupInfo = wordGroupInfo.getText().toString();
                        }
                        mLocalLexicon=getUpdateInfo(groupInfo);
                        ((EditText) findViewById(R.id.isr_text)).setText(mLocalLexicon);
                        mAsr.setParameter(SpeechConstant.PARAMS, null);
                        // 设置引擎类型
                        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
                        // 设置资源路径
                        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
                        // 设置语法构建路径
                        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
                        // 设置语法名称
                        mAsr.setParameter(SpeechConstant.GRAMMAR_LIST, "call");
                        // 设置文本编码格式
                        mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
                        //执行更新操作
                        ret = mAsr.updateLexicon(groupName, mLocalLexicon, lexiconListener);
                        if (ret != ErrorCode.SUCCESS) {
                            showTip("更新词典失败,错误码:" + ret);
                        }
                        else{
                            showTip("更新词典成功" );
                        }
                        dialog.cancel();
                    }
                });

                dialog.show();
                dialog.getWindow().setContentView(v);//自定义布局应该在这里添加,要在dialog.show()的后面
                dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
                dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
                break;
            // 开始识别
            case R.id.isr_recognize:
                //设置识别引擎参数
                setParam();
                ((EditText) findViewById(R.id.isr_text)).setText(null);// 清空显示内容
                ret = mAsr.startListening(mRecognizerListener);
                if (ret != ErrorCode.SUCCESS) {
                    showTip("识别失败,错误码: " + ret);
                }
                break;
            // 停止识别
            case R.id.isr_stop:
                mAsr.stopListening();
                showTip("停止识别");
                break;
            // 取消识别
            case R.id.isr_cancel:
                mAsr.cancel();
                showTip("取消识别");
                break;
        }
    }

    private String getUpdateInfo(String groupInfo) {
        String[] wordList=groupInfo.split(",");
        StringBuilder builder=new StringBuilder();
        for(int i=0;i<wordList.length;i++){
            if(i==wordList.length-1) {
                builder.append(wordList[i] );
                Log.d(TAG, "getUpdateInfo: "+wordList[i]);
            }else{
                builder.append(wordList[i] + "\n");
            }
        }
        return builder.toString();
    }

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

        @Override
        public void onInit(int code) {
            Log.d(TAG, "SpeechRecognizer init() code = " + code);
            if (code != ErrorCode.SUCCESS) {
                showTip("初始化失败,错误码:" + code);
            }
        }
    };

    /**
     * 更新词典监听器。
     */
    private LexiconListener lexiconListener = new LexiconListener() {
        @Override
        public void onLexiconUpdated(String lexiconId, SpeechError error) {
            if (error == null) {
                showTip("词典更新成功");
            } else {
                showTip("词典更新失败,错误码:" + error.getErrorCode());
            }
        }
    };

    /**
     * 构建语法监听器。
     */
    private GrammarListener grammarListener = new GrammarListener() {
        @Override
        public void onBuildFinish(String grammarId, SpeechError error) {
            if (error == null) {
                showTip("语法构建成功:" + grammarId);
            } else {
                showTip("语法构建失败,错误码:" + error.getErrorCode());
            }
        }
    };

    /**
     * 识别监听器。
     */
    private RecognizerListener mRecognizerListener = new RecognizerListener() {

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

        @Override
        public void onResult(final RecognizerResult result, boolean isLast) {
            if (null != result && !TextUtils.isEmpty(result.getResultString())) {
                Log.d(TAG, "recognizer result:" + result.getResultString());
                String text = "";
                if (mResultType.equals("json")) {
                    text = JsonParser.parseGrammarResult(result.getResultString(), SpeechConstant.TYPE_LOCAL);
                } else if (mResultType.equals("xml")) {
                    text = XmlParser.parseNluResult(result.getResultString());
                }
                // 显示
                ((EditText) findViewById(R.id.isr_text)).setText(text);
            } else {
                Log.d(TAG, "recognizer result : null");
            }
        }

        @Override
        public void onEndOfSpeech() {
            // 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
            showTip("结束说话");
        }

        @Override
        public void onBeginOfSpeech() {
            // 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
            showTip("开始说话");
        }

        @Override
        public void onError(SpeechError error) {
            showTip("onError Code:" + error.getErrorCode());
        }

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

        }
    };

    private void showTip(final String str) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mToast.setText(str);
                mToast.show();
            }
        });
    }

    /**
     * 参数设置
     *
     * @param
     * @return
     */
    public void setParam() {
        // 清空参数
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 设置识别引擎
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE,SpeechConstant.TYPE_LOCAL);
        // 设置本地识别资源
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        // 设置语法构建路径
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
        // 设置返回结果格式
        mAsr.setParameter(SpeechConstant.RESULT_TYPE, mResultType);
        // 设置本地识别使用语法id
        mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "call");
        // 设置识别的门限值
        mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "30");
        // 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
        // 注:AUDIO_FORMAT参数语记需要更新版本才能生效
        mAsr.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
        mAsr.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory() + "/msc/asr.wav");

    }

    //获取识别资源路径
    private String getResourcePath() {
        StringBuffer tempBuffer = new StringBuffer();
        //识别通用资源
        tempBuffer.append(ResourceUtil.generateResourcePath(this, RESOURCE_TYPE.assets, "asr/common.jet"));
        return tempBuffer.toString();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != mAsr) {
            // 退出时释放连接
            mAsr.cancel();
            mAsr.destroy();
        }
    }


    private boolean buildGrammer() {

             mLocalGrammar = FucUtil.readFile(this, "call.bnf", "utf-8");
            // 本地-构建语法文件,生成语法id
            ((EditText) findViewById(R.id.isr_text)).setText(mLocalGrammar);
            mContent = new String(mLocalGrammar);
            mAsr.setParameter(SpeechConstant.PARAMS, null);
            // 设置文本编码格式
            mAsr.setParameter(SpeechConstant.TEXT_ENCODING, "utf-8");
            // 设置引擎类型
            mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
            // 设置语法构建路径
            mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, grmPath);
            //使用8k音频的时候请解开注释
//             mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
            // 设置资源路径
            mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
            ret = mAsr.buildGrammar(GRAMMAR_TYPE_BNF, mContent, grammarListener);
            if (ret != ErrorCode.SUCCESS) {
                showTip("语法构建失败,错误码:" + ret);
            }
            else{
                showTip("语法构建成功");
            }
        return true;
    }
}

activity_off_line_test.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="10dip" >
    <include layout="@layout/title" />

    <EditText
        android:id="@+id/isr_text"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:gravity="top|left"
        android:textSize="20sp" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dip"
        android:layout_marginBottom="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/isr_recognize"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="开始识别"
            android:textSize="20sp" />

        <Button
            android:id="@+id/isr_lexcion"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="更新词典"
            android:textSize="20sp"
            android:enabled="true" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="2dip"
        android:layout_marginLeft="10dip"
        android:layout_marginRight="10dip"
        android:layout_marginTop="2dip"
        android:gravity="center_horizontal"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/isr_stop"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="停止录音"
            android:textSize="20sp" />

        <Button
            android:id="@+id/isr_cancel"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消"
            android:textSize="20sp" />
    </LinearLayout>

</LinearLayout>

 

word_info_editor.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#B0C4DE"
    android:orientation="vertical">

    <TextView
        android:layout_marginLeft="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="请编辑更新信息:"
        android:textColor="#000000"
        android:textSize="30dp" />

    <LinearLayout
        android:layout_marginLeft="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="词组名称:"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/enter_word_group_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:hint="请输入词组名称" />

    </LinearLayout>

    <LinearLayout
        android:layout_marginLeft="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="词组信息:"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/enter_word_group_info"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:hint="请编写词组信息" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/register_cancle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="350dp"
            android:text="取消" />

        <Button
            android:id="@+id/register_confirm"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dp"
            android:text="确定" />

    </LinearLayout>

</LinearLayout>

其中还有一个所有布局都用到的title.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_gravity="top"
  android:gravity="center">
  <TextView
          android:text="@string/app_name"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:gravity="center"
          android:textSize="30sp"
          android:layout_margin="10dip"
    />
</LinearLayout>

上面介绍的离线命令词识别都是基于我们自己编辑的bnf文件中的规则来识别,下面给出一个文件示例:

#BNF+IAT 1.0 UTF-8;
!grammar call;

//通用词槽
!slot <want>;
!slot <deal>;
!slot <how>;

//联系相关词槽声明
!slot <contact>;//联系人
!slot <callPhone>;//联系方式
!slot <callTo>;//联系动作

//巡游相关词槽声明
!slot <destination>;//巡游点
!slot <goTo>;//去
!slot <goToPre>;//准备去

/*
专业语料相关
 */
 //办卡业务
 !slot <cardType>;

 //公积金业务
 !slot <percent>;
 !slot <wagesDeal>;
 !slot <wages>;

!start <commands>;
<commands>:<callRule>|<guideRule>|<dealCardRule>|<wagesDealRule>;

//通用语料
<want>:我想|我要|我准备;
<how>:如何|怎么|怎样;
<deal>:办理|解决|处理;

//测试语料
<contact>:黄老板|王华洋|齐带华|火警!id(119);
<callPhone>:打电话|发微信|发短信;
<callTo>:给;
<callRule>:<callTo><contact><callPhone>|<callPhone><callTo><contact>;//联系语料相关规则

//巡游语料
<destination>:卫生间|饮水机|现金柜台|取款机|充电器|大堂经理;
<goTo>:去|到|找;
<goToPre>:带我|请带我|我想;
<guideRule>:[<goToPre>]<goTo><destination>;//巡游语料相关规则

//办卡语料
<cardType>:卡|信用卡|儿童卡|储蓄卡;//卡片类型
<dealCardRule>:[<want>]<deal><cardType>;

//公积金业务
<percent>:比例;//公积金比例
<wagesDeal>:转移|提取;//处理公积金
<wages>:公积金;
<wagesDealRule>:[<how>]<wagesDeal><wages>;


具体的编辑规则请参考bnf文档编辑指南,后续我还会对这个编辑规则进行介绍,具体就介绍到这里。

注:欢迎扫码关注

 

2018-09-27 11:05:52 weixin_43112746 阅读数 9423

#用java调用科大讯飞的离线语音识别dll实现离线识别(JNA实现)(一)

本人接的任务,做离线语音识别,用的是科大讯飞的离线识别,java不支持离线了,所以下载了windows的离线包,用JNA进行调用。之前用的是jni,但是一直没有测试通过,本人又不会C++,研究了一个星期终究放弃。转而使用JNA,JNA使用只要导入一个jar包即可,JNA实际上是对JNI的一种封装。

【在线语音识别的实现看我的另一篇】科大讯飞 在线语音识别 音频来源为【文件】的java接入实现, 适用于初学者

说明

本人一边做一边写,记录问题和解决办法,留给自己思考和大家交流。如有不对的地方,欢迎批评指正!
【ps】现在已经做完,参考第二篇

一、注册用户,下载dll这个先略过,很简单,可以参考其他的……【待】
1、先进入讯飞开放平台,先注册【地址】https://www.xfyun.cn/
2、创建应用,记得平台选windows,因为java不支持离线
在这里插入图片描述

3、创建完成之后,在 我的应用 中
在这里插入图片描述

选择【离线命令词识别】
就完成创建了!

4、下载需要的SDK,这个SDK包含了动态库dll,静态库lib,头文件.h,以及非常重要的例子!
在这里插入图片描述
【点亮需要的功能,下载相应的SDK】
在这里插入图片描述
下载好之后,找出其中的dll文件,两个版本,上面是32位系统,下面是64位系统,本人是64位的
在这里插入图片描述

二、调用JNA
1下载jna的jar包
【JNA包下载地址】:https://download.csdn.net/download/weixin_43112746/10690193

2导入java工程中
在这里插入图片描述
jar包的位置,可以放在根目录,也可以像我一样创立一个lib文件夹来放置
然后
右键工程–properties—java Build Path----libraries—add jars
在这里插入图片描述
【找到相应的JNA包的位置,添加之后点击Apply and Close】就完成JNA包的导入
【JNA包下载地址】:https://download.csdn.net/download/weixin_43112746/10690193

没有做过的,先加载个例子试试!
在这里插入图片描述

创建一个class,一个interface,下面的是讯飞的dll文件放在根目录下
【接口】这里只测试登录的函数

package test;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.win32.StdCallLibrary;

public interface VoiceLib extends Library {
	  int MSPLogin(String usr, String pwd, String params);
	  VoiceLib instance = (VoiceLib)Native.loadLibrary("msc_x64", VoiceLib.class);//接口直接对动态库文件进行调用,
	  //省去JNI的繁琐步骤
		

}

【类】
package test;

import com.sun.jna.Native;

public class Voice {
		public static void main(String[] args) {
		String usr=null;
		String pwd=null;
		String params="appid=5ba4bc08";//这里填写自己的appid,从讯飞的我的应用里面粘贴过来
		int a;
		a=VoiceLib.instance.MSPLogin(usr, pwd, params);//调用接口的方法传参数
		System.out.println(“a=+a);
		
		
	}
}




输出结果a=0
很明显测试成功了!
能够继续往下写了。

又遇到问题,参数的转换!
普通的倒是还好,转换关系如下:
在这里插入图片描述
来源:https://github.com/java-native-access/jna/blob/master/www/Mappings.md
可是问题来了,讯飞中有指针,句柄,回调函数等等各种乱七八糟的参数,这可怎么转换?头顶一直乌鸦飞过……

三、参数的转换(难点)
登录做完了,再做构建语法,其中用到的函数是

int MSPAPI QISRBuildGrammar(const char *grammarType, const char *grammarContent, unsigned int grammarLength,
 const char *params, GrammarCallBack callback, void *userData);

除了无符号int那个,其他都没有!又一只乌鸦飞过……
竟然还有语法回调函数!这是什么!又一只乌鸦飞过……
查一下回调函数
【百度】这是个链接哈

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

找个资源帖参考下:
https://blog.csdn.net/l527719041/article/details/77461328

哦!原来可以这么操作!

先把const char *解决,这是要转换成String 【待】

再来解决回调函数的参数问题!
【先做这么多,下回分解】
第二篇的链接:手把手的操作——用java调用科大讯飞的离线语音识别dll实现离线识别(JNA实现)(二)

2017-07-16 20:45:59 huosanghuakai1995 阅读数 13040

#实现android语音识别


下载地址:
https://download.csdn.net/download/huosanghuakai1995/10348660

##一、准备工作

科大讯飞开放云平台:http://www.xfyun.cn,自行注册。

  • 注册完毕后下载科大讯飞云平台提供的SDK,勾选下面的服务选择android平台后,下载SDK。

这里写图片描述


  • 按照提示完成SDK配置和权限添加工作
  • 将在官网下载的Android SDK 压缩包中libs目录下所有子文件拷贝至Android工程的libs目录下。
  • 在工程 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"/>
<!--允许程序改变网络连接状态 -->
<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"/>
<!--手机定位信息,用来为语义等功能提供定位,提供更精准的服务-->
<!--定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--如需使用人脸识别,还要添加:摄相头权限,拍照需要用到 -->
<uses-permission android:name="android.permission.CAMERA" />

  • 初始化即创建语音配置对象,只有初始化后才可以使用MSC的各项服务。建议将初始化放在程序入口处(如Application、Activity的onCreate方法),初始化代码如下:

SpeechUtility.createUtility(context, SpeechConstant.APPID +"=12345678");//APPID是你自己注册的APPID,可以自己去查看

##二、开始新建android工程

  • java代码:
    主activity
package com.demo.voice;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import sina.CreAmazing.voice.R;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.text.NoCopySpan.Concrete;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import android.widget.ToggleButton;
import com.iflytek.speech.RecognizerResult;
import com.iflytek.speech.SpeechError;
import com.iflytek.speech.SynthesizerPlayer;
import com.iflytek.ui.RecognizerDialog;
import com.iflytek.ui.RecognizerDialogListener;
import com.iflytek.ui.SynthesizerDialog;
import com.iflytek.ui.SynthesizerDialogListener;

public class VoiceRecognize extends Activity {

    // 声明控件
    private EditText et;
    private Button bt1;
    private Button bt2;
    private Button bt3;
    private ToggleButton tb;
    private PackageManager pm;
    // 全局只设一个String,因为String为final类型,这样做节省内存
    String text = "";
    private static final String APPID = "appid=11111111";//定义一个常量APPPID,我自己的不便公开需要修改为你自己的;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //这是后台朗读,实例化一个SynthesizerPlayer
        SynthesizerPlayer player = SynthesizerPlayer.createSynthesizerPlayer(VoiceRecognize.this, APPID);
        //设置语音朗读者,可以根据需要设置男女朗读,具体请看api文档和官方论坛
        player.setVoiceName("vivixiaoyan");//在此设置语音播报的人选例如:vivixiaoyan、vivixiaomei、vivixiaoqi
        player.playText("hello world", "ent=vivi21,bft=5",null);


        bt1 = (Button) findViewById(R.id.bt_recognize);
        bt2 = (Button) findViewById(R.id.bt_speek);
        bt3 = (Button) findViewById(R.id.bt_speek_bg);
        et = (EditText) findViewById(R.id.et);
        tb = (ToggleButton) findViewById(R.id.tb);
        // 初始化监听器
        initListener();

    }

    private void initListener() {
        bt1.setOnClickListener(myListener);
        bt2.setOnClickListener(myListener);
        bt3.setOnClickListener(myListener);

    }

    OnClickListener myListener = new OnClickListener() {

        @Override
        public void onClick(View v) {
            // 根据不同View的id调用不同方法
            switch (v.getId()) {
                case R.id.bt_recognize:
                    // 这是语言识别部分,最重要的实例化一个
                    // RecognizerDialog并把在官方网站申请的appid填入进去,非法id不能进行识别
                    RecognizerDialog isrDialog = new RecognizerDialog(VoiceRecognize.this, APPID);

                    /*
                     * 设置引擎目前支持五种 ”sms”:普通文本转写 “poi”:地名搜索 ”vsearch”:热词搜索
                     * ”video”:视频音乐搜索 ”asr”:命令词识别
                     */
                    isrDialog.setEngine("sms", null, null);
                    isrDialog.setListener(recoListener);
                    isrDialog.show();
                    break;

                case R.id.bt_speek:
                    // 这是语言合成部分 同样需要实例化一个SynthesizerDialog ,并输入appid
                    SynthesizerDialog syn = new SynthesizerDialog(VoiceRecognize.this, APPID);
                    syn.setListener(new SynthesizerDialogListener() {

                        @Override
                        public void onEnd(SpeechError arg0) {

                        }
                    });
                    // 根据EditText里的内容实现语音合成
                    syn.setText(et.getText().toString(), null);
                    syn.show();
                    break;


                case R.id.bt_speek_bg:
                    //这是后台朗读,实例化一个SynthesizerPlayer
                    SynthesizerPlayer player = SynthesizerPlayer.createSynthesizerPlayer(VoiceRecognize.this, APPID);
                    //设置语音朗读者,可以根据需要设置男女朗读,具体请看api文档和官方论坛
                    player.setVoiceName("vivixiaoyan");//在此设置语音播报的人选例如:vivixiaoyan、vivixiaomei、vivixiaoqi
                    player.playText(et.getText().toString(), "ent=vivi21,bft=5",null);
                    break;
                default:
                    break;
            }

        }
    };
    public  void openApp(String str){  
        //应用过滤条件,intent启动是应用的顶层  
        Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);  
        PackageManager mPackageManager =getPackageManager();  
        //返回给定条件的所有ResolveInfo对象(本质为activity)
        List<ResolveInfo> mAllApps = mPackageManager.queryIntentActivities(mainIntent, 0);  
        //按包名排序  
        Collections.sort(mAllApps, new ResolveInfo.DisplayNameComparator(mPackageManager));  
        for(ResolveInfo res : mAllApps){  
            //该应用的包名和主Activity  
            String pkg = res.activityInfo.packageName;  
            String cls = res.activityInfo.name;  
            System.out.println("pkg~~~~~~" +pkg);  
            // 打开QQ pkg中包含"qq",打开微信,pkg中包含"mm"  
            if(pkg.contains(str)){  
                ComponentName componet = new ComponentName(pkg, cls);
                
                Intent intent = new Intent();  
                intent.setComponent(componet);  
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  
                VoiceRecognize.this.startActivity(intent);  
            }  
        }
 }

    // 语言识别监听器,有两个方法
    RecognizerDialogListener recoListener = new RecognizerDialogListener() {

        @Override
        public void onResults(ArrayList<RecognizerResult> results,
                boolean isLast) {
            // 新增加了一个ToggleButton tb,首先检查tb是否被按下,如果被按下才进行语言控制,没被按下就进行文字识别
            if (tb.isChecked()) {
                // doVoice方法就是进行识别
                doVoice(results);
            } else {
                // 服务器识别完成后会返回集合,我们这里就只得到最匹配的那一项
                text += results.get(0).text;
                System.out.println(text);
            }

        }

        // 首先迭代结果,然后获取每个结果,并进行对比,如果包含有特定字符串,那么就执行相应Intent跳转。
        // 注意 凡是Intent能办到的(发邮件,跳到已安装应用,拨号,发短信,发彩信,浏览网页,播放多媒体。。。。),它就都能办到。
        private void doVoice(ArrayList<RecognizerResult> results) {
            Intent i = new Intent();
            for (RecognizerResult result : results) {
                if (result.text.contains("天气")) {
                    // 天气界面的跳转
                    i.setClass(VoiceRecognize.this, Weather.class);
                    startActivity(i);
                } else if (result.text.contains("QQ")) {
                    // 跳转到QQ
                	openApp("com.tencent.mobileqq");//QQ的包名
                } else if (result.text.contains("短信")) {
                    // 短信界面的跳转
                    i.setAction(Intent.ACTION_VIEW);
                    i.setType("vnd.android-dir/mms-sms");
                    startActivity(i);
                } else {
                    // 如果没有相应指令就用Toast提示用户
                    Toast.makeText(VoiceRecognize.this, "无法识别",Toast.LENGTH_SHORT).show();
                }
            }

        }

        @Override
        public void onEnd(SpeechError error) {
            if (error == null) {
                // 完成后就把结果显示在EditText上
                et.setText(text);
            }
        }
    };

}

demo里面的Wether是要跳转的acticity,当语言识别监听器接收到“天气”时会跳转到wether界面。

package com.demo.voice;

import sina.CreAmazing.voice.R;
import android.app.Activity;
import android.os.Bundle;

public class Weather extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.weather);
	}
}

  • 下面是xml文件
    wether.xml:这是wether界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical" 
    android:background="#F5F5DC">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Weather"
        android:textSize="70dp" 
        android:textColor="#FF1493"/>
</LinearLayout>

main.xml:这是主界面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <EditText
        android:id="@+id/et"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:editable="true" />

    <Button
        android:id="@+id/bt_recognize"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Recognize" />

    <Button
        android:id="@+id/bt_speek"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Speek" />

    <Button
        android:id="@+id/bt_speek_bg"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Speek-Background" />

    <TextView
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="语音控制开关" />

    <ToggleButton
        android:id="@+id/tb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

AndroidManifest.xml:里面有权限获取以及activity的注册

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="sina.CreAmazing.voice"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name="com.demo.voice.VoiceRecognize"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.demo.voice.Weather" >
        </activity>
        <activity android:name="com.demo.voice.News" >
        </activity>
    </application>

    <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" />

</manifest>

##三、功能简介
安装到手机上后,进入应用会朗读文本“hello world”;
可以看到有四个按钮,点击第一个按钮会启动语音听写并加载到文本框里面,点击第二个按钮会朗读文本框里面输入的文本,点击第三个按钮能在后台朗读,点击最后一个按钮可以开关语音控制,语音控制开启后识别到“QQ”,“短信”会打开相应的应用,识别到“天气”会跳转到wether界面;

2016-04-20 13:13:32 u014536527 阅读数 6875

科大讯飞语音识别

       随着当今社会的高速发展,我们所使用的产品也向着遍历和智能的方面发展着,当然了手机端的App也不例外。现在的App都在缩减用户操作的流程,优化用户的体验,为了更加便利用户,提高用户的DAU(日活),增加用户的粘性一般我们都会想出比较新颖的招式。

       如果现在有这样一个需求:用户需要绑定银行卡输入银行卡号,手动输入增加用户的操作度,所以用户可以通过语音读取银行卡号,是不是既方便又智能。现在做语音识别的有一些不错的开放平台供我们使用,一家是科大讯飞平台,一家是百度语音平台。我个人比较偏爱科大讯飞,因为科大讯飞的优势在于大段大段的文字识别上,准确率较高。这正好能符合我输入银行卡号准确无误的需求。这篇博客也主要讲的是讯飞语音SDK的使用。下面我们来详细看一下科大讯飞。

1.科大讯飞开放平台 

2.科大讯飞iOS - API开放平台

        那下面我们来看一下科大讯飞的开发步骤吧

第一步:申请账号ID

登陆到讯飞开放平台上,在用户菜单栏里创建应用,这里的登陆也可以采用第三方的方式,在创建应用的界面填写相关的信息即可,然后就会有一个SDK的下载链接,如果没有直接去SDK选项下下载即可。

第二步:导入讯飞SDK框架

下载下来SDK解压后有三个文件夹:doc文件夹:不用多说肯定是开发文档;重要的是接下来的那两个文件夹:一个是lib文件夹:存放科大讯飞SDK类库,这就是我们要导入的SDK;Sample:iOS的科大讯飞demo演示工程。

下面我们来创建一个工程,将lib文件夹下的“iflyMSC.framework”拷贝到工程目录,然后在工程中添加依赖库,如下图所示:



第三步:开始进行语音识别了

语音识别分两种,分别用在不同场合,一个是界面提示的语音识别,一个是无界面提示的语音识别,这里以有界面提示的语音识别为例先进性讲解。

3.1导入头文件

#import <iflyMSC/iflyMSC.h>

3.2登陆讯飞服务器

在使用讯飞的语音解析之前,需要进行用户身份验证,即登陆讯飞服务器,这个在viewDidLoad()方法中添加两行代码即可.即讯飞服务器需要根据你当前用户的APPID才能同意你登陆。代码如下:

    //2.登陆讯飞服务器
    NSString *appID = [NSString stringWithFormat:@"appid=%@",@"570f0a8b"];
    [IFlySpeechUtility createUtility:appID];
    

3.3创建有界面提示语音识别对象

创建一个讯飞语音识别对象,可以对他进行一系列的调用

///带界面的识别对象
@property (nonatomic,strong)IFlyRecognizerView *iflyRecognizerView;

3.4初始化带界面的识别对象

    _iflyRecognizerView = [[IFlyRecognizerView alloc] initWithCenter:self.view.center];
    _iflyRecognizerView.delegate = self;
    [_iflyRecognizerView setParameter:@"iat" forKey:[IFlySpeechConstant IFLY_DOMAIN]];
    //asr_audio_path保存录音文件名,如不再需要,设置value为nil表示取消,默认目录是documents
    [_iflyRecognizerView setParameter:@"asrview.pcm " forKey:[IFlySpeechConstant ASR_AUDIO_PATH]];

3.5实现代理方法

讯飞对识别结果的处理采用的代理回调的方法,实现IFlySpeechSynthesizerDelegate协议的onResult:isLast:方法.
注意!!!!这里的是onResult,不是onResults,后者是无界面提示的语音解析的结果回调函数.

回调时默认传回来的数据是json数据,咱们大可不必疑惑解析到底使用哪种方式,科大讯飞已经为我们考虑到这些问题了,他官方提供了一个叫做“ISRDataHelper”用它解析就可以了。代码如下:

- (void)onResult:(NSArray *)resultArray isLast:(BOOL)isLast{
    
    NSMutableString *result = [[NSMutableString alloc] init];
    NSDictionary *dic = [resultArray objectAtIndex:0];
    for (NSString *key in dic) {
        [result appendFormat:@"%@",key];
    }
    
    NSString * resu = [ISRDataHelper stringFromJson:result];

    
    //结果要显示在Lable上
    _showTextLable.text = [NSString stringWithFormat:@"%@%@",_showTextLable.text,resu];
}

3.6触发开始识别语音

拖动一个Button,给一个响应事件,用于开始监听语音识别;代码如下:

- (IBAction)voiceRecognize:(id)sender {
    
    //开始识别语音
    [_iflyRecognizerView start];
    
}

这时候运行app就可以进行语音识别了,界面如下图:




好了,科大讯飞的语音识别功能还有很多稍后继续。。。。。



2017-08-28 16:08:27 Android4strong 阅读数 2364

1.首先你要做的事情,打开讯飞开放平台,下载SDK(地址:http://www.xfyun.cn/sdk/dispatcher,平台上面的服务有很多种,根据项目的需求下载,下载前会让你先    创建一个应用,完后会生成一个唯一的appid。



2.这里以语音听写为例。(虽然是免费的,但是有次数限制,开发使用测试还可以,但是用户量多的还是建议购买服务)。

   SDK下载完成之后解压出来,我们需要用到的有三样东西armeab文件夹里的libmsc.so库Msc.jar和Sunflower.jar,这三个放进去就OK了。


3.讯飞还为我们提供了一套语音听写的UI,如果选择使用的话,需要将刚才解压的SDK资源包assets路径下的资源文件拷贝到项目的asstes下(没有的话自己建一个)。


4.接下来看代码怎么实现,开放平台上面有开发文档,但是其实我不建议参考那个,因为SDK里面有demo,你只需要找到入口就可以,根据线索看下去,不一会儿就搞定了。

第一步:权限(只管拷贝进去就好了)

  <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" />

第二步:调用SpeechUtility.createUtility(this, "appid=" + getString(R.string.app_id)); 初始化创建对象,可以放在项目的application里面初始化,也可以在使用的时候初始化。

   不然会报错:"创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化"


     接下来就把代码贴上去,带注释应该就能看懂了 。

     在oncreat中的代码

mIat = SpeechRecognizer.createRecognizer(this, mInitListener);//这个是没有UI功能的,但是有Toast提示

     mIatDialog = new RecognizerDialog(this, mInitListener);// 初始化听写Dialog,如果只使用有UI听写功能,无需创建SpeechRecognizer,这里我两个都做了,选择性使用

    

     recognizerDialog = new RecognizerDialog(this, mInitListener);
     sharedPreferences = getSharedPreferences(PREFER_NAME, Activity.MODE_PRIVATE);
     spedit = sharedPreferences.edit();
     btStart = (Button) findViewById(R.id.btStart);
     btStart.setOnClickListener(this);
     editText = (EditText) findViewById(R.id.editText);
     toggleButton1 = (ToggleButton) findViewById(R.id.toggleButton1);
     toggleButton1.setChecked(isShowDialog);

    上面这一堆代码没有什么可读性,UI开关按钮还有显示控件等。


  @Override
public void onClick(View v) {
if( null == mIat )
{
// 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
Toast.makeText(getApplicationContext(), "创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化", Toast.LENGTH_LONG).show();
return;
}
switch (v.getId()) {

case R.id.btStart:

editText.setText(null);
mIatResults.clear();
// 设置参数
setParam();
if (toggleButton1.isChecked()) {
// 显示听写对话框
mIatDialog.setListener(mRecognizerDialogListener);
mIatDialog.show();
Toast.makeText(this, "请开始说话", Toast.LENGTH_LONG).show();
} else {
ret = mIat.startListening(mRecognizerListener);
if (ret != ErrorCode.SUCCESS) {
Toast.makeText(this, "听写失败,错误码:" + ret, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "请开始说话", Toast.LENGTH_LONG).show();
}
}
break;
default:
break;
}
}


     /**
* 听写UI监听器
*/

private RecognizerDialogListener mRecognizerDialogListener = new RecognizerDialogListener() {
public void onResult(RecognizerResult results, boolean isLast) {
printResult(results);
}


/**
* 识别回调错误.
*/
public void onError(SpeechError error) {
if(mTranslateEnable && error.getErrorCode() == 14002) {
Toast.makeText(getApplicationContext(), error.getPlainDescription(true)+"\n请确认是否已开通翻译功能", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), error.getPlainDescription(true), Toast.LENGTH_LONG).show();
}
}
};

//设置参数

      public void setParam() 
{
// 清空参数
mIat.setParameter(SpeechConstant.PARAMS, null);

// 设置听写引擎
mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
// 设置返回结果格式
mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");

String lag = sharedPreferences.getString("iat_language_preference",  
"mandarin");  
if (lag.equals("en_us")) {  
// 设置语言  
mIat.setParameter(SpeechConstant.LANGUAGE, "en_us");  
} else {  
// 设置语言  
mIat.setParameter(SpeechConstant.LANGUAGE, "zh_cn");  
// 设置语言区域  
mIat.setParameter(SpeechConstant.ACCENT, lag);  
}  


// 设置语音前端点:静音超时时间,即用户多长时间不说话则当做超时处理  
mIat.setParameter(SpeechConstant.VAD_BOS, sharedPreferences.getString("iat_vadbos_preference", "4000"));  


// 设置语音后端点:后端点静音检测时间,即用户停止说话多长时间内即认为不再输入, 自动停止录音  
mIat.setParameter(SpeechConstant.VAD_EOS, sharedPreferences.getString("iat_vadeos_preference", "1000"));  


// 设置标点符号,设置为"0"返回结果无标点,设置为"1"返回结果有标点  
mIat.setParameter(SpeechConstant.ASR_PTT, sharedPreferences.getString("iat_punc_preference", "1"));  


// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限  
// 注:AUDIO_FORMAT参数语记需要更新版本才能生效  
mIat.setParameter(SpeechConstant.AUDIO_FORMAT,"wav");  
mIat.setParameter(SpeechConstant.ASR_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/iat.wav");  
}

//这个是没有UI界面的听写监听器

      /**
* 听写监听器。
*/
private RecognizerListener mRecognizerListener = new RecognizerListener() {

@Override
public void onBeginOfSpeech() {
// 此回调表示:sdk内部录音机已经准备好了,用户可以开始语音输入
Toast.makeText(getApplicationContext(), "开始说话", Toast.LENGTH_LONG).show();
}

@Override
public void onError(SpeechError error) {
// Tips:
// 错误码:10118(您没有说话),可能是录音机权限被禁,需要提示用户打开应用的录音权限。
// 如果使用本地功能(语记)需要提示用户开启语记的录音权限。
if(mTranslateEnable && error.getErrorCode() == 14002) {
Toast.makeText(getApplicationContext(), error.getPlainDescription(true)+"\n请确认是否已开通翻译功能", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), error.getPlainDescription(true), Toast.LENGTH_LONG).show();
}
}

@Override
public void onEndOfSpeech() {
// 此回调表示:检测到了语音的尾端点,已经进入识别过程,不再接受语音输入
Toast.makeText(getApplicationContext(), "结束说话", Toast.LENGTH_SHORT).show();
Log.e("TAG", "结束说话");
}

@Override
public void onResult(RecognizerResult results, boolean isLast) {
printResult(results);
if (isLast) {
// TODO 最后的结果
}
}

@Override
public void onVolumeChanged(int volume, byte[] data) {
// Toast.makeText(getApplicationContext(), "当前正在说话,音量大小:" + volume, Toast.LENGTH_LONG).show();
Log.e("TAG", "当前正在说话,音量大小:" + volume);
}

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


//最终结果

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));
}
editText.setText(resultBuffer.toString());//这儿是最终识别到的文字,显示在textview上面
editText.setSelection(editText.length());
}