TTS Ondone Callback никогда не стреляет в Samsung (Android 15) после SpeechRecognizer, даже с Audiofocus_Request_grantedAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 TTS Ondone Callback никогда не стреляет в Samsung (Android 15) после SpeechRecognizer, даже с Audiofocus_Request_granted

Сообщение Anonymous »

Я сталкиваюсь с очень специфической, воспроизводимой ошибкой, и я попал в стену после того, как попробовал все стандартные решения. Я был бы признателен за любую информацию.
Я разрабатываю поток настройки голосового помощника, в котором в приложении используется речевое определение (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 () , и служба останавливается.

    Код: Выделить всё

    AUDIOFOCUS_REQUEST_GRANTED
    ) Использование audiofocusrequest.builder .
  • Вызов texttospech.speak () , чтобы произнести первую фразу. Команда (Call to .speak () возвращает без ошибки), но затем замолчает. Он никогда не воспроизводит звук и, что наиболее критически, никогда не запускает обратные вызовы ondone или onerror в своих высказываниях progresslesslistener .


    Логика приложения теперь постоянно заблокирована, ожидая, что он никогда не будет прибывать. /> соответствующий код и журналы < /h3>

    Код: Выделить всё

    ConversationManager.kt
    [/b] - логика для обработки результата STT:

    Код: Выделить всё

    // 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
    [/b]

    Код: Выделить всё

    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]






Подробнее здесь: https://stackoverflow.com/questions/797 ... cognizer-e
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»