
介绍
自2011年推出以来,Siri一直是iOS的核心功能。现在,iOS 10带来了新功能,允许开发人员与Siri进行交互。 特别是,现在提供了两个新框架:Speech和SiriKit。
今天,我们将看一下“语音”框架,该框架使我们能够轻松地将音频转换为文本。 您将学习如何构建使用语音识别API来检查航班状态的真实应用。
用法
语音识别是将现场或预先录制的音频转换为转录文本的过程。 自从iOS 5中引入Siri以来,系统键盘中就有一个麦克风按钮,使用户可以轻松地进行命令。 此功能可以与任何UIKit文本输入一起使用,并且不需要编写其他代码即可支持标准文本输入。 它确实快速且易于使用,但有一些限制:
- 听写时键盘始终存在。
- 该语言无法由应用本身进行自定义。
- 听写开始和结束时无法通知应用程序。

为了使开发人员能够使用与Siri相同的口述技术来构建更多可自定义和功能强大的应用程序,Apple创建了Speech框架。 它允许运行iOS 10的每台设备将音频转换为50多种语言和方言的文本。
这个新的API更加强大,因为它不仅提供了简单的转录服务,而且还提供了用户可能所说的替代解释。 您可以控制何时停止听写,可以在用户说话时显示结果,语音识别引擎将自动适应用户的首选项(语言,词汇,姓名等)。
一个有趣的功能是支持转录预先录制的音频。 例如,如果您要构建即时消息传递应用程序,则可以使用此功能来转录新音频消息的文本。
建立
首先,您将需要征询用户的许可,以将其声音传送给Apple进行分析。
根据设备和要识别的语言,iOS可以透明地决定在设备本身上转录音频,或者,如果设备上不提供本地语音识别,则iOS将使用Apple的服务器来完成此工作。
这就是为什么语音识别通常需要活动的Internet连接的原因。 我将向您展示如何尽快检查该服务的可用性。
使用语音识别需要三个步骤:
- 说明:告诉您的用户为什么要访问他们的声音。
- 授权:明确要求授权以访问他们的声音。
- 要求:从磁盘加载使用预先录制的音频
SFSpeechURLRecognitionRequest
,或使用流式实况音频SFSpeechAudioBufferRecognitionRequest
和处理的转录。
如果您想进一步了解Speech框架,请观看WWDC 2016 Session 509 。 您也可以阅读官方文档 。
例
现在,我将向您展示如何构建利用语音识别API的真实应用程序。 我们将构建一个小型的航班跟踪应用程序,用户可以在其中简单地说出航班号,该应用程序将显示航班的当前状态。 是的,我们将建立一个像Siri这样的小助手来检查任何航班的状态!
在教程的GitHub存储库中,我提供了一个框架项目 ,该项目包含一个基本的UI,可帮助我们完成本教程。 下载并在Xcode 8.2或更高版本中打开项目。 从现有的UI开始,我们将专注于语音识别API。
看一下项目中的类。 UIViewController+Style.swift
包含负责更新UI的大多数代码。 表中显示的航班的示例数据源在FlightsDataSource.swift
声明。
如果您运行该项目,则它应如下所示。

用户按下麦克风按钮后,我们要启动语音识别以记录航班号。 因此,如果用户说“ LX40”,我们想显示有关登机口和航班当前状态的信息。 为此,我们将调用一个函数以自动在数据源中查找航班并显示航班状态。
我们首先将探讨如何从预先录制的音频中进行转录。 稍后,我们将学习如何实现更有趣的实时语音识别。
让我们从设置项目开始。 打开Info.plist
文件,并添加新行,其中包含解释,当要求其访问语音的权限时,该解释将显示给用户。 下图以蓝色突出显示了新添加的行。

