欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 基于百度语音的语音交互功能(推荐)

程序员文章站 2024-03-02 10:42:34
项目里面用到了语音唤醒功能,前面一直在用讯飞的语音识别,本来打算也是直接用讯飞的语音唤醒,但是讯飞的语音唤醒要收费,试用版只有35天有效期。只好改用百度语音,百度语音所有功...

项目里面用到了语音唤醒功能,前面一直在用讯飞的语音识别,本来打算也是直接用讯飞的语音唤醒,但是讯飞的语音唤醒要收费,试用版只有35天有效期。只好改用百度语音,百度语音所有功能免费,功能也比较简单实用,包括语音识别,语音合成和语音唤醒,正好可以组成一套完整的语音交互功能。

效果图:

Android 基于百度语音的语音交互功能(推荐)

首先是语音唤醒功能,说出关键词即可叫语音识别,唤醒成功会有语音提示,这里采用了百度语音的合成功能。然后百度语音识别会根据wifi情况自动切换在线或者离线识别,但是离线识别只能识别已经导入的关键词,而且离线第一次识别需要联网,识别成功,同样会有语音提示。效果图gif没有声音,toast显示的时候就是语音提示的内容。

这里说一点,百度语音的demo里给的语音唤醒是在onresume()开始唤醒监听,唤醒成功后在onpause()里就停止唤醒监听。而我现在要在唤醒成功后弹出语音识别的ui界面,所以弹出ui的同时就会停止唤醒监听。如果语音识别成功,ui界面消失,唤醒监听会重新开始,此时说出唤醒词即可重新唤醒。但是如果识别失败,封装好的ui界面会变成下图情况,这时候就要手动点击重试或者取消才可以,不符合全语音交互的理念。为了解决这个情况,要将停止唤醒监听写到onstop()里,这样即使语音识别失败,也可以重新唤醒。

Android 基于百度语音的语音交互功能(推荐)

具体的集成步骤官方文档里都有,也可以参考下面的文章

