百度语音SDK提供:
语音识别:将声音转成文字
语音合成:将文字转成语音文件,然后播放语音文件,即文字变声音。
语音唤醒:语音唤醒,激活运用程序
在这里,本篇介绍百度语音合成的使用。
百度语音介绍:
- 永久免费
- 多语言(中文,中英混读)多音色(男音,女音)可选
- 离线与在线,节省流量
详情参考,百度语音介绍
百度语音使用流程指南:
- 成为开发者,创建运用,选择服务
- 下载对应的SDK,集成开发。
详情参考,百度语音接入指南、百度语音SDK下载.
项目集成百度语音SDK:
在androidStudio中按照其AS特有的使用方式添加添加so库,相关的jar。
添加完so库,jar库后,需Gradle中配置如下:
android { ........sourceSets {main { //设置so库依赖路径jniLibs.srcDirs = ['libs']} } } dependencies {..... compile files('libs/com.baidu.tts_2.3.0.jar') }
在androidStudio中assert文件夹下添加文本模型文件.dat,和声音模型文件.dat(无网络,离线下使用的)。这里因项目需求,只添加下载到女生模型文件。
添加以下权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
Proguard配置(不需要混淆sdk中jar包)
编写相关使用代码如下:
5.1 加载离线资源,先创建存储的文件夹,然后将assert中模型文件一个个通过Stream读写(异步操作,可考虑工作线程或者RxJava完成)指定的文件夹下:
/*** 百度tts文件夹*/ public static final String BAIDU_TTS_DIR_NAME = "baiduTts"; public static final String SPEECH_FEMALE_MODE_NAME = "bd_etts_ch_speech_female.dat"; public static final String TEXT_MODEL_NAME = "bd_etts_ch_text.dat";/*** 初始化语音文件的配置** @param context*/ private void initFileConfig(Context context) {File dirFile = getDirFile(context);if (dirFile != null && !dirFile.exists()) {dirFile.mkdir();copyFromAssertsToSDCard(context, SPEECH_FEMALE_MODE_NAME, getFilePath(dirFile, SPEECH_FEMALE_MODE_NAME));copyFromAssertsToSDCard(context, TEXT_MODEL_NAME, getFilePath(dirFile, TEXT_MODEL_NAME));} }/*** 获取目录** @param context* @return*/ public File getDirFile(Context context) {return MyUtils.getCacheFile(context, BAIDU_TTS_DIR_NAME); }/*** 获取文件路径** @param dirFile* @param fileName* @return*/ public String getFilePath(File dirFile, String fileName) {return dirFile.getAbsolutePath() + File.separator + fileName; }/*** 将文件写入sdcard中** @param context* @param assertFileName* @param filePath*/ private void copyFromAssertsToSDCard(Context context, String assertFileName, String filePath) {FileOutputStream outputStream = null;InputStream inputStream = null;try {inputStream = context.getResources().getAssets().open(assertFileName);outputStream = new FileOutputStream(filePath);byte[] buffer = new byte[1024];int length = 0;while ((length = inputStream.read(buffer)) > 0) {outputStream.write(buffer, 0, length);}} catch (Exception e) {e.printStackTrace();} finally {try {if (outputStream != null) {outputStream.close();}if (inputStream != null) {inputStream.close();}} catch (Exception e) {e.printStackTrace();}} }
MyUtil工具类中创建文件夹的代码如下:
/*** 获得存储文件** @param* @param* @return*/ public static File getCacheFile(Context context,String name) {String cachePath;if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())|| !Environment.isExternalStorageRemovable()) {cachePath = context.getExternalCacheDir().getPath();} else {cachePath = context.getCacheDir().getPath();}return new File(cachePath + File.separator + name); }
5.2 配置语音合成客户端:
/** * 运用的配置信息,由百度语音官网给定 */ public static final String API_KEY = "hBuMkBgzGR0YQQHflkufAWcvFRaqTxxx"; public static final String SECRET_KEY = "FemUYlEKxzr0moQ0jydZcmQ3fo11xxx"; public static final String APP_ID = "9369xxx";/*** 语音合成的客户端*/ private SpeechSynthesizer speechSynthesizer; /*** 合成状态的监听器*/ private SpeechSynthesizerListener speechSynthesizerListener;/*** 初始化语音的client*/ private void initBaiduTts(Context context) {speechSynthesizer = SpeechSynthesizer.getInstance();speechSynthesizer.setContext(context);//设置监听器if (getSpeechSynthesizerListener() != null) {speechSynthesizer.setSpeechSynthesizerListener(getSpeechSynthesizerListener());}//设置运用的api_key和secret_keyspeechSynthesizer.setApiKey(API_KEY, SECRET_KEY);//设置运用的app_idspeechSynthesizer.setAppId(APP_ID);File dirFile = getDirFile(context);//设置语音合成中文本模型文件(离线使用)speechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, getFilePath(dirFile, TEXT_MODEL_NAME));//设置语音合成中声音模型文件(离线使用)speechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, getFilePath(dirFile, SPEECH_FEMALE_MODE_NAME));//发音人(在线引擎),可用参数为0,1,2,3。。。(服务器端会动态增加,各值含义参考文档,以文档说明为准。0--普通女声,1--普通男声,2--特别男声,3--情感男声。。。)speechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");//设置Mix模式的合成策略speechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);//上线的时候,移除授权检查操作,即移除以下if语句if (isAuthSuccess()) {LogController.i(BaiduTtsController.class.getSimpleName(), "首次验证成功");}//初始化ttsspeechSynthesizer.initTts(TtsMode.MIX);}/*** 用于首次使用的使用,测试检查运用是成功申请到授权。注意点:若是测试无误后,可省略该步骤。** @return*/ public boolean isAuthSuccess() {AuthInfo info = speechSynthesizer.auth(TtsMode.MIX);if (!info.isSuccess()) {LogController.i(BaiduTtsController.class.getSimpleName(), "错误信息: " + info.getTtsError().getMessage());}return info.isSuccess(); }public SpeechSynthesizerListener getSpeechSynthesizerListener() {return speechSynthesizerListener; }/*** 设置语音合成的监听器** @param speechSynthesizerListener*/ public void setSpeechSynthesizerListener(SpeechSynthesizerListener speechSynthesizerListener) {this.speechSynthesizerListener = speechSynthesizerListener;if(speechSynthesizerListener!=null){this.speechSynthesizer.setSpeechSynthesizerListener(this.speechSynthesizerListener);} }
5.3 语音合成客户端的生命周期管理:
在Activity或者Service的生命周期中使用,onCreate()调用客户端的初始化操作(即以上两个配置步骤), onResume()调用客户端的resume(),Onstop()中调用客户端的stop().最后在onDestory()中调用客户端的release()
/*** 释放资源*/ public void release() {this.speechSynthesizer.release(); }/*** 停止正在执行的任务*/ public void stop() {this.speechSynthesizer.stop(); }/*** 恢复暂停的任务*/ public void resume() {this.speechSynthesizer.resume(); }
5.4 通过语音合成的客户端,将多段文本按一定顺序或者单独一段文本进行播放:
/*** 将text转成一个语音文件,然后自动播放。*** @param text*/ public void speeckText(String text) {if (!TextUtils.isEmpty(text)) {this.speechSynthesizer.speak(text);}}/*** 有顺序的合成多段语音,然后按一定顺序播放。** @param values*/ public void speeckTextValues(String[] values) {if (values == null && values.length == 0) {return;}List<SpeechSynthesizeBag> bags = new ArrayList<>();for (int i = 0; i < values.length; ++i) {bags.add(getSpeechSynthesizeBag(values[i], String.valueOf(i)));}this.speechSynthesizer.batchSpeak(bags); }/**** @param text 播放的文本内容* @param utterancedId 播放顺序,即第几个播放* @return*/ private SpeechSynthesizeBag getSpeechSynthesizeBag(String text, String utterancedId) {SpeechSynthesizeBag speechSynthesizeBag = new SpeechSynthesizeBag();//需要合成的文本text的长度不能超过1024个GBK字节。speechSynthesizeBag.setText(text);speechSynthesizeBag.setUtteranceId(utterancedId);return speechSynthesizeBag; }
5.5 监控语音合成客户端的监听器(即SpeechSynthesizerListener)
public class MessageDialogActivity extends BaseActivity implements View.OnClickListener,SpeechSynthesizerListener {private static final String TAG=MessageDialogActivity.class.getSimpleName();@Override public void onSynthesizeStart(String s) {LogController.i(TAG," onSynthesizeStart "+s); }@Override public void onSynthesizeDataArrived(String s, byte[] bytes, int i) {LogController.i(TAG," onSynthesizeDataArrived "+s); }@Override public void onSynthesizeFinish(String s) {LogController.i(TAG," onSynthesizeFinish "+s); }@Override public void onSpeechStart(String s) {LogController.i(TAG," onSpeechStart "+s); }@Override public void onSpeechProgressChanged(String s, int i) {LogController.i(TAG," onSpeechProgressChanged "+s); }@Override public void onSpeechFinish(String s) {LogController.i(TAG," onSpeechFinish "+s); }@Override public void onError(String s, SpeechError speechError) {LogController.i(TAG," onError "+speechError.description); } }
(个人)在实际项目中做法:
将百度语音客户端的初始化操作,生命周期管理操作,播放语音操作,封装成一个操作类。
public class BaiduTtsController { public static BaiduTtsController instance;private BaiduTtsController(Context context) {initConfig(context); }public synchronized static BaiduTtsController getInstance(Context context) {return instance = instance == null ? new BaiduTtsController(context) : instance; }public void initConfig(Context context) {initFileConfig(context);initBaiduTts(context); } //剩下的是,初始化,生命周期,播放的具体代码,以上步骤已经贴出。 }
将这个操作类保存到自定义的Application子类中,作为一个全局共享的对象。
public class BaseApplication extends Application {private BaiduTtsController controller; public BaiduTtsController initBaiduTts(){return controller= controller==null?BaiduTtsController.getInstance(this):controller; } public BaiduTtsController getBaiduTtsController(){return controller; } public void setBaiduTtsController(BaiduTtsController controller){this.controller=controller; } }
在service中通过Application子类对象,获取到操作类对象,拿到需要播放的文本,调用播放语音的操作。
private BaiduTtsController controller; //获取操作类的对象 controller=BaseApplication.getAppContext().getBaiduTtsController(); controller.setSpeechSynthesizerListener(this);//播放文本 controller.speeckText(msg1);
开发遇到问题:
- so库没有加载成功,控制台日志如下:
No implementation found for int com.baidu.tts.jni.EmbeddedSynthesizerEngine.bdTTSGetLicenseNo implementation found for int com.baidu.speechsynthesizer.utility.SpeechDecoder.decodeWithCallback
解决方式:正确导入so库,可以在手机中安装的运用查看so库是否随运用一起安装在手机上。
运用的签名错误,控制台日志如下:
Authentication Error=============================================----------------- 鉴权错误信息 ------------sha1;package:B3:88:6A:66:18:70:4E:FE:48:60:4C:4C:37:A9:2D:84:5D:16:56:xx;com.xxx.mjqzclientkey:RWpAlKWQE8SBoE354xUYeRcy906Asxxxerrorcode: -11 uid: -1 appid -1 msg: httpsPost failed,IOException:Unable to resolve host "api.map.baidu.com": No address associated with hostname请仔细核查 SHA1、package与key申请信息是否对应,key是否删除,平台是否匹配errorcode为230时,请参考论坛链接:http://bbs.lbsyun.baidu.com/forum.php?mod=viewthread&tid=106461
解决方式:正确配置App的签名,然后在开发平台上,填写对应的包名,和sha1值。