1. 程式人生 > >訊飛語音——離線命令詞識別

訊飛語音——離線命令詞識別

離線命令詞識別

  • 效果圖

P1

  • 示例原始碼

步驟:

1. 下載SDK

2. 整合方法

3. 正題,開始整合

1. 新增許可權

這裡用到的喚醒功能不是所有的許可權都用到的,具體用到了哪些許可權,可以看上面的連結,用到哪寫許可權就加哪些許可權,這個為了快速方便測試,把訊飛用到的許可權都加上了。

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

2. 初始化appid

我是將appid的初始化放在的Applicaiton下,具體可以下載原始碼

// 應用程式入口處呼叫,避免手機記憶體過小,殺死後臺程序後通過歷史intent進入Activity造成SpeechUtility物件為null
// 如在Application中呼叫初始化,需要在Mainifest中註冊該Applicaiton
// 注意:此介面在非主程序呼叫會返回null物件,如需在非主程序使用語音功能,請增加引數:SpeechConstant.FORCE
_LOGIN+"=true" // 引數間使用“,”分隔。 // 設定你申請的應用appid StringBuffer param = new StringBuffer(); param.append("appid=55d33f09"); param.append(","); param.append(SpeechConstant.ENGINE_MODE + "=" + SpeechConstant.MODE_MSC); // param.append(","); // param.append(SpeechConstant.FORCE_LOGIN + "=true"); SpeechUtility.createUtility(InitKqwSpeech.this, param.toString());

3. 工具類

package com.example.kqwspeechdemo2.utils;

import java.io.InputStream;
import android.content.Context;
import android.util.Log;

/**
 * 功能性函式擴充套件類
 * 
 * @author kongqw
 * 
 */
public class FucUtil {

    // Log標籤
    private static final String TAG = "FucUtil";

    /**
     * 讀取asset目錄下檔案內容
     * 
     * @return content
     */
    public static String readFile(Context mContext, String file, String code) {
        int len = 0;
        byte[] buf = null;
        String result = "";
        try {
            InputStream in = mContext.getAssets().open(file);
            len = in.available();
            buf = new byte[len];
            in.read(buf, 0, len);

            result = new String(buf, code);
        } catch (Exception e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
        }
        return result;
    }

}

4. 構建語法檔案的工具類

package com.example.kqwspeechdemo2.engine;

import com.example.kqwspeechdemo2.utils.FucUtil;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.GrammarListener;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechRecognizer;
import com.iflytek.cloud.util.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;

import android.content.Context;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

/**
 * 構建離線命令詞語法
 * 
 * @author kongqw
 * 
 */
public abstract class BuildLocalGrammar {

    /**
     * 構建語法的回撥
     * 
     * @param errMsg
     *            null 構造成功
     */
    public abstract void result(String errMsg, String grammarId);

    // Log標籤
    private static final String TAG = "BuildLocalGrammar";

    public static final String GRAMMAR_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/test";
    // 上下文
    private Context mContext;
    // 語音識別物件
    private SpeechRecognizer mAsr;

    public BuildLocalGrammar(Context context) {
        mContext = context;

        // 初始化識別物件
        mAsr = SpeechRecognizer.createRecognizer(context, new InitListener() {

            @Override
            public void onInit(int code) {
                Log.d(TAG, "SpeechRecognizer init() code = " + code);
                if (code != ErrorCode.SUCCESS) {
                    result(code + "", null);
                    Log.d(TAG, "初始化失敗,錯誤碼:" + code);
                    Toast.makeText(mContext, "初始化失敗,錯誤碼:" + code, Toast.LENGTH_SHORT).show();
                }
            }
        });
    };

