百度离线语音合成SDK可以帮助我们实现一些文本转语音的功能,并提供了多种声音类型选择。本篇博客总结了如何以SDK的形式使用该工具
目录
创建应用
登录百度智能云
语音能力引擎功能
创建应用
SDK集成
下载SDK
集成指南
添加权限
导入jar包及相关文件
初步使用
SpeechSynthesizer对象的初始化
说话人声音模式的选择
合成语音
在使用SDK前,我们需要先在百度智能云控制平台创建自己的应用并获得应用专属的App ID、Api Key、Secret Key
首先需要在官网百度智能云-登录,登录自己的百度账号(这里我的账号已进行实名认证,如果是新用户还需要进行实名认证处理,认证网址:百度智能云-管理中心)
这里点击菜单栏弹出的语音能力引擎,即可进入语音能力引擎页面:
点击操作指引中的 2创建应用下的去创建,即可弹出创建应用页面,如下图
我们需要填写应用名称,选择所需要的功能,选择App的开发平台,以及应用的描述
选择Android或IOS都需要提供软件开发的id名,这个id可以在应用文件的应用级build.gradle中找到,如下图:
填写完成后,即可在应用列表页内看到所创建的应用
创建完应用后,我们需要将短语音在线识别的SDK集成到我们新建的App项目中,集成过程如下:
在SDK下载_文字识别SDK_语音识别SDK-百度AI开放平台官网中下载离线语音合成SDK(根据开发平台选择Android Or IOS,这里我们以Android为例)
下载的是压缩包zip格式,我们需要将其解压
百度官方提供了 集成指南 供我们查阅
短语音合成的SDK我们需要请求如下权限:网络、获取网络状态、修改音频设置、访问及修改存储、获取和更改WiFi状态。
操作过程如下:
- 在AndroidManifest.xml文件中进行静态申请:
将上述代码 复制粘贴到
- 在活动代码中进行动态请求:
动态权限的申请已封装成函数,直接在控件初始化或使用语音合成前调用函数即可
private void initPermission() { String permissions[] = { Manifest.permission.INTERNET, Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.MODIFY_AUDIO_SETTINGS, Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.CHANGE_WIFI_STATE }; ArrayList
toApplyList = new ArrayList (); for (String perm : permissions) { if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) { toApplyList.add(perm); //进入到这里代表没有权限,可以弹窗提示,并设置相应控件不可操作disabled } } String tmpList[] = new String[toApplyList.size()]; if (!toApplyList.isEmpty()) { ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123); } }
- 导入相关文件
将之前下载并解压的SDK文件夹内的app/src/main/jniLibs 下armeabi等5个目录复制到项目的同名目录中(直接复制jniLibs文件夹),如下图:
- 导入jar包
将之前下载并解压的SDK文件夹内的app/libs/com.baidu.tts_2.6.xxxxxx.jar复制到项目的同名目录中。确认在应用级build.gradle文件中引入。
在应用级build.gradle文件内的 dependencies 字典内添加下列代码并重新sync:
implementation fileTree(dir: "libs", include: ["*.jar"])
我已经将 SpeechSynthesizer对象的初始化过程封装为函数,具体使用过程如下:
- 在活动的属性声明处进行相关变量的定义
首先在初始化前,我们需要在活动的属性声明处声明 SpeechSynthesizer类变量,定义并赋值String变量AppId,Api Key和Secret Key(这三个值在应用列表中可复制)
- 初始化函数的调用
private void initSynthesizer(){ synthesizer=SpeechSynthesizer.getInstance();// 构建实例 synthesizer.setContext(this); synthesizer.setAppId(AppId);// 设置AppId synthesizer.setApiKey(ApiKey,SecretKey);// 设置ApiKey和SecretKey String sn="98847f91-81423892-1761-0140-40e47-00"; synthesizer.setParam(SpeechSynthesizer.PARAM_AUTH_SN, sn);// sn码可以通用 //String mode=getSelectedVoice();//这个主要是进行说话人声音的选择,选择代码后续给出 //synthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER,mode);// 0,1,111(尽量用111) synthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER,0);// 这里暂时选择普通女声 synthesizer.initTts(TtsMode.MIX);// 设置语音合成模式,可以Online在线,Offline离线,Mix混合 LoggerProxy.printable(true);// 是否打印调用日志 @SuppressLint("HandlerLeak") Handler mainHandler=new Handler() { /* * @param msg */ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.obj != null) { System.out.println("msg = " + msg.obj.toString()); } } }; synthesizer.setSpeechSynthesizerListener(new UiMessageListener(mainHandler));//设置监听 }
只需要在使用合成前(比如控件初始化处)调用该函数即可
在监听设置时,用到了几个工具类,这里贴出代码:
UiMessageListener类
package util; import android.os.Handler; import android.os.Message; import android.util.Log; import util.MessageListener; /** * 在 MessageListener的基础上,和UI配合。 * Created by fujiayi on 2017/9/14. */ public class UiMessageListener extends MessageListener { private Handler mainHandler; private static final String TAG = "UiMessageListener"; public UiMessageListener(Handler mainHandler) { super(); this.mainHandler = mainHandler; } /** * 语音流 16K采样率 16bits编码 单声道 。 *
* 合成数据和进度的回调接口,分多次回调。 * 注意:progress表示进度,与播放到哪个字无关 * * @param utteranceId * @param data 合成的音频数据。该音频数据是采样率为16K,2字节精度,单声道的pcm数据。 * @param progress 文本按字符划分的进度,比如:你好啊 进度是0-3 * engineType 下版本提供 1:音频数据由离线引擎合成; 0:音频数据由在线引擎(百度服务器)合成。 */ @Override public void onSynthesizeDataArrived(String utteranceId, byte[] data, int progress) { // sendMessage("onSynthesizeDataArrived"); super.onSynthesizeDataArrived(utteranceId, data, progress); mainHandler.sendMessage(mainHandler.obtainMessage(UI_CHANGE_SYNTHES_TEXT_SELECTION, progress, 0)); } /** * 播放进度回调接口,分多次回调 * 注意:progress表示进度,与播放到哪个字无关 * * @param utteranceId * @param progress 文本按字符划分的进度,比如:你好啊 进度是0-3 */ @Override public void onSpeechProgressChanged(String utteranceId, int progress) { // sendMessage("onSpeechProgressChanged"); mainHandler.sendMessage(mainHandler.obtainMessage(UI_CHANGE_INPUT_TEXT_SELECTION, progress, 0)); } protected void sendMessage(String message) { sendMessage(message, false); } @Override protected void sendMessage(String message, boolean isError) { sendMessage(message, isError, PRINT); } protected void sendMessage(String message, boolean isError, int action) { super.sendMessage(message, isError); if (mainHandler != null) { Message msg = Message.obtain(); msg.what = action; msg.obj = message + "\n"; mainHandler.sendMessage(msg); Log.i(TAG, message); } } }
MessageListener类
package util; import android.util.Log; import com.baidu.tts.client.SpeechError; import com.baidu.tts.client.SpeechSynthesizerListener; import util.MainHandlerConstant; /** * SpeechSynthesizerListener 简单地实现,仅仅记录日志 * Created by fujiayi on 2017/5/19. */ public class MessageListener implements SpeechSynthesizerListener, MainHandlerConstant { private static final String TAG = "MessageListener"; /** * 播放开始,每句播放开始都会回调 * * @param utteranceId */ @Override public void onSynthesizeStart(String utteranceId) { sendMessage("准备开始合成,序列号:" + utteranceId); } /** * 语音流 16K采样率 16bits编码 单声道 。 * * @param utteranceId * @param bytes 二进制语音 ,注意可能有空data的情况,可以忽略 * @param progress 如合成“百度语音问题”这6个字, progress肯定是从0开始,到6结束。 但progress无法和合成到第几个字对应。 * engineType 1:音频数据由离线引擎合成; 0:音频数据由在线引擎(百度服务器)合成。 */ public void onSynthesizeDataArrived(String utteranceId, byte[] bytes, int progress) { Log.i(TAG, "合成进度回调, progress:" + progress + ";序列号:" + utteranceId); // + ";" + (engineType == 1? "离线合成":"在线合成")); } @Override // engineType 1:音频数据由离线引擎合成; 0:音频数据由在线引擎(百度服务器)合成。 public void onSynthesizeDataArrived(String utteranceId, byte[] bytes, int progress, int engineType) { onSynthesizeDataArrived(utteranceId, bytes, progress); } /** * 合成正常结束,每句合成正常结束都会回调,如果过程中出错,则回调onError,不再回调此接口 * * @param utteranceId */ @Override public void onSynthesizeFinish(String utteranceId) { sendMessage("合成结束回调, 序列号:" + utteranceId); } @Override public void onSpeechStart(String utteranceId) { sendMessage("播放开始回调, 序列号:" + utteranceId); } /** * 播放进度回调接口,分多次回调 * * @param utteranceId * @param progress 如合成“百度语音问题”这6个字, progress肯定是从0开始,到6结束。 但progress无法保证和合成到第几个字对应。 */ @Override public void onSpeechProgressChanged(String utteranceId, int progress) { // Log.i(TAG, "播放进度回调, progress:" + progress + ";序列号:" + utteranceId ); } /** * 播放正常结束,每句播放正常结束都会回调,如果过程中出错,则回调onError,不再回调此接口 * * @param utteranceId */ @Override public void onSpeechFinish(String utteranceId) { sendMessage("播放结束回调, 序列号:" + utteranceId); } /** * 当合成或者播放过程中出错时回调此接口 * * @param utteranceId * @param speechError 包含错误码和错误信息 */ @Override public void onError(String utteranceId, SpeechError speechError) { sendErrorMessage("错误发生:" + speechError.description + ",错误编码:" + speechError.code + ",序列号:" + utteranceId); } private void sendErrorMessage(String message) { sendMessage(message, true); } private void sendMessage(String message) { sendMessage(message, false); } protected void sendMessage(String message, boolean isError) { if (isError) { Log.e(TAG, message); } else { Log.i(TAG, message); } } }
MainHandlerConstant接口package util; /** * Created by fujiayi on 2017/9/13. */ public interface MainHandlerConstant { static final int PRINT = 0; static final int UI_CHANGE_INPUT_TEXT_SELECTION = 1; static final int UI_CHANGE_SYNTHES_TEXT_SELECTION = 2; static final int INIT_SUCCESS = 2; }
这些类都可以在SDK文件夹中找到
可以设置一个Spinner展示可供选择的声音类型,监听用户选择,并将结果返回给初始化函数中的mode,并进行设置(initSynthesizer函数中的注释部分)
- Spinner的设置可以参考我的另一篇博客
Android 第三方下拉框Nice Spinner的使用_尘心平的博客-CSDN博客- 声音模型选择函数:
spinner.setOnSpinnerItemSelectedListener(new OnSpinnerItemSelectedListener() { @Override public void onItemSelected(NiceSpinner parent, View view, int position, long id) { String item = (String) parent.getItemAtPosition(position); switch (item){ case "普通女声": return "0"; case "成熟男声": return "1"; case "磁性男声": return "3"; case "可爱女童": return "4"; case "甜美女声": return "5118"; case "情感男声": return "106"; case "情感女声": return "5"; } } });
相对应的声音mode可以在https://ai.baidu.com/ai-doc/SPEECH/zk8hh4v39中查到
合成语音的操作较为简单,代码如下。需要注意的是短语音合成每次speak的字符不能超过60,否则无法播放——可以通过多次speak创建合成队列解决长文本的问题。
synthesizer.speak("需要转换成语音的文本");
为了解决切换页面语音仍播放的问题,可以重写finish函数,在其中停止播放并释放资源:
@Override public void finish() { super.finish(); synthesizer.stop(); synthesizer.release(); }