Elección estratégica de APIs
No todos los motores de voz son iguales. Para proyectos que requieren alta precisión, soporte offline y flexibilidad en entornos ruidosos, Google ML Kit Speech Recognition supera claramente a SpeechRecognizer nativo. Este último depende del servicio de voz del sistema —que varía entre fabricantes— y carece de control sobre modelos, ajustes acústicos o modos de streaming.
Configuración mínima efectiva
En lugar de depender de versiones obsoletas o dependencias redundantes, usamos la versión actualizada y modular:
implementation 'com.google.mlkit:speech-recognition:18.0.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.5'
Se omite voice-recognition, ya que no es un artefacto válido en ML Kit v2 — el reconocimiento se gestiona exclusivamente mediante speech-recognition.
Manejo seguro de permisos
Más allá de solicitar RECORD_AUDIO, implementamos una verificación escalonada:
private fun requestMicrophonePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Usa el nuevo permiso granular
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), NOTIFICATION_REQUEST_CODE)
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.RECORD_AUDIO),
AUDIO_REQUEST_CODE
)
}
}
Reconocimiento continuo con control de flujo
Evitamos llamadas bloqueantes y usamos un patrón de escucha reactiva con manejo explícito del ciclo de vida:
private lateinit var speechClient: SpeechRecognition
private fun setupSpeechClient() {
speechClient = SpeechRecognition.getClient(
SpeechRecognitionOptions.Builder()
.setLanguage("zh-CN")
.setEnableWordTimeOffsets(true) // Útil para resaltado en tiempo real
.build()
)
}
private fun startStreamingRecognition() {
val intent = Intent(this, StreamingRecognitionService::class.java)
startService(intent) // Ejecuta en foreground service para evitar limitaciones de background
}
Síntesis de voz con personalización contextual
En lugar de usar TextToSpeech.SPEECH_QUEUE_FLUSH —que interrumpe todo— optamos por una cola inteligente basada en prioridad:
private val ttsEngine = TextToSpeech(this) { status ->
if (status == TextToSpeech.SUCCESS) {
ttsEngine.setLanguage(Locale.forLanguageTag("zh-CN"))
ttsEngine.setSpeechRate(0.9f)
ttsEngine.setPitch(1.1f)
}
}
fun speakWithPriority(text: String, priority: Int = 1) {
val params = Bundle().apply {
putInt(TextToSpeech.Engine.KEY_PARAM_STREAM, AudioManager.STREAM_VOICE_CALL)
putInt("queueMode", priority) // Personalizado: 0=override, 1=append
}
ttsEngine.speak(text, TextToSpeech.QUEUE_ADD, params, "utterance_id_$System.currentTimeMillis()")
}
Optimizaciones clave
- Reducción de latencia: Carga previa del modelo con
downloadModelIfNotAvailable()y uso deStreamingRecognitionen vez de frases completas. - Robustez acústica: Integración opcional con
AudioProcessorpara supresión de eco y normalización dinámica de gananica. - Gestión efciiente de recursos: Detención automática tras 3 segundos de silencio usando
AudioRecorden modo low-latency + umbral RMS personalizable.
Privacidad por diseño
Todas las operaciones de reconocimiento se configuran en modo ON_DEVICE por defecto. Cuando se requiere procesamiento remoto, los datos se envían cifrados vía HTTPS y se eliminan inmediatamente tras la transcripción. No se almacenan grabaciones ni metadatos sensibles sin consentimiento explícito.
Ejemplo práctico: Asistente de notas por voz
Una aplicación minimalista que permite:
- Grabar fragmentos de voz y convertirlos a texto localmente.
- Guardar transcripciones en Room con timestamp y etiquetas automáticas (ej. "reunión", "recordatorio").
- Reproducir cualquier nota guardada con voz natural y velocidad ajustable.
El código fuente incluye gestión de estado de micrófono, detección de finalización automática y recuperación ante fallos de red sin reinicio manual.