Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)



Introducción
Siri ha sido una característica nuclear de iOS desde que fue presentada en 2011. Ahora, iOS 10 trae nuevas características para permitir a los desarrolladores interactuar con Siri. En particular, dos nuevos frameworks están ahora disponibles: Speech y SiriKit.
Hoy, vamos a echar un vistazo al framework Speech, que nos permite traducir fácilmente audio a texto. Aprenderás cómo construir una aplicación de la vida real que usa la API de reconocimiento de voz para revisar el estado de un vuelo.
Si quieres aprender más sobre SiriKit, lo cubrí en mi tutorial Crea Extensiones SiriKit en iOS 10. Para más sobre otras nuevas características para desarrolladores en iOS 10, revisa el curso de Markus Mühlberger, justo aquí en Envato Tuts+.
- SDK de iOSCrea Extensiones SiriKit en iOS 10Patrick Balestra
- iOSQué Hay de Nuevo en iOS 10Markus Mühlberger
Uso
El reconocimiento de voz es el proceso de traducir audio en vivo o pre-grabado a texto transcrito. Desde que Siri fue presentada en iOS 5, ha habido un botón de micrófono en el teclado del sistema que permite a los usuarios dictar fácilmente. Esta característica puede ser usada con cualquier entrada de texto UIKit, y no requiere que escribas código adicional más allá de lo que escribirías para soportar una entrada de texto estándar. Es realmente rápido y fácil de usar, pero viene con unas cuántas limitaciones:
- El teclado siempre está presente cuando se dicta.
- El lenguaje no puede ser personalizado por la app misma.
- La app no puede ser notificada cuando el dictado comienza y termina.



Para permitir a los desarrolladores construir aplicaciones más personalizables y poderosas con la misma tecnología de dictado que Siri, Apple creó el framework Speech. Este permite a cada dispositivo que ejecute iOS 10 para traducir audio a texto en más de 50 lenguajes y dialectos.
Esta nueva API es mucho más poderosa porque no solo proporciona un simple sistema de transcripción, sino que también proporciona interpretaciones alternativas de lo que cada usuario pudiera haber dicho. Puedes controlar cuando la app detiene un dictado, puedes mostrar los resultados mientras tu usuario habla, y el motor de reconocimiento de voz se adaptará automáticamente a las preferencias del usuario (lenguaje, vocabulario, nombres, etc.).
Una característica interesante es el soporte para transcribir audio pre-grabado. Si estás construyendo una aplicación de mensajería instantánea, por ejemplo, podrías usar esta funcionalidad para transcribir el texto a nuevos mensajes de audio.
Configuración
Primero que todo, necesitarás pedir al usuario permiso para transmitir su voz a Apple para análisis.
Dependiendo del dispositivo y el lenguaje que será reconocido, iOS podría debería decidir transparentemente transcribir el audio en el dispositivo mismo o, si el reconocimiento de voz local no está disponible en el dispositivo, iOS usará los servidores de Apple para hacer el trabajo.
Es por esto que una conexión activa a internet usualmente es requerida para reconocimiento de voz. Te mostraré como revisar la disponibilidad del servicio muy pronto.
Hay tres pasos para usar reconocimiento de voz:
- Explica: dile a tu usuario por qué quieres acceder su voz.
- Autoriza: pide autorización explícitamente para acceder su voz.
- Solicita: carga un audio pre-grabado desde el disco usando
SFSpeechURLRecognitionRequest
, o transmite audio en vivo usandoSFSpeechAudioBufferRecognitionRequest
u procesa la transcripción.
Si quieres saber más sobre el framework Speech, mira la Sesión 509 de WWDC 2016. También puedes leer la documentación oficial.
Ejemplo
Ahora te mostraré cómo puedes construir una aplicación de la vida real que saca ventaja de la API de reconocimiento de voz. Vamos a construir una pequeña aplicación de seguimiento de vuelo en la cuál el usuario puede simplemente decir un número de vuelo, y la app mostrará el estado actual del vuelo. Si, ¡vamos a construir un pequeño asistente como Siri para revisar el estado de cualquier vuelo!
En el repositorio de GitHub del tutorial, he provisto un proyecto esqueleto que contiene una UI básica que nos ayudará para este tutorial. Descarga y abre el proyecto en Xcode 8.2 o superior. Comenzar con UI existente nos permitirá enfocarnos en la API de reconocimiento de voz.
Echa un vistazo a las clases en el proyecto. UIViewController+Style.swift
contiene la mayoría del código responsable de actualizar la UI. El origen de datos de ejemplo de los vuelos mostrados en la tabla es declarado en FlightsDataSource.swift
.
Si ejecutas el proyecto, este debería lucir como sigue:



