我尝试在手机中触发特定事件时播放 TextToSpeech 对象。
但是,我遇到了大多数手机上安装的默认Google TTS引擎的问题。截至目前,我正在初始化TextToSpeech对象后立即播放一些文本,并在语音完成后立即关闭资源,如下代码所示:
public class VoiceGenerator {
private Context context = null;
private static TextToSpeech voice = null;
public VoiceGenerator(Context context)
{
this.context = context;
}
public void voiceInit(String text)
{
try {
if (voice == null) {
new Thread(new Runnable() {
@Override
public void run() {
voice = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
@Override
public void onInit(final int status) {
try {
if (status != TextToSpeech.ERROR) {
voice.setLanguage(Locale.US);
Log.d("VoiceTTS", "TTS being initialized");
HashMap p = new HashMap<String, String>();
p.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "ThisUtterance");
//Speaking here
voice.speak(text, TextToSpeech.QUEUE_ADD, p);
voice.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
}
@Override
public void onDone(String utteranceId) {
Log.d("VoiceTTS", "TTS being released");
clearTtsEngine();
}
@Override
public void onError(String utteranceId) {
}
});
}
} catch (Exception e) {
clearTtsEngine();
Log.d("ErrorLog", "Error occurred while voice play");
e.printStackTrace();
}
}
});
}
}).start();
}
}
catch(Exception e)
{
clearTtsEngine();
Log.d("ErrorLog","Error occurred while voice play");
e.printStackTrace();
}
}
public static void clearTtsEngine()
{
if(voice!=null)
{
voice.stop();
voice.shutdown();
voice = null;
}
}
}
然而,我面临的问题是与初始化Google TTS引擎相关的有限延迟 - 我的设备上大约6-8秒。
我已经读过其他帖子,可以通过使用其他TTS引擎来避免这种延迟。由于我总是在我的三星手机上开发,默认情况下配置了自己专有的TTS,直到我在其他品牌手机上检查我的应用程序之前我才注意到这个问题,其中谷歌TTS引擎配置为默认值。但是,我理想的是不想强迫用户与我自己一起安装另一个应用程序,因此我希望这可以使用默认的 Google TTS引擎本身。
通过我稍后纠正的一些错误编码,我意识到如果我能保持TextToSpeech对象事先初始化 并始终不为null - 一旦初始化,我可能看似绕过这种延迟。
但是,由于有必要在完成后关闭资源,我无法保持对象存活并初始化很长时间,而且我不知道何时初始化/关闭资源,因为我在技术上需要在特定事件发生的任何时候播放语音,这主要是在我的应用程序未在手机上打开时。
所以我的问题如下:
我们可以以某种方式减少或消除初始化延迟 谷歌TTS引擎,以编程方式或其他方式?
有什么方法可以保留TextToSpeech对象 通过服务来生存并初始化 喜欢说什么? 或者这会是一个糟糕的,耗费资源的设计?
也正确使用 static TextToSpeech对象, 根据我的要求?
任何解决方案和代码都将受到赞赏。
更新:我已确认延迟与
更新:我似乎通过将此TTS对象绑定到服务并从服务访问它来绕过此问题。该服务是STICKY(如果服务由于内存问题而终止,Android OS将在内存再次可用时重新启动服务)并配置为在重启设备时重新启动。
该服务仅初始化TTS对象,不执行任何其他工作。我没有明确停止服务,允许它尽可能长时间运行。我已经将TTS对象定义为静态,因此我可以从我的应用程序的其他类访问它。
虽然这看起来效果非常好,但我担心这是否会导致内存或电池问题(在我的特定情况下,服务只处理对象初始化然后保持休眠状态)。我的设计有任何问题,或者我的设计可以进行任何进一步的改进/检查吗?
清单文件:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:name="activity.MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name="services.BroadcastReceiverOnBootComplete"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<data android:scheme="package" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<data android:scheme="package" />
</intent-filter>
</receiver>
<service android:name="services.TTSService"></service>
BroadcastReceiver代码:
public class BroadcastReceiverOnBootComplete extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equalsIgnoreCase(Intent.ACTION_BOOT_COMPLETED)) {
Intent serviceIntent = new Intent(context, TTSService.class);
context.startService(serviceIntent);
}
}
}
TTSService代码:
public class TTSService extends Service {
private static TextToSpeech voice =null;
public static TextToSpeech getVoice() {
return voice;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
// not supporting binding
return null;
}
public TTSService() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try{
Log.d("TTSService","Text-to-speech object initializing");
voice = new TextToSpeech(TTSService.this,new TextToSpeech.OnInitListener() {
@Override
public void onInit(final int status) {
Log.d("TTSService","Text-to-speech object initialization complete");
}
});
}
catch(Exception e){
e.printStackTrace();
}
return Service.START_STICKY;
}
@Override
public void onDestroy() {
clearTtsEngine();
super.onDestroy();
}
public static void clearTtsEngine()
{
if(voice!=null)
{
voice.stop();
voice.shutdown();
voice = null;
}
}
}
修改后的VoiceGenerator代码:
public class VoiceGenerator {
private TextToSpeech voice = null;
public VoiceGenerator(Context context)
{
this.context = context;
}
public void voiceInit(String text)
{
try {
if (voice == null) {
new Thread(new Runnable() {
@Override
public void run() {
voice = TTSService.getVoice();
if(voice==null)
return;
voice.setLanguage(Locale.US);
HashMap p = new HashMap<String, String>();
p.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "ThisUtterance");
voice.speak(text, TextToSpeech.QUEUE_ADD, p);
voice.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String utteranceId) {
}
@Override
public void onDone(String utteranceId) {
}
@Override
public void onError(String utteranceId) {
}
});
}
}).start();
}
}
catch(Exception e)
{
Log.d("ErrorLog","Error occurred while voice play");
e.printStackTrace();
}
}
}
答案 0 :(得分:10)
我是Android应用程序Saiy的开发人员。这不是一个无耻的插件,它是为了证明我使用你正在考虑的设计模式而且我已经经历过了#39;是什么促使你的问题。
我心中很新鲜,因为我去年重写了我的代码,并且必须充分考虑周围的问题。
我问了一个similar question some time ago并在后台线程上初始化Text to Speech对象,它没有与其他任务竞争,可以稍微减少延迟(因为我看到你已经在你发布的代码中做了)。
您还可以通过选择嵌入式语音而不是依赖网络的语音来确保发言请求不会被进一步延迟:
在API 21+中,查看Voice class上的选项。特别是getFeatures(),您可以在其中检查网络的延迟和要求。
在API&lt; 21中 - 在参数中将KEY_FEATURE_NETWORK_SYNTHESIS设置为false。
无论如何,Google TTS引擎的初始化时间都是我测试的任何引擎(我认为都是这些引擎)的最长初始化时间。我相信这只是因为他们正在使用设备上的所有可用资源来提供最高质量的语音。
根据我自己的个人测试,此延迟与设备的硬件成正比。 RAM越多,处理器性能越高,初始化时间越短。对于设备的当前状态也是如此 - 我认为你会发现在重新启动后,有可用内存并且Android不需要杀死其他进程,初始化时间将会减少。
总之,除了上面提到的否之外,您无法缩短初始化时间。
有没有什么方法可以保持TextToSpeech对象在所有时间都保持活动和初始化,比如通过服务?或者这是一个糟糕的,耗费资源的设计?
根据我的要求,还使用静态TextToSpeech对象吗?
正如您所指出的,避免初始化时间的一种方法是继续绑定引擎。但是,在执行此操作之前,您可能还需要考虑其他问题。
如果设备处于需要释放资源的状态,这是导致扩展初始化延迟的相同状态,则Android完全有权对垃圾收集此绑定。如果您在后台服务中持有此绑定,则可以终止该服务,使您回到原点。
此外,如果您仍然绑定引擎,您的用户将在Android运行的应用程序设置中看到集体内存使用情况。对于许多不正确地考虑(休眠)内存使用量与电池消耗成正比的用户,根据我的经验,这将导致卸载和应用程序评级不佳。
在撰写本文时,Google TTS将以70mb的成本绑定到我的应用程序。
如果您仍希望在此基础上继续,可以尝试让Android优先处理您的流程并最后终止它 - 您可以使用Foreground Service执行此操作。这打开了另一种蠕虫病毒,我不会进入。
实际上,绑定到服务中的引擎并在您希望引擎发言时检查该服务是否正在运行,这是一种“单一模式”。在这项服务中使引擎保持静止不会有任何我能想到的目的。
您可以看到here我如何开始处理TTS初始化以及可能发生的相关问题 - 包括延迟。
最后,分享一下我如何处理上述问题的经验。
我有&#39; Google的初始化速度很慢&#39;在我已知的错误的顶部&#39;和&#39;常见问题解答&#39;在申请中。
我监控引擎呼叫onInit
所需的时间。如果花了太长时间,我会向用户发出通知,并将其引导至常见问题解答,在那里他们会建议他们尝试使用其他TTS引擎。
我运行一个后台计时器,在一段时间不活动后释放引擎。这段时间可由用户配置,并附带初始化延迟警告......
我知道上述内容并没有解决您的问题,但也许我的建议会让您的用户安心,这是解决问题的第二步,但是嘿......
毫无疑问,谷歌将逐渐提高初始化性能 - 四年前,我遇到了IVONA的这个问题,他最终在初始化时做得很好。答案 1 :(得分:0)
好,伙计们!我想我已经找到了正确的代码来完成此任务。
您可以像这样在onCreate()中初始化TextToSpeach:
TextToSpeach textToSpeech = new TextToSpeech(this, this);
但是首先您需要 implement TextToSpeech.OnInitListener
,然后您需要覆盖onInit()
方法:
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
int result = tts.setLanguage(Locale.US);
if (result == TextToSpeech.LANG_MISSING_DATA
|| result == TextToSpeech.LANG_NOT_SUPPORTED) {
Toast.makeText(getApplicationContext(), "Language not supported", Toast.LENGTH_SHORT).show();
} else {
button.setEnabled(true);
}
} else {
Toast.makeText(getApplicationContext(), "Init failed", Toast.LENGTH_SHORT).show();
}
}
我还注意到,如果您未在onInit()
中设置语言,那将会有延迟!
现在您可以编写显示文字的方法:
private void speakOut(final String detectedText){
if(textToSpeech !=null){
textToSpeech.stop(); //stop and say the new word
textToSpeech.speak(detectedText ,TextToSpeech.QUEUE_FLUSH, null, null);
}
}