绕过 Google TTS 引擎初始化延迟 Android

Bypassing Google TTS Engine initialization lag in Android

我试过在 phone 中触发特定事件时播放 TextToSpeech 对象。

但是,我遇到了安装在大多数 phone 上的默认 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 引擎可以避免这种延迟。因为我总是在我的三星 phone 上开发,它默认配置了自己专有的 TTS,所以我从来没有注意到这个问题,直到我检查了我在其他品牌 phones 上的应用程序,它有 Google TTS 引擎配置为默认。但是,理想情况下,我不想强​​迫用户在安装我自己的应用程序的同时安装另一个应用程序,因此我希望它能与默认的 Google TTS Engine 本身一起使用.

通过我后来纠正的一些错误编码,我意识到如果我可以保持 TextToSpeech 对象在之前初始化并且总是not null - 一旦初始化,我似乎可以绕过这个延迟。

但是,由于一旦我们使用完资源就必须关闭它,我无法长时间保持对象活动和初始化,而且我不知道什么时候 initialize/shutdown资源,因为技术上我需要在特定事件发生时随时播放语音​​,这主要是在我的应用程序未在 phone.

上打开时

所以我的问题如下:

  1. 我们能否以某种方式减少或消除初始化延迟 Google TTS 引擎,以编程方式或其他方式?

  2. 有什么方法可以保留 TextToSpeech 对象 一直活着并初始化 比如说,通过服务? 或者这是一个糟糕的、耗费资源的设计?

  3. 也是使用 static TextToSpeech 对象的正确方法, 满足我的要求?

任何解决方案以及代码将不胜感激。

更新: 我已确认延迟与 排他性地 与 Google TTS 引擎有关,因为我已尝试使用其他免费和付费的 TTS 引擎,其中几乎没有或没有延迟。但如果可能的话,我仍然希望没有任何第三方依赖项,并且希望使用 Google TTS Engine 来完成这项工作。

更新: 我似乎通过将此 TTS 对象绑定到服务并从服务访问它来绕过这个问题。该服务是粘性的(如果服务由于内存问题而终止,Android OS 将在内存再次可用时重新启动服务)并配置为在设备重新启动时重新启动。

该服务仅初始化 TTS 对象,不做其他工作。我没有明确停止服务,允许它尽可能长时间地 运行。我已将 TTS 对象定义为静态对象,以便我可以从我的应用程序的其他 类 访问它。

虽然这似乎工作得非常好,但我担心这是否会导致内存或电池问题(在我的特定情况下,服务仅处理对象初始化然后保持休眠状态)。我的设计有什么问题吗,或者我的设计可以进一步improvements/checks吗?

清单文件:

<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);
    }
}

}

TTS服务代码:

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();
    }
}




}

我是 Android 应用程序 Saiy 的开发者。这不是无耻的插件,它是为了证明我使用了您正在考虑的设计模式并且我 'been through' 是什么提示了您的问题。

这让我记忆犹新,因为去年我一直在重写我的代码,并且不得不认真考虑周围的问题。

  • 我们能否以某种方式以编程方式或其他方式减少或消除 Google TTS 引擎的初始化延迟?

我问了一个 similar question some time ago 并在不与其他任务竞争的后台线程上初始化 Text to Speech 对象,可以稍微减少延迟(正如我看到您已经在发布的代码中所做的那样) ).

您还可以通过选择嵌入式语音而不是依赖于网络的语音来确保发言请求不会进一步延迟:

在 API 21+ 中检查 Voice class. Particularly getFeatures() 上的选项,您可以在其中检查延迟和网络要求。

在 API <21 - 将参数中的 KEY_FEATURE_NETWORK_SYNTHESIS 设置为 false。

尽管如此,Google TTS 引擎的初始化时间是我测试过的所有引擎中最长的(我认为是所有引擎)。我相信这仅仅是因为他们正在使用设备上的所有可用资源来提供他们所能提供的最高质量的语音。

根据我自己的个人测试,这个延迟与设备的硬件成正比。处理器的 RAM 和性能越高,初始化时间就越短。对于设备的当前状态也是如此 - 我想你会发现在重新启动后,有可用内存并且 Android 不需要终止其他进程,初始化时间将会减少。

综上所述,除了上面提到的没有,你不能减少初始化时间。

  • 有什么方法可以让 TextToSpeech 对象保持活动状态并始终初始化,比如通过服务?或者这是一个糟糕的、耗费资源的设计?

  • 对于我的要求,使用静态 TextToSpeech 对象也是正确的方法吗?

如您所述,避免初始化时间的一种方法是保持与引擎的绑定。但是,在执行此操作之前,您可能还需要考虑其他问题。

如果设备处于需要释放资源的状态,这与导致初始化延迟延长的状态相同,Android 完全有权对此绑定进行垃圾收集。如果您在后台服务中保留此绑定,则可以终止该服务,让您回到原点。

此外,如果您仍然绑定到引擎,您的用户将在 Android 运行ning 应用程序设置中看到集体内存使用情况。对于许多错误地认为(休眠)内存使用与电池消耗成正比的用户,根据我的经验,这将导致卸载和应用评级不佳。

在撰写本文时,Google TTS 已绑定到我的应用程序,费用为 70mb。

如果您仍然想在此基础上继续,您可以尝试让 Android 确定您的进程的优先级并最后终止它 - 您可以使用前台服务来执行此操作。不过,这会打开另一罐蠕虫,我不会深入讨论。

有效地,绑定到服务中的引擎并在您希望引擎说话时检查该服务 运行ning 是 'singleton pattern'。在此服务中使引擎静态化将没有任何我能想到的目的。

您可以看到 here 我如何开始处理 TTS 初始化以及可能发生的相关问题 - 包括延迟。

最后,分享一下我是如何处理上述问题的。

我在应用程序的 'known bugs' 和 'FAQ' 顶部有“Google 初始化缓慢”。

我监控引擎调用 onInit 所需的时间。如果花费的时间太长,我会向用户发出通知并将他们引导至常见问题解答,在那里他们被温和地建议尝试另一个 TTS 引擎。

我 运行 一个后台计时器,它会在一段时间不活动后释放引擎。此时间量可由用户配置,并带有初始化延迟警告...

我知道以上并不能解决您的问题,但也许我的建议可以安抚您的用户,这与解决问题相去甚远,但是嘿...

我毫不怀疑 Google 会逐渐提高初始化性能 - 四年前,我遇到了 IVONA 的这个问题,他们最终在初始化时间上做得很好。

好吧,伙计们!我想我找到了正确的代码来立即执行此操作..

您可以像这样在 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);
        }
    }