Después de que el usuario presiona el botón de micrófono, queremos iniciar el reconocimiento de voz para transcribir el número de vuelo. Así que si el usuario dice "LX40", nos gustaría mostrar la información en relación a la puerta y el estado actual del vuelo. Para hacer esto, llamaremos a una función para buscar automáticamente el vuelo en una fuente de datos y mostrar el estado del vuelo.
Primero vamos a explorar cómo transcribir desde audio pre-grabado. Después, aprenderemos cómo implementar el más interesante reconocimiento de voz en vivo.
Comencemos configurando el proyecto. abre el archivo Info.plist
y agrega una nueva fila con la explicación que será mostrada al usuario cuando se le pida permiso para acceder a su voz. La fila recién agregada está resaltada en azul en la siguiente imagen.



Una vez que está hecho, abre ViewController.swift
. No te fijes en el código que ya está en esta clase; solo se está encargando de actualizar la UI por nosotros.
El primer paso con cualquier framework que quieres usar es importarlo en la parte superior del archivo.
1 |
import Speech |
Para mostrar el diálogo de permiso al usuario, agrega este código en el método viewDidLoad(animated:)
:
1 |
switch SFSpeechRecognizer.authorizationStatus() { |
2 |
case .notDetermined: |
3 |
askSpeechPermission() |
4 |
case .authorized: |
5 |
self.status = .ready |
6 |
case .denied, .restricted: |
7 |
self.status = .unavailable |
8 |
}
|
La variable status
se encarga de cambiar la UI para advertir al usuario de que el reconocimiento de voz no está disponible en caso de que algo salga mal. Vamos a asignar un nuevo estado a la misma variable cada vez que quisiéramos cambiar la UI.
Si la app no ha pedido permiso al usuario aún, el estado de autorización será notDetermined
, y llamaremos al método askSpeechPermission
para pedirlo como está definido en el siguiente paso.
Deberías siempre fallar agraciadamente si una característica específica no está disponible. También es muy importante comunicar al usuario cuando estás grabando su voz. Nunca intentes reconocer su voz sin primero actualizar la UI y hacer a tu usuario consciente de ello.
Aquí está la implementación de la función para pedir permiso al usuario.
1 |
func askSpeechPermission() { |
2 |
SFSpeechRecognizer.requestAuthorization { status in |
3 |
OperationQueue.main.addOperation { |
4 |
switch status { |
5 |
case .authorized: |
6 |
self.status = .ready |
7 |
default: |
8 |
self.status = .unavailable |
9 |
}
|
10 |
}
|
11 |
}
|
12 |
}
|
Invocamos al método requestAuthorization
para mostrar la petición de privacidad de reconocimiento de voz que agregamos al Info.plist
. Después cambiamos al hilo principal en caso de que el cierre sea invocado en un hilo diferente--queremos actualizar la UI solo desde el hilo principal. Asignamos el nuevo status
para actualizar el botón de micrófono para avisar al usuario la disponibilidad (o no) del reconocimiento de voz.
Reconocimiento de Audio Pre-Grabado
Antes de escribir el código para reconocer audio pre-grabado, necesitamos encontrar la URL para el archivo de audio. En el navegador de proyecto, revisa que tengas un archivo llamado LX40.m4a
. Grabé este archivo yo mismo con la aplicación Voice Memos en mi iPhone diciendo "LX40". Podemos revisar fácilmente si tenemos una transcripción correcta de audio.
Almacena la URL de archivo de audio en una propiedad:
1 |
var preRecordedAudioURL: URL = { |
2 |
return Bundle.main.url(forResource: "LX40", withExtension: "m4a")! |
3 |
}()
|
Es tiempo de ver finalmente el poder y simplicidad del framework Speech. Este es el código que hace todo el reconocimiento de voz por nosotros:
1 |
func recognizeFile(url: URL) { |
2 |
guard let recognizer = SFSpeechRecognizer(), recognizer.isAvailable else { |
3 |
return
|
4 |
}
|
5 |
|
6 |
let request = SFSpeechURLRecognitionRequest(url: url) |
7 |
recognizer.recognitionTask(with: request) { result, error in |
8 |
guard let recognizer = SFSpeechRecognizer(), recognizer.isAvailable else { |
9 |
return self.status = .unavailable |
10 |
}
|
11 |
if let result = result { |
12 |
self.flightTextView.text = result.bestTranscription.formattedString |
13 |
if result.isFinal { |
14 |
self.searchFlight(number: result.bestTranscription.formattedString) |
15 |
}
|
16 |
} else if let error = error { |
17 |
print(error) |
18 |
}
|
19 |
}
|
20 |
}
|
Aquí está lo que está haciendo el método:
- Inicializa una instancia de
SFSpeechRecognizer
y revisa que el reconocimiento de voz está disponible con una declaración de guardia. Si no está disponible, simplemente establecemos el estado aunavailable
y devolvemos. (El inicializador por defecto usa el locale por defecto del usuario, pero puedes también usar el inicializadorSFSpeechRecognizer(locale:)
para proporcionar un locale diferente.) - Si el reconocimiento de voz está disponible, crea una instancia
SFSpeechURLRecognitionRequest
pasando la URL del audio pre-grabado. - Comienza el reconocimiento de voz invocando el método
recognitionTask(with:)
con la petición previamente creada.
El cierre será llamado múltiples veces con dos parámetros: un resultado y un objeto de error.
El recognizer
está de hecho reproduciendo el archivo e intentando reconocer el texto incrementalmente. Por esta razón, el cierre es llamado varias veces. Cada vez que reconoce una letra o palabra o hace algunas correcciones, el cierre es invocado con objetos actualizados.
El objeto result
tiene la propiedad isFinal
establecida a true cuando el archivo de audio fue completamente analizado. En este caso, comenzamos una búsqueda en nuestra fuente de información de vuelo para ver si podemos encontrar un vuelo con el número de vuelo reconocido. La función searchFlight
se encargará de mostrar el resultado.
La última cosa que nos falta es invocar la función recognizeFile(url:)
cuando el botón de micrófono es presionado:
1 |
@IBAction func microphonePressed(_ sender: Any) { |
2 |
recognizeFile(url: preRecordedAudioURL) |
3 |
}
|
Ejecuta la aplicación en tu dispositivo corriendo iOS 10, presiona el botón de micrófono, y verás el resultado. El audio "LX40" es reconocido de manera incremental, y el estado del vuelo es mostrado!

