在 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í)語音
題")
下面通過一個(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 ?給起來