BaiduSpeechDemo【百度語音SDK集成】(基於v3.0.7.3)

版權聲明:本文爲HaiyuKing原創文章,轉載請註明出處!html

前言

本Demo將百度語音SDK(其中一部分功能)和自定義的UI對話框封裝到一個module中,便於後續的SDK版本更新以及調用。java

本Demo使用的百度語音SDK版本是audiobd_speech_sdk_asr_v3.0.7.3_bdasr_20180313_726f26ereact

本Demo中使用的appkey已失效,請自行建立應用,使用新的appkey。android

效果圖

前提

(1)新建項目(獲取包名)

(2)在百度AI開發平臺上建立應用,獲取API Key及Secret Key

官網地址:http://ai.baidu.com/tech/speechgit

一、成爲開發者

參考《接入指南github

二、建立應用

2.一、點擊百度AI開放平臺導航右側的控制檯,選擇須要使用的AI服務項【這裏選擇語音技術】。json

 2.二、建立應用網絡

 2.三、填寫應用信息app

 2.四、建立成功ide

2.五、應用列表

(3)下載SDK

3.一、管理應用

3.二、下載SDK

下載地址:https://ai.baidu.com/sdk#asr

 

代碼分析

普通話 search搜索模型:參考SpeechBottomSheetDialog.java類

普通話 input輸入法模型,適用於長句及長語音,有逗號分割,無語義:參考SpeechLongBottomSheetDialog.java類

注意:關於語音識別狀態維護,API調用的代碼,是本身根據官網demo的理解進行整理的,可能有所偏頗,僅供參考。【但願官網demo能夠添加百度APP的語音對話框效果就行了】

使用步驟

1、項目組織結構圖

注意事項:

一、  導入類文件後須要change包名以及從新import R文件路徑

二、  Values目錄下的文件(strings.xml、dimens.xml、colors.xml等),若是項目中存在,則複製裏面的內容,不要整個覆蓋

2、導入步驟

(1)新建module,命名爲BaiduSpeech

(2)在baiduspeech的AndroidManifest.xml中添加如下代碼

從官方demo的AndroidManifest.xml中找到以下信息,而後複製到您本身的同名文件中。此處須要您複製一、權限二、官網申請的應用信息三、SDK的Service。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.why.project.baiduspeech">

    <!-- ======================百度語音====================== -->
    <!-- begin: baidu speech sdk 權限 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!-- end: baidu speech sdk 權限 -->

    <application>

        <!-- ======================百度語音========================== -->
        <!-- 正式發佈時,請替換成您本身的appId 本demo的appId會不定時下線 -->
        <meta-data android:name="com.baidu.speech.APP_ID" android:value="11588936" />
        <meta-data android:name="com.baidu.speech.API_KEY" android:value="XRF3IOf2tNGePzlv47cBnvF3" />
        <meta-data android:name="com.baidu.speech.SECRET_KEY" android:value="diC8lQ7XDcGBKQ6FzCpvnan54F5CnMZI" />
        <service android:name="com.baidu.speech.VoiceRecognitionService" android:exported="false" />
    </application>
</manifest>

注意:此時<service>標籤那裏是紅色錯誤的標記,暫時不用管,導入jar、so文件後編譯下就正常了。

(3)複製jar 文件

 將官方demo中的app\libs\bdasr_V3_20180320_9066860.jar複製進您項目的同名目錄。

在build.gradle中確認是否含有如下紅色標記的代碼

(4)複製jni庫的so文件

複製官方demo中 app\src\main\jniLibs 至項目的同名目錄。 

這個時候編譯下,就會發現AndroidManifest.xml文件的<service>標籤那裏正常了。

(5)在官方demo中找到下面的文件複製到項目中(按照下面的包名進行查找)【注意,複製過來後,須要從新import 相關類

(6)修改MessageStatusRecogListener.java文件【根據實際狀況進行修改

package com.why.project.baiduspeech.recognization;

import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * Created by fujiayi on 2017/6/16.
 */

public class MessageStatusRecogListener extends StatusRecogListener {
    private Handler handler;

    private long speechEndTime;

    private boolean needTime = true;

    private static final String TAG = "MesStatusRecogListener";

    public MessageStatusRecogListener(Handler handler) {
        this.handler = handler;
    }


    @Override
    public void onAsrReady() {
        super.onAsrReady();
        sendStatusMessage("引擎就緒,能夠開始說話。");
    }

    @Override
    public void onAsrBegin() {
        super.onAsrBegin();
        sendStatusMessage("檢測到用戶說話");
    }

    @Override
    public void onAsrEnd() {
        super.onAsrEnd();
        speechEndTime = System.currentTimeMillis();
        sendMessage("檢測到用戶說話結束");
    }

    @Override
    public void onAsrPartialResult(String[] results, RecogResult recogResult) {
        sendStatusMessage("臨時識別結果,結果是「" + results[0] + "」;原始json:" + recogResult.getOrigalJson());
        super.onAsrPartialResult(results, recogResult);
    }