Consejo: El número de vuelo es mostrado en un UITextView. Cómo habrás notado, si habilitas el detector de datos del Número de Vuelo en el UITextView, ¡puedes presionar sobre este y el estado actual del vuelo se mostrará!
El código completo de ejemplo hasta este punto puede ser visto en la rama de audio-pre-grabado en GitHub.
Reconocimiento de Audio en Vivo
Veamos ahora cómo implementar reconocimiento de voz en vivo. Va a ser un poco más complicado comparado con lo que hicimos. Puedes nuevamente descargar el mismo esqueleto de proyecto y continuar.
Necesitamos una nueva llave en el archivo Info.plist
para explicar al usuario por qué necesitamos acceder al micrófono. Agrega una nueva fila para tu Info.plist
como se muestra en la imagen.



No necesitamos pedir permiso de manera manual al usuario porque iOS hará eso por nosotros tan pronto intentemos acceder cualquier API relacionada con el micrófono.
Podemos re-usar el mismo código que usamos en la sección previa (recuerda import_Speech
) para pedir la autorización. El método viewDidLoad (animated:)
es implementado exactamente como antes.
1 |
switch SFSpeechRecognizer.authorizationStatus() { |
2 |
case .notDetermined: |
3 |
askSpeechPermission() |
4 |
case .authorized: |
5 |
self.status = .ready |
6 |
case .denied, .restricted: |
7 |
self.status = .unavailable |
8 |
}
|
También, el método para pedir permiso al usuario es el mismo.
1 |
func askSpeechPermission() { |
2 |
SFSpeechRecognizer.requestAuthorization { status in |
3 |
OperationQueue.main.addOperation { |
4 |
switch status { |
5 |
case .authorized: |
6 |
self.status = .ready |
7 |
default: |
8 |
self.status = .unavailable |
9 |
}
|
10 |
}
|
11 |
}
|
12 |
}
|
La implementación de startRecording
va a ser un poco diferente. Agreguemos primero unas cuantas variables de instancia que serán útiles cuando administremos la sesión de audio y tarea de reconocimiento de voz.
1 |
let audioEngine = AVAudioEngine() |
2 |
let speechRecognizer: SFSpeechRecognizer? = SFSpeechRecognizer() |
3 |
let request = SFSpeechAudioBufferRecognitionRequest() |
4 |
var recognitionTask: SFSpeechRecognitionTask? |
Echemos un vistazo a cada variable de manera separada:
-
AVAudioEngine
es usado para procesar un flujo de audio. Crearemos un nodo de audio y lo adjuntaremos a este motor para que podamos actualizarnos cuando el micrófono recibe algunas señales de audio. -
SFSpeechRecognizer
es la misma clase que hemos visto en la parte previa del tutorial, y se encarga de reconocer el habla. Dado que el inicializador puede fallar y devolver nil, los declaramos como opcional para evitar un fallo en tiempo de ejecución. -
SFSpeechAudioBufferRecognitionRequest
es un buffer usado para reconocer voz en vivo. Dado que no tenemos el archivo de audio completo como antes, necesitamos un buffer para asignar la voz mientras el usuario habla. -
SFSpeechRecognitionTask
administra la tarea actual de reconocimiento de voz y puede ser usado para detenerlo o cancelarlo.
Una vez que hemos declarado todas las variables requeridas, implementemos startRecording
.
1 |
func startRecording() { |
2 |
// Setup audio engine and speech recognizer
|
3 |
guard let node = audioEngine.inputNode else { return } |
4 |
let recordingFormat = node.outputFormat(forBus: 0) |
5 |
node.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { buffer, _ in |
6 |
self.request.append(buffer) |
7 |
}
|
8 |
|
9 |
// Prepare and start recording
|
10 |
audioEngine.prepare() |
11 |
do { |
12 |
try audioEngine.start() |
13 |
self.status = .recognizing |
14 |
} catch { |
15 |
return print(error) |
16 |
}
|
17 |
|
18 |
// Analyze the speech
|
19 |
recognitionTask = speechRecognizer?.recognitionTask(with: request, resultHandler: { result, error in |
20 |
if let result = result { |
21 |
self.flightTextView.text = result.bestTranscription.formattedString |
22 |
self.searchFlight(number: result.bestTranscription.formattedString) |
23 |
} else if let error = error { |
24 |
print(error) |
25 |
}
|
26 |
})
|
27 |
}
|
Este es el código principal para nuestra característica. Lo explicaré paso a paso:
- Primero obtenemos el
inputNode
delaudioEngine
. Un dispositivo posiblemente puede tener múltiples entradas de audio, y aquí seleccionamos la primera. - Le decimos al nodo de entrada que queremos monitorear el flujo de audio. El bloque que proporcionamos será invocado en cada flujo de audio recibido de 1024 bytes. Inmediatamente anexamos el buffer de audio al
request
para que puede comenzar el proceso de reconocimiento. - Preparamos el motor de audio para comenzar a grabar. Si la grabación comienza exitósamente, establecemos el estado a
.recognizing
de manera que actualicemos el botón de icono para hacer saber al usuario que su voz está siendo reconocida. - Asignemos el objeto devuelto de
speechRecognizer.recognitionTask(with:resultHandler:)
a la variablerecognitionTask
. Si el reconocimiento es exitoso, buscamos el vuelo en nuestra fuente de datos y actualizamos la UI.
La función para cancelar la grabación es tan simple como detener el motor de audio, remover la escucha del nodo de entrada, y cancelar la tarea de reconocimiento.
1 |
func cancelRecording() { |
2 |
audioEngine.stop() |
3 |
if let node = audioEngine.inputNode { |
4 |
node.removeTap(onBus: 0) |
5 |
}
|
6 |
recognitionTask?.cancel() |
7 |
}
|
Ahora solo necesitamos comenzar y detener la grabación. Modifica el método microphonePressed
como sigue:
1 |
@IBAction func microphonePressed() { |
2 |
switch status { |
3 |
case .ready: |
4 |
startRecording() |
5 |
status = .recognizing |
6 |
case .recognizing: |
7 |
cancelRecording() |
8 |
status = .ready |
9 |
default: |
10 |
break
|
11 |
}
|
12 |
}
|
Dependiendo del status
actual, comenzamos o detenemos el reconocimiento de voz.
Construye y ejecuta la app para ver el resultado. Intenta deletrear cualquiera de los números de vuelo listados y deberías ver su estado aparecer.

