iOS 10 的 Speech 框架實(shí)現(xiàn)語音識(shí)別 (詳解附Demo)

在 2016 年的 WWDC 上,Apple 介紹了一個(gè)十分有用的語音識(shí)別 API,分分鐘秒殺科大訊飛的


早在2011年iPhone4s 的上,iOS 5系統(tǒng)就有了語音識(shí)別.

有以下缺陷

- 需要- 彈出鍵盤

- 只支持實(shí)時(shí)語音

- 無法自定義錄音

- 單一的輸出結(jié)果

在 2016 年的 WWDC 上,Apple 終于開發(fā)了語音識(shí)別 API,那就是 Speech 框架。事實(shí)上,Siri 的語音識(shí)別正是由 Speech Kit 提供支持。

- 超過50種語言獲得支持

- 任何運(yùn)行iOS10的設(shè)備都可用

- 加入用戶授權(quán)使其更安全

- 可以轉(zhuǎn)化音頻文件和實(shí)時(shí)語音

![輸入圖片說明](https://static.oschina.net/uploads/img/201612/05145117_5OMv.png "在這里輸入圖片標(biāo)題")

下面通過一個(gè)語音轉(zhuǎn)換為文本介紹Speech 框架的使用

##界面設(shè)計(jì)

首先,讓我們來創(chuàng)建一個(gè) iOS Single View Application 工程。然后在 Main.storyboard 上添加

UILabel用于標(biāo)題

UITextView用于顯示識(shí)別內(nèi)容

UIButton 用于觸發(fā)

下一步,連線 textView變量,Button變量和事件

```

@IBOutlet weak var textView: UITextView!

@IBOutlet weak var speakerBtn: UIButton!

@IBAction func speakAction(_ sender: Any) {

}

```

##使用 Speech 框架

import這個(gè)框架,并遵循 SFSpeechRecognizerDelegate 協(xié)議。

##用戶權(quán)限

在使用 Speech 框架進(jìn)行語音識(shí)別之前,你必須先請(qǐng)求用戶許可,原因是識(shí)別不僅發(fā)生在 iOS 設(shè)備本地,還需要依賴 Apple 的服務(wù)器。具體來說,所有音頻數(shù)據(jù)都會(huì)被傳輸?shù)教O果后臺(tái)進(jìn)行處理。因此需要獲取用戶的權(quán)限,其中包括用戶必須允許應(yīng)用使用的音頻輸入和語音識(shí)別權(quán)限。

```

//用于apple語言識(shí)別的變量

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

```

```

// MARK: - *** 獲取用戶權(quán)限 ***

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

}

}

}

```

* 創(chuàng)建一個(gè)區(qū)域標(biāo)志符 (locale identifier) 為 zh-CN 的 SFSpeechRecognizer 實(shí)例,這時(shí)候語音識(shí)別就會(huì)知道用戶錄入的語種。簡單說,這就是語音識(shí)別的處理對(duì)象。

* 在語音識(shí)別被激活之前,默認(rèn)設(shè)置麥克風(fēng)按鈕為禁用狀態(tài)。

* 然后,將語音識(shí)別的 delegate 設(shè)置為 ViewController 中的 self。

* 之后,就到了請(qǐng)求語音識(shí)別權(quán)限的階段了,這時(shí)我們通過調(diào)用 SFSpeechRecognizer.requestAuthorization 來達(dá)到目的。

* 最后,檢查驗(yàn)證狀態(tài),如果得到了授權(quán),則啟用麥克風(fēng)按鈕。否則,打印錯(cuò)誤信息,繼續(xù)禁用麥克風(fēng)按鈕。

你可能會(huì)認(rèn)為,現(xiàn)在我們啟動(dòng)應(yīng)用將會(huì)看到一個(gè)授權(quán)提示框,很遺憾你錯(cuò)了。運(yùn)行應(yīng)用帶來的是崩潰。你可能會(huì)想問,這是為什么?

## 提供授權(quán)信息Apple 要求應(yīng)用為所有請(qǐng)求的權(quán)限提供自定義消息,對(duì)于語音權(quán)限的情況,我們必須為兩個(gè)行為請(qǐng)求授權(quán):麥克風(fēng)的使用語音的識(shí)別要自定義消息,你需要在 info.plist 文件中定義這些消息。讓我們打開 info.plist 文件的源代碼。方法是在 info.plist 上點(diǎn)擊右鍵。然后選擇 Open As > Source Code。最后,復(fù)制下面的 XML 代碼并將它們插入到? 標(biāo)簽前。

```

NSMicrophoneUsageDescription麥克風(fēng)輸入請(qǐng)求信息NSSpeechRecognitionUsageDescription語音識(shí)別請(qǐng)求信息

```

_注意:務(wù)必在IPhone真機(jī)上運(yùn)行測試,iOS 模擬器并不會(huì)連接 Mac 的麥克風(fēng)。

_處理語音識(shí)別

```

// 可以將識(shí)別請(qǐng)求的結(jié)果返回給你,它帶來了極大的便利,必要時(shí),可以取消或停止任務(wù)。

private var recognitionTask: SFSpeechRecognitionTask?

//對(duì)象用于處理語音識(shí)別請(qǐng)求,為語音識(shí)別提供音頻輸入

private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?

// 音頻引擎 用于進(jìn)行音頻輸入

private let audioEngine = AVAudioEngine()

```

