讯飞离线语音合成接入:
文字转语音的方法
1.Google TextToSpeech + 中文语音引擎
Google提供了原生的方法TextToSpeech,但是不支持中文,sad…
不过可以用第三方的语音引擎,eg,讯飞,百度…
详情参考:
Android 文字转语音(中文) TextToSpeech+科大讯飞语音引擎3.0
Android文字转语音引擎(TTS)简单比较及下载
个人项目可以尝试用,如果上线项目总不能让用户去下载语音引擎吧
2.第三方语音服务商(讯飞,百度)
接入的是讯飞离线语音SDK
- 注册讯飞开放平台账号
创建新应用-SDK下载-选择你需要的功能/服务
- SDK下载/导入
有readme文件,这个必须要读,
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() {@Overridepublic 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() {@Overridepublic void onSpeakBegin() { //开始播放}@Overridepublic void onSpeakPaused() { //暂停播放}@Overridepublic void onSpeakResumed() { //继续播放}@Overridepublic void onBufferProgress(int i, int i1, int i2, String s) {//合成进度}@Overridepublic void onSpeakProgress(int i, int i1, int i2) {//播放进度}@Overridepublic void onCompleted(SpeechError speechError) {//播放完成}@Overridepublic 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<String,String> getTtsParam(){HashMap<String,String> 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");// 设置播放合成音频打断音乐播放,默认为truehashMap.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"));// 设置播放合成音频打断音乐播放,默认为truemTts.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<String,String> 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"));// 设置播放合成音频打断音乐播放,默认为truemTts.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()}<br>* {@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()}<br>* {@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()}<br>* {@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()}<br>* {@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()}<br>* {@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()}<br>* {@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.<String>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}: 存在<br>{@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()}<br>* {@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()}<br>* {@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() {@Overridepublic void initError() {}@Overridepublic 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平台上初始化不成功,卡在初始方法上
参考:
科大讯飞在线语音合成
科大讯飞语音合成实例