    @Override
    public void onAsrFinalResult(String[] results, RecogResult recogResult) {
        super.onAsrFinalResult(results, recogResult);
        //String message = "識別結束,結果是」" + results[0] + "」";//why 實際中能夠去掉,不須要
        String message = recogResult.getOrigalJson();//{"results_recognition":["什麼什麼"],"origin_result":{"corpus_no":6522034498058113957,"err_no":0,"result":{"word":["什麼什麼"]},"sn":"bfa8b286-ab0e-4f86-9209-1d36d38b1224","voice_energy":16191.7705078125},"error":0,"best_result":"什麼什麼","result_type":"final_result"}
        sendStatusMessage(message + "「;原始json:" + recogResult.getOrigalJson());
        if (speechEndTime > 0) {
            long diffTime = System.currentTimeMillis() - speechEndTime;
            //message += ";說話結束到識別結束耗時【" + diffTime + "ms】";// why 實際中能夠去掉,不須要

        }
        speechEndTime = 0;
        sendMessage(message, status, true);
    }

    @Override
    public void onAsrFinishError(int errorCode, int subErrorCode, String errorMessage, String descMessage,
                                 RecogResult recogResult) {
        super.onAsrFinishError(errorCode, subErrorCode, errorMessage, descMessage, recogResult);
        //String message = "識別錯誤, 錯誤碼:" + errorCode + " ," + subErrorCode + " ; " + descMessage;// why 實際中能夠去掉,不須要
        String message = recogResult.getOrigalJson();//{"origin_result":{"sn":"","error":7,"desc":"No recognition result match","sub_error":7001},"error":7,"desc":"No recognition result match","sub_error":7001}
        sendStatusMessage(message + ";錯誤消息:" + errorMessage + ";描述信息:" + descMessage);
        if (speechEndTime > 0) {
            long diffTime = System.currentTimeMillis() - speechEndTime;
            //message += "。說話結束到識別結束耗時【" + diffTime + "ms】";// why實際中能夠去掉,不須要
        }
        speechEndTime = 0;
        sendMessage(message, status, true);
        speechEndTime = 0;
    }

    @Override
    public void onAsrOnlineNluResult(String nluResult) {
        super.onAsrOnlineNluResult(nluResult);
        if (!nluResult.isEmpty()) {
            sendStatusMessage("原始語義識別結果json:" + nluResult);
        }
    }

    @Override
    public void onAsrFinish(RecogResult recogResult) {
        super.onAsrFinish(recogResult);
        sendStatusMessage("識別一段話結束。若是是長語音的狀況會繼續識別下段話。");

    }

    /**
     * 長語音識別結束
     */
    @Override
    public void onAsrLongFinish() {
        super.onAsrLongFinish();
        sendStatusMessage("長語音識別結束。");
    }


    /**
     * 使用離線命令詞時,有該回調說明離線語法資源加載成功
     */
    @Override
    public void onOfflineLoaded() {
        sendStatusMessage("【重要】asr.loaded:離線資源加載成功。沒有此回調可能離線語法功能不能使用。");
    }

    /**
     * 使用離線命令詞時,有該回調說明離線語法資源加載成功
     */
    @Override
    public void onOfflineUnLoaded() {
        sendStatusMessage(" 離線資源卸載成功。");
    }

    @Override
    public void onAsrExit() {
        super.onAsrExit();
        sendStatusMessage("識別引擎結束並空閒中");
    }

    private void sendStatusMessage(String message) {
        sendMessage(message, status);
    }

    private void sendMessage(String message) {
        sendMessage(message, WHAT_MESSAGE_STATUS);
    }

    private void sendMessage(String message, int what) {
        sendMessage(message, what, false);
    }


    private void sendMessage(String message, int what, boolean highlight) {


        if (needTime && what != STATUS_FINISHED) {
            message += "  ;time=" + System.currentTimeMillis();
        }
        if (handler == null){
            Log.i(TAG, message );
            return;
        }
        Message msg = Message.obtain();
        msg.what = what;
        msg.arg1 = status;
        if (highlight) {
            msg.arg2 = 1;
        }
        msg.obj = message + "\n";
        handler.sendMessage(msg);
    }
}

至此,百度語音SDK集成到baiduspeech中了,下一步就是在baiduspeech中建立UI對話框。

(7)建立底部對話框SpeechBottomSheetDialog【根據實際狀況自行修改UI佈局

一、在baiduspeech的build.gradle中引用recyclerview【版本號和項目的appcompat保持一致】【由於demo中用到了】

apply plugin: 'com.android.library'

android {
    compileSdkVersion 27



    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'com.android.support:appcompat-v7:27.1.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    //RecyclerView compile "com.android.support:recyclerview-v7:27.1.1"
}

二、對話框類、列表適配器類、佈局文件xml文件、圖片資源、動畫style樣式等複製到baiduspeech中

三、這裏主要標註下SpeechBottomSheetDialog.java中百度語音的相關代碼

package com.why.project.baiduspeech.dialog;

