Android RecognitionListener: onResults being called twice
Asked Answered
P

6

15

I have a project using RecognitionListener written in Kotlin. The speech-to-text function was always a success and never presented any problems.

Since last week, it's onResult function started to be called twice. No changes were made on the project. I tested old versions of the project (from months ago) and those had the same problem.

There are three different cases:

  1. Small text (1 to 8 words) and SpeechRecognizer being stopped automatically -> onResult() called twice;
  2. Big text (9 words or more) and SpeechRecognizer being stopped automatically -> Normal behavior (onResult() called once);
  3. Any text size and SpeechRecognizer stopListening() function called manually (from code) -> Normal behavior.

Here is the VoiceRecognition speech-to-text class code:

class VoiceRecognition(private val activity: Activity, language: String = "pt_BR") : RecognitionListener {

    private val AudioLogTag = "AudioInput"

    var voiceRecognitionIntentHandler: VoiceRecognitionIntentHandler? = null
    var voiceRecognitionOnResultListener: VoiceRecognitionOnResultListener? = null //Must have this
    var voiceRecognitionLayoutChanger: VoiceRecognitionLayoutChanger? = null

    var isListening = false

    private val intent: Intent
    private var speech: SpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(activity)

    init {
        speech.setRecognitionListener(this)

        intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
        intent.putExtra(
            RecognizerIntent.EXTRA_LANGUAGE_MODEL,
            RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
        )
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language)
    }

    //It is important to put this function inside a clickListener
    fun listen(): Boolean {
        if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.RECORD_AUDIO), 1)
            return false
        }

        speech.startListening(intent)

        Log.i(AudioLogTag, "startListening")

        return true
    }

    //Use this if you want to stop listening but still get recognition results
    fun endListening(){
        Log.i(AudioLogTag, "stopListening")

        speech.stopListening()
        isListening = false
    }

    fun cancelListening(){
        Log.i(AudioLogTag, "cancelListening")

        speech.cancel()
        voiceRecognitionLayoutChanger?.endListeningChangeLayout()
        isListening = false
    }

    override fun onReadyForSpeech(p0: Bundle?) {
        Log.i(AudioLogTag, "onReadyForSpeech")

        voiceRecognitionLayoutChanger?.startListeningChangeLayout()
        isListening = true
    }

    override fun onRmsChanged(p0: Float) {
//        Log.i(AudioLogTag, "onRmsChanged: $p0")
//        progressBar.setProgress((Int) p0)
    }

    override fun onBufferReceived(p0: ByteArray?) {
        Log.i(AudioLogTag, "onBufferReceived: $p0")
    }

    override fun onPartialResults(p0: Bundle?) {
        Log.i(AudioLogTag, "onPartialResults")
    }

    override fun onEvent(p0: Int, p1: Bundle?) {
        Log.i(AudioLogTag, "onEvent")
    }

    override fun onBeginningOfSpeech() {
        Log.i(AudioLogTag, "onBeginningOfSpeech")
    }

    override fun onEndOfSpeech() {
        Log.i(AudioLogTag, "onEndOfSpeech")

        voiceRecognitionLayoutChanger?.endListeningChangeLayout()
        isListening = false
    }

    override fun onError(p0: Int) {
        speech.cancel()
        val errorMessage = getErrorText(p0)
        Log.d(AudioLogTag, "FAILED: $errorMessage")
        voiceRecognitionLayoutChanger?.endListeningChangeLayout()
        isListening = false
    }

    override fun onResults(p0: Bundle?) {

        val results: ArrayList<String> = p0?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION) as ArrayList<String>

        Log.i(AudioLogTag, "onResults -> ${results.size}")

        val voiceIntent: Int? = voiceRecognitionIntentHandler?.getIntent(results[0])
        if (voiceIntent != null && voiceIntent != 0) {
            voiceRecognitionIntentHandler?.handle(voiceIntent)
            return
        }

        voiceRecognitionOnResultListener!!.onResult(results[0])
    }

    private fun getErrorText(errorCode: Int): String {
        val message: String
        when (errorCode) {
            SpeechRecognizer.ERROR_AUDIO -> message = "Audio recording error"
            SpeechRecognizer.ERROR_CLIENT -> message = "Client side error"
            SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS -> message = "Insufficient permissions"
            SpeechRecognizer.ERROR_NETWORK -> message = "Network error"
            SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> message = "Network timeout"
            SpeechRecognizer.ERROR_NO_MATCH -> message = "No match"
            SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> message = "RecognitionService busy"
            SpeechRecognizer.ERROR_SERVER -> message = "Error from server"
            SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> message = "No speech input"
            else -> message = "Didn't understand, please try again."
        }
        return message
    }

    //Use it in your overriden onPause function.
    fun onPause() {
        voiceRecognitionLayoutChanger?.endListeningChangeLayout()
        isListening = false

        speech.cancel()
        Log.i(AudioLogTag, "pause")
    }

    //Use it in your overriden onDestroy function.
    fun onDestroy() {
        speech.destroy()
    }