注:我这里语音识别和语音合成都用到了,所以官网下的两个sdk都要导入到工程里,这里还有个小问题,正常来说,jar包导入到工程之后,还要将assert和jnilibs文件夹放到工程里,我这里只放了语音识别的assert文件夹,jnilibs文件夹我都没放入工程里,这样可以使用。如果我将语音识别和语音合成的assert和jnilibs都放到工程里,反而会报下面的错误,不知道为什么。

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("请说唤醒词: 小度你好或者百度一下\n\n"+
"离在线语法识别(首次使用需要联网授权)\n"+
"语音识别开始后你可以说(可以根据语法自行定义离线说法):\n" +
" 1. 打电话给张三(离线)\n" +
" 2. 打电话给李四(离线)\n" +
" 3. 打开计算器(离线)\n" +
" 4. 明天天气怎么样(需要联网)\n" +
" ..." +
"\n");
minput= (edittext) findviewbyid(r.id.input);
minput.setvisibility(view.gone);
initialenv();
initialtts();
}
@override
protected void onresume() {
super.onresume();
// 唤醒功能打开步骤
// 1) 创建唤醒事件管理器
mwpeventmanager = eventmanagerfactory.create(mainactivity.this, "wp");
// 2) 注册唤醒事件监听器
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)) { // 每次唤醒成功, 将会回调name=wp.data的时间, 被激活的唤醒词在params的word字段
string word = json.getstring("word");
txtresult.append("唤醒成功, 唤醒词: " + word + "\r\n");
minput.settext("唤醒成功,请说出指令");
//minput.settext("succeed");
toast.maketext(mainactivity.this,minput.gettext(),toast.length_long).show();
speak();
//延时3秒,防止语音合成的内容又被语音识别
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"); // 设置离线的授权文件(离线模块需要授权), 该语法可以用自定义语义工具生成, 链接http://yuyin.baidu.com/asr#m5
//intent.putextra("slot-data", your slots); // 设置grammar中需要覆盖的词条,如联系人名
startactivityforresult(intent, 1);
} else if ("wp.exit".equals(name)) {
txtresult.append("唤醒已经停止: " + params + "\r\n");
}
} catch (jsonexception e) {
throw new androidruntimeexception(e);
}
}
});
// 3) 通知唤醒管理器, 启动唤醒功能
hashmap params = new hashmap();
params.put("kws-file", "assets:///wakeupdemo.bin"); // 设置唤醒资源, 唤醒资源请到 http://yuyin.baidu.com/wake#m4 来评估和导出
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("识别结果(数组形式): " + results_recognition + "\n");
//将数组形式的识别结果变为正常的string类型,例:[给张三打电话]变成给张三打电话
string str=results_recognition+"";
string res=str.substring(str.indexof("[")+1,str.indexof("]"));
txtresult.append(res+"\n");
minput.settext("好的,马上执行"+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);
// 声学模型文件路径 (离线引擎使用)
this.mspeechsynthesizer.setparam(speechsynthesizer.param_tts_speech_model_file, msampledirpath + "/"
+ speech_female_model_name);
// 本地授权文件路径,如未设置将使用默认路径.设置临时授权文件路径,licence_file_name请替换成临时授权文件的实际路径,仅在使用临时license文件时需要进行设置,如果在[应用管理]中开通了正式离线授权,不需要设置该参数,建议将该行代码删除(离线引擎)
// 如果合成结果出现临时授权文件将要到期的提示,说明使用了临时授权文件,请删除临时授权即可。
this.mspeechsynthesizer.setparam(speechsynthesizer.param_tts_licence_file, msampledirpath + "/"
+ license_file_name);
// 请替换为语音开发者平台上注册应用得到的app id (离线授权)
this.mspeechsynthesizer.setappid("xxx"/*这里只是为了让demo运行使用的appid,请替换成自己的id。*/);
// 请替换为语音开发者平台注册应用得到的apikey和secretkey (在线授权)
this.mspeechsynthesizer.setapikey("xxx",
"xxx"/*这里只是为了让demo正常运行使用apikey,请替换成自己的apikey*/);
// 发音人(在线引擎),可用参数为0,1,2,3。。。(服务器端会动态增加,各值含义参考文档,以文档说明为准。0--普通女声,1--普通男声,2--特别男声,3--情感男声。。。)
this.mspeechsynthesizer.setparam(speechsynthesizer.param_speaker, "0");
// 设置mix模式的合成策略
this.mspeechsynthesizer.setparam(speechsynthesizer.param_mix_mode, speechsynthesizer.mix_mode_default);
// 授权检测接口(只是通过authinfo进行检验授权是否成功。)
// authinfo接口用于测试开发者是否成功申请了在线或者离线授权,如果测试授权成功了,可以删除authinfo部分的代码(该接口首次验证时比较耗时),不会影响正常使用(合成使用时sdk内部会自动验证授权)
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个gbk字节。
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卡中使用(授权文件为临时授权文件,请注册正式授权)
*
* @param iscover 是否覆盖已存在的目标文件
* @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();
// 停止唤醒监听
mwpeventmanager.send("wp.stop", null, null, 0, 0);
}
}

注:源码是将官网给的demo进行整合,并且删除了一些用不到的方法,简少了代码量。

activity_main:只有一个textview和一个editview,很简单。textview用于显示结果,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:添加权限和一个作为语音识别的ui的活动

<?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. 在百度语音开放平台注册应用,http://yuyin.baidu.com/app
2. 为您的应用的“申请离线授权页面”,填写程序包名
3. 在当前应用的androidmanifest.xml中填写相应的app_id(或在代码中设置appid参数)
4. 根据场景下载并集成相应的资源,见http://yuyin.baidu.com/docs/asr/131和http://yuyin.baidu.com/asr/download
另外需要注意的是离线功能只是在线功能的“增强”,不能永久不联网使用(特别是首次使用)。
-->
<!-- 请填写真实的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 基于百度语音的语音交互功能,希望对大家有所帮助