import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.DialogFragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.baidu.speech.asr.SpeechConstant;
import com.baidu.speech.utils.LogUtil;
import com.why.project.baiduspeech.R;
import com.why.project.baiduspeech.control.MyRecognizer;
import com.why.project.baiduspeech.recognization.IStatus;
import com.why.project.baiduspeech.recognization.MessageStatusRecogListener;
import com.why.project.baiduspeech.recognization.StatusRecogListener;
import com.why.project.baiduspeech.util.Logger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by HaiyuKing
 * Used 語音識別底部對話框
 */

public class SpeechBottomSheetDialog extends DialogFragment {
    private static final String TAG = SpeechBottomSheetDialog.class.getSimpleName();

    private Context mContext;
    /**View實例*/
    private View myView;

    private ImageView img_close;
    private ProgressBar loadProgressBar;
    private TextView tv_tishi;
    private RecyclerView result_list;
    private Button btn_start;

    private ArrayList<String> resultWordList;
    private SpeechResultAdapter speechResultAdapter;

    private String BtnStartText = "按一下開始聽音"; private String BtnStopText = "按一下結束聽音"; private String BtnSearchingText = "正在識別"; private String TishiNoText = "沒聽清,請重說一遍"; /**識別控制器,使用MyRecognizer控制識別的流程*/
    protected MyRecognizer myRecognizer; /**控制UI按鈕的狀態*/
    protected int status; protected Handler handler; public static SpeechBottomSheetDialog getInstance(Context mContext)
    {
        SpeechBottomSheetDialog speechBottomSheetDialog = new SpeechBottomSheetDialog();
        speechBottomSheetDialog.mContext = mContext;

        return speechBottomSheetDialog;
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(0));//設置背景爲透明,而且沒有標題
        myView = inflater.inflate(R.layout.dialog_bottomsheet_speech, container, false);
        return myView;

    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onActivityCreated(savedInstanceState);

        initHandler();//初始化handler
        initRecog();//初始化語音

