Я разрабатываю поток настройки голосового помощника, в котором в приложении используется речевое определение (STT), чтобы получить имя пользователя, а затем TextTospeech (tts), чтобы подтвердить настройки. завершается. This issue has been reliably reproduced on a specific test device.
Environment
- Device: Samsung SM-A736B
- OS: Android 15 (One UI 7)
- stt : system android.speech.speechRecognizer
- tts : google tts двигатель (), к которому я явно связываю. услуга (
Код: Выделить всё
com.google.android.tts) и начинает слушать.Код: Выделить всё
VoiceSessionService - Пользователь говорит, а обратный вызов onResults успешно запускается с транскрибированным текстом. destroy () , и служба останавливается.) Использование audiofocusrequest.builder .
Код: Выделить всё
AUDIOFOCUS_REQUEST_GRANTED - Вызов texttospech.speak () , чтобы произнести первую фразу. Команда (Call to .speak () возвращает без ошибки), но затем замолчает. Он никогда не воспроизводит звук и, что наиболее критически, никогда не запускает обратные вызовы ondone или onerror в своих высказываниях progresslesslistener .
Логика приложения теперь постоянно заблокирована, ожидая, что он никогда не будет прибывать. /> соответствующий код и журналы < /h3>
[/b] - логика для обработки результата STT:Код: Выделить всё
ConversationManager.kt[/b] - Обеспечение речевого определения уничтожено:Код: Выделить всё
// This method is called after the STT result is received fun onUserInput(text: String) { if (awaitingFirstRunName) { awaitingFirstRunName = false val nameToSave = if (text.isNotBlank()) text.trim() else "Default Name" settings.saveUserName(nameToSave) Handler(Looper.getMainLooper()).postDelayed({ val audioManager = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager val audioAttributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .build() val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) .setAudioAttributes(audioAttributes) .build() val result = audioManager.requestAudioFocus(focusRequest) if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { Log.d("MY_APP_LOG", "Audio focus granted. Forcing TTS engine restart.") // Forcing a restart of the TTS Engine TtsManager.shutdown() TtsManager.initialize(appContext) val confirmationPhrase = "OK, $nameToSave" TtsManager.speak( context = appContext, text = confirmationPhrase, queueMode = TextToSpeech.QUEUE_ADD, onDone = { // This callback is never triggered Log.d("MY_APP_LOG", "Confirmation phrase DONE. This is never logged.") speakFinalSettings(appContext) } ) } else { Log.e("MY_APP_LOG", "Could not get audio focus.") } }, 500) return } // ... logic for other commands follows } < /code> [b]VoiceSessionService.kt[/b]Код: Выделить всё
private fun processCommand(text: String) { val intent = Intent(CommandReceiver.ACTION_PROCESS_COMMAND).apply { putExtra(CommandReceiver.EXTRA_RECO_TEXT, text) } sendBroadcast(intent) // Immediately destroy the recognizer to release all audio resources speechRecognizer?.stopListening() speechRecognizer?.destroy() speechRecognizer = null stopSelf() } < /code> [b]Logcat Snippet:[/b] D/RescueService: onResults: Andrei D/RescueService: First run: User name saved as 'Andrei' // --- 500ms delay occurs here --- D/RescueService: Audio focus granted for the first time setup confirmation. D/RescueService: Forcing TTS engine restart. D/RescueService: TTS speaking: 'OK, Andrei' (queue=1) id=... // --- SILENCE --- // --- No "Confirmation phrase DONE" log ever appears --- < /code> Обновление: Определения классов вспомогательного класса как запрошенные < /h3> Вот исходный код для вспомогательных классов, используемых в фрагментах выше. < /p> [b]TtsManager.kt[/b]Код: Выделить всё
package com.babenko.rescueservice.voice import android.content.Context import android.os.Handler import android.os.Looper import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener import com.babenko.rescueservice.core.Logger import java.util.* object TtsManager : TextToSpeech.OnInitListener { private var tts: TextToSpeech? = null private var isInitialized = false private var appContext: Context? = null private val handler = Handler(Looper.getMainLooper()) private val pending = ArrayDeque>() private val utteranceCallbacks = mutableMapOf Unit>() fun initialize(context: Context) { if (tts != null && isInitialized) return appContext = context.applicationContext try { // Binding explicitly to Google TTS Engine tts = TextToSpeech(context.applicationContext, this, "com.google.android.tts") } catch (e: Exception) { Logger.e(e, "Failed to create TextToSpeech") } } override fun onInit(status: Int) { if (status != TextToSpeech.SUCCESS) { isInitialized = false pending.clear() return } isInitialized = true tts?.setOnUtteranceProgressListener(object : UtteranceProgressListener() { override fun onStart(utteranceId: String?) {} override fun onDone(utteranceId: String?) { utteranceId?.let { handler.post { utteranceCallbacks[it]?.invoke() utteranceCallbacks.remove(it) } } } @Deprecated("Deprecated in Java") override fun onError(utteranceId: String?) { utteranceId?.let { utteranceCallbacks.remove(it) } } }) // Process any pending speech requests while (pending.isNotEmpty()) { val (text, mode, onDone) = pending.removeFirst() internalSpeak(text, mode, onDone) } } fun speak(context: Context, text: String, queueMode: Int = TextToSpeech.QUEUE_ADD, onDone: (() -> Unit)? = null) { if (!isInitialized) { pending.addLast(Triple(text, queueMode, onDone)) initialize(context) return } internalSpeak(text, mode, onDone) } private fun internalSpeak(text: String, queueMode: Int, onDone: (() -> Unit)? = null) { val utteranceId = text.hashCode().toString() + System.currentTimeMillis() onDone?.let { utteranceCallbacks[utteranceId] = it } tts?.speak(text, queueMode, null, utteranceId) } fun shutdown() { tts?.stop() tts?.shutdown() tts = null isInitialized = false pending.clear() utteranceCallbacks.clear() } } < /code> [b]ConversationManager.kt).Код: Выделить всё
package com.babenko.rescueservice.voice import android.content.Context import android.media.AudioAttributes import android.media.AudioFocusRequest import android.media.AudioManager import android.os.Handler import android.os.Looper import android.speech.tts.TextToSpeech import android.util.Log import com.babenko.rescueservice.data.SettingsManager // Assuming this is your settings helper object ConversationManager { private lateinit var appContext: Context private var awaitingFirstRunName = false private val settings: SettingsManager by lazy { SettingsManager.getInstance(appContext) } fun init(context: Context) { appContext = context.applicationContext } fun startFirstRunSetup() { // ... (Code to speak the welcome message and start STT) } fun onUserInput(text: String) { if (awaitingFirstRunName) { awaitingFirstRunName = false val nameToSave = if (text.isNotBlank()) text.trim() else "Default Name" settings.saveUserName(nameToSave) Handler(Looper.getMainLooper()).postDelayed({ val audioManager = appContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager val audioAttributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .build() val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) .setAudioAttributes(audioAttributes) .build() val result = audioManager.requestAudioFocus(focusRequest) if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { Log.d("MY_APP_LOG", "Audio focus granted. Forcing TTS engine restart.") // Forcing a restart of the TTS Engine TtsManager.shutdown() TtsManager.initialize(appContext) val confirmationPhrase = "OK, $nameToSave" TtsManager.speak( context = appContext, text = confirmationPhrase, queueMode = TextToSpeech.QUEUE_ADD, onDone = { Log.d("MY_APP_LOG", "Confirmation phrase DONE. This is never logged.") speakFinalSettings(appContext) } ) } else { Log.e("MY_APP_LOG", "Could not get audio focus.") } }, 500) return } } fun speakFinalSettings(context: Context) { val finalName = settings.getUserName() // ... (builds final string and calls TtsManager.speak) val confirmationMessage = "Settings confirmed for $finalName." TtsManager.speak(context, confirmationMessage, TextToSpeech.QUEUE_ADD) } } < /code> Я уже внедрил серию исправлений, основанных на лучших практиках для разрешения конфликтов аудиоуправления между STT и TTS.com.google.android.tts - Я ожидал , чтобы предотвратить нестабильность, вызванную специфичными для поставщиками (Samsung) двигателями TTS. Связывание было успешным, но подвеска все еще происходит. Микрофон и аудиоканал, делая их доступными для двигателя TTS.
[*] обновленная проверка Audio Focus для современного audioFocusRequest.Builder , что система будет приоритет и предоставлен приоритетом и предоставлена. Фактический результат [/b] заключается в том, что запрос - успешный (logCat подтверждает Audiofocus_Request_granted ), но двигатель TTS все еще
[/list]
. Ttsmanager.shutdown () , а затем ttsmanager.initialize () непосредственно перед выступлением. Это была моя последняя попытка, но проблема сохраняется даже с этой мерой. Двигатель TTS, механизм TextTospeech нормально функционирует и воспроизводит аудио. Он принимает команду .speak () , но никогда не выполняет ее и не предоставляет обратный вызов завершения (
Код: Выделить всё
onDoneЭто приводит к моему окончательному вопросу: Почему TextTospeech двигатель видит таким образом после сеанса речи , даже когда все задокументированные лучшие практики и аудио -фокус успешно предоставлены? Является ли это известной ошибкой на уровне системного уровня на этой конкретной платформе (Samsung с Android 15), и есть ли другие обходные пути, которые можно попытаться?>
Подробнее здесь: https://stackoverflow.com/questions/797 ... cognizer-e