Android文字转语音API听起来机器人

我是第一次学习android开发,我的目标是创建一个简单的Hello World应用程序,它接收一些文本,并大声读出它们。

我的代码基于我发现的一个例子,这是我的代码:

class MainFeeds : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main_feeds) card.setOnClickListener{ Toast.makeText(this, "Hello", Toast.LENGTH_LONG).show() TTS(this, "Hello this is leo") } } } class TTS(private val activity: Activity, private val message: String) : TextToSpeech.OnInitListener { private val tts: TextToSpeech = TextToSpeech(activity, this, "com.google.android.tts") override fun onInit(i: Int) { if (i == TextToSpeech.SUCCESS) { val localeUS = Locale.US val result: Int result = tts.setLanguage(localeUS) if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Toast.makeText(activity, "This Language is not supported", Toast.LENGTH_SHORT).show() } else { speakOut(message) } } else { Toast.makeText(activity, "Initilization Failed!", Toast.LENGTH_SHORT).show() } } private fun speakOut(message: String) { tts.speak(message, TextToSpeech.QUEUE_FLUSH, null, null) } } 

它运行得非常好,我遇到的问题是从合成器发出的音频听起来非常机器人,几乎就像我使用谷歌地图并且我从互联网断开连接。 使用语音Google智能助理会利用我必须启用的其他API吗?

编辑:我已经尝试在我的像素2xl上运行应用程序,它仍然听起来机器人,因为它没有使用谷歌助理语音。

语音质量首先归结为您创建的TextToSpeech对象使用的“语音引擎”:

 private val tts: TextToSpeech = TextToSpeech(activity, this) 

如果您输入了:

 private val tts: TextToSpeech = TextToSpeech(activity, this, "com.google.android.tts") 

然后,您运行该代码的任何设备都将尝试使用Google语音引擎…但只有在设备上存在时才会实际使用它。

同样,使用“com.samsung.SMT”将尝试使用三星语音引擎(也是高质量的,但通常只安装在三星[真实]设备上)。

Google语音引擎是否可用并不是很依赖于设备的Android API级别(只要它最近足以运行Google引擎),而是实际的Google文本到语音引擎完全安装在设备上。

要确保已安装Google引擎:

在Android Studio模拟器上:

创建新的模拟器,然后在“目标”列中选择具有“Google API”或“Google Play”的系统图像。

在真实的设备上:

转到Play商店并安装Google语音引擎 。

我正在编写一个“TTS诊断”应用程序,我将在Github准备好后发布。 我已经了解到Android上的TTS(或至少试图预测其行为)可能是一个真正的野兽。

我还建议文档,当然: Java | 科特林

我做了一个小测试程序,应该为你回答这个问题。

它会显示Google引擎所有语音的列表,然后您点击它们并收听它们! 好极了!

它实际上做了什么:

  • 如果设备上存在Google文本转语音引擎,则使用Google文本转语音引擎初始化TextToSpeech对象。
  • 允许您从ListView中选择特定语音,其中包含与代码中指定的语言环境(在本例中为英语)对应的所有可能可用语音…并且对应于您已安装的Google文本转语音引擎的版本。

通过这种方式,您可以测试所有语音,看看您所寻找的“Google智能助理”语音是否在某处,如果不可用,您可以继续检查Google文本转语音引擎的新版本是否已发布。 在我看来,此测试中最高质量的声音都是质量:400,并指定需要网络连接。