        initViews();
        initDatas();
        initEvents();
    }

    /**
     * 設置寬度和高度值,以及打開的動畫效果
     */
    @Override
    public void onStart() {
        super.onStart();
        //設置對話框的寬高,必須在onStart中
        DisplayMetrics metrics = new DisplayMetrics();
        this.getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
        Window window = this.getDialog().getWindow();
        window.setLayout(metrics.widthPixels, this.getDialog().getWindow().getAttributes().height);
        window.setGravity(Gravity.BOTTOM);//設置在底部
        //打開的動畫效果
        //設置dialog的 進出 動畫
        getDialog().getWindow().setWindowAnimations(R.style.speechbottomsheetdialog_animation);
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        LogUtil.w(TAG,"{onDismiss}");
        //當對話框消失的時候統一執行銷燬語音功能
        destroyRecog();//銷燬語音
    }


    private void initViews() {
        img_close = (ImageView) myView.findViewById(R.id.img_close);
        loadProgressBar = (ProgressBar) myView.findViewById(R.id.loadProgressBar);
        tv_tishi = (TextView) myView.findViewById(R.id.tv_tishi);
        result_list = (RecyclerView) myView.findViewById(R.id.result_list);
        btn_start = (Button) myView.findViewById(R.id.btn_start);
    }

    /**初始化數據*/
    private void initDatas() {
        resultWordList = new ArrayList<String>();
        speechResultAdapter = null;
        //設置佈局管理器
        LinearLayoutManager linerLayoutManager = new LinearLayoutManager(getActivity());
        result_list.setLayoutManager(linerLayoutManager);

        //能夠設置爲打開後自動識別語音
 startRecog(); showProgress();
    }

    private void initEvents() {
        //關閉圖標的點擊事件
        img_close.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        //按鈕的點擊事件
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (status) { case IStatus.STATUS_NONE: // 初始狀態
 startRecog(); status = IStatus.STATUS_WAITING_READY; updateBtnTextByStatus();//更改按鈕的文本 //顯示加載區域
 showProgress(); break; case IStatus.STATUS_WAITING_READY: // 調用本類的start方法後,即輸入START事件後,等待引擎準備完畢。
                    case IStatus.STATUS_READY: // 引擎準備完畢。
                    case IStatus.STATUS_SPEAKING: case IStatus.STATUS_FINISHED: // 長語音狀況
                    case IStatus.STATUS_RECOGNITION: stopRecog(); status = IStatus.STATUS_STOPPED; // 引擎識別中
                        updateBtnTextByStatus();//更改按鈕的文本
                        break; case IStatus.STATUS_STOPPED: // 引擎識別中
 cancelRecog(); status = IStatus.STATUS_NONE; // 識別結束,回到初始狀態
                        updateBtnTextByStatus();//更改按鈕的文本
                        break; default: break; }
            }
        });
    }


    /**
     * 顯示加載進度區域,隱藏其餘區域*/
    private void showProgress(){
        loadProgressBar.setVisibility(View.VISIBLE);
        tv_tishi.setVisibility(View.GONE);
        result_list.setVisibility(View.GONE);
    }

    /**
     * 顯示文本提示區域,隱藏其餘區域*/
    private void showTishi(){
        tv_tishi.setVisibility(View.VISIBLE);
        loadProgressBar.setVisibility(View.GONE);
        result_list.setVisibility(View.GONE);
    }

    /**
     * 顯示語音結果區域,隱藏其餘區域*/
    private void showListView(){
        result_list.setVisibility(View.VISIBLE);
        loadProgressBar.setVisibility(View.GONE);
        tv_tishi.setVisibility(View.GONE);
    }

    //======================================語音相關代碼==========================================
    /**
     * 初始化handler*/
    private void initHandler(){
        handler = new Handler() {
            /*@param msg*/
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                handleMsg(msg);
            }
        };
        Logger.setHandler(handler);
    }


    /**
     * 在onCreate中調用。初始化識別控制類MyRecognizer
     */
    protected void initRecog() {

        StatusRecogListener listener = new MessageStatusRecogListener(handler);
        myRecognizer = new MyRecognizer(mContext,listener);

        status = IStatus.STATUS_NONE;//默認什麼也沒有作
    }

    /**
     * 銷燬時須要釋放識別資源。
     */
    protected void destroyRecog() {
        myRecognizer.release();
        Log.i(TAG, "destroyRecog");
    }

    /**
     * 開始錄音,點擊「開始」按鈕後調用。
     */
    protected void startRecog() {
        Map<String, Object> params = new LinkedHashMap<String, Object>();
        params.put(SpeechConstant.ACCEPT_AUDIO_DATA, false);//是否保存音頻
        params.put(SpeechConstant.DISABLE_PUNCTUATION, false);//是否禁用標點符號,在選擇輸入法模型的前提下生效【不由用的話,說完一段話,就自帶標點符號】
        params.put(SpeechConstant.ACCEPT_AUDIO_VOLUME, false);//暫時不知道什麼意思
        params.put(SpeechConstant.PID, 1536); // 普通話 search搜索模型,默認,適用於短句,無逗號,能夠有語義 //params.put(SpeechConstant.VAD_ENDPOINT_TIMEOUT, 0); // 長語音,建議搭配input輸入法模型
        myRecognizer.start(params);
    }

    /**
     * 開始錄音後,手動中止錄音。SDK會識別在此過程當中的錄音。點擊「中止」按鈕後調用。
     */
    private void stopRecog() {
        myRecognizer.stop();
    }

    /**
     * 開始錄音後,取消此次錄音。SDK會取消本次識別,回到原始狀態。點擊「取消」按鈕後調用。
     */
    private void cancelRecog() {
        myRecognizer.cancel();
    }


    protected void handleMsg(Message msg) {
        Log.e(TAG,"msg.what="+msg.what);
        Log.e(TAG,"msg.obj.toString()="+msg.obj.toString());
        Log.e(TAG,"msg.arg2="+msg.arg2);
        switch (msg.what) { // 處理MessageStatusRecogListener中的狀態回調
            case IStatus.STATUS_FINISHED:
                //識別結束時候的調用【判斷顯示結果列表區域仍是提示區域】
                if (msg.arg2 == 1) {
                    //解析json字符串
                    try {
                        JSONObject msgObj = new JSONObject(msg.obj.toString());
                        String error = msgObj.getString("error");
                        if(error.equals("0")){
                            //解析結果集合,展示列表
                            JSONObject origin_resultObj = msgObj.getJSONObject("origin_result"); JSONObject resultObj = origin_resultObj.getJSONObject("result"); JSONArray wordList = resultObj.getJSONArray("word"); initList(wordList);//初始化集合數據
 showListView();
                        }else if(error.equals("7")){
                            tv_tishi.setText(TishiNoText);
                            showTishi();
                        }else{//應該根據不一樣的狀態值,顯示不一樣的提示
                            tv_tishi.setText(TishiNoText);
                            showTishi();
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                        tv_tishi.setText(TishiNoText);
                        showTishi();
                    }
                }else if(msg.arg2 == 0){//無網絡的狀況 //解析json字符串{"origin_result":{"sn":"","error":2,"desc":"Network is not available","sub_error":2100},"error":2,"desc":"Network is not available","sub_error":2100}
                    try { JSONObject msgObj = new JSONObject(msg.obj.toString()); JSONObject origin_resultObj = msgObj.getJSONObject("origin_result"); String error = origin_resultObj.getString("error"); if(error.equals("2")){ //解析結果集合,展示列表
                            String desc = origin_resultObj.getString("desc"); Toast.makeText(mContext,desc,Toast.LENGTH_SHORT).show(); } } catch (JSONException e) { e.printStackTrace(); } }
                status = msg.what;
                updateBtnTextByStatus();
                break;
            case IStatus.STATUS_NONE:
            case IStatus.STATUS_READY:
            case IStatus.STATUS_SPEAKING:
            case IStatus.STATUS_RECOGNITION:
                status = msg.what;
                updateBtnTextByStatus();
                break;
            default:
                break;
        }
    }

    /**更改按鈕的文本*/
    private void updateBtnTextByStatus() {
        switch (status) {
            case IStatus.STATUS_NONE:
                btn_start.setText(BtnStartText);
                btn_start.setEnabled(true);
                break;
            case IStatus.STATUS_WAITING_READY:
            case IStatus.STATUS_READY:
            case IStatus.STATUS_SPEAKING:
            case IStatus.STATUS_RECOGNITION:
                btn_start.setText(BtnStopText);
                btn_start.setEnabled(true);
                break;
            case IStatus.STATUS_STOPPED:
                btn_start.setText(BtnSearchingText);
                btn_start.setEnabled(true);
                break;
            default:
                break;
        }
    }

    //========================================更改列表==========================
    /**獲取集合數據,並顯示*/
    private void initList(JSONArray wordList){
        //先清空
        if(resultWordList.size() > 0){
            resultWordList.clear();
        }
        //再賦值
        for(int i=0;i<wordList.length();i++){
            String wordItem = "";
            try {
                wordItem = wordList.getString(i);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            resultWordList.add(wordItem);
        }

        if(speechResultAdapter == null){
            //設置適配器
            speechResultAdapter = new SpeechResultAdapter(getActivity(), resultWordList);
            result_list.setAdapter(speechResultAdapter);
            //添加分割線
            //設置添加刪除動畫

            //調用ListView的setSelected(!ListView.isSelected())方法,這樣就能及時刷新佈局
            result_list.setSelected(true);
        }else{
            speechResultAdapter.notifyDataSetChanged();
        }

        speechResultAdapter.setOnItemClickLitener(new SpeechResultAdapter.OnItemClickLitener() {
            @Override
            public void onItemClick(int position) {
                dismiss();
                if(mOnResultListItemClickListener != null){
                    mOnResultListItemClickListener.onItemClick(resultWordList.get(position));
                }
            }
        });
    }

    //=========================語音列表項的點擊事件監聽==============================
    public static abstract interface OnResultListItemClickListener
    {
        //語音結果列表項的點擊事件接口
        public abstract void onItemClick(String title);
    }

    private OnResultListItemClickListener mOnResultListItemClickListener;

    public void seOnResultListItemClickListener(OnResultListItemClickListener mOnResultListItemClickListener)
    {
        this.mOnResultListItemClickListener = mOnResultListItemClickListener;
    }

}

四、若是想要使用長語音功能,請參考SpeechLongBottomSheetDialog.java文件

package com.why.project.baiduspeech.dialog;

import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.DialogFragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.baidu.speech.asr.SpeechConstant;
import com.baidu.speech.utils.LogUtil;
import com.why.project.baiduspeech.R;
import com.why.project.baiduspeech.control.MyRecognizer;
import com.why.project.baiduspeech.recognization.IStatus;
import com.why.project.baiduspeech.recognization.MessageStatusRecogListener;
import com.why.project.baiduspeech.recognization.StatusRecogListener;
import com.why.project.baiduspeech.util.Logger;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by HaiyuKing
 * Used 普通話 input輸入法模型,適用於長句及長語音,有逗號分割,無語義【基本上和SpeechBottomSheetDialog代碼相同】
 */

public class SpeechLongBottomSheetDialog extends DialogFragment {
    private static final String TAG = SpeechBottomSheetDialog.class.getSimpleName();

    private Context mContext;
    /**View實例*/
    private View myView;

    private ImageView img_close;
    private ProgressBar loadProgressBar;
    private TextView tv_tishi;
    private RecyclerView result_list;
    private Button btn_start;

    private ArrayList<String> resultWordList;
    private SpeechResultAdapter speechResultAdapter;

    private String BtnStartText = "按一下開始聽音";
    private String BtnStopText = "按一下結束聽音";
    private String BtnSearchingText = "正在識別";

    private String TishiNoText = "沒聽清,請重說一遍";

    /**識別控制器,使用MyRecognizer控制識別的流程*/
    protected MyRecognizer myRecognizer;
    /**控制UI按鈕的狀態*/
    protected int status;

    protected Handler handler;


    public static SpeechLongBottomSheetDialog getInstance(Context mContext)
    {
        SpeechLongBottomSheetDialog speechLongBottomSheetDialog = new SpeechLongBottomSheetDialog();
        speechLongBottomSheetDialog.mContext = mContext;

        return speechLongBottomSheetDialog;
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(0));//設置背景爲透明,而且沒有標題
        myView = inflater.inflate(R.layout.dialog_bottomsheet_speech, container, false);
        return myView;

    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onActivityCreated(savedInstanceState);

        initHandler();//初始化handler
        initRecog();//初始化語音

        initViews();
        initDatas();
        initEvents();
    }

    /**
     * 設置寬度和高度值,以及打開的動畫效果
     */
    @Override
    public void onStart() {
        super.onStart();
        //設置對話框的寬高,必須在onStart中
        DisplayMetrics metrics = new DisplayMetrics();
        this.getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
        Window window = this.getDialog().getWindow();
        window.setLayout(metrics.widthPixels, this.getDialog().getWindow().getAttributes().height);
        window.setGravity(Gravity.BOTTOM);//設置在底部
        //打開的動畫效果
        //設置dialog的 進出 動畫
        getDialog().getWindow().setWindowAnimations(R.style.speechbottomsheetdialog_animation);
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        LogUtil.w(TAG,"{onDismiss}");
        //當對話框消失的時候統一執行銷燬語音功能
        destroyRecog();//銷燬語音
    }


    private void initViews() {
        img_close = (ImageView) myView.findViewById(R.id.img_close);
        loadProgressBar = (ProgressBar) myView.findViewById(R.id.loadProgressBar);
        tv_tishi = (TextView) myView.findViewById(R.id.tv_tishi);
        result_list = (RecyclerView) myView.findViewById(R.id.result_list);
        btn_start = (Button) myView.findViewById(R.id.btn_start);
    }

    /**初始化數據*/
    private void initDatas() {
        resultWordList = new ArrayList<String>();
        speechResultAdapter = null;
        //設置佈局管理器
        LinearLayoutManager linerLayoutManager = new LinearLayoutManager(getActivity());
        result_list.setLayoutManager(linerLayoutManager);

        btn_start.setText(BtnStartText);//顯示文字,和下面的二選一便可 why //能夠設置爲打開後自動識別語音
        /*startRecog(); showProgress();*/
    }

    private void initEvents() {
        //關閉圖標的點擊事件
        img_close.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        //按鈕的點擊事件
        btn_start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                switch (status) {
                    case IStatus.STATUS_NONE: // 初始狀態
                        startRecog();
                        status = IStatus.STATUS_WAITING_READY;
                        updateBtnTextByStatus();//更改按鈕的文本
                        //顯示加載區域
                        showProgress();
                        break;
                    case IStatus.STATUS_WAITING_READY: // 調用本類的start方法後,即輸入START事件後,等待引擎準備完畢。
                    case IStatus.STATUS_READY: // 引擎準備完畢。
                    case IStatus.STATUS_SPEAKING:
                    case IStatus.STATUS_FINISHED: // 長語音狀況
                    case IStatus.STATUS_RECOGNITION:
                        stopRecog();
                        status = IStatus.STATUS_STOPPED; // 引擎識別中
                        updateBtnTextByStatus();//更改按鈕的文本
                        //對於長語音來說,須要手動執行代碼,不然還得點擊一次才能取消why
                        btn_start.callOnClick();
                        break;
                    case IStatus.STATUS_STOPPED: // 引擎識別中
                        cancelRecog();
                        hiddenAll();//隱藏加載區域why
                        status = IStatus.STATUS_NONE; // 識別結束,回到初始狀態
                        updateBtnTextByStatus();//更改按鈕的文本
                        break;
                    default:
                        break;
                }
            }
        });
    }


    /**
     * 顯示加載進度區域,隱藏其餘區域*/
    private void showProgress(){
        loadProgressBar.setVisibility(View.VISIBLE);
        tv_tishi.setVisibility(View.GONE);
        result_list.setVisibility(View.GONE);
    }

    /**
     * 顯示文本提示區域,隱藏其餘區域*/
    private void showTishi(){
        tv_tishi.setVisibility(View.VISIBLE);
        loadProgressBar.setVisibility(View.GONE);
        result_list.setVisibility(View.GONE);
    }

    /**
     * 顯示語音結果區域,隱藏其餘區域*/
    private void showListView(){
        result_list.setVisibility(View.VISIBLE);
        loadProgressBar.setVisibility(View.GONE);
        tv_tishi.setVisibility(View.GONE);
    }

    /**隱藏全部的區域【主要用於長語音】why*/
    private void hiddenAll(){
        result_list.setVisibility(View.GONE);
        loadProgressBar.setVisibility(View.GONE);
        tv_tishi.setVisibility(View.GONE);
    }

    //======================================語音相關代碼==========================================
    /**
     * 初始化handler*/
    private void initHandler(){
        handler = new Handler() {
            /*@param msg*/
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                handleMsg(msg);
            }
        };
        Logger.setHandler(handler);
    }


    /**
     * 在onCreate中調用。初始化識別控制類MyRecognizer
     */
    protected void initRecog() {

        StatusRecogListener listener = new MessageStatusRecogListener(handler);
        myRecognizer = new MyRecognizer(mContext,listener);

        status = IStatus.STATUS_NONE;//默認什麼也沒有作
    }

    /**
     * 銷燬時須要釋放識別資源。
     */
    protected void destroyRecog() {
        myRecognizer.release();
        Log.i(TAG, "destroyRecog");
    }

    /**
     * 開始錄音,點擊「開始」按鈕後調用。
     */
    protected void startRecog() {
        Map<String, Object> params = new LinkedHashMap<String, Object>();
        params.put(SpeechConstant.ACCEPT_AUDIO_DATA, false);//是否保存音頻
        params.put(SpeechConstant.DISABLE_PUNCTUATION, false);//是否禁用標點符號,在選擇輸入法模型的前提下生效【不由用的話,說完一段話,就自帶標點符號】
        params.put(SpeechConstant.ACCEPT_AUDIO_VOLUME, false);//暫時不知道什麼意思
        //下面的1936和1537選擇其中一個 why //params.put(SpeechConstant.PID, 1936); // 普通話 far,遠場模型,高級,適用於音源離麥克風較遠(>1m)的錄音,有逗號分隔,能夠有語義
        params.put(SpeechConstant.PID, 1537); // 普通話 input輸入法模型,適用於長句及長語音,有逗號分割,無語義
        params.put(SpeechConstant.VAD_ENDPOINT_TIMEOUT, 0); // 長語音,建議搭配input輸入法模型
 myRecognizer.start(params);
    }

    /**
     * 開始錄音後,手動中止錄音。SDK會識別在此過程當中的錄音。點擊「中止」按鈕後調用。
     */
    private void stopRecog() {
        myRecognizer.stop();
    }

    /**
     * 開始錄音後,取消此次錄音。SDK會取消本次識別,回到原始狀態。點擊「取消」按鈕後調用。
     */
    private void cancelRecog() {
        myRecognizer.cancel();
    }


    protected void handleMsg(Message msg) {
        switch (msg.what) { // 處理MessageStatusRecogListener中的狀態回調
            case IStatus.STATUS_FINISHED:
                //識別結束時候的調用【判斷顯示結果列表區域仍是提示區域】
                if (msg.arg2 == 1) {
                    //解析json字符串
                    try {
                        JSONObject msgObj = new JSONObject(msg.obj.toString());
                        String error = msgObj.getString("error");
                        if(error.equals("0")){
                            //直接輸入到文本框中 why
                            JSONArray recognitionObj = msgObj.getJSONArray("results_recognition"); String result = recognitionObj.getString(0); if(mOnResultListItemClickListener != null){ mOnResultListItemClickListener.onItemClick(result); }
                        }else if(error.equals("7")){
                            tv_tishi.setText(TishiNoText);
                            showTishi();
                        }else{//應該根據不一樣的狀態值,顯示不一樣的提示
                            tv_tishi.setText(TishiNoText);
                            showTishi();
                        }
                    } catch (JSONException e) {
                        e.printStackTrace();
                        tv_tishi.setText(TishiNoText);
                        showTishi();
                    }
                }else if(msg.arg2 == 0){//無網絡的狀況 //解析json字符串{"origin_result":{"sn":"","error":2,"desc":"Network is not available","sub_error":2100},"error":2,"desc":"Network is not available","sub_error":2100}
                    try { JSONObject msgObj = new JSONObject(msg.obj.toString()); JSONObject origin_resultObj = msgObj.getJSONObject("origin_result"); String error = origin_resultObj.getString("error"); if(error.equals("2")){ //解析結果集合,展示列表
                            String desc = origin_resultObj.getString("desc"); Toast.makeText(mContext,desc,Toast.LENGTH_SHORT).show(); } } catch (JSONException e) { e.printStackTrace(); } }
                status = msg.what;
                updateBtnTextByStatus();
                break;
            case IStatus.STATUS_NONE:
            case IStatus.STATUS_READY:
            case IStatus.STATUS_SPEAKING:
            case IStatus.STATUS_RECOGNITION:
                status = msg.what;
                updateBtnTextByStatus();
                break;
            default:
                break;
        }
    }

    /**更改按鈕的文本*/
    private void updateBtnTextByStatus() {
        switch (status) {
            case IStatus.STATUS_NONE:
                btn_start.setText(BtnStartText);
                btn_start.setEnabled(true);
                break;
            case IStatus.STATUS_WAITING_READY:
            case IStatus.STATUS_READY:
            case IStatus.STATUS_SPEAKING:
            case IStatus.STATUS_RECOGNITION:
                btn_start.setText(BtnStopText);
                btn_start.setEnabled(true);
                break;
            case IStatus.STATUS_STOPPED:
                btn_start.setText(BtnSearchingText);
                btn_start.setEnabled(true);
                break;
            default:
                break;
        }
    }

    //========================================更改列表==========================
    /**獲取集合數據,並顯示*/
    private void initList(JSONArray wordList){
        //先清空
        if(resultWordList.size() > 0){
            resultWordList.clear();
        }
        //再賦值
        for(int i=0;i<wordList.length();i++){
            String wordItem = "";
            try {
                wordItem = wordList.getString(i);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            resultWordList.add(wordItem);
        }

        if(speechResultAdapter == null){
            //設置適配器
            speechResultAdapter = new SpeechResultAdapter(getActivity(), resultWordList);
            result_list.setAdapter(speechResultAdapter);
            //添加分割線
            //設置添加刪除動畫

            //調用ListView的setSelected(!ListView.isSelected())方法,這樣就能及時刷新佈局
            result_list.setSelected(true);
        }else{
            speechResultAdapter.notifyDataSetChanged();
        }

        speechResultAdapter.setOnItemClickLitener(new SpeechResultAdapter.OnItemClickLitener() {
            @Override
            public void onItemClick(int position) {
                dismiss();
                if(mOnResultListItemClickListener != null){
                    mOnResultListItemClickListener.onItemClick(resultWordList.get(position));
                }
            }
        });
    }

    //=========================語音列表項的點擊事件監聽==============================
    public static abstract interface OnResultListItemClickListener
    {
        //語音結果列表項的點擊事件接口
        public abstract void onItemClick(String title);
    }

    private OnResultListItemClickListener mOnResultListItemClickListener;

    public void seOnResultListItemClickListener(OnResultListItemClickListener mOnResultListItemClickListener)
    {
        this.mOnResultListItemClickListener = mOnResultListItemClickListener;
    }
}

