Android 基于百度語音的語音交互功能(推薦)
項(xiàng)目里面用到了語音喚醒功能,前面一直在用訊飛的語音識(shí)別,本來打算也是直接用訊飛的語音喚醒,但是訊飛的語音喚醒要收費(fèi),試用版只有35天有效期。只好改用百度語音,百度語音所有功能免費(fèi),功能也比較簡單實(shí)用,包括語音識(shí)別,語音合成和語音喚醒,正好可以組成一套完整的語音交互功能。
效果圖:

首先是語音喚醒功能,說出關(guān)鍵詞即可叫語音識(shí)別,喚醒成功會(huì)有語音提示,這里采用了百度語音的合成功能。然后百度語音識(shí)別會(huì)根據(jù)wifi情況自動(dòng)切換在線或者離線識(shí)別,但是離線識(shí)別只能識(shí)別已經(jīng)導(dǎo)入的關(guān)鍵詞,而且離線第一次識(shí)別需要聯(lián)網(wǎng),識(shí)別成功,同樣會(huì)有語音提示。效果圖gif沒有聲音,Toast顯示的時(shí)候就是語音提示的內(nèi)容。
這里說一點(diǎn),百度語音的demo里給的語音喚醒是在onResume()開始喚醒監(jiān)聽,喚醒成功后在onPause()里就停止喚醒監(jiān)聽。而我現(xiàn)在要在喚醒成功后彈出語音識(shí)別的UI界面,所以彈出UI的同時(shí)就會(huì)停止喚醒監(jiān)聽。如果語音識(shí)別成功,UI界面消失,喚醒監(jiān)聽會(huì)重新開始,此時(shí)說出喚醒詞即可重新喚醒。但是如果識(shí)別失敗,封裝好的UI界面會(huì)變成下圖情況,這時(shí)候就要手動(dòng)點(diǎn)擊重試或者取消才可以,不符合全語音交互的理念。為了解決這個(gè)情況,要將停止喚醒監(jiān)聽寫到onStop()里,這樣即使語音識(shí)別失敗,也可以重新喚醒。