笔记:

  • 声音(特别是英语)很可能仍然“播放”,即使它“未安装”。 这是因为当使用setVoice(Voice v)时,即使请求的语音不可用(!),(Google)引擎也将返回“成功”int,只要它上面还有其他“备用”语音即可同一种语言。 不幸的是,它在后台完成了这一切,并且仍然偷偷地报告它使用了您请求的相同的确切语音,即使您使用getVoice()并比较对象。 :(。

  • 通常,如果某个声音表明已安装,则您听到的声音就是您要求的声音。

  • 出于这些原因,您需要确保在测试这些声音时在互联网上(以便在请求不可用的声音时自动安装)…以及需要网络连接的声音不会“自动降级。”

  • 您可以滑动/刷新语音列表视图以检查语音是否已安装,或使用系统的下拉菜单观看下载…或进入设备系统设置中的Google文本转语音设置。

  • 在列表视图中,“网络必需”和“已安装”之类的语音function只是Google引擎报告的内容的回声,可能并不准确。 🙁

  • Voice类文档中指定的最大语音质量为500.在我的测试中,我只能find质量达到400的语音。这可能是因为我的测试设备上没有安装最新版本的Google文本转语音(我没有Play Store访问权限以便更新它)。 如果您使用的是真实设备,我建议您使用Google Play商店安装最新版本的Google TTS。 您可以在日志中validation引擎版本。 据维基百科称,截至本文撰写时的最新版本为3.15.18.200023596。 我的测试设备上的版本是3.13.1。

要重新创建此测试应用程序,请在Android Studio中创建一个空白Java项目,最小API为21.(getVoices()在21之前不起作用)。

表现:

 < ?xml version="1.0" encoding="utf-8"?>           

主要活动:

 package [ your package name ]; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Color; import android.speech.tts.TextToSpeech; import android.speech.tts.UtteranceProgressListener; import android.speech.tts.Voice; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.EditText; import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Locale; public class MainActivity extends AppCompatActivity { EditText textToSpeak; TextView progressView; TextToSpeech googleTTS; ListView voiceListView; SwipeRefreshLayout swipeRefreshLayout; Long timeOfSpeakRequest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textToSpeak = findViewById(R.id.textToSpeak); textToSpeak.setText("Do I sound robotic to you? 1,2,3,4... yabadabadoo. " + "ooo! ahh! la-la-la-la-la! num-num-dibby-dibby-num-tick-tock... " + "Can I pronounce the word, Antidisestablishmentarianism? " + "Gerp! My pants are too tight! " + "CODE RED! CODE RED! Initiate disassemble! Ice Cream is cold " + "...in my pants. Exterminate! exterminate! Directive 4 is " + "classified." ); progressView = findViewById(R.id.progressView); voiceListView = findViewById(R.id.voiceListView); swipeRefreshLayout = findViewById(R.id.swipeRefresh); // Create the TTS and wait until it's initialized to do anything else if (isGoogleEngineInstalled()) { createGoogleTTS(); } else { Log.i("XXX", "onCreate(): Google not installed -- nothing done."); } } @Override protected void onStart() { super.onStart(); swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { assignFullSetOfVoicesToVoiceListView(); } }); } // this is where the program really begins (when the TTS is initialized) private void onTTSInitialized() { setUpWhatHappensWhenAVoiceItemIsClicked(); setUtteranceProgressListenerOnTheTTS(); assignFullSetOfVoicesToVoiceListView(); } // FACTORED/EXTRACTED METHODS ---------------------------------------------------------------- // These are just pulled out to make onCreate() easier to read and the basic sequence // of events more obvious. private void createGoogleTTS() { googleTTS = new TextToSpeech(this, new TextToSpeech.OnInitListener() { @Override public void onInit(int status) { if (status != TextToSpeech.ERROR) { Log.i("XXX", "Google tts initialized"); onTTSInitialized(); } else { Log.i("XXX", "Internal Google engine init error."); } } }, "com.google.android.tts"); } private void setUpWhatHappensWhenAVoiceItemIsClicked() { voiceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView< ?> parent, View view, int position, long id) { Voice desiredVoice = (Voice) parent.getAdapter().getItem(position); // if (setting the desired voice is "successful")... // in the case of google engine, this does not necessarily mean the voice you // want will actually be used. :( if (googleTTS.setVoice(desiredVoice) == 0) { Log.i("XXX", "Speech voice set to: " + desiredVoice.toString()); // TTS did may "auto-downgrade" voice selection // due to internal reason such as no data // Unfortunately it will not tell you, and there seems to be no // way of checking whether the presently selected voice (getVoice()) "equals" // the desired voice. speak(); } } }); } private void setUtteranceProgressListenerOnTheTTS() { UtteranceProgressListener blurp = new UtteranceProgressListener() { @Override // MIN API 15 public void onStart(String s) { long timeSinceSpeakCall = System.currentTimeMillis() - timeOfSpeakRequest; Log.i("XXX", "progress.onStart() callback. " + timeSinceSpeakCall + " millis since speak() was called."); runOnUiThread(new Runnable() { @Override public void run() { progressView.setTextColor(Color.GREEN); progressView.setText("PROGRESS: STARTED"); } }); } @Override // MIN API 15 public void onDone(String s) { long timeSinceSpeakCall = System.currentTimeMillis() - timeOfSpeakRequest; Log.i("XXX", "progress.onDone() callback. " + timeSinceSpeakCall + " millis since speak() was called."); runOnUiThread(new Runnable() { @Override public void run() { progressView.setTextColor(Color.GREEN); progressView.setText("PROGRESS: DONE"); } }); } // Getting an error can simply mean that the particular voice is not available // to the device yet... and still needs to be downloaded / is still downloading @Override // MIN API 15 (depracated at API 21) public void onError(String s) { long timeSinceSpeakCall = System.currentTimeMillis() - timeOfSpeakRequest; Log.i("XXX", "progress.onERROR() callback. " + timeSinceSpeakCall + " millis since speak() was called."); runOnUiThread(new Runnable() { @Override public void run() { progressView.setTextColor(Color.RED); progressView.setText("PROGRESS: ERROR"); } }); } }; googleTTS.setOnUtteranceProgressListener(blurp); } // must happens AFTER tts is initialized private void assignFullSetOfVoicesToVoiceListView() { googleTTS.stop(); List tempVoiceList = new ArrayList<>(); for (Voice v : googleTTS.getVoices()) { if (v.getLocale().getLanguage().contains("en")) { // only English voices tempVoiceList.add(v); } } // Sort the list alphabetically by name Collections.sort(tempVoiceList, new Comparator() { @Override public int compare(Voice v1, Voice v2) { Log.i("XXX", "comparing item"); return (v2.getName().compareToIgnoreCase(v1.getName())); } }); VoiceAdapter tempAdapter = new VoiceAdapter(this, tempVoiceList); voiceListView.setAdapter(tempAdapter); swipeRefreshLayout.setRefreshing(false); progressView.setTextColor(Color.BLACK); progressView.setText("PROGRESS: ..."); } private void speak() { HashMap map = new HashMap<>(); map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "merp"); timeOfSpeakRequest = System.currentTimeMillis(); googleTTS.speak(textToSpeak.getText().toString(), TextToSpeech.QUEUE_FLUSH, map); } // Checks if Google Engine is installed // ... (and gives more info in Logs). // The version number is going to dictate the quality of voices available private boolean isGoogleEngineInstalled() { final Intent ttsIntent = new Intent(); ttsIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); final PackageManager pm = getPackageManager(); final List list = pm.queryIntentActivities(ttsIntent, PackageManager.GET_META_DATA); boolean googleIsInstalled = false; for (int i = 0; i < list.size(); i++) { ResolveInfo resolveInfoUnderScrutiny = list.get(i); String engineName = resolveInfoUnderScrutiny.activityInfo.applicationInfo.packageName; if (engineName.equals("com.google.android.tts")) { String version = "null"; try { version = pm.getPackageInfo(engineName, PackageManager.GET_META_DATA).versionName; } catch (Exception e) { Log.i("XXX", "Error getting google engine verion: " + e.toString()); } Log.i("XXX", "Google engine version " + version + " is installed!"); googleIsInstalled = true; } else { Log.i("XXX", "Google Engine is not installed!"); } } return googleIsInstalled; } } 

VoiceAdapter.java:

 package [ your package name ]; import android.content.Context; import android.graphics.Color; import android.speech.tts.Voice; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import java.util.List; public class VoiceAdapter extends BaseAdapter { private Context mContext; private LayoutInflater mInflater; private List mDataSource; public VoiceAdapter(Context context, List voicesToDisplay) { mContext = context; mDataSource = voicesToDisplay; mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @Override public int getCount() { return mDataSource.size(); } @Override public Object getItem(int position) { return mDataSource.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // In a real app this method is not efficient, // and "View Holder Pattern" shoudl be used instead. View rowView = mInflater.inflate(R.layout.list_item_voice, parent, false); if (position%2 == 0) { rowView.setBackgroundColor(Color.rgb(245,245,245)); } Voice voiceUnderScrutiny = mDataSource.get(position); // example output of Voice.toString() : // "Voice[Name: pt-br-x-afs#male_2-local, locale: pt_BR, quality: 400, latency: 200, // requiresNetwork: false, features: [networkTimeoutMs, notInstalled, networkRetriesCount]]" // Get title element TextView voiceTitleTextView = (TextView) rowView.findViewById(R.id.voice_title); TextView qualityTextView = (TextView) rowView.findViewById(R.id.voice_quality); TextView networkRequiredTextView = (TextView) rowView.findViewById(R.id.voice_network); TextView isInstalledTextView = (TextView) rowView.findViewById(R.id.voice_installed); TextView featuresTextView = (TextView) rowView.findViewById(R.id.voice_features); voiceTitleTextView.setText("VOICE NAME: " + voiceUnderScrutiny.getName()); // Voice Quality... // ( https://developer.android.com/reference/android/speech/tts/Voice.html ) // 100 = Very Low, 200 = Low, 300 = Normal, 400 = High, 500 = Very High qualityTextView.setText( "QLTY: " + ((Integer) voiceUnderScrutiny.getQuality()).toString() ); if (voiceUnderScrutiny.getQuality() == 500) { qualityTextView.setTextColor(Color.GREEN); // set v. high quality to green } if (!voiceUnderScrutiny.isNetworkConnectionRequired()) { networkRequiredTextView.setText("NET_REQ?: NO"); } else { networkRequiredTextView.setText("NET_REQ?: YES"); } if (!voiceUnderScrutiny.getFeatures().contains("notInstalled")) { isInstalledTextView.setTextColor(Color.GREEN); isInstalledTextView.setText("INSTLLD?: YES"); } else { isInstalledTextView.setTextColor(Color.RED); isInstalledTextView.setText("INSTLLD?: NO"); } featuresTextView.setText("FEATURES: " + voiceUnderScrutiny.getFeatures().toString()); return rowView; } } 

activity_main.xml中:

 < ?xml version="1.0" encoding="utf-8"?>         

list_item_voice.xml:

 < ?xml version="1.0" encoding="utf-8"?>