讓你的Hybrid App聽懂你的話(Android篇)

Harry_Chen發表於2019-03-04

前言

最近需要對接語音識別業務,畢竟現在是AI時代,一個產品如果能通過AI能力給使用者帶來全新的體驗,也是很值得嘗試的。技術對接上選擇的是科大訊飛開放平臺,也算是國內最早一批做語音識別的企業了,文件方面都比較全面,對接起來也很方便。

技術基礎

開發技術棧為Cordova+Angular+Ionic,這篇分享會介紹如何從頭開始建立Cordova外掛,並實現科大訊飛Android sdk與App端的資料互動。

Apache Cordova是一個開源的移動開發框架。允許你用標準的web技術——HTML5,CSS3和JavaScript做跨平臺開發。 應用在每個平臺的具體執行被封裝了起來,並依靠符合標準的API繫結去訪問每個裝置的功能,比如說:感測器、資料、網路狀態等。

在繼續閱讀之前,應該確保你有通過Cordova建立並打包一個簡單Hybrid App的經驗,感興趣的童鞋可以到Ionic官網Cordova官網學習下。

建立Cordova外掛

全域性安裝plugman

plugman用於建立Cordova外掛,在專案目錄下執行cnpm i -g plugman

讓你的Hybrid App聽懂你的話(Android篇)

建立外掛

建立一個外掛並新增android平臺,並生成package.json,外掛名xFeiVoice,外掛idcom.qinsilk.xFeiVoice,版本號為0.01

plugman create --name xFeiVoice --plugin_id com.qinsilk.xFeiVoice --plugin_version 0.0.1
cd xFeiVoice
plugman createpackagejson ./
plugman platform add --platform_name android
複製程式碼
讓你的Hybrid App聽懂你的話(Android篇)

建立成功可以看到對應目錄如下

讓你的Hybrid App聽懂你的話(Android篇)
  • src目錄存放原生程式碼,此處為java檔案
  • www目錄存放js暴露給裝置的介面,如下
var exec = require(`cordova/exec`);

exports.coolMethod = function (arg0, success, error) {
    exec(success, error, `xFeiVoice`, `coolMethod`, [arg0]);
};
複製程式碼

此時我們做下修改,讓引數名跟業務命名更加相關。

var exec = require(`cordova/exec`);

exports.record = function (arg0, success, error) {
    exec(success, error, `xFeiVoice`, `record`, [arg0]);
};
複製程式碼

安裝外掛

執行cordova plugin add xFeiVoice,再執行cordova plugin ls可以檢視當前App安裝的外掛。

讓你的Hybrid App聽懂你的話(Android篇)

此時用Android Studio開啟專案,可以看到這個外掛已經新增成功。

讓你的Hybrid App聽懂你的話(Android篇)

到這裡我們的準備工作就完成了。

對接語音識別

匯入sdk

Android sdk可以去科大訊飛開放平臺下載。將在官網下載的Android SDK 壓縮包中libs目錄下所有子檔案拷貝至Android工程的libs目錄下。如下圖所示:

讓你的Hybrid App聽懂你的話(Android篇)

新增許可權

在工程 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" />
複製程式碼

初始化語音識別物件

此處呼叫的是60秒語音聽寫功能。excute是在開發外掛時,使用者的自定義方法,當頁面呼叫外掛時系統首先將會執行此方法。

@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
    this.callbackContext = callbackContext;
    if (action.equals("record")) {
        // 初始化語音識別物件
        SpeechUtility.createUtility(cordova.getActivity(), "appid=yourAppid,force_login=true");
        // 使用SpeechRecognizer物件,可根據回撥訊息自定義介面;
        mIat = SpeechRecognizer.createRecognizer(cordova.getActivity(), mInitListener);
        // 設定引數
        setParam();
        // 監聽事件
        mIat.startListening(mRecognizerListener);
        return true;
    }
    return false;
}
複製程式碼

初始化監聽器

private InitListener mInitListener = new InitListener() {
    @Override
    public void onInit(int code) {
        Log.d(LOG_TAG, "SpeechRecognizer init() code = " + code);
        if (code != ErrorCode.SUCCESS) {
            Log.d(LOG_TAG, "初始化失敗,錯誤碼:" + code);
        }
    }
};
複製程式碼

