Подтвердить что ты не робот

Обход отставания от инициализации двигателя TTS Engine в Android

Я попытался воспроизвести объект 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 Engine - около 6-8 секунд на моих устройствах.

Я прочитал на других сообщениях, что эту задержку можно избежать, используя другие двигатели TTS. Поскольку я всегда развиваюсь на своем телефоне Samsung, у которого есть собственный собственный TTS, настроенный по умолчанию, я никогда не замечал эту проблему, пока не проверил мое приложение на других фирменных телефонах, у которых двигатель Google TTS настроен по умолчанию. Но я в идеале не хочу заставлять пользователей устанавливать другое приложение вместе с моим собственным, и поэтому я хотел бы, чтобы это работало с самим Google TTS Engine по умолчанию.

Через некоторое ошибочное кодирование, которое я позже исправил, я понял, что если бы я мог предварительно инициализировать объект TextToSpeech и всегда не null - после инициализации я мог бы, похоже, обойти эту задержку.

Однако, поскольку есть необходимость отключить ресурс, как только мы закончим с ним, я не смогу сохранить объект в ожидании и инициализироваться надолго, и я не знаю, когда инициализировать/выключать ресурс, поскольку я технически нужен голос для воспроизведения в любое время, когда происходит конкретное событие, которое в основном было бы, когда мое приложение не было открыто на телефоне.

Итак, мои вопросы таковы:

  • Можно ли как-то уменьшить или устранить задержку инициализации Google TTS Engine, программно или иначе?

  • Есть ли способ, с помощью которого я могу сохранить объект TextToSpeech живой и инициализированный во все времена, например, через службу? Или это будет плохой, ресурсоемкий дизайн?

  • Также используется статический объект TextToSpeech - правильный путь, для моих требований?

Любые решения вместе с кодом будут оценены.

Обновление: Я подтвердил, что задержка связана исключительно с движком Google TTS, так как я пытался использовать другие бесплатные и платные двигатели TTS, в которых мало или вообще нет задержки. Но я по-прежнему предпочитаю не иметь зависимостей третьих сторон, если это возможно, и хотел бы сделать эту работу с Google TTS Engine.

ОБНОВЛЕНИЕ: Я, похоже, обошел эту проблему, связывая этот объект TTS с сервисом и получая его от службы. Служба STICKY (если услуга завершается из-за проблемы с памятью, ОС Android перезапустит службу, когда память снова будет доступна) и настроен на перезагрузку при перезагрузке устройства.

Служба только инициализирует объект 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();
    }
}




}
4b9b3361

Ответ 1

Я разработчик приложения для Android Saiy. Это не бесстыдный плагин, он демонстрирует, что я использую шаблон дизайна, который вы рассматриваете, и я "прошел", что вызвало ваш вопрос.

Это свежо в моем сознании, поскольку я провел последний год, переписывая свой код и должен был уделять большое внимание окружающей проблеме.

  • Можно ли как-то уменьшить или устранить задержку инициализации Google TTS Engine, программно или иначе?

Я задал аналогичный вопрос некоторое время назад и инициализировал объект Text to Speech в фоновом потоке, где он не конкурирует с другими задачами, может немного уменьшить задержку (как я вижу, вы уже делаете в своем опубликованном коде).

Вы также можете убедиться, что запрос на разговор еще не отложен, выбрав встроенный голос, а не один, зависящий от сети:

В API 21+ проверьте параметры класс голоса. В частности, getFeatures(), где вы можете проверить латентность и требования к сети.

В API < 21 - установите KEY_FEATURE_NETWORK_SYNTHESIS в значение false внутри ваших параметров.

Независимо от вышесказанного, Google TTS Engine имеет самое длинное время инициализации любого из моторов, которые я тестировал (все они, я думаю). Я считаю, что это просто потому, что они используют все доступные ресурсы на устройстве, чтобы предоставлять самый качественный голос, который они могут.

Из моего личного тестирования эта задержка прямо пропорциональна аппаратной части устройства. Чем больше оперативной памяти и производительности процессора, тем меньше время инициализации. То же самое можно сказать о текущем состоянии устройства - думаю, вы обнаружите, что после перезагрузки, где есть свободная память, и Android не нужно будет убивать другие процессы, время инициализации будет уменьшено.

В целом, кроме вышеперечисленного, нет, вы не можете уменьшить время инициализации.

  • Есть ли способ, посредством которого я могу сохранить объект TextToSpeech живым и инициализированным в любое время, например, через службу? Или это будет плохой, ресурсоемкий дизайн?

  • Также использует статический объект TextToSpeech правильный путь для моих требований?

Как вы уже отмечали, способ избежать времени инициализации - оставаться привязанным к движку. Но есть дополнительные проблемы, которые вы, возможно, захотите рассмотреть, прежде чем делать это.

Если устройство находится в состоянии, когда ему необходимо освободить ресурсы, что является тем же самым состоянием, которое вызывает расширенную задержку инициализации, Android вполне соответствует его правам на сбор мусора. Если вы удерживаете это привязку в фоновом режиме, услуга может быть убита, возвращая вас обратно к квадрату.

Кроме того, если вы останетесь связанными с движком, ваши пользователи будут видеть использование коллективной памяти в настройках приложения для Android. Для многих, многих пользователей, которые неправильно рассматривают (бездействующее) использование памяти, прямо пропорционально утечке батареи, по моему опыту это приведет к удалению и снижению рейтинга приложений.

На момент написания статьи Google TTS привязана к моему приложению стоимостью 70 МБ.

Если вы все еще хотите продолжить на этой основе, вы можете попытаться заставить Android расставить приоритеты по вашему процессу и убить его последним - вы сделали бы это, используя службу переднего плана. Это открывает еще одну банку червей, но я не буду в нее входить.

Эффективно, привязка к движку в службе и проверка того, что служба запущена, когда вы хотите, чтобы движок говорил, является "шаблоном одноэлементности". Создание статичности в рамках этой службы не будет иметь никакой цели, о которой я могу думать.

Вы можете увидеть здесь, как я начинаю обрабатывать инициализацию TTS и связанные с ней проблемы, которые могут возникнуть - включен лаг.

Наконец, чтобы поделиться своим опытом относительно того, как я справился с вышеуказанным.

У меня "Google медленно инициализируется" в верхней части моих "известных ошибок" и "FAQ" в приложении.

Я отслеживаю время, которое требуется движку для вызова onInit. Если он занимает слишком много времени, я подниму уведомление пользователю и направлю его в FAQ, где им нередко советуют попробовать другой движок TTS.

Я запускаю фоновый таймер, который освобождает движок после периода бездействия. Это время настраивается пользователем и поставляется с предупреждениями о задержке инициализации...

Я знаю, что вышеупомянутое не решает ваши проблемы, но, возможно, мои предложения успокоят ваших пользователей, что является далекой секундой для решения проблемы, но эй...

Я не сомневаюсь, что Google постепенно увеличит производительность инициализации. Четыре года назад у меня была эта проблема с IVONA, которая в конце концов неплохо справилась с их инициализацией.

Ответ 2

ну ребята! Я думаю, что нашел подходящий код, чтобы сделать это без каких-либо задержек..

Вы можете инициализировать TextToSpeach в onCreate() следующим образом:

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