完成此操作后,打开ViewController.swift
。 不要介意此类中已经存在的代码。 它只是为我们更新UI。
要使用任何新框架的第一步是将其导入文件顶部。
import Speech
要向用户显示权限对话框,请在viewDidLoad(animated:)
方法中添加以下代码:
switch SFSpeechRecognizer.authorizationStatus() {
case .notDetermined:
askSpeechPermission()
case .authorized:
self.status = .ready
case .denied, .restricted:
self.status = .unavailable
}
status
变量负责更改UI,以警告用户在出现问题时语音识别不可用。 每次我们想要更改UI时,都将为同一变量分配新状态。
如果该应用尚未请求用户许可,则授权状态将为notDetermined
,我们将按照askSpeechPermission
中的定义调用askSpeechPermission
方法进行询问。
如果特定功能不可用,则应始终正常地失败 。 在录制用户的声音时始终与用户交流也很重要。 在未先更新用户界面并使用户意识到之前,切勿尝试识别其声音。
这是请求用户许可的功能的实现。
func askSpeechPermission() {
SFSpeechRecognizer.requestAuthorization { status in
OperationQueue.main.addOperation {
switch status {
case .authorized:
self.status = .ready
default:
self.status = .unavailable
}
}
}
}
我们调用requestAuthorization
方法来显示添加到Info.plist
的语音识别隐私请求。 然后,如果在另一个线程上调用了闭包,我们将切换到主线程-我们只希望从主线程更新UI。 我们为新status
分配了更新麦克风按钮的功能,以向用户发送语音(或不语音)语音识别的信号。
预先录制的音频识别
在编写代码以识别预先录制的音频之前,我们需要找到音频文件的URL。 在项目浏览器中,检查是否有一个名为LX40.m4a
的文件。 我说了“ LX40”,然后用我的iPhone上的Voice Memos应用程序自己录制了此文件。 我们可以轻松检查我们是否获得了正确的音频转录。
将音频文件URL存储在属性中:
var preRecordedAudioURL: URL = {
return Bundle.main.url(forResource: "LX40", withExtension: "m4a")!
}()
现在是时候终于了解一下语音框架的功能和简单性了。 这是为我们完成所有语音识别的代码:
func recognizeFile(url: URL) {
guard let recognizer = SFSpeechRecognizer(), recognizer.isAvailable else {
return
}
let request = SFSpeechURLRecognitionRequest(url: url)
recognizer.recognitionTask(with: request) { result, error in
guard let recognizer = SFSpeechRecognizer(), recognizer.isAvailable else {
return self.status = .unavailable
}
if let result = result {
self.flightTextView.text = result.bestTranscription.formattedString
if result.isFinal {
self.searchFlight(number: result.bestTranscription.formattedString)
}
} else if let error = error {
print(error)
}
}
}
这是此方法的作用:
- 初始化
SFSpeechRecognizer
实例,并使用防护语句检查语音识别是否可用。 如果不可用,我们只需将状态设置为unavailable
并返回。 (默认的初始值设定项使用默认的用户区域设置,但您也可以使用SFSpeechRecognizer(locale:)
初始值设定项来提供不同的语言环境。) - 如果语音识别可用,则通过传递预先录制的音频URL来创建
SFSpeechURLRecognitionRequest
实例。 - 通过使用先前创建的请求调用
recognitionTask(with:)
方法来启动语音识别。
将使用两个参数多次调用该闭包:结果和错误对象。
recognizer
器实际上正在播放文件,并尝试逐步识别文本。 因此,多次调用该闭包。 每次识别字母或单词或进行某些更正时,都会使用最新的对象来调用闭包。
当音频文件被完全分析时, result
对象的isFinal
属性设置为true。 在这种情况下,我们开始在航班数据源中进行搜索,以查看是否可以找到带有识别航班号的航班。 searchFlight
函数将负责显示结果。
我们缺少的最后一件事是在按下麦克风按钮时调用recognizeFile(url:)
函数:
@IBAction func microphonePressed(_ sender: Any) {
recognizeFile(url: preRecordedAudioURL)
}
在运行iOS 10的设备上运行应用程序,按麦克风按钮,您将看到结果。 音频“ LX40”被逐渐识别,并显示飞行状态!
提示:航班号显示在UITextView中。 您可能已经注意到,如果在UITextView中启用了航班号数据检测器,则可以按一下它,航班的当前状态将实际显示出来!
到目前为止的完整示例代码可以在GitHub的pre-recorded-audio分支中查看。
实时音频识别
现在让我们看看如何实现实时语音识别。 与我们刚才所做的相比,它将变得更加复杂。 您可以再次下载相同的框架项目,然后继续。
我们需要在Info.plist
文件中使用一个新密钥来向用户解释为什么我们需要访问麦克风。 如图所示,将新行添加到Info.plist
中。