設定引數

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

    // 設定聽寫引擎
    mIat.setParameter(SpeechConstant.ENGINE_TYPE, mEngineType);
    // 設定返回結果格式
    mIat.setParameter(SpeechConstant.RESULT_TYPE, "json");

    // 設定語音前端點:靜音超時時間,即使用者多長時間不說話則當做超時處理
    mIat.setParameter(SpeechConstant.VAD_BOS, "4000");

    // 設定語音後端點:後端點靜音檢測時間,即使用者停止說話多長時間內即認為不再輸入, 自動停止錄音
    mIat.setParameter(SpeechConstant.VAD_EOS, "1000");

    // 設定標點符號,設定為"0"返回結果無標點,設定為"1"返回結果有標點
    mIat.setParameter(SpeechConstant.ASR_PTT, "0");

    // 設定音訊儲存路徑,儲存音訊格式支援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");
}
複製程式碼

識別監聽器

private RecognizerListener mRecognizerListener = new RecognizerListener() {
    @Override
    public void onVolumeChanged(int volume, byte[] data) {
        // showTip("當前正在說話,音量大小:" + volume);
        Log.d(LOG_TAG, "返回音訊資料:"+data.length);
    }

    @Override
    public void onResult(final RecognizerResult result, boolean isLast) {
        //此處有坑,isLast為true時會返回標點符號
        if (null != result && !isLast) {
            String text = parseIatResult(result.getResultString());
            JSONObject obj = new JSONObject();
            try {
                obj.put("searchText", text);
            } catch (JSONException e) {
                Log.d(LOG_TAG, "This should never happen");
            }
            if( null != mIat ){
                // 退出時釋放連線
                mIat.cancel();
                mIat.destroy();
            }
            getSearchText(obj);
        } else {
            Log.d(LOG_TAG, "recognizer result : null");
        }
    }

    @Override
    public void onEndOfSpeech() {
        // 此回撥錶示:檢測到了語音的尾端點,已經進入識別過程,不再接受語音輸入
        Log.d(LOG_TAG, "結束說話");
    }

    @Override
    public void onBeginOfSpeech() {
        // 此回撥錶示:sdk內部錄音機已經準備好了,使用者可以開始語音輸入
        Log.d(LOG_TAG, "開始說話");
    }

    @Override
    public void onError(SpeechError error) {
        Log.d(LOG_TAG, "onError Code:"	+ error.getErrorCode());
    }

    @Override
    public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
        // 以下程式碼用於獲取與雲端的會話id,當業務出錯時將會話id提供給技術支援人員,可用於查詢會話日誌,定位出錯原因
        // 若使用本地能力,會話id為null
    }

};
複製程式碼

處理結果並返回App端

// 處理結果
public static String parseIatResult(String json) {
    StringBuffer ret = new StringBuffer();
    try {
        JSONTokener tokener = new JSONTokener(json);
        JSONObject joResult = new JSONObject(tokener);

        JSONArray words = joResult.getJSONArray("ws");
        for (int i = 0; i < words.length(); i++) {
            // 轉寫結果詞,預設使用第一個結果
            JSONArray items = words.getJSONObject(i).getJSONArray("cw");
            JSONObject obj = items.getJSONObject(0);
            ret.append(obj.getString("w"));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return ret.toString();
}
//將結果通過callbackContext返回App端
public void getSearchText(JSONObject obj) {
    this.callbackContext.success(obj);
}
複製程式碼

App端呼叫sdk並監聽資料返回

//語音識別
$scope.record = function () {
    if (window.cordova && window.cordova.plugins) {
        if(!$scope.recording){
            //呼叫sdk
            cordova.plugins.xFeiVoice.record({}, function (result) {
                if(result){
                    //返回識別結果
                    $scope.search.goodKey = result.searchText;
                    $scope.openModal();
                    $scope.recording = false;
                }
                console.log(`success`);
            }, function (result) {
                console.log(`fail`);
            });
        }else{
            //$scope.recordMedia.stopRecord();
        }
        $scope.recording = !$scope.recording;
    }
};
複製程式碼

結語

外掛程式碼我已經上傳到github了,有需要的可以clone。走過路過給個star吧~

相關文章