1. 程式人生 > >科大訊飛離線語音命令詞識別的使用說明

科大訊飛離線語音命令詞識別的使用說明

      最近因為專案的需求,需要在無網路的情況下實現語音識別的功能,因為之前線上識別一直用的科大的,所以經理就和我說,你花半天時間簡單熟悉一下,然後出一個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="請編輯更新資訊:"