Я сталкиваюсь с очень специфической, воспроизводимой ошибкой, и я попал в стену после того, как попробовал все стандартные решения. Я был бы признателен за любую информацию.
Я разрабатываю поток настройки голосового помощника, в котором в приложении используется речевое определение (STT), чтобы получить имя пользователя, а затем TextTospeech (tts), чтобы подтвердить настройки. завершается. This issue has been reliably reproduced on a specific test device.
Environment
Вызов texttospech.speak () , чтобы произнести первую фразу. Команда (Call to .speak () возвращает без ошибки), но затем замолчает. Он никогда не воспроизводит звук и, что наиболее критически, никогда не запускает обратные вызовы ondone или onerror в своих высказываниях progresslesslistener .
Логика приложения теперь постоянно заблокирована, ожидая, что он никогда не будет прибывать. /> соответствующий код и журналы < /h3>
// 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
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.
[*] введенный постделейенный Barrier 500 мс между разрушительным речевым речевом и попыткой использовать TextTospeepe percognizer и попытки использовать Texttospeepheep Я ожидал, что это даст аудиосистеме Android достаточно времени, чтобы обработать выпуск ресурса и избежать условия гонки.
[*] обновленная проверка Audio Focus для современного audioFocusRequest.Builder , что система будет приоритет и предоставлен приоритетом и предоставлена. Фактический результат [/b] заключается в том, что запрос - успешный (logCat подтверждает Audiofocus_Request_granted ), но двигатель TTS все еще
[/list]
Я сталкиваюсь с очень специфической, воспроизводимой ошибкой, и я попал в стену после того, как попробовал все стандартные решения. Я был бы признателен за любую информацию. Я разрабатываю поток настройки голосового помощника, в котором в приложении используется речевое определение (STT), чтобы получить имя пользователя, а затем TextTospeech (tts), чтобы подтвердить настройки. завершается. This issue has been reliably reproduced on a specific test device. Environment [list] [*][b]Device[/b]: Samsung SM-A736B
[*][b]OS[/b]: Android 15 (One UI 7)
[*] [b] stt [/b]: system android.speech.speechRecognizer
[*] [b] tts [/b]: google tts двигатель ([code]com.google.android.tts[/code]), к которому я явно связываю. услуга ([code]VoiceSessionService[/code]) и начинает слушать.
[*] Пользователь говорит, а обратный вызов onResults успешно запускается с транскрибированным текстом. destroy () , и служба останавливается.[code]AUDIOFOCUS_REQUEST_GRANTED[/code]) Использование audiofocusrequest.builder .
[*] Вызов texttospech.speak () , чтобы произнести первую фразу. Команда (Call to .speak () возвращает без ошибки), но затем замолчает. Он никогда не воспроизводит звук и, что наиболее критически, никогда не запускает обратные вызовы ondone или onerror в своих высказываниях progresslesslistener .
Логика приложения теперь постоянно заблокирована, ожидая, что он никогда не будет прибывать. /> соответствующий код и журналы < /h3> [b][code]ConversationManager.kt[/code] [/b] - логика для обработки результата STT: [code]// 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)
// 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[/code] [/b] - Обеспечение речевого определения уничтожено: [code]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[/code] [/b] [code]package com.babenko.rescueservice.voice
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) }
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)
// 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[/code]).
[*] [b] Я ожидал [/b], чтобы предотвратить нестабильность, вызванную специфичными для поставщиками (Samsung) двигателями TTS. Связывание было успешным, но подвеска все еще происходит. Микрофон и аудиоканал, делая их доступными для двигателя TTS. [/list]
[*] [b] введенный постделейенный Barrier [/b] 500 мс между разрушительным речевым речевом и попыткой использовать TextTospeepe percognizer и попытки использовать Texttospeepheep [b] Я ожидал, что [/b] это даст аудиосистеме Android достаточно времени, чтобы обработать выпуск ресурса и избежать условия гонки.
[*] [b] обновленная проверка Audio Focus [/b] для современного audioFocusRequest.Builder , что система будет приоритет и предоставлен приоритетом и предоставлена. Фактический результат [/b] заключается в том, что запрос - [b] успешный [/b] (logCat подтверждает Audiofocus_Request_granted ), но двигатель TTS все еще [/list]