讯飞离线语音合成接入:
文字转语音的方法
1.Google TextToSpeech + 中文语音引擎
Google提供了原生的方法TextToSpeech,但是不支持中文,sad…
不过可以用第三方的语音引擎,eg,讯飞,百度…
详情参考:
Android 文字转语音(中文) TextToSpeech+科大讯飞语音引擎3.0
Android文字转语音引擎(TTS)简单比较及下载
个人项目可以尝试用,如果上线项目总不能让用户去下载语音引擎吧
2.第三方语音服务商(讯飞,百度)
接入的是讯飞离线语音SDK
创建新应用-SDK下载-选择你需要的功能/服务
sample文件夹里,就是Demo代码,主要包含以下功能
因为他的demo缺少了很多配置文件,并不能跑起来,所以不仅要配置app目录,project的build.gradle等,还要按照readme提示,添加文件
从讯飞下载的Demo中,是自带APPID的,没有的话需要自己添加
讯飞初始化的地方放在了应用的Application的onCreate中
StringBuffer param = new StringBuffer();
param.append("appid="+getString(R.string.app_id));
param.append(",");
// 设置使用v5+
param.append(SpeechConstant.ENGINE_MODE+"="+SpeechConstant.MODE_MSC);
SpeechUtility.createUtility(SpeechApp.this, param.toString());
就可以操作了
封装了一个类处理SpeechSynthesizer
public class TtsManager {
private SpeechSynthesizer mTts;
private static final String TAG = TtsManager.class.getSimpleName();
// 默认云端发音人
public static String voicerCloud="xiaoyan";
// 默认本地发音人
public static String voicerLocal="xiaoyan";
private Context mContext;
public TtsManager(Context context) {
mContext = context;
Log.d(TAG,mTtsInitListener+"");
mTts = SpeechSynthesizer.createSynthesizer(mContext, mTtsInitListener);
Log.d(TAG,"SUCCESS");
}
// 初始化成功,之后可以调用startSpeaking方法
// 注:有的开发者在onCreate方法中创建完合成对象之后马上就调用startSpeaking进行合成,
// 正确的做法是将onCreate中的startSpeaking调用移至这里
InitListener mTtsInitListener = new InitListener() {
@Override
public void onInit(int code) {
Log.d(TAG, "InitListener init() code = " + code);
if (code != ErrorCode.SUCCESS) {
if (onTtsInitListener != null) {
onTtsInitListener.initError();
}
Log.d(TAG, "初始化失败,错误码:" + code + ",请点击网址https://www.xfyun.cn/document/error-code查询解决方案");
} else {
// 初始化成功,之后可以调用startSpeaking方法
// 注:有的开发者在onCreate方法中创建完合成对象之后马上就调用startSpeaking进行合成,
// 正确的做法是将onCreate中的startSpeaking调用移至这里
if (onTtsInitListener != null) {
Log.d(TAG, "开始播放");
onTtsInitListener.initSpeak();
}
Log.d(TAG, "初始化成功,开始播放");
}
}
};
private OnTtsInitListener onTtsInitListener;
public void setOnTtsInitListener(OnTtsInitListener ttsInitListener){
this.onTtsInitListener = ttsInitListener;
}
public interface OnTtsInitListener{
void initError();
void initSpeak();
}
private SynthesizerListener mTtsListener = new SynthesizerListener() {
@Override
public void onSpeakBegin() { //开始播放
}
@Override
public void onSpeakPaused() { //暂停播放
}
@Override
public void onSpeakResumed() { //继续播放
}
@Override
public void onBufferProgress(int i, int i1, int i2, String s) {
//合成进度
}
@Override
public void onSpeakProgress(int i, int i1, int i2) {
//播放进度
}
@Override
public void onCompleted(SpeechError speechError) {
//播放完成
}
@Override
public void onEvent(int i, int i1, int i2, Bundle bundle) {
// 以下代码用于获取与云端的会话id,当业务出错时将会话id提供给技术支持人员,可用于查询会话日志,定位出错原因
// 若使用本地能力,会话id为null
// if (SpeechEvent.EVENT_SESSION_ID == eventType) {
// String sid = obj.getString(SpeechEvent.KEY_EVENT_SESSION_ID);
// Log.d(TAG, "session id =" + sid);
// }
//实时音频流输出参考
/*if (SpeechEvent.EVENT_TTS_BUFFER == eventType) {
byte[] buf = obj.getByteArray(SpeechEvent.KEY_EVENT_TTS_BUFFER);
Log.e("MscSpeechLog", "buf is =" + buf);
}*/
}
};
/**
* 默认本地
* @param text
* @return
*/
public int startLocalSpeaking(String text){
setParam(SpeechConstant.TYPE_LOCAL,voicerLocal);
return mTts.startSpeaking(text,mTtsListener);
}
//开始合成/播放
public int startSpeaking(String text,String type,String voicer){
setParam(type,voicer);
return mTts.startSpeaking(text,mTtsListener);
}
//停止播放
public void stopSpeaking(){
if( null != mTts ) {
mTts.stopSpeaking();
}
}
//暂停播放
public void pauseSpeaking(){
mTts.pauseSpeaking();
}
public void destroySpeaking(){
if( null != mTts ){
mTts.stopSpeaking();
// 退出时释放连接
mTts.destroy();
}
}
//继续播放
private void resumeSpeaking(){
mTts.resumeSpeaking();
}
private HashMap getTtsParam(){
HashMap hashMap = new HashMap();
hashMap.put(SpeechConstant.SPEED,"50");////设置合成语速
hashMap.put(SpeechConstant.PITCH,"50");// //设置合成音调
hashMap.put(SpeechConstant.VOLUME,"50");// //设置合成音量
hashMap.put(SpeechConstant.STREAM_TYPE,"3");// // //设置播放器音频流类型
hashMap.put(SpeechConstant.KEY_REQUEST_FOCUS,"50");// 设置播放合成音频打断音乐播放,默认为true
hashMap.put(SpeechConstant.AUDIO_FORMAT,"wav");// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
hashMap.put(SpeechConstant.TTS_AUDIO_PATH,Environment.getExternalStorageDirectory()+"/msc/tts.wav");
return hashMap;
/* //设置合成音调
mTts.setParameter(SpeechConstant.PITCH, TtsSpUtils.getInstance().getString("pitch_preference", "50"));
//设置合成音量
mTts.setParameter(SpeechConstant.VOLUME, TtsSpUtils.getInstance().getString("volume_preference", "50"));
//设置播放器音频流类型
mTts.setParameter(SpeechConstant.STREAM_TYPE, TtsSpUtils.getInstance().getString("stream_preference", "3"));
// 设置播放合成音频打断音乐播放,默认为true
mTts.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true");
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
mTts.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/tts.wav");*/
}
private void setTtsParam(String key,String value){
HashMap hashMap = getTtsParam();
if (key!= null) {
hashMap.put(key, value);
}
for (String mkey:hashMap.keySet()) {
mTts.setParameter(mkey,hashMap.get(mkey));
}
}
private void setLocalParam(){
// 清空参数
mTts.setParameter(SpeechConstant.PARAMS, null);
//设置使用本地引擎
mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
//设置发音人资源路径
mTts.setParameter(ResourceUtil.TTS_RES_PATH,getResourcePath());
//设置发音人
mTts.setParameter(SpeechConstant.VOICE_NAME,voicerLocal);
//mTts.setParameter(SpeechConstant.TTS_DATA_NOTIFY,"1");//支持实时音频流抛出,仅在synthesizeToUri条件下支持
//设置合成语速
mTts.setParameter(SpeechConstant.SPEED, TtsSpUtils.getInstance().getString("speed_preference", "50"));
}
private void setParam(String mEngineType,String voicer){
// 清空参数
mTts.setParameter(SpeechConstant.PARAMS, null);
//设置合成
if(mEngineType.equals(SpeechConstant.TYPE_CLOUD))
{
//设置使用云端引擎
mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_CLOUD);
//设置发音人
mTts.setParameter(SpeechConstant.VOICE_NAME, voicer == null?voicerCloud:voicer);
}else {
//设置使用本地引擎
mTts.setParameter(SpeechConstant.ENGINE_TYPE, SpeechConstant.TYPE_LOCAL);
//设置发音人资源路径
mTts.setParameter(ResourceUtil.TTS_RES_PATH,getResourcePath());
//设置发音人
mTts.setParameter(SpeechConstant.VOICE_NAME,voicer == null?voicerLocal:voicer);
}
//mTts.setParameter(SpeechConstant.TTS_DATA_NOTIFY,"1");//支持实时音频流抛出,仅在synthesizeToUri条件下支持
//设置合成语速
mTts.setParameter(SpeechConstant.SPEED, TtsSpUtils.getInstance().getString("speed_preference", "50"));
//设置合成音调
mTts.setParameter(SpeechConstant.PITCH, TtsSpUtils.getInstance().getString("pitch_preference", "50"));
//设置合成音量
mTts.setParameter(SpeechConstant.VOLUME, TtsSpUtils.getInstance().getString("volume_preference", "50"));
//设置播放器音频流类型
mTts.setParameter(SpeechConstant.STREAM_TYPE, TtsSpUtils.getInstance().getString("stream_preference", "3"));
// 设置播放合成音频打断音乐播放,默认为true
mTts.setParameter(SpeechConstant.KEY_REQUEST_FOCUS, "true");
// 设置音频保存路径,保存音频格式支持pcm、wav,设置路径为sd卡请注意WRITE_EXTERNAL_STORAGE权限
mTts.setParameter(SpeechConstant.AUDIO_FORMAT, "wav");
mTts.setParameter(SpeechConstant.TTS_AUDIO_PATH, Environment.getExternalStorageDirectory()+"/msc/tts.wav");
}
//获取发音人资源路径
private String getResourcePath(){
StringBuffer tempBuffer = new StringBuffer();
//合成通用资源
tempBuffer.append(ResourceUtil.generateResourcePath(mContext, ResourceUtil.RESOURCE_TYPE.assets, "tts/common.jet"));
tempBuffer.append(";");
//发音人资源
tempBuffer.append(ResourceUtil.generateResourcePath(mContext, ResourceUtil.RESOURCE_TYPE.assets, "tts/"+voicerLocal+".jet"));
return tempBuffer.toString();
}
}
还有一个SharedPreferences工具类:
public class TtsSpUtils {
private static final String NAME = "Tts";
private static TtsSpUtils instance = null;
private SharedPreferences sp;
public static TtsSpUtils getInstance() {
synchronized (TtsSpUtils.class) {
if (null == instance) {
instance = new TtsSpUtils();
}
}
return instance;
}
private TtsSpUtils() {
}
public void init(Context context){
init(context,NAME);
}
public void init(Context context,String spName){
sp = context.getApplicationContext().getSharedPreferences(spName,Context.MODE_PRIVATE);
}
/**
* SP 中写入 String
*
* @param key 键
* @param value 值
*/
public void put(@NonNull final String key, @NonNull final String value) {
put(key, value, false);
}
/**
* SP 中写入 String
*
* @param key 键
* @param value 值
* @param isCommit {@code true}: {@link SharedPreferences.Editor#commit()}
* {@code false}: {@link SharedPreferences.Editor#apply()}
*/
public void put(@NonNull final String key, @NonNull final String value, final boolean isCommit) {
if (isCommit) {
sp.edit().putString(key, value).commit();
} else {
sp.edit().putString(key, value).apply();
}
}
/**
* SP 中读取 String
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值{@code ""}
*/
public String getString(@NonNull final String key) {
return getString(key, "");
}
/**
* SP 中读取 String
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值{@code defaultValue}
*/
public String getString(@NonNull final String key, @NonNull final String defaultValue) {
return sp.getString(key, defaultValue);
}
/**
* SP 中写入 int
*
* @param key 键
* @param value 值
*/
public void put(@NonNull final String key, final int value) {
put(key, value, false);
}
/**
* SP 中写入 int
*
* @param key 键
* @param value 值
* @param isCommit {@code true}: {@link SharedPreferences.Editor#commit()}
* {@code false}: {@link SharedPreferences.Editor#apply()}
*/
public void put(@NonNull final String key, final int value, final boolean isCommit) {
if (isCommit) {
sp.edit().putInt(key, value).commit();
} else {
sp.edit().putInt(key, value).apply();
}
}
/**
* SP 中读取 int
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值-1
*/
public int getInt(@NonNull final String key) {
return getInt(key, -1);
}
/**
* SP 中读取 int
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值{@code defaultValue}
*/
public int getInt(@NonNull final String key, final int defaultValue) {
return sp.getInt(key, defaultValue);
}
/**
* SP 中写入 long
*
* @param key 键
* @param value 值
*/
public void put(@NonNull final String key, final long value) {
put(key, value, false);
}
/**
* SP 中写入 long
*
* @param key 键
* @param value 值
* @param isCommit {@code true}: {@link SharedPreferences.Editor#commit()}
* {@code false}: {@link SharedPreferences.Editor#apply()}
*/
public void put(@NonNull final String key, final long value, final boolean isCommit) {
if (isCommit) {
sp.edit().putLong(key, value).commit();
} else {
sp.edit().putLong(key, value).apply();
}
}
/**
* SP 中读取 long
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值-1
*/
public long getLong(@NonNull final String key) {
return getLong(key, -1L);
}
/**
* SP 中读取 long
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值{@code defaultValue}
*/
public long getLong(@NonNull final String key, final long defaultValue) {
return sp.getLong(key, defaultValue);
}
/**
* SP 中写入 float
*
* @param key 键
* @param value 值
*/
public void put(@NonNull final String key, final float value) {
put(key, value, false);
}
/**
* SP 中写入 float
*
* @param key 键
* @param value 值
* @param isCommit {@code true}: {@link SharedPreferences.Editor#commit()}
* {@code false}: {@link SharedPreferences.Editor#apply()}
*/
public void put(@NonNull final String key, final float value, final boolean isCommit) {
if (isCommit) {
sp.edit().putFloat(key, value).commit();
} else {
sp.edit().putFloat(key, value).apply();
}
}
/**
* SP 中读取 float
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值-1
*/
public float getFloat(@NonNull final String key) {
return getFloat(key, -1f);
}
/**
* SP 中读取 float
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值{@code defaultValue}
*/
public float getFloat(@NonNull final String key, final float defaultValue) {
return sp.getFloat(key, defaultValue);
}
/**
* SP 中写入 boolean
*
* @param key 键
* @param value 值
*/
public void put(@NonNull final String key, final boolean value) {
put(key, value, false);
}
/**
* SP 中写入 boolean
*
* @param key 键
* @param value 值
* @param isCommit {@code true}: {@link SharedPreferences.Editor#commit()}
* {@code false}: {@link SharedPreferences.Editor#apply()}
*/
public void put(@NonNull final String key, final boolean value, final boolean isCommit) {
if (isCommit) {
sp.edit().putBoolean(key, value).commit();
} else {
sp.edit().putBoolean(key, value).apply();
}
}
/**
* SP 中读取 boolean
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值{@code false}
*/
public boolean getBoolean(@NonNull final String key) {
return getBoolean(key, false);
}
/**
* SP 中读取 boolean
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值{@code defaultValue}
*/
public boolean getBoolean(@NonNull final String key, final boolean defaultValue) {
return sp.getBoolean(key, defaultValue);
}
/**
* SP 中写入 String 集合
*
* @param key 键
* @param values 值
*/
public void put(@NonNull final String key, @NonNull final Set<String> values) {
put(key, values, false);
}
/**
* SP 中写入 String 集合
*
* @param key 键
* @param values 值
* @param isCommit {@code true}: {@link SharedPreferences.Editor#commit()}
* {@code false}: {@link SharedPreferences.Editor#apply()}
*/
public void put(@NonNull final String key, @NonNull final Set<String> values, final boolean isCommit) {
if (isCommit) {
sp.edit().putStringSet(key, values).commit();
} else {
sp.edit().putStringSet(key, values).apply();
}
}
/**
* SP 中读取 StringSet
*
* @param key 键
* @return 存在返回对应值,不存在返回默认值{@code Collections.emptySet()}
*/
public Set<String> getStringSet(@NonNull final String key) {
return getStringSet(key, Collections.<String>emptySet());
}
/**
* SP 中读取 StringSet
*
* @param key 键
* @param defaultValue 默认值
* @return 存在返回对应值,不存在返回默认值{@code defaultValue}
*/
public Set<String> getStringSet(@NonNull final String key, @NonNull final Set<String> defaultValue) {
return sp.getStringSet(key, defaultValue);
}
/**
* SP 中获取所有键值对
*
* @return Map 对象
*/
public Map<String, ?> getAll() {
return sp.getAll();
}
/**
* SP 中是否存在该 key
*
* @param key 键
* @return {@code true}: 存在
{@code false}: 不存在
*/
public boolean contains(@NonNull final String key) {
return sp.contains(key);
}
/**
* SP 中移除该 key
*
* @param key 键
*/
public void remove(@NonNull final String key) {
remove(key, false);
}
/**
* SP 中移除该 key
*
* @param key 键
* @param isCommit {@code true}: {@link SharedPreferences.Editor#commit()}
* {@code false}: {@link SharedPreferences.Editor#apply()}
*/
public void remove(@NonNull final String key, final boolean isCommit) {
if (isCommit) {
sp.edit().remove(key).commit();
} else {
sp.edit().remove(key).apply();
}
}
/**
* SP 中清除所有数据
*/
public void clear() {
clear(false);
}
/**
* SP 中清除所有数据
*
* @param isCommit {@code true}: {@link SharedPreferences.Editor#commit()}
* {@code false}: {@link SharedPreferences.Editor#apply()}
*/
public void clear(final boolean isCommit) {
if (isCommit) {
sp.edit().clear().commit();
} else {
sp.edit().clear().apply();
}
}
private static boolean isSpace(final String s) {
if (s == null) return true;
for (int i = 0, len = s.length(); i < len; ++i) {
if (!Character.isWhitespace(s.charAt(i))) {
return false;
}
}
return true;
}
}
使用方式:
Application中的onCreate()中
TtsSpUtils.getInstance().init(this);
在需要用的的Activity或者BaseActivity中
private TtsManager ttsManager;
/**
* 开始语音播放.必须调该方法
*/
public void initSpeech(String speakText){
initSpeech(speakText,true);
}
public void initSpeech(String speakText,boolean isSpeak){
if (!isSpeak) return;
ttsManager = new TtsManager(this);
ttsManager.setOnTtsInitListener(new TtsManager.OnTtsInitListener() {
@Override
public void initError() {
}
@Override
public void initSpeak() {
ttsManager.startLocalSpeaking(speakText);
}
});
}
Note: APPID 和 资源文件必须匹配,否则Log会提示错误
DEMO/Lib下载地址
1.使用下载的Demo,需要替换libs和assets的所有资源文件以及appid,考虑layout中的文件是否替换
2.如果依赖module,和上面一样需要替换资源文件,必须把libs中的libmsc.so文件放入 主项目的src/main/jniLibs/armeabi-v7a中
如果放在libs中,你必须sourceSet指明libs文件夹
把下面的maven地址放在Project的build.gradle里
allprojects {
repositories {
// Msc.jar线上maven地址
maven{
url 'http://libmsc.xfyun.cn/repository/maven-releases/'
}
jcenter()
mavenCentral()
}
}
别忘了Application中的讯飞初始化
题外:目前讯飞1137版本在rk3399平台上初始化不成功,卡在初始方法上
参考:
科大讯飞在线语音合成
科大讯飞语音合成实例