listen(), endListening() and cancelListening() are all called from a button.

Piperine answered 25/3, 2020 at 16:39 Comment(7)
I am having the same issue, the problem is only on Samsung s8 with api 9 - here I can see also partial results when recognition is in progress. On older devices I dont experience this.Module
I saw this problem from Android 7 and above... I didn't even changed my project.. it just started happening.Piperine
"the problem is only on Samsung s8 with api 9" - by that I meant in my tests on devices I haveModule
same problem on Pocophone F1, my solution was to check if the results are identical, then ignore the second result if they areInflated
This just started happening in one of my apps yesterday. I added a boolean to allow the code to execute only once, but I'd love an explanation as to why it suddenly started doing this. Any updates?Hervey
Was able to reproduce in Pixel 3 & LG 7Clupeid
I also added a boolean to block the second result.. But the "success" audio keeps playing "doubled"Piperine
C
4

I found this open issue: https://issuetracker.google.com/issues/152628934

As I commented, I assume it is an issue with the "speech recognition service" and not with the Android RecognitionListener class.

Clupeid answered 30/3, 2020 at 9:35 Comment(0)
D
3

this is my temporary workaround

singleResult=true;

@Override
 public void onResults(Bundle results) {
            Log.d(TAG, "onResults"); //$NON-NLS-1$
            if (singleResult) {
                ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);

                if (matches != null && matches.size() > 0) {
                    Log.d("single Result", "" + matches.get(0));

                }
                singleResult=false;
            }

            getHandler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    singleResult=true;

                }
            },100);

        }
Derwent answered 23/5, 2020 at 4:34 Comment(0)
Z
2

I had the same problem and I've just added a boolean flag in my code, but ofcourse it's a temporary solution and I don't know the source of this problem.

val recognizer = SpeechRecognizer.createSpeechRecognizer(context)
recognizer.setRecognitionListener(
    object : RecognitionListener {

        var singleResult = true

        override fun onResults(results: Bundle?) {
            if (singleResult) {
                results?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION).let {
                    // do something with result
                }
                // next result will be ignored
                singleResult = false
            }
        }
    }
Zilpah answered 2/5, 2020 at 11:30 Comment(3)
How is singleResult getting set back to true? Do you set it back to true in onBeginningOfSpeech or something?Maxentia
Actually i don't set it back to true, but this object is beeing created more than onceZilpah
I've added a broader context to my example. In my app recognizer variable is created every time I want to call SpeechRecognizer. I'm not sure if i'ts optimal, but I'll be happy to get any opinions.Zilpah
H
1

This just started happening in one of my apps yesterday. I added a boolean to allow the code to execute only once, but I'd love an explanation as to why it suddenly started doing this. Any updates?

Hervey answered 29/3, 2020 at 11:40 Comment(0)
S
0

I use the the following code based on time differences, which should continue to work if Google ever gets around to fixing this bug.

long mStartTime = System.currentTimeMillis(); // Global Var


@Override
public void onResults(Bundle results)
{
   long difference = System.currentTimeMillis() - mStartTime;
   if (difference < 100)
   {
       return;
   }
   mStartTime = System.currentTimeMillis();

   ArrayList<String> textMatchList = 
   results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
   
   Event_Handler(VOICE_DATA, textMatchList.get(0));
   // process event
}
Selfmortification answered 25/6, 2020 at 19:41 Comment(0)
W
0

ya even I faced the same issue with my app but I fixed it with a custom logic that is using a flag means a variable let it be a temp variable and by default set it as a false.

what you need to do set the temp as true wherever you are starting listening voice. Then in your handler what you need to do is just add a if condition on the basis of temp variable like

if (temp) { do something temp = false }

so what will happen you handler will be called twice as usual but you will be able to handle this data only.

Wunderlich answered 22/7, 2020 at 17:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.