O processamento de língua falada, como tantos outros, é um capitulo interessante do Processamento de Linguagem Natural (PLN) e esta vinculado ao subcampo da Natural Languge Understand (NLU). Nesse processo busca-se basicamente fazer com que o dispositivo (computador, celular, etc.) seja capaz de "ouvir" e então transcrever o que foi falado em texto.
“O processamento da língua falada depende de uma vasta gama de conhecimentos que inclui acústica, fonologia, fonética, linguística geral, semântica, sintaxe, pragmática, estruturas discursivas, entre outras. Para além disso, outros conhecimentos mais comuns à ciência da computação, à engenharia elétrica, à matemática e, até mesmo à psicologia, também são necessários” (Caseli et al., 2023, p. 15)
Atualmente existem diversas bibliotecas que já nos fornecem a parte básica desse processo, sendo conhecidos como ASR Convencionais. Neles o resultado trata-se apenas de uma sequência de palavras, sem pontuações ou validações gramaticais, e que podem ser utilizados como base para outras aplicações.
Nesse artigo vamos explorar aspectos da implementação da biblioteca android.speech, nativa nos dispositivos Android e que trata-se exatamente de uma dessas ASR Convencionais. Não é nosso objetivo servir como tutorial, para essa finalidade sugerimos os artigos, em inglês, de Atitienei e Khare onde um passo-a-passo foi dado e que aplicamos em nosso aplicativo de exemplos que esta no GitHub.
Bibliotecas e permissões
Para incluir o STT (speech-to-text) em uma aplicação Android nenhuma biblioteca externa é necessária, porém é preciso que a aplicação tenha permissões para "gravar" audios. Dessa forma é preciso incluir a linha abaixo no manifesto da aplicação:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
De forma complementar também será necessário solicitar a permissão de audio em algum momento o que pode ser feito da seguinte forma:
ActivityCompat.requestPermissions(
context as Activity,
arrayOf(Manifest.permission.RECORD_AUDIO),
123
)
Reconhecendo a fala do usuário e convertendo em texto
Existem dois elementos básicos necessários para utilizar a função de texto para fala da biblioteca nativa do android:
- SpeechRecognizer: Essa é a classe responsável por efetivamente executar a função de reconhecimento de voz.
- RecognitionListener: Essa é uma interface onde os retornos das chamadas do reconhecimento de voz podem ser manipulados através de sua implementação em um objeto concreto.
O início do processo acontece quando uma instância do SpeechRecognizer é criada a partir do método estático factory da própria classe como no exemplo abaixo:
import android.speech.SpeechRecognizer
val recognizer = SpeechRecognizer.createSpeechRecognizer(context)
Nesse caso o contexto pode ser tanto a aplicação quanto a Activity, no nosso app de exemplo utilizamos como contexto a própria aplicação.
Com o recognizer criado vamos agora setar o listener, que será responsável por intermediar o que será reconhecido e tratar o texto resultante. Para setar utilize o método setRecognitionListener como no exemplo abaixo:
recognizer.setRecognitionListener(listener)
Agora será preciso definir uma Intent que irá iniciar nossa aplicação, esse intent passa para o SpeechRecognizer vários parâmetros que desejamos. Abaixo um exemplo:
import android.speech.RecognizerIntent
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply {
putExtra(
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
)
putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)
putExtra(RecognizerIntent.EXTRA_LANGUAGE, "pt-BR")
}
Nessa Intent informamos que nossa ação será de reconhecimento de voz (ACTION_RECOGNIZE_SPEECH), o extra EXTRA_LANGUAGE_MODEL é obrigatório pois precisamos definir o modelo de reconhecimento de falar que será utilizado, no nosso caso utilizamos o LANGUAGE_MODEL_FREE_FORM que indica um modelo de fala livre. Já o extra EXTRA_PARTIAL_RESULTS setado como true nos permite receber parciais do texto durante o reconhecimento da fala e o EXTRA_LANGUAGE com o valor pt-BR garante que haverá reconhecimento de voz para o português brasileiro. Mais detalhes sobre os parâmetros pode ser obtido na documentação do Intent.
Com esses passos feitos podemos iniciar o reconhecimento de voz chamando o método startListening passando nossa Intent como parâmetro:
recognizer.startListening(intent)
Para interromper basta chamar o método stopListening. Abaixo o exemplo:
recognizer.stopListening()
Como mencionado anteriormente os texto gerado pelo SpeechRecognizer é tratado em uma instância concreta da interface RecognitionListener, pois é nela que estão os eventos. Essa interface obriga a implementação dos seguintes métodos:
- onReadyForSpeech: É chamado quando o SpeechRecognizer foi corretamente inicializado e esta pronto para processar a voz do usuário. Pode ser utilizado para indicar na interface com o usuário que ele pode começar a falar.
- onBeginningOfSpeech: É chamado após o SpeechRecognizer identificar que o usuário começou a falar. Essa informação pode ser utilizada para indicar graficamente na interface que o usuário começou a falar.
- onRmsChanged: Esse método recebe como um float (rmsdB) que indica uma métrica da variação do volume da fala durante o reconhecimento da voz. Pode ser utilizado para indicar graficamente na interface que a voz esta sendo reconhecida.
- onEndOfSpeech: É chamado após o SpeechRecognizer identificar que o usuário terminou de falar. Essa informação pode ser utilizada para indicar graficamente na interface que o usuário terminou a falar.
- onResults e onPartialResults: recebe um Bundle como parâmetro com o resultado do processamento total (onResults) ou parcial (onPartialResults), antes do usuário terminar de falar. É através desses métodos que o texto do que foi falados é gerado.
- onError: Quando acontece algum erro no processamento da fala (fala irreconhecida, por exemplo) esse método será chamado e ele recebe um inteiro com o código do erro correspondente.
- onBufferReceived: Recebe um buffer como um array de bytes que pode ser utilizado para salvar o audio gravado se for desejado.
- onEvent: Esse método esta reservado para uso futuro e no momento não é chamado.
Mais detalhes sobre cada um dos callbacks pode ser encontrada na documentação. Podemos verificar que os métodos mais relevantes são o onResults e o onError, pois contemplam o texto transcrito do que foi falado ou o erro, caso aconteça algum problema. Abaixo um exemplo de implementação simples.
import android.speech.RecognitionListener
import android.widget.Toast
recognizer.setRecognitionListener(object:RecognitionListener {
override fun onReadyForSpeech(params: Bundle?) = Unit
override fun onBeginningOfSpeech() = Unit
override fun onRmsChanged(rmsdB: Float) = Unit
override fun onBufferReceived(buffer: ByteArray?) = Unit
override fun onEndOfSpeech() = Unit
override fun onError(error: Int) {
val msgError = when (error) {
SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> "Network timeout"
SpeechRecognizer.ERROR_NETWORK -> "Network error"
SpeechRecognizer.ERROR_AUDIO -> "Audio recording error"
SpeechRecognizer.ERROR_SERVER -> "Server error"
SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> "Speech input"
SpeechRecognizer.ERROR_NO_MATCH -> "No recognition result matched."
SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> "RecognitionService busy"
SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS -> "Insufficient permissions"
else -> "Didn't understand, please try again."
}
Toast.makeText(context,"Error: $msgError", Toast.LENGTH_SHORT).show()
}
override fun onResults(results: Bundle?) {
results
?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
?.getOrNull(0)
?.let { text ->
Toast.makeText(context,"Text: $text", Toast.LENGTH_LONG).show()
}
}
override fun onPartialResults(partialResults: Bundle?) = Unit
override fun onEvent(eventType: Int, params: Bundle?) = Unit
})
Conclusões
A implementação do ASR em aplicações Android Nativas com a biblioteca nativa android.speech provou ser extremamente simplificada e prática, sem grandes complexidades no processo. Se o dispositivo estiver corretamente configurado em testes iniciais provou-se possível o seu uso offline, entende-se que seja necessário solicitar que o pacote de reconhecimento de fala padrão do assistente de voz local seja baixado para esse tipo de uso.
Tendo em vista sua facilidade e a ausência de pacotes adicionais entendem-se que trata-se de uma forma viável para o uso embarcado da tecnologia em aplicações Android Nativas.
Por ser um modelo convencional pode ser necessário algum tratamento do seu resultado conforme o uso desejado para o texto. Supondo, por exemplo, que seja desejado utilizá-lo para transcrições talvez seja necessário treinar um modelo para predição de pontuação para então utilizá-lo em uma aplicação produtiva, um exemplo da arquitetura esta na figura abaixo.
Um exemplo de prático aplicação dos conceitos aqui descritos pode ser encontrado no GitHub do autor. Esse aplicativo não dispõe ainda da habilidade de prever pontuação porém pode ser um ótimo ponto de partida, ele foi construído tomando como base o passo-a-passo descrito por Atitienei (2023) em seu artigo do Medium.
REFERÊNCIAS
ANDROID API REFERENCE: android.speech. [S. l.], [s. d.]. Disponível em: https://developer.android.com/reference/android/speech/package-summary. Acesso em: 12 jan. 2024.
ATITIENEI, D. Voice to Text in Jetpack Compose — Android. In: MEDIUM. 10 mar. 2023. Disponível em: https://medium.com/@daniel.atitienei/voice-to-text-in-jetpack-compose-android-c1e077627abe. Acesso em: 9 jan. 2024.
CASELI, H. M. (org.) et al. Processamento de Linguagem Natural: Conceitos, Técnicas e Aplicações em Português. 1. ed. In: Brasileiras em Processamento de Linguagem Natural. São Carlos: BPLN, 2023. E-Book. cap. 2 e cap.3 p. 15-63. Disponível em: https://brasileiraspln.com/livro-pln. Acesso em: 20 dez. 2023.
KHARE, A. Add Voice Commands to Android Apps. In: MEDIUM. GEEK CULTURE. 6 jul. 2021. Disponível em: https://medium.com/geekculture/add-voice-commands-to-android-apps-80157c0d5bcc. Acesso em: 9 jan. 2024.
Top comments (0)