最近在开发android的项目,在项目需求中要用到语音唤醒功能。之前都没接触过,今天小编就给大家分享android基于百度语音的语音交互功能,非常不错,感兴趣的朋友一起看看吧项目里面用到了语音唤醒功能,前面一直在用讯飞的语音识别,本来打算也是直接用讯飞的语音唤醒,但是讯飞的语音唤醒要收费,试用版只有35天有效期。只好改用百度语音,百度语音所有功能免费,功能也比较简单实用,包括语音识别,语音合成和语音唤醒,正好可以组成一套完整的语音交互功能。

效果图:

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

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

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

http://www.jb51.net/article/97329.htm

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

java.lang.UnsatisfiedLinkError: Native method not found: com.baidu.speech.easr.easrNativeJni.WakeUpFree:()I

MainActivity:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 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 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用于语音合成的文字信息

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version= "1.0" encoding= "utf-8" ?> "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" > android:layout_width= "match_parent" android:layout_height= "wrap_content" android:textSize= "18dp" android:padding= "8dp" android:id= "@+id/txtResult" /> android:id= "@+id/input" android:layout_width= "fill_parent" android:layout_height= "wrap_content" android:hint= "input" />

AndroidManifest:添加权限和一个作为语音识别的UI的活动

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 <?xml version= "1.0" encoding= "utf-8" ?> "http://schemas.android.com/apk/res/android" package = "com.example.administrator.baiduvoicetest" > "android.permission.RECORD_AUDIO" /> "android.permission.ACCESS_NETWORK_STATE" /> "android.permission.INTERNET" /> "android.permission.READ_PHONE_STATE" /> "android.permission.WRITE_EXTERNAL_STORAGE" /> "android.permission.ACCESS_WIFI_STATE" /> "android.permission.CHANGE_WIFI_STATE" /> "android.permission.MODIFY_AUDIO_SETTINGS" /> "android.permission.WRITE_SETTINGS" /> android:allowBackup= "true" android:icon= "@mipmap/ic_launcher" android:label= "@string/app_name" android:supportsRtl= "true" android:theme= "@style/AppTheme" > "com.baidu.speech.APP_ID" android:value= "8888274" /> "com.baidu.speech.API_KEY" android:value= "FOFOGnjFERG3UTZC4FdDnXhM" />

更多相关文章

  1. android 语音即时通讯之录音、播放实现
  2. Android开发学习之使用百度语音识别SDK实现语音识别(上)
  3. ilbc编解码在android实现
  4. 扒一扒Android应用的续命大法
  5. android语音即时通讯之录音、播放功能实现代码
  6. 【Android语音合成TTS】国内主流引擎对比
  7. 用srec库实现普通话语音识别
  8. Android(安卓)- 智能 wifi 插座控制开关
  9. JPush 推送消息给 Android

随机推荐

  1. SQL Server中identity(自增)的用法详解
  2. Microsoft SQL Server数据库各版本下载地
  3. SqlServer生成连续数字根据指定的数字操
  4. Zabbix监控SQL Server服务状态的方法详解
  5. SQL Server 开窗函数 Over()代替游标的使
  6. SQL Server中row_number函数的常见用法示
  7. SQL Server如何通过创建临时表遍历更新数
  8. hive中将string数据转为bigint的操作
  9. SQL Server 使用join all优化 or 查询速
  10. SQL删除语句DROP、TRUNCATE、 DELETE 的