iOS 10 的 Speech 框架实现语音识别 (详解附Demo)

在 2016 年的 WWDC 上,Apple 介绍了一个十分有用的语音识别 API,分分钟秒杀科大讯飞的


早在2011年iPhone4s 的上,iOS 5系统就有了语音识别.

有以下缺陷

- 需要- 弹出键盘

- 只支持实时语音

- 无法自定义录音

- 单一的输出结果

在 2016 年的 WWDC 上,Apple 终于开发了语音识别 API,那就是 Speech 框架。事实上,Siri 的语音识别正是由 Speech Kit 提供支持。

- 超过50种语言获得支持

- 任何运行iOS10的设备都可用

- 加入用户授权使其更安全

- 可以转化音频文件和实时语音

![输入图片说明](https://static.oschina.net/uploads/img/201612/05145117_5OMv.png "在这里输入图片标题")

下面通过一个语音转换为文本介绍Speech 框架的使用

##界面设计

首先,让我们来创建一个 iOS Single View Application 工程。然后在 Main.storyboard 上添加

UILabel用于标题

UITextView用于显示识别内容

UIButton 用于触发

下一步,连线 textView变量,Button变量和事件

```

@IBOutlet weak var textView: UITextView!

@IBOutlet weak var speakerBtn: UIButton!

@IBAction func speakAction(_ sender: Any) {

}

```

##使用 Speech 框架

import这个框架,并遵循 SFSpeechRecognizerDelegate 协议。

##用户权限

在使用 Speech 框架进行语音识别之前,你必须先请求用户许可,原因是识别不仅发生在 iOS 设备本地,还需要依赖 Apple 的服务器。具体来说,所有音频数据都会被传输到苹果后台进行处理。因此需要获取用户的权限,其中包括用户必须允许应用使用的音频输入和语音识别权限。

```

//用于apple语言识别的变量

private let speechRecognizer = SFSpeechRecognizer(locale: Locale.init(identifier: "zh-CN"))

```

```

// MARK: - *** 获取用户权限 ***

func authRequest(){

speakerBtn.isEnabled = false

speechRecognizer?.delegate = self

SFSpeechRecognizer.requestAuthorization { (authStatus) in

var isBtnEndable = false

switch authStatus{

case.authorized:

isBtnEndable = true

case .denied:

isBtnEndable = false

print("User denied access to speech recognition")

case .restricted:

isBtnEndable = false

print("Speech recognition restricted on this device")

case .notDetermined:

isBtnEndable = false

}

OperationQueue.main.addOperation {

self.speakerBtn.isEnabled = isBtnEndable

}

}

}

```

* 创建一个区域标志符 (locale identifier) 为 zh-CN 的 SFSpeechRecognizer 实例,这时候语音识别就会知道用户录入的语种。简单说,这就是语音识别的处理对象。

* 在语音识别被激活之前,默认设置麦克风按钮为禁用状态。

* 然后,将语音识别的 delegate 设置为 ViewController 中的 self。

* 之后,就到了请求语音识别权限的阶段了,这时我们通过调用 SFSpeechRecognizer.requestAuthorization 来达到目的。

* 最后,检查验证状态,如果得到了授权,则启用麦克风按钮。否则,打印错误信息,继续禁用麦克风按钮。

你可能会认为,现在我们启动应用将会看到一个授权提示框,很遗憾你错了。运行应用带来的是崩溃。你可能会想问,这是为什么?

## 提供授权信息Apple 要求应用为所有请求的权限提供自定义消息,对于语音权限的情况,我们必须为两个行为请求授权:麦克风的使用语音的识别要自定义消息,你需要在 info.plist 文件中定义这些消息。让我们打开 info.plist 文件的源代码。方法是在 info.plist 上点击右键。然后选择 Open As > Source Code。最后,复制下面的 XML 代码并将它们插入到  标签前。

```

NSMicrophoneUsageDescription麦克风输入请求信息NSSpeechRecognitionUsageDescription语音识别请求信息

```

_注意:务必在IPhone真机上运行测试,iOS 模拟器并不会连接 Mac 的麦克风。

_处理语音识别

```

// 可以将识别请求的结果返回给你,它带来了极大的便利,必要时,可以取消或停止任务。

private var recognitionTask: SFSpeechRecognitionTask?

//对象用于处理语音识别请求,为语音识别提供音频输入

private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?

// 音频引擎 用于进行音频输入

private let audioEngine = AVAudioEngine()

```

``` // MARK: - *** 处理语音识别 ***

func startRecording(){

if recognitionTask != nil{

recognitionTask?.cancel()

recognitionTask = nil

}

let audioSession = AVAudioSession.sharedInstance()

do {

try audioSession.setCategory(AVAudioSessionCategoryRecord)

try audioSession.setMode(AVAudioSessionModeMeasurement)

try audioSession.setActive(true, with: .notifyOthersOnDeactivation)

}catch{

fatalError("会话创建失败")

}

recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

guard let inputNode = audioEngine.inputNode else {

fatalError("音频引擎 没有输入节点")

}

guard let recognitionRequest = recognitionRequest else {

fatalError("创建音频缓存失败")

}

//结果报告

recognitionRequest.shouldReportPartialResults = true

//开启授权任务

recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in

var isFinal = false

if result != nil {

self.textView.text = result?.bestTranscription.formattedString

isFinal = (result?.isFinal)!

}

if error != nil || isFinal {

self.audioEngine.stop()

inputNode.removeTap(onBus: 0)

self.recognitionRequest = nil

self.recognitionTask = nil

self.speakerBtn.isEnabled = true

}

})

let recordingFormat = inputNode.outputFormat(forBus: 0)

inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in

self.recognitionRequest?.append(buffer)

}

audioEngine.prepare()

do {

try audioEngine.start()

} catch {

print("audioEngine couldn't start because of an error.")

}

//        textView.text = "说点啥......"

}

}

```

* 检查 recognitionTask 的运行状态,如果正在运行,取消任务。

* 创建一个 AVAudioSession 对象为音频录制做准备。这里我们将录音分类设置为 Record,模式设为 Measurement,然后启动。注意,设置这些属性有可能会抛出异常,因此你必须将其置于 try catch 语句中。

* 实例化 recognitionResquest。创建 SFSpeechAudioBufferRecognitionRequest 对象,然后我们就可以利用它将音频数据传输到 Apple 的服务器。

* 检查 audioEngine (你的设备)是否支持音频输入以录音。如果不支持,报一个 fatal error。

* 检查 recognitionRequest 对象是否已被实例化,并且值不为 nil。

* 告诉 recognitionRequest 不要等到录音完成才发送请求,而是在用户说话时一部分一部分发送语音识别数据。

* 在调用 speechRecognizer 的 recognitionTask 函数时开始识别。该函数有一个完成回调函数,每次识别引擎收到输入时都会调用它,在修改当前识别结果,亦或是取消或停止时,返回一个最终记录。

* 定义一个 boolean 变量来表示识别是否已结束。

* 倘若结果非空,则设置 textView.text 属性为结果中的最佳记录。同时若为最终结果,将 isFinal 置为 true。

* 如果请求没有错误或已经收到最终结果,停止 audioEngine (音频输入),recognitionRequest 和 recognitionTask。同时,将开始录音按钮的状态切换为可用。

* 向 recognitionRequest 添加一个音频输入。值得留意的是,在 recognitionTask 启动后再添加音频输入完全没有问题。Speech 框架会在添加了音频输入之后立即开始识别任务。

* 将 audioEngine 设为准备就绪状态,并启动引擎。


## 触发语音识别

在创建语音识别任务时,我们首先得确保语音识别的可用性,需要实现delegate 方法。如果语音识别不可用,或是改变了状态,应随之设置 按钮的enable ,我们通过扩展来实现代理

```

// MARK: - *** delegate ***

//这个方法会在按钮的可用性改变时被调用。如果语音识别可用,录音按钮也将被启用。

extension ViewController: SFSpeechRecognizerDelegate{

func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {

if available {

speakerBtn.isEnabled = true

} else {

speakerBtn.isEnabled = false

}

}

}

```


最后,我们还需要更新一下 按钮的点击方法:

```

@IBAction func speakAction(_ sender: Any) {

if audioEngine.isRunning {

audioEngine.stop()

recognitionRequest?.endAudio()

speakerBtn.isEnabled = false

speakerBtn.setTitle("开始说话", for: .normal)

//            textView.text = "说点啥"

} else {

startRecording()

speakerBtn.setTitle("说完了", for: .normal)

}

}

```

**Apple忠告**

* 确保使用语音之别之前,通过UI界面告知用户

* 在涉及密码或者敏感信息时,请勿使用

* 在你操作识别结果之前,请先把结果展示给用户

* Apple 对每台设备的识别有限制。详情未知,不过你可以尝试联系 Apple 获得更多信息。

* Apple 对每个应用的识别也有限制。

* 如果你总是遭遇限制,务必联系 Apple,他们或许可以解决这个问题。

* 语音识别会消耗不少电量和流量。

* 语音识别每次只能持续大概一分钟。

参考 [WWDC 2016 - Session 509 - iOS](https://developer.apple.com/videos/play/wwdc2016/509/)

Github地址:https://github.com/roycehe/SpeechToText-use-Speech-Framework ✨给起来

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容