    /**
     * 構建語法
     * 
     * @return
     */
    public void buildLocalGrammar() {
        try {
            /*
             * TODO 如果你要在程式裡維護bnf檔案,可以在這裡加上你維護的一些邏輯
             * 如果不嫌麻煩,要一直改bnf檔案,這裡的程式碼可以不用動,不過我個人不建議一直手動修改bnf檔案
             * ,內容多了以後很容易出錯,不好找Bug,建議每次改之前先備份。 建議用程式維護bnf檔案。
             */

            /*
             * 構建語法
             */
            String mContent;// 語法、詞典臨時變數
            String mLocalGrammar = FucUtil.readFile(mContext, "kqw.bnf", "utf-8");
            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, GRAMMAR_PATH);
            // 使用8k音訊的時候請解開註釋
            // mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
            // 設定資源路徑
            mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
            // 構建語法
            int ret = mAsr.buildGrammar("bnf", mContent, new GrammarListener() {

                @Override
                public void onBuildFinish(String grammarId, SpeechError error) {
                    if (error == null) {
                        Log.d(TAG, "語法構建成功");
                        result(null, grammarId);
                    } else {
                        Log.d(TAG, "語法構建失敗,錯誤碼:" + error.getErrorCode());
                        result(error.getErrorCode() + "", grammarId);
                    }
                }
            });
            if (ret != ErrorCode.SUCCESS) {
                Log.d(TAG, "語法構建失敗,錯誤碼:" + ret);
                result(ret + "", null);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 獲取識別資源路徑
    private String getResourcePath() {
        StringBuffer tempBuffer = new StringBuffer();
        // 識別通用資源
        tempBuffer.append(ResourceUtil.generateResourcePath(mContext, 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();
    }

}

5. 命令詞識別工具類

package com.example.kqwspeechdemo2.engine;

import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.InitListener;
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.ResourceUtil;
import com.iflytek.cloud.util.ResourceUtil.RESOURCE_TYPE;

import android.content.Context;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;

/**
 * 命令詞識別
 * 
 * @author kongqw
 * 
 */
public abstract class KqwSpeechRecognizer {
    /**
     * 初始化的回撥
     * 
     * @param flag
     *            true 初始化成功 false 初始化失敗
     */
    public abstract void initListener(boolean flag);

    public abstract void resultData(String data);

    public abstract void speechLog(String log);

    // Log標籤
    private static final String TAG = "KqwLocalCommandRecognizer";

    public static final String GRAMMAR_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/msc/test";
    // 上下文
    private Context mContext;
    // 語音識別物件
    private SpeechRecognizer mAsr;

    public KqwSpeechRecognizer(Context context) {
        mContext = context;

        // 初始化識別物件
        mAsr = SpeechRecognizer.createRecognizer(context, new InitListener() {

            @Override
            public void onInit(int code) {
                Log.d(TAG, "SpeechRecognizer init() code = " + code);
                if (code != ErrorCode.SUCCESS) {
                    initListener(false);
                    Toast.makeText(mContext, "初始化失敗,錯誤碼:" + code, Toast.LENGTH_SHORT).show();
                } else {
                    initListener(true);
                }
            }
        });

    }

    /**
     * 引數設定
     */
    public void setParam() {
        // 清空引數
        mAsr.setParameter(SpeechConstant.PARAMS, null);
        // 設定識別引擎 本地引擎
        mAsr.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
        // mAsr.setParameter(SpeechConstant.ENGINE_TYPE,
        // SpeechConstant.TYPE_MIX);
        // mAsr.setParameter(SpeechConstant.ENGINE_TYPE, "mixed");
        // // 設定本地識別資源
        mAsr.setParameter(ResourceUtil.ASR_RES_PATH, getResourcePath());
        // 設定語法構建路徑
        mAsr.setParameter(ResourceUtil.GRM_BUILD_PATH, GRAMMAR_PATH);
        // 設定返回結果格式
        mAsr.setParameter(SpeechConstant.RESULT_TYPE, "json");
        // 設定本地識別使用語法id
        mAsr.setParameter(SpeechConstant.LOCAL_GRAMMAR, "kqw");
        // 設定識別的門限值
        mAsr.setParameter(SpeechConstant.MIXED_THRESHOLD, "60");
        // 使用8k音訊的時候請解開註釋
        // mAsr.setParameter(SpeechConstant.SAMPLE_RATE, "8000");
        mAsr.setParameter(SpeechConstant.DOMAIN, "iat");
        mAsr.setParameter(SpeechConstant.NLP_VERSION, "2.0");
        mAsr.setParameter("asr_sch", "1");
        // mAsr.setParameter(SpeechConstant.RESULT_TYPE, "json");
    }

    // 獲取識別資源路徑
    private String getResourcePath() {
        StringBuffer tempBuffer = new StringBuffer();
        // 識別通用資源
        tempBuffer.append(ResourceUtil.generateResourcePath(mContext, 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();
    }

    int ret = 0;// 函式呼叫返回值

    /**
     * 開始識別
     */
    public void startListening() {
        // 設定引數
        setParam();

        ret = mAsr.startListening(mRecognizerListener);
        if (ret != ErrorCode.SUCCESS) {
            Log.i(TAG, "識別失敗,錯誤碼: " + ret);
        }
    }

    /**
     * 識別監聽器。
     */
    private RecognizerListener mRecognizerListener = new RecognizerListener() {

        StringBuffer stringBuffer = new StringBuffer();

        public void onVolumeChanged(int volume) {
            Log.i(TAG, "當前正在說話,音量大小:" + volume);
            speechLog("當前正在說話,音量大小:" + volume);
        }

        @Override
        public void onResult(final RecognizerResult result, boolean isLast) {
            /*
             * TODO 拼接返回的資料
             * 
             * 這裡返回的是Json資料,具體返回的是離線名命令詞返回的Json還是語義返回的Json,需要做判斷以後在對資料資料進行拼接
             */
            stringBuffer.append(result.getResultString()).append("\n\n");
            // isLast為true的時候,表示一句話說完,將拼接後的完整的一句話返回
            if (isLast) {
                // 資料回撥
                resultData(stringBuffer.toString());
            }
        }

        @Override
        public void onEndOfSpeech() {
            Log.i(TAG, "結束說話");
            speechLog("結束說話");
        }

        @Override
        public void onBeginOfSpeech() {
            stringBuffer.delete(0, stringBuffer.length());
            Log.i(TAG, "開始說話");
            speechLog("開始說話");
        }

        @Override
        public void onError(SpeechError error) {
            Log.i(TAG, "error = " + error.getErrorCode());
            if (error.getErrorCode() == 20005) {
                // 本地命令詞沒有識別,也沒有請求到網路
                resultData("沒有構建的語法");
                speechLog("沒有構建的語法");
                /*
                 * TODO
                 * 當網路正常的情況下是不會回撥20005的錯誤,只有當本地命令詞識別不匹配,網路請求也失敗的情況下,會返回20005
                 * 這裡可以自己再做處理,例如回覆“沒有聽清”等回覆
                 */
            } else {
                /*
                 * TODO
                 * 其他錯誤有很多,需要具體問題具體分析,正常在程式沒有錯誤的情況下,只會回撥一個沒有檢測到說話的錯誤,沒記錯的話錯誤碼是10118
                 */
            }
        }

        @Override
        public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
            Log.i(TAG, "eventType = " + eventType);
        }

    };

}

這裡的識別引擎設定的是SpeechConstant.TYPE_LOCAL,這種是本地識別引擎,只走本地識別,不走網路,如果換成SpeechConstant.TYPE_MIX,就是混合引擎,這種引擎方式,當本地沒有識別到語法,返回20005錯誤碼的時候,會直接請求語義介面,如果你語義開通了對應的場景,會走網路把你的語音轉為語義,如果沒有開通對應的場景,會把語音轉為文字。

6. 測試類

package com.example.kqwspeechdemo2;

import com.example.kqwspeechdemo2.engine.BuildLocalGrammar;
import com.example.kqwspeechdemo2.engine.KqwSpeechRecognizer;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private TextView mTvResult;
    private TextView mTvLog;
    private BuildLocalGrammar buildLocalGrammar;
    private KqwSpeechRecognizer kqwSpeechRecognizer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTvResult = (TextView) findViewById(R.id.tv_result);
        mTvLog = (TextView) findViewById(R.id.tv_log);

        /**
         * 初始化本地語法構造器
         */
        buildLocalGrammar = new BuildLocalGrammar(this) {

            @Override
            public void result(String errMsg, String grammarId) {
                // errMsg為null 構造成功
                if (TextUtils.isEmpty(errMsg)) {
                    Toast.makeText(MainActivity.this, "構造成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, "構造失敗", Toast.LENGTH_SHORT).show();
                }
            }
        };

        /**
         * 初始化離線命令詞識別器
         */
        kqwSpeechRecognizer = new KqwSpeechRecognizer(this) {

            @Override
            public void speechLog(String log) {
                // 錄音Log資訊的回撥
                mTvLog.setText(log);
            }

            @Override
            public void resultData(String data) {
                // 是識別結果的回撥
                mTvResult.setText(data);
            }

            @Override
            public void initListener(boolean flag) {
                // 初始化的回撥
                if (flag) {
                    Toast.makeText(MainActivity.this, "初始化成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, "初始化失敗", Toast.LENGTH_SHORT).show();
                }
            }
        };

        /**
         * 構造本地語法檔案,只有語法檔案有變化的時候構造成功一次即可,不用每次都構造
         */
        buildLocalGrammar.buildLocalGrammar();

    }

    /**
     * 開始識別按鈕
     * 
     * @param view
     */
    public void start(View view) {
        mTvResult.setText(null);
        // 開始識別
        kqwSpeechRecognizer.startListening();
    }

}

7. 介面佈局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.kqwspeechdemo2.MainActivity" >

    <Button
        android:id="@+id/bt_start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:onClick="start"
        android:text="開始錄音" />

    <TextView
        android:id="@+id/tv_log"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/bt_start"
        android:gravity="center"
        android:text="錄音資訊" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/tv_log" >

        <TextView
            android:id="@+id/tv_result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="返回結果" />
    </ScrollView>

</RelativeLayout>

8. BNF語法檔案

#BNF+IAT 1.0 UTF-8;
!grammar kqw;
!slot <contact>;
!slot <callPre>;
!slot <callPhone>;
!slot <callTo>;
!slot <go>;
!slot <position>;

!slot <move>;

!start <callStart>;
<callStart>:
[<callPre>][<callTo>]<contact><callPhone>
|[<callPre>]<callPhone>[<callTo>]<contact>
|[<callPre>]<callPhone>

|<move>

|<go><position>;

<contact>:慶威|小孔;
<callPre>:我要|我想|我想要;
<callPhone>:打電話;
<callTo>:給;

<move>:前進|後退|左轉|右轉;

<go>:去|到;
<position>:廚房|臥室|陽臺|客廳;

在構建語法的時候,我們不是必要在assets目錄下建立一個xxx.bnf檔案,構建的時候我們只要能夠拿到滿足BNF語法檔案的字串就行,至於這個檔案內容,你存在哪都無所謂,在程式裡寫死、存sp、資料庫、自己程式維護都OK,只要滿足BNF的語法就行。

BNF語法開發指南

最後

  1. 如果你直接用我的Demo,我用的是測試版的離線包,只有35天的試用期,而且裝機量只有3個,如果大家都用,很可能是不能正常執行的
  2. 如果是參考我的demo自己寫一個,千萬不要忘記替換appid和資原始檔。