3、使用方法

(1)由於須要使用到運行時權限,因此參考《Android6.0運行時權限(基於RxPermission開源庫)》在APP的build.gradle中引入第三方庫

(2)在APP的build.gradle中引入baiduspeech

(3)在Activity中調用

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.why.project.baiduspeechdemo.MainActivity">

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.448"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.325"/>

    <Button
        android:id="@+id/btn_openSpeechDialog"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="32dp"
        android:text="打開搜索模型語音識別對話框"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.419"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <Button
        android:id="@+id/btn_openSpeechLongDialog"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="16dp"
        android:text="打開input輸入模型語音識別對話框"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.4"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_openSpeechDialog"/>

</android.support.constraint.ConstraintLayout>
activity_main.xml

 

package com.why.project.baiduspeechdemo;

import android.Manifest;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.tbruyelle.rxpermissions2.RxPermissions;
import com.why.project.baiduspeech.dialog.SpeechBottomSheetDialog;
import com.why.project.baiduspeech.dialog.SpeechLongBottomSheetDialog;

import io.reactivex.functions.Action;
import io.reactivex.functions.Consumer;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();

    private Button mOpenSpeechDialogBtn;
    private Button mOpenSpeechLongDialogBtn;
    private TextView mResultTv;

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

        onePermission();

        initViews();
        initEvents();
    }

    private void initViews() {
        mOpenSpeechDialogBtn = findViewById(R.id.btn_openSpeechDialog);
        mOpenSpeechLongDialogBtn = findViewById(R.id.btn_openSpeechLongDialog);
        mResultTv = findViewById(R.id.tv_result);
    }

    private void initEvents() {
        mOpenSpeechDialogBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //打開百度語音對話框
                SpeechBottomSheetDialog speechBottomSheetDialog = SpeechBottomSheetDialog.getInstance(MainActivity.this); speechBottomSheetDialog.seOnResultListItemClickListener(new SpeechBottomSheetDialog.OnResultListItemClickListener() { @Override public void onItemClick(String title) { //填充到輸入框中
 mResultTv.setText(title); } }); speechBottomSheetDialog.show(getSupportFragmentManager(), TAG);
            }
        });
        mOpenSpeechLongDialogBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //打開百度語音對話框
                SpeechLongBottomSheetDialog speechLongBottomSheetDialog = SpeechLongBottomSheetDialog.getInstance(MainActivity.this); speechLongBottomSheetDialog.seOnResultListItemClickListener(new SpeechLongBottomSheetDialog.OnResultListItemClickListener() { @Override public void onItemClick(String title) { //填充到輸入框中
                        mResultTv.setText(mResultTv.getText()+title); } }); speechLongBottomSheetDialog.show(getSupportFragmentManager(), TAG);
            }
        });

    }

    /**只有一個運行時權限申請的狀況*/
    private void onePermission(){
        RxPermissions rxPermissions = new RxPermissions(MainActivity.this); // where this is an Activity instance
        rxPermissions.request(Manifest.permission.RECORD_AUDIO,
                Manifest.permission.READ_PHONE_STATE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) //權限名稱,多個權限之間逗號分隔開
                .subscribe(new Consumer<Boolean>() {
                    @Override
                    public void accept(Boolean granted) throws Exception {
                        Log.e(TAG, "{accept}granted=" + granted);//執行順序——1【多個權限的狀況,只有全部的權限均容許的狀況下granted==true】
                        if (granted) { // 在android 6.0以前會默認返回true
                            // 已經獲取權限
                        } else {
                            // 未獲取權限
                            Toast.makeText(MainActivity.this, "您沒有受權該權限,請在設置中打開受權", Toast.LENGTH_SHORT).show();
                        }
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(Throwable throwable) throws Exception {
                        Log.e(TAG,"{accept}");//多是受權異常的狀況下的處理
                    }
                }, new Action() {
                    @Override
                    public void run() throws Exception {
                        Log.e(TAG,"{run}");//執行順序——2
                    }
                });
    }
}

混淆配置

#=====================百度語音混淆=====================
-keep class com.baidu.speech.**{*;}

參考資料

http://ai.baidu.com/tech/speech

集成指南

項目demo下載地址

https://github.com/haiyuKing/BaiduSpeechDemo

相關文章
相關標籤/搜索