``` // MARK: - *** 處理語音識(shí)別 ***

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("會(huì)話創(chuàng)建失敗")

}

recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

guard let inputNode = audioEngine.inputNode else {

fatalError("音頻引擎 沒有輸入節(jié)點(diǎn)")

}

guard let recognitionRequest = recognitionRequest else {

fatalError("創(chuàng)建音頻緩存失敗")

}

//結(jié)果報(bào)告

recognitionRequest.shouldReportPartialResults = true

//開啟授權(quán)任務(wù)

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 = "說點(diǎn)啥......"

}

}

```

* 檢查 recognitionTask 的運(yùn)行狀態(tài),如果正在運(yùn)行,取消任務(wù)。

* 創(chuàng)建一個(gè) AVAudioSession 對(duì)象為音頻錄制做準(zhǔn)備。這里我們將錄音分類設(shè)置為 Record,模式設(shè)為 Measurement,然后啟動(dòng)。注意,設(shè)置這些屬性有可能會(huì)拋出異常,因此你必須將其置于 try catch 語句中。

* 實(shí)例化 recognitionResquest。創(chuàng)建 SFSpeechAudioBufferRecognitionRequest 對(duì)象,然后我們就可以利用它將音頻數(shù)據(jù)傳輸?shù)?Apple 的服務(wù)器。

* 檢查 audioEngine (你的設(shè)備)是否支持音頻輸入以錄音。如果不支持,報(bào)一個(gè) fatal error。

* 檢查 recognitionRequest 對(duì)象是否已被實(shí)例化,并且值不為 nil。

* 告訴 recognitionRequest 不要等到錄音完成才發(fā)送請(qǐng)求,而是在用戶說話時(shí)一部分一部分發(fā)送語音識(shí)別數(shù)據(jù)。

* 在調(diào)用 speechRecognizer 的 recognitionTask 函數(shù)時(shí)開始識(shí)別。該函數(shù)有一個(gè)完成回調(diào)函數(shù),每次識(shí)別引擎收到輸入時(shí)都會(huì)調(diào)用它,在修改當(dāng)前識(shí)別結(jié)果,亦或是取消或停止時(shí),返回一個(gè)最終記錄。

* 定義一個(gè) boolean 變量來表示識(shí)別是否已結(jié)束。

* 倘若結(jié)果非空,則設(shè)置 textView.text 屬性為結(jié)果中的最佳記錄。同時(shí)若為最終結(jié)果,將 isFinal 置為 true。

* 如果請(qǐng)求沒有錯(cuò)誤或已經(jīng)收到最終結(jié)果,停止 audioEngine (音頻輸入),recognitionRequest 和 recognitionTask。同時(shí),將開始錄音按鈕的狀態(tài)切換為可用。

* 向 recognitionRequest 添加一個(gè)音頻輸入。值得留意的是,在 recognitionTask 啟動(dòng)后再添加音頻輸入完全沒有問題。Speech 框架會(huì)在添加了音頻輸入之后立即開始識(shí)別任務(wù)。

* 將 audioEngine 設(shè)為準(zhǔn)備就緒狀態(tài),并啟動(dòng)引擎。


## 觸發(fā)語音識(shí)別

在創(chuàng)建語音識(shí)別任務(wù)時(shí),我們首先得確保語音識(shí)別的可用性,需要實(shí)現(xiàn)delegate 方法。如果語音識(shí)別不可用,或是改變了狀態(tài),應(yīng)隨之設(shè)置 按鈕的enable ,我們通過擴(kuò)展來實(shí)現(xiàn)代理

```

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

//這個(gè)方法會(huì)在按鈕的可用性改變時(shí)被調(diào)用。如果語音識(shí)別可用,錄音按鈕也將被啟用。

extension ViewController: SFSpeechRecognizerDelegate{

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

if available {

speakerBtn.isEnabled = true

} else {

speakerBtn.isEnabled = false

}

}

}

```


最后,我們還需要更新一下 按鈕的點(diǎn)擊方法:

```

@IBAction func speakAction(_ sender: Any) {

if audioEngine.isRunning {

audioEngine.stop()

recognitionRequest?.endAudio()

speakerBtn.isEnabled = false

speakerBtn.setTitle("開始說話", for: .normal)

//? ? ? ? ? ? textView.text = "說點(diǎn)啥"

} else {

startRecording()

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

}

}

```

**Apple忠告**

* 確保使用語音之別之前,通過UI界面告知用戶

* 在涉及密碼或者敏感信息時(shí),請(qǐng)勿使用

* 在你操作識(shí)別結(jié)果之前,請(qǐng)先把結(jié)果展示給用戶

* Apple 對(duì)每臺(tái)設(shè)備的識(shí)別有限制。詳情未知,不過你可以嘗試聯(lián)系 Apple 獲得更多信息。

* Apple 對(duì)每個(gè)應(yīng)用的識(shí)別也有限制。

* 如果你總是遭遇限制,務(wù)必聯(lián)系 Apple,他們或許可以解決這個(gè)問題。

* 語音識(shí)別會(huì)消耗不少電量和流量。

* 語音識(shí)別每次只能持續(xù)大概一分鐘。

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

Github地址:https://github.com/roycehe/SpeechToText-use-Speech-Framework ?給起來

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容