I'm working on an English lessons app, and one feature I'm trying to implement is "Listen And Repeat" lessons. For that purpose I'm using Android's SpeechRecognizer, but I'm not having the expected results.
This next is my "Speech Recognition" activity:
class MainActivity : AppCompatActivity(), RecognitionListener {
private val permission = 100
private lateinit var returnedText: TextView
private lateinit var toggleButton: ToggleButton
private lateinit var progressBar: ProgressBar
private lateinit var speech: SpeechRecognizer
private lateinit var recognizerIntent: Intent
private var logTag = "VoiceRecognitionActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "KotlinApp"
returnedText = findViewById(R.id.textView)
progressBar = findViewById(R.id.progressBar)
toggleButton = findViewById(R.id.toggleButton)
progressBar.visibility = View.VISIBLE
speech = SpeechRecognizer.createSpeechRecognizer(this)
Log.i(logTag, "isRecognitionAvailable: " + SpeechRecognizer.isRecognitionAvailable(this))
speech.setRecognitionListener(this)
recognizerIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, "en-US") //also tried "en_US" or "en-UK" and also tried Locale.US.toString(). Also replaced EXTRA_LANGUAGE_PREFERENCE with EXTRA_LANGUAGE
recognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
recognizerIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 3)
toggleButton.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
progressBar.visibility = View.VISIBLE
progressBar.isIndeterminate = true
ActivityCompat.requestPermissions(this@MainActivity,
arrayOf(Manifest.permission.RECORD_AUDIO),
permission)
} else {
progressBar.isIndeterminate = false
progressBar.visibility = View.VISIBLE
speech.stopListening()
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>,
grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
permission -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager
.PERMISSION_GRANTED) {
speech.startListening(recognizerIntent)
} else {
Toast.makeText(this@MainActivity, "Permission Denied!",
Toast.LENGTH_SHORT).show()
}
}
}
override fun onStop() {
super.onStop()
speech.destroy()
Log.i(logTag, "destroy")
}
override fun onReadyForSpeech(params: Bundle?) {
TODO("Not yet implemented")
}
override fun onRmsChanged(rmsdB: Float) {
progressBar.progress = rmsdB.toInt()
}
override fun onBufferReceived(buffer: ByteArray?) {
TODO("Not yet implemented")
}
override fun onPartialResults(partialResults: Bundle?) {
TODO("Not yet implemented")
}
override fun onEvent(eventType: Int, params: Bundle?) {
TODO("Not yet implemented")
}
override fun onBeginningOfSpeech() {
Log.i(logTag, "onBeginningOfSpeech")
progressBar.isIndeterminate = false
progressBar.max = 10
}
override fun onEndOfSpeech() {
progressBar.isIndeterminate = true
toggleButton.isChecked = false
}
override fun onError(error: Int) {
val errorMessage: String = getErrorText(error)
Log.d(logTag, "FAILED $errorMessage")
returnedText.text = errorMessage
toggleButton.isChecked = false
}
private fun getErrorText(error: Int): String {
var message = ""
message = when (error) {
SpeechRecognizer.ERROR_AUDIO -> "Audio recording error"
SpeechRecognizer.ERROR_CLIENT -> "Client side error"
SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS -> "Insufficient permissions"
SpeechRecognizer.ERROR_NETWORK -> "Network error"
SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> "Network timeout"
SpeechRecognizer.ERROR_NO_MATCH -> "No match"
SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> "RecognitionService busy"
SpeechRecognizer.ERROR_SERVER -> "error from server"
SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> "No speech input"
else -> "Didn't understand, please try again."
}
return message
}
override fun onResults(results: Bundle?) {
Log.i(logTag, "onResults")
val matches = results!!.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
var text = ""
for (result in matches) text = """
$result
""".trimIndent()
returnedText.text = text
}
}
The microphone permissions are being requested correctly and recognition engine is working, but the results are not satisfactory, and I'll give two examples:
- I say "This is my bedroom" > it recognices "This is my bedrun" (among others).
- I say "The soldier was killed during the war" > it recognices (in the best of cases) "The souldier was killed during the wall/world/some others".
I know the recognition engine is not perfect and of course also know every voice is different, but there are some notes:
- In Spanish (my phone default language) the recognition is far more precise than in English.
- If I set my phone language to English (in Settings) it improves recognition a little, but don't think much.
- I've noticed that Google Chrome speech recognition is far more precise that Android's (why?)
In the end, do you know -given my activity code- of any way I could improve English speech recognition in my app?