我们不需要手动询问用户的许可,因为一旦我们尝试访问任何与麦克风相关的API,iOS就会为我们执行此操作。
我们可以重用上一节中使用的相同代码(记住要import Speech
)以请求授权。 viewDidLoad(animated:)
方法的实现与之前完全相同:
switch SFSpeechRecognizer.authorizationStatus() {
case .notDetermined:
askSpeechPermission()
case .authorized:
self.status = .ready
case .denied, .restricted:
self.status = .unavailable
}
另外,向用户请求许可的方法是相同的。
func askSpeechPermission() {
SFSpeechRecognizer.requestAuthorization { status in
OperationQueue.main.addOperation {
switch status {
case .authorized:
self.status = .ready
default:
self.status = .unavailable
}
}
}
}
startRecording
的实现将有所不同。 首先,我们添加一些新的实例变量,这些变量在管理音频会话和语音识别任务时会派上用场。
let audioEngine = AVAudioEngine()
let speechRecognizer: SFSpeechRecognizer? = SFSpeechRecognizer()
let request = SFSpeechAudioBufferRecognitionRequest()
var recognitionTask: SFSpeechRecognitionTask?
让我们分别看一下每个变量:
-
AVAudioEngine
用于处理音频流。 我们将创建一个音频节点并将其附加到此引擎,以便在麦克风收到一些音频信号时可以进行更新。 -
SFSpeechRecognizer
与我们在本教程的上半部分中看到的类相同,它负责识别语音。 鉴于初始化程序可能失败并返回nil,我们将其声明为可选操作,以避免在运行时崩溃。 -
SFSpeechAudioBufferRecognitionRequest
是用于识别实时语音的缓冲区。 鉴于我们没有像以前那样完整的音频文件,我们需要一个缓冲区来在用户讲话时分配语音。 -
SFSpeechRecognitionTask
管理当前的语音识别任务,可用于停止或取消它。
声明所有必需的变量后,让我们实现startRecording
。
func startRecording() {
// Setup audio engine and speech recognizer
guard let node = audioEngine.inputNode else { return }
let recordingFormat = node.outputFormat(forBus: 0)
node.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { buffer, _ in
self.request.append(buffer)
}
// Prepare and start recording
audioEngine.prepare()
do {
try audioEngine.start()
self.status = .recognizing
} catch {
return print(error)
}
// Analyze the speech
recognitionTask = speechRecognizer?.recognitionTask(with: request, resultHandler: { result, error in
if let result = result {
self.flightTextView.text = result.bestTranscription.formattedString
self.searchFlight(number: result.bestTranscription.formattedString)
} else if let error = error {
print(error)
}
})
}
这是我们功能的核心代码。 我将逐步解释它:
- 首先,我们获得
inputNode
的audioEngine
。 一个设备可能有多个音频输入,在这里我们选择第一个。 - 我们告诉输入节点我们要监视音频流。 我们提供的块将在收到的每个1024字节音频流上调用。 我们立即将音频缓冲区附加到
request
以便它可以开始识别过程。 - 我们准备音频引擎以开始录制。 如果录制成功开始,请将状态设置为
.recognizing
以便我们更新按钮图标以使用户知道他们的声音正在录制。 - 让我们从返回的对象分配
speechRecognizer.recognitionTask(with:resultHandler:)
到recognitionTask
变量。 如果识别成功,我们将在数据源中搜索航班并更新UI。
取消录音的功能很简单,例如停止音频引擎,从输入节点上删除抽头并取消识别任务。
func cancelRecording() {
audioEngine.stop()
if let node = audioEngine.inputNode {
node.removeTap(onBus: 0)
}
recognitionTask?.cancel()
}
现在,我们只需要开始和停止记录。 修改microphonePressed
方法,如下所示:
@IBAction func microphonePressed() {
switch status {
case .ready:
startRecording()
status = .recognizing
case .recognizing:
cancelRecording()
status = .ready
default:
break
}
}
根据当前status
,我们开始或停止语音识别。
生成并运行该应用程序以查看结果。 尝试拼写任何列出的航班号,您应该会看到其状态。
同样,可以在GitHub的live-audio分支中查看示例代码。
最佳实践
语音识别是Apple提供给面向iOS 10的iOS开发人员的一种非常强大的API。它是完全免费的,但请记住,它的使用范围不是无限的。 每个语音识别任务的时间限制为一分钟左右,如果您的应用程序需要太多的计算,那么它可能还会受到Apple服务器的限制。 由于这些原因,它对网络流量和电源使用有很大影响。
确保正确指导用户如何使用语音识别,并在录制他们的声音时使其尽可能透明。
回顾
在本教程中,您已经了解了如何在iOS 10中使用快速,准确和灵活的语音识别。利用它的优势,可以为用户提供与您的应用进行交互的新方法,并同时提高其可访问性。
翻译自: https://code.tutsplus.com/tutorials/using-the-speech-recognition-api-in-ios-10--cms-28032