最近因为一些特殊需求,公司希望使用离线TTS,不花钱的中文离线TTS,最好音色还要多一点,语速,语调都要可控;总之一句话,以不花钱的代价达到最好的效果。其实现在很多的开发者平台也都提供一两种音色的离线TTS开发SDK,比如科大,云知声等等,我之前也做过一个百度在线ASR与云知声离线TTS结合实现的一个复读机的小Demo,有兴趣的朋友可以点击看一下
配合最近我上传的github项目以及本篇(本篇github地址)的介绍,就可以实现TTS播报,暂停以及恢复等功能
因为了解过Android有原生的TTS功能,于是就简单使用了一下,Google的产品不支持中文也很容易理解,毕竟市场都不给别人,而且中文的处理相较于英文等处理起来也比较麻烦,所以,原生的TTS是不支持中文的。但是里面是有预留的常量中文普通话,台湾话等,但是是设置不了的。所以在具体使用的时候需要借助中文TTS引擎的帮助,所以就需要安装其他的软件或者服务,网上推荐比较多的是科大讯飞+这个APK,里面可以设置发音人,语速,语调等。我简单看了一下,里面支持五种普通话音色;但是我们从科大官网上看上面只提供两种离线的音色语音;所以使用这个APK可以丰富一下你的离线TTS音色;从另一个方面来说,不知道有没有考虑过,如果我们能找到一个开源框架可以实现加载这些离线语音包,然后想办法获取到这些离线语音包,那么我们就可以多使用其另外的三种离线语音包。但是这样的开源框架估计不好找。其他还有度秘语音引擎,google的文字转语音引擎;可以点击下载,感谢这位博主。说的好像有点多了,下面进入正题;今天主要介绍一下Android原生的TTS接口的基本使用。
1,主要使用步骤
(1)新建一个类(内部类也是可以的),实现OnInitListener接口,重写onInit()方法,通常是判断TTS引擎初始化的状态
private class TTSListener implements OnInitListener {
@Override
public void onInit(int status) {
// TODO Auto-generated method stub
if (status == TextToSpeech.SUCCESS) {
// int supported = mSpeech.setLanguage(Locale.US);
// if ((supported != TextToSpeech.LANG_AVAILABLE) && (supported != TextToSpeech.LANG_COUNTRY_AVAILABLE)) {
// Toast.makeText(MainActivity.this, "不支持当前语言!", Toast.LENGTH_SHORT).show();
// Log.i(TAG, "onInit: 支持当前选择语言");
// }else{
//
// }
Log.i(TAG, "onInit: TTS引擎初始化成功");
}
else{
Log.i(TAG, "onInit: TTS引擎初始化失败");
}
}
}
(2)获取TTS引擎
mSpeech = new TextToSpeech(MainActivity.this, new TTSListener());
(3)在使用的时候,如果有需要可以调整TTS引擎参数,包括上面说的语速,语调,语言等等(当然,当前不支持中文,使用的话,先下载上面提到的服务或者应用并安装,然后在“设置”--》“语音与输入”--》“文本转语音输出”--》选择你安装中文TTS就可以了)
mSpeech.setLanguage(SharedData.languageList.get(choosedLanguage));
mSpeech.setSpeechRate(SharedData.voice_speed);
mSpeech.setPitch(SharedData.voice_pitch);
2,示例代码:
示例代码很简单,我简单加了一点东西,也很好理解。
MainActivity.java代码:
package com.hfut.operationandroidtts;
import android.app.AlertDialog;
import android.speech.tts.TextToSpeech;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextToSpeech mSpeech = null;
private EditText edit = null;
private String choosedLanguage;
private RadioButton english,chainese,german,french,taiWan;
private RadioGroup languageGroup;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
initLanguageList();
mSpeech = new TextToSpeech(MainActivity.this, new TTSListener());
// langs = getResources().getStringArray(R.array.languages); // 得到语言数组
// langSpinner = (Spinner) findViewById(R.id.spinner);
// edit = (EditText) findViewById(R.id.edit);
// btn = (Button) findViewById(R.id.btn);
// btn.setOnClickListener(new BtnListener());
//
// for (int i = 0; i < langs.length; i++) {
// langList.add(langs[i]);
// }
// // 设置下拉框的适配器和样式
// langAdapter = new ArrayAdapter(MainActivity.this,
// android.R.layout.simple_spinner_item, langList);
// langAdapter
// .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// langSpinner.setAdapter(langAdapter);
//
// // 下拉框监听器
// langSpinner
// .setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
// @Override
// public void onItemSelected(AdapterView> adapter,
// View view, int position, long id) {
// // TODO Auto-generated method stub
// curLang = (String) langSpinner.getAdapter().getItem(
// (int) id);
// if(mSpeech != null)
// {
// mSpeech.stop();
// mSpeech.shutdown();
// mSpeech = null;
// }
// // 创建TTS对象
// mSpeech = new TextToSpeech(MainActivity.this, new TTSListener());
// Toast.makeText(MainActivity.this, "select = " + curLang, Toast.LENGTH_SHORT).show();
// }
//
// @Override
// public void onNothingSelected(AdapterView> arg0) {
// // TODO Auto-generated method stub
//
// }
// });
// }
// private int SetLanguage(String lang) {
// int result = 0;
// if (lang.equals("CANADA")) {
// result = mSpeech.setLanguage(Locale.CANADA);
// } else if (lang.equals("CANADA_FRENCH")) {
// result = mSpeech.setLanguage(Locale.CANADA_FRENCH);
// } else if (lang.equals("CHINA")) {
// result = mSpeech.setLanguage(Locale.CHINA);
// } else if (lang.equals("CHINESE")) {
// result = mSpeech.setLanguage(Locale.CHINESE);
// } else if (lang.equals("ENGLISH")) {
// result = mSpeech.setLanguage(Locale.ENGLISH);
// } else if (lang.equals("FRANCE")) {
// result = mSpeech.setLanguage(Locale.FRANCE);
// } else if (lang.equals("FRENCH")) {
// result = mSpeech.setLanguage(Locale.FRENCH);
// } else if (lang.equals("GERMAN")) {
// result = mSpeech.setLanguage(Locale.GERMAN);
// } else if (lang.equals("GERMANY")) {
// result = mSpeech.setLanguage(Locale.GERMANY);
// } else if (lang.equals("ITALIAN")) {
// result = mSpeech.setLanguage(Locale.ITALIAN);
// } else if (lang.equals("ITALY")) {
// result = mSpeech.setLanguage(Locale.ITALY);
// } else if (lang.equals("JAPAN")) {
// result = mSpeech.setLanguage(Locale.JAPAN);
// } else if (lang.equals("JAPANESE")) {
// result = mSpeech.setLanguage(Locale.JAPANESE);
// } else if (lang.equals("KOREA")) {
// result = mSpeech.setLanguage(Locale.KOREA);
// } else if (lang.equals("KOREAN")) {
// result = mSpeech.setLanguage(Locale.KOREAN);
// } else if (lang.equals("PRC")) {
// result = mSpeech.setLanguage(Locale.PRC);
// } else if (lang.equals("ROOT")) {
// result = mSpeech.setLanguage(Locale.ROOT);
// } else if (lang.equals("SIMPLIFIED_CHINESE")) {
// result = mSpeech.setLanguage(Locale.SIMPLIFIED_CHINESE);
// } else if (lang.equals("TAIWAN")) {
// result = mSpeech.setLanguage(Locale.TAIWAN);
// } else if (lang.equals("TRADITIONAL_CHINESE")) {
// result = mSpeech.setLanguage(Locale.TRADITIONAL_CHINESE);
// } else if (lang.equals("UK")) {
// result = mSpeech.setLanguage(Locale.UK);
// } else if (lang.equals("US")) {
// result = mSpeech.setLanguage(Locale.US);
// }
// return result;
// }
//
// private class TTSListener implements OnInitListener {
//
// @Override
// public void onInit(int status) {
// // TODO Auto-generated method stub
// if (status == TextToSpeech.SUCCESS) {
// //int result = mSpeech.setLanguage(Locale.ENGLISH);
// int result = SetLanguage(curLang);
// //如果打印为-2,说明不支持这种语言
// Toast.makeText(MainActivity.this, "-------------result = " + result, Toast.LENGTH_LONG).show();
// if (result == TextToSpeech.LANG_MISSING_DATA
// || result == TextToSpeech.LANG_NOT_SUPPORTED) {
// System.out.println("-------------not use");
// } else {
// mSpeech.speak("i love you", TextToSpeech.QUEUE_FLUSH, null);
// }
// }
// }
//
// }
//
// private class BtnListener implements OnClickListener {
//
// @Override
// public void onClick(View v) {
// // TODO Auto-generated method stub
// mSpeech.speak(edit.getText().toString(), TextToSpeech.QUEUE_FLUSH,
// null);
// }
//
// }
//
// @Override
// protected void onDestroy() {
// // TODO Auto-generated method stub
// if (mSpeech != null) {
// mSpeech.stop();
// mSpeech.shutdown();
// mSpeech = null;
// }
// super.onDestroy();
// }
//
//}
}
private void initUI() {
edit = findViewById(R.id.test_text);
languageGroup=findViewById(R.id.language_Group);
english = findViewById(R.id.language_English);
chainese = findViewById(R.id.language_Chainese);
german = findViewById(R.id.language_German);
french = findViewById(R.id.language_French);
taiWan=findViewById(R.id.language_TaiWan);
// View.OnClickListener handle = new View.OnClickListener(){
// public void onClick(View v){
// switch(v.getId()){
// case R.id.language_English:
// choosedLanguage="英文";
// break;
// case R.id.language_Chainese:
// choosedLanguage="中文";
// break;
// case R.id.language_German:
// choosedLanguage="德语";
// break;
// case R.id.language_French:
// choosedLanguage="法语";
// break;
// }
//
// }
// };
// english.setOnClickListener(handle);
// chainese.setOnClickListener(handle);
// german.setOnClickListener(handle);
// french.setOnClickListener(handle);
}
private void initLanguageList() {
SharedData.languageList.put("英语",Locale.ENGLISH);
SharedData.languageList.put("中文",Locale.CHINESE);
SharedData.languageList.put("德语",Locale.GERMAN);
SharedData.languageList.put("法语",Locale.FRENCH);
SharedData.languageList.put("台湾话",Locale.TAIWAN);
}
public void openAudioFile(View view) {
choosedLanguage=getLanguage(languageGroup);
int supported = mSpeech.setLanguage(SharedData.languageList.get(choosedLanguage));
mSpeech.setSpeechRate(SharedData.voice_speed);
mSpeech.setPitch(SharedData.voice_pitch);
Log.i(TAG, "选择语言: "+choosedLanguage+"--"+SharedData.languageList.get(choosedLanguage));
//mSpeech.setAudioAttributes(new AudioAttributes());
// mSpeech.setVoice(new Voice(null,Locale.US,Voice.QUALITY_HIGH,Voice.LATENCY_NORMAL,false,null));
if((supported != TextToSpeech.LANG_AVAILABLE) && (supported != TextToSpeech.LANG_COUNTRY_AVAILABLE)){
//语言设置失败
Log.i(TAG, "语言设置失败: "+choosedLanguage);
}
else{
Log.i(TAG, "语言设置成功: "+choosedLanguage);
}
String tempStr = edit.getText().toString();
mSpeech.speak(tempStr, TextToSpeech.QUEUE_FLUSH, null);
Log.i(TAG, "测试文本: "+tempStr);
Log.i(TAG, "当前语速: "+SharedData.voice_speed+", 最快语速1.5");
Log.i(TAG, "当前音调:"+SharedData.voice_pitch+", 最高音调2.0");
//Log.i(TAG, "test: 执行了");
}
//保存音频文件
public void saveAudioFile(View view) {
HashMap myHashRender = new HashMap<>();
myHashRender.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, edit.getText().toString());
mSpeech.synthesizeToFile(edit.getText().toString(), myHashRender,
"/mnt/sdcard/"+ new Date().toString().replace(" ","_").trim()+".wav");
Log.i(TAG, "saveAudioFile: "+"/mnt/sdcard/"+ new Date().toString().replace(" ","_").trim()+".wav"+"文件保存成功");
Toast.makeText(this,"文件保存成功",Toast.LENGTH_SHORT).show();
}
private String getLanguage(RadioGroup languageGroup) {
int choosedButtonID=languageGroup.getCheckedRadioButtonId();
String tempStr="";
if(choosedButtonID==english.getId()){
tempStr="英语";
}
else if(choosedButtonID==chainese.getId()){
tempStr="中文";
}
else if(choosedButtonID==german.getId()){
tempStr="德语";
}
else if(choosedButtonID==french.getId()){
tempStr="法语";
}
else if(choosedButtonID==taiWan.getId()){
tempStr="台湾话";
}
else{
}
return tempStr;
}
//增加音量
public void increVoice(View view){
if(SharedData.voice_speed>=1.5f){
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setMessage("速度已经最快,无法调整");
dialog.show();
}
else{
SharedData.voice_speed+=0.1f;
}
}
//减小音量
public void decreVoice(View view){
if(SharedData.voice_speed<=0.1f){
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setMessage("速度已经最慢,无法调整");
dialog.show();
}
else{
SharedData.voice_speed-=0.1f;
}
}
//升高音调
public void increPitch(View view){
if(SharedData.voice_pitch>=2.0f){
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setMessage("音调已经最高,无法调整");
dialog.show();
}
else{
SharedData.voice_pitch+=0.1f;
}
}
//减低音调
public void decrePitch(View view){
if(SharedData.voice_pitch<=0.1f){
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setMessage("音调已经最低,无法调整");
dialog.show();
}
else{
SharedData.voice_pitch-=0.1f;
}
}
private class TTSListener implements OnInitListener {
@Override
public void onInit(int status) {
// TODO Auto-generated method stub
if (status == TextToSpeech.SUCCESS) {
// int supported = mSpeech.setLanguage(Locale.US);
// if ((supported != TextToSpeech.LANG_AVAILABLE) && (supported != TextToSpeech.LANG_COUNTRY_AVAILABLE)) {
// Toast.makeText(MainActivity.this, "不支持当前语言!", Toast.LENGTH_SHORT).show();
// Log.i(TAG, "onInit: 支持当前选择语言");
// }else{
//
// }
Log.i(TAG, "onInit: TTS引擎初始化成功");
}
else{
Log.i(TAG, "onInit: TTS引擎初始化失败");
}
}
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
if (mSpeech != null) {
mSpeech.stop();
mSpeech.shutdown();
mSpeech = null;
}
super.onDestroy();
}
// private int SetLanguage(String lang) {
// int result = 0;
// if (lang.equals("CANADA")) {
// result = mSpeech.setLanguage(Locale.CANADA);
// } else if (lang.equals("CANADA_FRENCH")) {
// result = mSpeech.setLanguage(Locale.CANADA_FRENCH);
// } else if (lang.equals("CHINA")) {
// result = mSpeech.setLanguage(Locale.CHINA);
// } else if (lang.equals("CHINESE")) {
// result = mSpeech.setLanguage(Locale.CHINESE);
// } else if (lang.equals("ENGLISH")) {
// result = mSpeech.setLanguage(Locale.ENGLISH);
// } else if (lang.equals("FRANCE")) {
// result = mSpeech.setLanguage(Locale.FRANCE);
// } else if (lang.equals("FRENCH")) {
// result = mSpeech.setLanguage(Locale.FRENCH);
// } else if (lang.equals("GERMAN")) {
// result = mSpeech.setLanguage(Locale.GERMAN);
// } else if (lang.equals("GERMANY")) {
// result = mSpeech.setLanguage(Locale.GERMANY);
// } else if (lang.equals("ITALIAN")) {
// result = mSpeech.setLanguage(Locale.ITALIAN);
// } else if (lang.equals("ITALY")) {
// result = mSpeech.setLanguage(Locale.ITALY);
// } else if (lang.equals("JAPAN")) {
// result = mSpeech.setLanguage(Locale.JAPAN);
// } else if (lang.equals("JAPANESE")) {
// result = mSpeech.setLanguage(Locale.JAPANESE);
// } else if (lang.equals("KOREA")) {
// result = mSpeech.setLanguage(Locale.KOREA);
// } else if (lang.equals("KOREAN")) {
// result = mSpeech.setLanguage(Locale.KOREAN);
// } else if (lang.equals("PRC")) {
// result = mSpeech.setLanguage(Locale.PRC);
// } else if (lang.equals("ROOT")) {
// result = mSpeech.setLanguage(Locale.ROOT);
// } else if (lang.equals("SIMPLIFIED_CHINESE")) {
// result = mSpeech.setLanguage(Locale.SIMPLIFIED_CHINESE);
// } else if (lang.equals("TAIWAN")) {
// result = mSpeech.setLanguage(Locale.TAIWAN);
// } else if (lang.equals("TRADITIONAL_CHINESE")) {
// result = mSpeech.setLanguage(Locale.TRADITIONAL_CHINESE);
// } else if (lang.equals("UK")) {
// result = mSpeech.setLanguage(Locale.UK);
// } else if (lang.equals("US")) {
// result = mSpeech.setLanguage(Locale.US);
// }
// return result;
// }
}
SharedData.java代码:
package com.hfut.operationandroidtts;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* author:why
* created on: 2018/6/5 11:28
* description:
*/
public class SharedData {
//语速
public static float voice_speed=0.5f;
//音调
public static float voice_pitch=1.0f;
//
public static Map languageList=new HashMap();
}
activity_main.xml代码:
主配置文件AndroidManifest.xml:
3,运行效果图
主界面
运行日志
保存的音频文件
(1)这里我把语速,语调做了上限2.0,事实不是这样的
(2)这里没有展示具体中文TTS引擎配置,比较简单,上面说了步骤,只是没有附图
(3)很多其他好玩的东西,可以自己结合兴趣和API学习
(4)关于语音识别,语音合成,语义识别,包括上下文等等和语音相关以及人际交互相关的应用知识,这段时间看了不少,也写过一些博客,有兴趣的朋友可以参考一下。当然,我只是停留在应用层。
注:欢迎扫码关注