Una vez más, el código de ejemplo puede ser visto en la rama live-audio en GitHub.
Mejores Prácticas
El reconocimiento de voz es una API muy importante que Apple proporcionó a desarrolladores iOS apuntando a iOS 10. Es completamente gratuito de usar, pero ten en mente que no es de uso ilimitado. Está limitado a cerca de un minuto por cada tarea de reconocimiento de voz, y tu app podría también ser sofocada por los servidores de Apple si requiere mucho cómputo. Por estas razones, tiene un impacto alto sobre el tráfico de la red y uso de poder.
Asegúrate de que tus usuarios son instruidos propiamente sobre cómo usar el reconocimiento de voz, y ser tan transparente como sea posible cuando estás grabando su voz.
Recapitulación
En este tutorial, has visto cómo usar reconocimiento de voz rápido, preciso y flexible en iOS 10. Úsalo a tu favor para dar a tus usuarios una nueva manera de interactuar con tu app y mejorar su accesibilidad al mismo tiempo.
Si quieres aprender más sobre integrar Siri en tu app, o si quieres averiguar sobre algunas otras características interesantes de desarrollador de iOS 10, revisa el curso de Markus Mühlberger.
También, revisa algunos de nuestros otros tutoriales gratuitos sobre características de iOS 10.
- iOSActualiza Tu App a iOS 10Bart Jacobs
- iOS SDKiOS 10: Creando Interfaces Personalizadas de NotificaciónDavis Allie
- iOS 10Retroalimentación Háptica en iOS 10Patrick Balestra
- iOS SDKCrea Extensiones SiriKit en iOS 10Patrick Balestra