具體的集成步驟官方文檔里都有,也可以參考下面的文章
http://www.dhdzp.com/article/97329.htm
注:我這里語音識(shí)別和語音合成都用到了,所以官網(wǎng)下的兩個(gè)sdk都要導(dǎo)入到工程里,這里還有個(gè)小問題,正常來說,Jar包導(dǎo)入到工程之后,還要將assert和jniLibs文件夾放到工程里,我這里只放了語音識(shí)別的assert文件夾,jniLibs文件夾我都沒放入工程里,這樣可以使用。如果我將語音識(shí)別和語音合成的assert和jniLibs都放到工程里,反而會(huì)報(bào)下面的錯(cuò)誤,不知道為什么。
java.lang.UnsatisfiedLinkError: Native method not found: com.baidu.speech.easr.easrNativeJni.WakeUpFree:()I
MainActivity:
package com.example.administrator.baiduvoicetest;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.baidu.speech.EventListener;
import com.baidu.speech.EventManager;
import com.baidu.speech.EventManagerFactory;
import com.baidu.tts.auth.AuthInfo;
import com.baidu.tts.client.SpeechError;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.SpeechSynthesizerListener;
import com.baidu.tts.client.TtsMode;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
public class MainActivity extends AppCompatActivity {
private TextView txtResult;
private EditText mInput;
private EventManager mWpEventManager;
private SpeechSynthesizer mSpeechSynthesizer;
private String mSampleDirPath;
private static final String SAMPLE_DIR_NAME = "baiduTTS";
private static final String SPEECH_FEMALE_MODEL_NAME = "bd_etts_speech_female.dat";
private static final String SPEECH_MALE_MODEL_NAME = "bd_etts_speech_male.dat";
private static final String TEXT_MODEL_NAME = "bd_etts_text.dat";
private static final String LICENSE_FILE_NAME = "temp_license";
private static final String ENGLISH_SPEECH_FEMALE_MODEL_NAME = "bd_etts_speech_female_en.dat";
private static final String ENGLISH_SPEECH_MALE_MODEL_NAME = "bd_etts_speech_male_en.dat";
private static final String ENGLISH_TEXT_MODEL_NAME = "bd_etts_text_en.dat";
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtResult = (TextView) findViewById(R.id.txtResult);
txtResult.setText("請(qǐng)說喚醒詞: 小度你好或者百度一下\n\n"+
"離在線語法識(shí)別(首次使用需要聯(lián)網(wǎng)授權(quán))\n"+
"語音識(shí)別開始后你可以說(可以根據(jù)語法自行定義離線說法):\n" +
" 1. 打電話給張三(離線)\n" +
" 2. 打電話給李四(離線)\n" +
" 3. 打開計(jì)算器(離線)\n" +
" 4. 明天天氣怎么樣(需要聯(lián)網(wǎng))\n" +
" ..." +
"\n");
mInput= (EditText) findViewById(R.id.input);
mInput.setVisibility(View.GONE);
initialEnv();
initialTts();
}
@Override
protected void onResume() {
super.onResume();
// 喚醒功能打開步驟
// 1) 創(chuàng)建喚醒事件管理器
mWpEventManager = EventManagerFactory.create(MainActivity.this, "wp");
// 2) 注冊(cè)喚醒事件監(jiān)聽器
mWpEventManager.registerListener(new EventListener() {
@Override
public void onEvent(String name, String params, byte[] data, int offset, int length) {
Log.d(TAG, String.format("event: name=%s, params=%s", name, params));
try {
JSONObject json = new JSONObject(params);
if ("wp.data".equals(name)) { // 每次喚醒成功, 將會(huì)回調(diào)name=wp.data的時(shí)間, 被激活的喚醒詞在params的word字段
String word = json.getString("word");
txtResult.append("喚醒成功, 喚醒詞: " + word + "\r\n");
mInput.setText("喚醒成功,請(qǐng)說出指令");
//mInput.setText("succeed");
Toast.makeText(MainActivity.this,mInput.getText(),Toast.LENGTH_LONG).show();
speak();
//延時(shí)3秒,防止語音合成的內(nèi)容又被語音識(shí)別
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Intent intent = new Intent("com.baidu.action.RECOGNIZE_SPEECH");
intent.putExtra("grammar", "asset:///baidu_speech_grammardemo.bsg"); // 設(shè)置離線的授權(quán)文件(離線模塊需要授權(quán)), 該語法可以用自定義語義工具生成, 鏈接http://yuyin.baidu.com/asr#m5
//intent.putExtra("slot-data", your slots); // 設(shè)置grammar中需要覆蓋的詞條,如聯(lián)系人名
startActivityForResult(intent, 1);
} else if ("wp.exit".equals(name)) {
txtResult.append("喚醒已經(jīng)停止: " + params + "\r\n");
}
} catch (JSONException e) {
throw new AndroidRuntimeException(e);
}
}
});
// 3) 通知喚醒管理器, 啟動(dòng)喚醒功能
HashMap params = new HashMap();
params.put("kws-file", "assets:///WakeUpDemo.bin"); // 設(shè)置喚醒資源, 喚醒資源請(qǐng)到 http://yuyin.baidu.com/wake#m4 來評(píng)估和導(dǎo)出
mWpEventManager.send("wp.start", new JSONObject(params).toString(), null, 0, 0);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
Bundle results = data.getExtras();
ArrayList<String> results_recognition = results.getStringArrayList("results_recognition");
//txtResult.append("識(shí)別結(jié)果(數(shù)組形式): " + results_recognition + "\n");
//將數(shù)組形式的識(shí)別結(jié)果變?yōu)檎5腟tring類型,例:[給張三打電話]變成給張三打電話
String str=results_recognition+"";
String res=str.substring(str.indexOf("[")+1,str.indexOf("]"));
txtResult.append(res+"\n");
mInput.setText("好的,馬上執(zhí)行"+res);
speak();
Toast.makeText(MainActivity.this,mInput.getText(),Toast.LENGTH_LONG).show();
}
}
private void initialTts() {
this.mSpeechSynthesizer = SpeechSynthesizer.getInstance();
this.mSpeechSynthesizer.setContext(this);
this.mSpeechSynthesizer.setSpeechSynthesizerListener(new SpeechSynthesizerListener() {
@Override
public void onSynthesizeStart(String s) {
}
@Override
public void onSynthesizeDataArrived(String s, byte[] bytes, int i) {
}
@Override
public void onSynthesizeFinish(String s) {
}
@Override
public void onSpeechStart(String s) {
}
@Override
public void onSpeechProgressChanged(String s, int i) {
}
@Override
public void onSpeechFinish(String s) {
}
@Override
public void onError(String s, SpeechError speechError) {
}
});
// 文本模型文件路徑 (離線引擎使用)
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, mSampleDirPath + "/"
+ TEXT_MODEL_NAME);
// 聲學(xué)模型文件路徑 (離線引擎使用)
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, mSampleDirPath + "/"
+ SPEECH_FEMALE_MODEL_NAME);
// 本地授權(quán)文件路徑,如未設(shè)置將使用默認(rèn)路徑.設(shè)置臨時(shí)授權(quán)文件路徑,LICENCE_FILE_NAME請(qǐng)?zhí)鎿Q成臨時(shí)授權(quán)文件的實(shí)際路徑,僅在使用臨時(shí)license文件時(shí)需要進(jìn)行設(shè)置,如果在[應(yīng)用管理]中開通了正式離線授權(quán),不需要設(shè)置該參數(shù),建議將該行代碼刪除(離線引擎)
// 如果合成結(jié)果出現(xiàn)臨時(shí)授權(quán)文件將要到期的提示,說明使用了臨時(shí)授權(quán)文件,請(qǐng)刪除臨時(shí)授權(quán)即可。
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_LICENCE_FILE, mSampleDirPath + "/"
+ LICENSE_FILE_NAME);
// 請(qǐng)?zhí)鎿Q為語音開發(fā)者平臺(tái)上注冊(cè)應(yīng)用得到的App ID (離線授權(quán))
this.mSpeechSynthesizer.setAppId("xxx"/*這里只是為了讓Demo運(yùn)行使用的APPID,請(qǐng)?zhí)鎿Q成自己的id。*/);
// 請(qǐng)?zhí)鎿Q為語音開發(fā)者平臺(tái)注冊(cè)應(yīng)用得到的apikey和secretkey (在線授權(quán))
this.mSpeechSynthesizer.setApiKey("xxx",
"xxx"/*這里只是為了讓Demo正常運(yùn)行使用APIKey,請(qǐng)?zhí)鎿Q成自己的APIKey*/);
// 發(fā)音人(在線引擎),可用參數(shù)為0,1,2,3。。。(服務(wù)器端會(huì)動(dòng)態(tài)增加,各值含義參考文檔,以文檔說明為準(zhǔn)。0--普通女聲,1--普通男聲,2--特別男聲,3--情感男聲。。。)
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
// 設(shè)置Mix模式的合成策略
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
// 授權(quán)檢測(cè)接口(只是通過AuthInfo進(jìn)行檢驗(yàn)授權(quán)是否成功。)
// AuthInfo接口用于測(cè)試開發(fā)者是否成功申請(qǐng)了在線或者離線授權(quán),如果測(cè)試授權(quán)成功了,可以刪除AuthInfo部分的代碼(該接口首次驗(yàn)證時(shí)比較耗時(shí)),不會(huì)影響正常使用(合成使用時(shí)SDK內(nèi)部會(huì)自動(dòng)驗(yàn)證授權(quán))
AuthInfo authInfo = this.mSpeechSynthesizer.auth(TtsMode.MIX);
if (authInfo.isSuccess()) {
Toast.makeText(this,"auth success",Toast.LENGTH_LONG).show();
} else {
String errorMsg = authInfo.getTtsError().getDetailMessage();
Toast.makeText(this,"auth failed errorMsg=" + errorMsg,Toast.LENGTH_LONG).show();
}
// 初始化tts
mSpeechSynthesizer.initTts(TtsMode.MIX);
// 加載離線英文資源(提供離線英文合成功能)
int result =
mSpeechSynthesizer.loadEnglishModel(mSampleDirPath + "/" + ENGLISH_TEXT_MODEL_NAME, mSampleDirPath
+ "/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME);
//Toast.makeText(this,"loadEnglishModel result=" + result,Toast.LENGTH_LONG).show();
//打印引擎信息和model基本信息
//printEngineInfo();
}
private void speak() {
String text = this.mInput.getText().toString();
//需要合成的文本text的長度不能超過1024個(gè)GBK字節(jié)。
if (TextUtils.isEmpty(mInput.getText())) {
text = "歡迎使用百度語音合成SDK,百度語音為你提供支持。";
mInput.setText(text);
}
int result = this.mSpeechSynthesizer.speak(text);
if (result < 0) {
Toast.makeText(this,"error,please look up error code in doc or URL:http://yuyin.baidu.com/docs/tts/122 ",Toast.LENGTH_LONG).show();
}
}
private void initialEnv() {
if (mSampleDirPath == null) {
String sdcardPath = Environment.getExternalStorageDirectory().toString();
mSampleDirPath = sdcardPath + "/" + SAMPLE_DIR_NAME;
}
makeDir(mSampleDirPath);
copyFromAssetsToSdcard(false, SPEECH_FEMALE_MODEL_NAME, mSampleDirPath + "/" + SPEECH_FEMALE_MODEL_NAME);
copyFromAssetsToSdcard(false, SPEECH_MALE_MODEL_NAME, mSampleDirPath + "/" + SPEECH_MALE_MODEL_NAME);
copyFromAssetsToSdcard(false, TEXT_MODEL_NAME, mSampleDirPath + "/" + TEXT_MODEL_NAME);
copyFromAssetsToSdcard(false, LICENSE_FILE_NAME, mSampleDirPath + "/" + LICENSE_FILE_NAME);
copyFromAssetsToSdcard(false, "english/" + ENGLISH_SPEECH_FEMALE_MODEL_NAME, mSampleDirPath + "/"
+ ENGLISH_SPEECH_FEMALE_MODEL_NAME);
copyFromAssetsToSdcard(false, "english/" + ENGLISH_SPEECH_MALE_MODEL_NAME, mSampleDirPath + "/"
+ ENGLISH_SPEECH_MALE_MODEL_NAME);
copyFromAssetsToSdcard(false, "english/" + ENGLISH_TEXT_MODEL_NAME, mSampleDirPath + "/"
+ ENGLISH_TEXT_MODEL_NAME);
}
private void makeDir(String dirPath) {
File file = new File(dirPath);
if (!file.exists()) {
file.mkdirs();
}
}
/**
* 將sample工程需要的資源文件拷貝到SD卡中使用(授權(quán)文件為臨時(shí)授權(quán)文件,請(qǐng)注冊(cè)正式授權(quán))
*
* @param isCover 是否覆蓋已存在的目標(biāo)文件
* @param source
* @param dest
*/
private void copyFromAssetsToSdcard(boolean isCover, String source, String dest) {
File file = new File(dest);
if (isCover || (!isCover && !file.exists())) {
InputStream is = null;
FileOutputStream fos = null;
try {
is = getResources().getAssets().open(source);
String path = dest;
fos = new FileOutputStream(path);
byte[] buffer = new byte[1024];
int size = 0;
while ((size = is.read(buffer, 0, 1024)) >= 0) {
fos.write(buffer, 0, size);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
protected void onStop() {
super.onStop();
// 停止喚醒監(jiān)聽
mWpEventManager.send("wp.stop", null, null, 0, 0);
}
}
注:源碼是將官網(wǎng)給的demo進(jìn)行整合,并且刪除了一些用不到的方法,簡少了代碼量。
activity_main:只有一個(gè)TextView和一個(gè)EditView,很簡單。TextView用于顯示結(jié)果,EditView用于語音合成的文字信息
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.administrator.baiduvoicetest.MainActivity"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18dp" android:padding="8dp" android:id="@+id/txtResult" /> <EditText android:id="@+id/input" android:layout_width="fill_parent" android:layout_height="wrap_content" android:hint="input" /> </LinearLayout>
AndroidManifest:添加權(quán)限和一個(gè)作為語音識(shí)別的UI的活動(dòng)
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.administrator.baiduvoicetest"> <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" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <!-- begin: baidu speech sdk--> <!-- 離線功能指南: 1. 在百度語音開放平臺(tái)注冊(cè)應(yīng)用,http://yuyin.baidu.com/app 2. 為您的應(yīng)用的“申請(qǐng)離線授權(quán)頁面”,填寫程序包名 3. 在當(dāng)前應(yīng)用的AndroidManifest.xml中填寫相應(yīng)的APP_ID(或在代碼中設(shè)置appid參數(shù)) 4. 根據(jù)場(chǎng)景下載并集成相應(yīng)的資源,見http://yuyin.baidu.com/docs/asr/131和http://yuyin.baidu.com/asr/download 另外需要注意的是離線功能只是在線功能的“增強(qiáng)”,不能永久不聯(lián)網(wǎng)使用(特別是首次使用)。 --> <!-- 請(qǐng)?zhí)顚懻鎸?shí)的APP_ID API_KEY SECRET_KEY --> <meta-data android:name="com.baidu.speech.APP_ID" android:value="8888274"/> <meta-data android:name="com.baidu.speech.API_KEY" android:value="FOFOGnjFERG3UTZC4FdDnXhM"/> <meta-data android:name="com.baidu.speech.SECRET_KEY" android:value="63830985f5b05d2863f13ad07c7feaa3"/> <service android:name="com.baidu.speech.VoiceRecognitionService" android:exported="false" /> <activity android:name="com.baidu.voicerecognition.android.ui.BaiduASRDigitalDialog" android:configChanges="orientation|keyboardHidden|screenLayout" android:theme="@android:style/Theme.Dialog" android:exported="false" android:screenOrientation="portrait"> <intent-filter> <action android:name="com.baidu.action.RECOGNIZE_SPEECH" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <!-- end : baidu speech sdk--> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
以上所述是小編給大家介紹的android 基于百度語音的語音交互功能,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
MotionLayout自定義開關(guān)按鈕實(shí)例詳解
這篇文章主要為大家介紹了MotionLayout自定義開關(guān)按鈕實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Android socket實(shí)現(xiàn)原理詳解 服務(wù)端和客戶端如何搭建
這篇文章主要為大家詳細(xì)介紹了Android socket實(shí)現(xiàn)原理詳解,以及服務(wù)端和客戶端的搭建方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
Android實(shí)現(xiàn)橫屏切換科學(xué)計(jì)算器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)橫屏切換科學(xué)計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06
Android Studio實(shí)現(xiàn)登錄功能案例講解
這篇文章主要介紹了Android Studio實(shí)現(xiàn)登錄功能案例講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08
Android實(shí)現(xiàn)微信側(cè)滑刪除當(dāng)前頁面
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)微信側(cè)滑刪除當(dāng)前頁面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Android開發(fā)之ProgressBar字體隨著進(jìn)度條的加載而滾動(dòng)
這篇文章主要介紹了Android開發(fā)之ProgressBar字體隨著進(jìn)度條的加載而滾動(dòng),需要的朋友可以參考下2017-09-09
android實(shí)現(xiàn)定位與目的地的導(dǎo)航示例代碼
本篇文章主要介紹了android實(shí)現(xiàn)定位與目的地的導(dǎo)航示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02

