1 简介
百度开发文档
https://ai.baidu.com/docs#/TTS-Android-SDK/top
本文实现了1种离线语言合成和多种在线语音合成。
百度语音合成需要经过以下几个步骤,先注册百度账号,再创建百度语音应用,然后下载百度语音的SDK(jar文件)、语音模型(dat文件)、NDK so库文件,接着将jar文件放在Project->app->libs目录下、dat文件放在Project->src->main->assets目录下、NDK so库文件放在Project->src->main->jniLibs目录下(如果目录不存在,手动建立即可),最后调用相应的方法即可。
注意:(1)要实现离线语音合成Android工程的包名必须与在创建的百度语音应用时输入的包名相同;
(2)需要实现将assets下的语音模型复制到SD卡上。
2 源代码
工程目录
2.1 java源代码
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.baidu.tts.auth.AuthInfo;
import com.baidu.tts.client.SpeechSynthesizer;
import com.baidu.tts.client.TtsMode;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
Button buttonSpeak = null;
Button buttonStop = null;
// 语言模型文件路径
private String dirFile = Environment.getExternalStorageDirectory() + File.separator +"my_voice"+ File.separator ;
private String textFilename = "bd_etts_text.dat";
private String modelFilename ="bd_etts_speech_f7.dat";
// 百度语言的账号
private String appId = "17246190";
private String appKey = "UfrBeadhzS2UhuDiGuPa8TlD";
private String secretKey = "G73dYXjcEgZflVgWURVhAuVUM25nAuAT";
// TtsMode.MIX; 离在线融合,在线优先; TtsMode.ONLINE 纯在线;
private TtsMode ttsMode = TtsMode.MIX;
// 设置语言合成器
protected SpeechSynthesizer mSpeechSynthesizer = null;
// 设置TAG
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化权限
initPermission();
// 初始化语言合成
initTTS();
// 初始化控件
this.initView();
}
/**
* 初始化控件
*/
private void initView(){
this.buttonSpeak = findViewById(R.id.speak);
this.buttonSpeak.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSpeechSynthesizer.speak("Hello world !");
}
});
this.buttonStop = findViewById(R.id.stop);
this.buttonStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stop();
}
});
}
/**
* 初始化语言合成
*/
private void initTTS() {
// 1. 获取实例
this.mSpeechSynthesizer = SpeechSynthesizer.getInstance();
this.mSpeechSynthesizer.setContext(MainActivity.this);
// 2. 设置appId,appKey.secretKey
int result = this.mSpeechSynthesizer.setAppId(appId);
result = this.mSpeechSynthesizer.setApiKey(appKey, secretKey);
System.out.println("https://ai.baidu.com/docs#/TTS-Android-SDK/top-错误码:" + result);
// 3. 支持离线的话,需要设置离线模型
boolean isMix = this.ttsMode.equals(TtsMode.MIX);
if (isMix) {
if(isDirExist(dirFile)){
copyAssetsToSdcard(MainActivity.this, dirFile+textFilename, textFilename);
copyAssetsToSdcard(MainActivity.this, dirFile+modelFilename, modelFilename);
}
// 设置语言模型
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_TEXT_MODEL_FILE, dirFile+textFilename);
// 设置使用的语言模型
mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_TTS_SPEECH_MODEL_FILE, dirFile+modelFilename);
// 检查离线授权文件是否下载成功
AuthInfo authInfo = mSpeechSynthesizer.auth(this.ttsMode);
// 文本模型文件路径 (离线引擎使用)
if(authInfo.isSuccess()){
print("授权成功!");
}else {
print("授权失败!");
}
}
// 4. 以下setParam 参数选填。不填写则默认值生效
// 设置在线发声音人: 0 普通女声(默认),本文仅仅设置了1种离线语言,其他的3中离线语言可以自己下载
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEAKER, "0");
// 设置合成的音量,0-15 ,默认 5
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_VOLUME, "5");
// 设置合成的语速,0-15 ,默认 5
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_SPEED, "5");
// 设置合成的语调,0-15 ,默认 5
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_PITCH, "5");
// 该参数设置为TtsMode.MIX生效。即纯在线模式不生效。
// MIX_MODE_DEFAULT 默认 ,wifi状态下使用在线,非wifi离线。在线状态下,请求超时6s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE_WIFI wifi状态下使用在线,非wifi离线。在线状态下, 请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_NETWORK , 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
// MIX_MODE_HIGH_SPEED_SYNTHESIZE, 2G 3G 4G wifi状态下使用在线,其它状态离线。在线状态下,请求超时1.2s自动转离线
this.mSpeechSynthesizer.setParam(SpeechSynthesizer.PARAM_MIX_MODE, SpeechSynthesizer.MIX_MODE_DEFAULT);
// 5. 初始化
result = this.mSpeechSynthesizer.initTts(ttsMode);
System.out.println("https://ai.baidu.com/docs#/TTS-Android-SDK/top-错误码:" + result);
}
private void stop() {
print("停止合成引擎 按钮已经点击");
int result = this.mSpeechSynthesizer.stop();
System.out.println("https://ai.baidu.com/docs#/TTS-Android-SDK/top-错误码:" + result);
}
private void print(String message) {
Log.i(TAG, message);
}
@Override
protected void onDestroy() {
if (this.mSpeechSynthesizer != null) {
this.mSpeechSynthesizer.stop();
this.mSpeechSynthesizer.release();
this.mSpeechSynthesizer = null;
print("释放资源成功");
}
super.onDestroy();
}
/**
* 下面是android 6.0以上的动态授权
* android 6.0 以上需要动态申请权限
*/
private void initPermission() {
String[] permissions = {
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.MODIFY_AUDIO_SETTINGS,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.WRITE_SETTINGS,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE
};
ArrayList<String> toApplyList = new ArrayList<String>();
for (String perm : permissions) {
if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
toApplyList.add(perm);
// 进入到这里代表没有权限.
}
}
String[] tmpList = new String[toApplyList.size()];
if (!toApplyList.isEmpty()) {
ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
}
}
/**
* 检查目录是否存在
* @param dirPath 目录的路径
* @return true代表目录存在
*/
private boolean isDirExist(String dirPath){
File dirFile = new File(dirPath);
if(dirFile.exists()){
return true;
}else {
return dirFile.mkdirs();
}
}
/**
* 复制Assets目录下的文件到SD卡
* @param context 代表Activity.this
* @param filePath 文件路径
* @param fileName 文件名
*/
private void copyAssetsToSdcard(Context context, String filePath, String fileName){
File file = new File(filePath);
// Determine if the file exists
if (!file.exists()) {
// Initial the stream
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
inputStream = context.getResources().getAssets().open(fileName);
fileOutputStream = new FileOutputStream(filePath);
byte[] buffer = new byte[1024];
int bufferSize = 0;
// Copy the data
while ((bufferSize = inputStream.read(buffer, 0, 1024)) >= 0) {
fileOutputStream.write(buffer, 0, bufferSize);
}
// Close the stream
inputStream.close();
fileOutputStream.close();
} catch (IOException e) {
Log.d("Baidu-TTS","Baidu-TTS write dat error!");
e.printStackTrace();
}
}
}
}
2.2 xml布局源代码
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="50dp"
android:orientation="horizontal"
android:weightSum="3">
<Button
android:id="@+id/speak"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:lines="2"
android:text="合成并播放"
android:textSize="12dp" />
<Button
android:id="@+id/stop"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="2"
android:lines="2"
android:text="停止合成引擎"
android:textSize="12dp" />
</LinearLayout>
</LinearLayout>
2.3 xml的manifests文件的权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<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.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />